diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81e9d48..348a029 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,7 @@ jobs: fail-fast: false matrix: ${{fromJson(needs.get-matrix.outputs.container_os_matrix)}} env: + ASAN: ${{ matrix.asan }} BUILD: ${{ matrix.build }} GITHUB_MESSAGE: ${{ github.event.head_commit.message || github.event.inputs.build-mode }} GITHUB_REPOSITORY: ${{ github.repository }} @@ -127,7 +128,7 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: php-sapi${{ matrix.php-version }}-${{ matrix.build }}+${{ matrix.dist }}-${{ matrix.dist-version }} + name: php-sapi${{ matrix.php-version }}-${{ matrix.build }}${{ matrix.asan && '-asan' || '' }}+${{ matrix.dist }}-${{ matrix.dist-version }} path: /tmp/debian/*.zst merge: @@ -151,7 +152,7 @@ jobs: - uses: actions/download-artifact@v5 with: - name: php-sapi${{ matrix.php-version }}-${{ matrix.build }}+${{ matrix.dist }}-${{ matrix.dist-version }} + name: php-sapi${{ matrix.php-version }}-${{ matrix.build }}${{ matrix.asan && '-asan' || '' }}+${{ matrix.dist }}-${{ matrix.dist-version }} path: /tmp - name: Stage builds @@ -166,6 +167,7 @@ jobs: - name: Build and package run: bash scripts/build.sh merge env: + ASAN: ${{ matrix.asan }} BUILD: ${{ matrix.build }} SAPI_LIST: ${{ env.SAPI_LIST }} GITHUB_USER: ${{ github.repository_owner }} @@ -181,7 +183,7 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: php${{ matrix.php-version }}-${{ matrix.build }}+${{ matrix.dist }}-${{ matrix.dist-version }} + name: php${{ matrix.php-version }}-${{ matrix.build }}${{ matrix.asan && '-asan' || '' }}+${{ matrix.dist }}-${{ matrix.dist-version }} path: | /tmp/*.xz /tmp/*.zst @@ -230,13 +232,13 @@ jobs: - uses: actions/download-artifact@v5 with: - name: php${{ matrix.php-version }}-${{ matrix.build }}+${{ matrix.dist }}-${{ matrix.dist-version }} + name: php${{ matrix.php-version }}-${{ matrix.build }}${{ matrix.asan && '-asan' || '' }}+${{ matrix.dist }}-${{ matrix.dist-version }} path: /tmp - name: Install PHP run: | sed -i '/download/d' scripts/install.sh - bash scripts/install.sh ${{ matrix.php-version }} local ${{ matrix.debug }} ${{ matrix.build }} + bash scripts/install.sh ${{ matrix.php-version }} local ${{ matrix.debug }} ${{ matrix.build }} ${{ matrix.asan }} - name: Test run: | @@ -271,13 +273,13 @@ jobs: - uses: actions/download-artifact@v5 with: - name: php${{ matrix.php-version }}-${{ matrix.build }}+${{ matrix.os }} + name: php${{ matrix.php-version }}-${{ matrix.build }}${{ matrix.asan && '-asan' || '' }}+${{ matrix.os }} path: /tmp - name: Install PHP run: | sed -i '/download/d' scripts/install.sh - bash scripts/install.sh ${{ matrix.php-version }} github ${{ matrix.debug }} ${{ matrix.build }} + bash scripts/install.sh ${{ matrix.php-version }} github ${{ matrix.debug }} ${{ matrix.build }} ${{ matrix.asan }} - name: Test run: | diff --git a/README.md b/README.md index 960d651..845a09a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [Install](#install) - [Extensions](#extensions) - [JIT](#jit) +- [ASAN](#asan-addresssanitizer) - [SAPI Support](#sapi-support) - [Builds](#builds) - [Uninstall](#uninstall) @@ -42,15 +43,16 @@ chmod a+x ./install.sh The installer takes the following options: ```bash -./install.sh +./install.sh [release|debug] [nts|zts] [asan] ``` -The `php-version` is required, and `release` and `nts` are the defaults. +The `php-version` is required, and `release` and `nts` are the defaults. - release: No debugging symbols - debug: With debugging symbols - nts: Non Thread Safe - zts: Thread Safe +- asan: AddressSanitizer build (PHP 8.0+ only) ### Examples @@ -66,6 +68,12 @@ The `php-version` is required, and `release` and `nts` are the defaults. ./install.sh 8.4 debug zts ``` +- or, to install `PHP 8.4` with AddressSanitizer (for memory error detection): + +```bash +./install.sh 8.4 asan +``` + - Finally, test your PHP version: ```bash @@ -134,6 +142,35 @@ To disable JIT: switch_jit -v -s disable ``` +## ASAN (AddressSanitizer) + +PHP 8.0 and above versions have builds with AddressSanitizer (ASAN) and UndefinedBehaviorSanitizer (UBSan) enabled. These builds are useful for detecting memory issues. + +To install an ASAN build: + +```bash +./install.sh 8.4 asan +``` + +You can combine ASAN with other options: + +```bash +# ASAN + Thread Safe +./install.sh 8.4 zts asan + +# ASAN + Debug symbols +./install.sh 8.4 debug asan +``` + +**Notes:** + +- ASAN builds are only available for PHP 8.0 and above. +- Running PHP with ASAN will be slower than regular builds due to the instrumentation overhead. +- You can configure ASAN behavior using the `ASAN_OPTIONS` environment variable: +```bash +ASAN_OPTIONS=detect_leaks=1 php your_script.php +``` + ## SAPI support These SAPIs are installed by default: @@ -160,7 +197,7 @@ switch_sapi -v -s ## Builds -The following releases have `nts` and `zts` builds for the following PHP versions along with builds with and without debugging symbols. +The following releases have `nts` and `zts` builds for the following PHP versions along with builds with and without debugging symbols. PHP 8.0+ versions also include AddressSanitizer (ASAN) builds for memory error detection. - [PHP 8.6.0-dev](https://github.com/shivammathur/php-builder/releases/tag/8.6) - [PHP 8.5.x](https://github.com/shivammathur/php-builder/releases/tag/8.5) diff --git a/config/definitions/8.0 b/config/definitions/8.0 index 433309d..28511d2 100644 --- a/config/definitions/8.0 +++ b/config/definitions/8.0 @@ -129,6 +129,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.1 b/config/definitions/8.1 index fd6ecfd..95b378a 100644 --- a/config/definitions/8.1 +++ b/config/definitions/8.1 @@ -129,6 +129,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.2 b/config/definitions/8.2 index fd6ecfd..95b378a 100644 --- a/config/definitions/8.2 +++ b/config/definitions/8.2 @@ -129,6 +129,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.3 b/config/definitions/8.3 index fd6ecfd..95b378a 100644 --- a/config/definitions/8.3 +++ b/config/definitions/8.3 @@ -129,6 +129,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.4 b/config/definitions/8.4 index c2bb044..e6fe9ac 100644 --- a/config/definitions/8.4 +++ b/config/definitions/8.4 @@ -122,6 +122,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.5 b/config/definitions/8.5 index f1615bd..873e628 100644 --- a/config/definitions/8.5 +++ b/config/definitions/8.5 @@ -121,6 +121,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/config/definitions/8.6 b/config/definitions/8.6 index f1615bd..873e628 100644 --- a/config/definitions/8.6 +++ b/config/definitions/8.6 @@ -121,6 +121,9 @@ # Placeholder for thread-safe build. ZTS +# Placeholder for ASAN build. +ASAN + # Placeholder for patch commands. PATCHES diff --git a/scripts/build.sh b/scripts/build.sh index 9f5abf5..2ae5546 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -51,14 +51,26 @@ build_php() { echo "::group::$1" SAPI=$1 + # Force disable LTO for ASAN builds (incompatible) + if [ "${ASAN:-}" = "asan" ]; then + lto="-lto" + fi + # Set and export FLAGS - CFLAGS="$(get_buildflags CFLAGS "$lto") $(getconf LFS_CFLAGS)" + CFLAGS="$(get_buildflags CFLAGS "$lto") $(getconf LFS_CFLAGS)" CFLAGS=$(echo "$CFLAGS" | sed -E 's/-Werror=implicit-function-declaration//g') CFLAGS="$CFLAGS -DOPENSSL_SUPPRESS_DEPRECATED" CPPFLAGS="$(get_buildflags CPPFLAGS "$lto")" CXXFLAGS="$(get_buildflags CXXFLAGS "$lto")" LDFLAGS="$(get_buildflags LDFLAGS "$lto") -Wl,-z,now -Wl,--as-needed" + + # Add ASAN/UBSan flags + if [ "${ASAN:-}" = "asan" ]; then + CFLAGS="$CFLAGS -fsanitize=address,undefined -fno-omit-frame-pointer" + CXXFLAGS="$CXXFLAGS -fsanitize=address,undefined -fno-omit-frame-pointer" + LDFLAGS="$LDFLAGS -fsanitize=address,undefined" + fi if [[ "$PHP_VERSION" =~ 5.6|7.[0-4]|8.0 ]]; then EXTRA_CFLAGS="-fpermissive -Wno-deprecated -Wno-deprecated-declarations" @@ -273,6 +285,12 @@ if [ "${BUILD:?}" = "zts" ]; then export PHP_PKG_SUFFIX=-zts fi +# Set ASAN options. +if [ "${ASAN:-}" = "asan" ]; then + PHP_PKG_SUFFIX="${PHP_PKG_SUFFIX:-}-asan" + export PHP_PKG_SUFFIX +fi + # Import OS information to the environment. . /etc/os-release diff --git a/scripts/build_partials/php_build.sh b/scripts/build_partials/php_build.sh index ff94ca0..b8b6491 100644 --- a/scripts/build_partials/php_build.sh +++ b/scripts/build_partials/php_build.sh @@ -30,6 +30,12 @@ configure_phpbuild() { zts="$(sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g' "${definitions:?}"/zts/"$PHP_VERSION")" fi + # Path the definition for ASAN. + asan='' + if [ "${ASAN:-}" = "asan" ]; then + asan='configure_option "--enable-address-sanitizer"' + fi + # Copy all local patches to the php-build patches directory. patches_dir=config/patches/"$PHP_VERSION" if [ -d "$patches_dir" ]; then @@ -44,6 +50,7 @@ configure_phpbuild() { sed -i -e "s|BUILD_MACHINE_SYSTEM_TYPE|$(dpkg-architecture -q DEB_BUILD_GNU_TYPE)|" \ -e "s|HOST_MACHINE_SYSTEM_TYPE|$(dpkg-architecture -q DEB_HOST_GNU_TYPE)|" \ -e "s|ZTS|$zts|" \ + -e "s|ASAN|$asan|" \ -e "s|INSTALL|$install_command|" \ -e "s|PHP_VERSION|$PHP_VERSION|" \ -e "s|PHP_VERSION|$PHP_VERSION|" \ diff --git a/scripts/get-matrix.sh b/scripts/get-matrix.sh index dfb1479..98d2c1a 100644 --- a/scripts/get-matrix.sh +++ b/scripts/get-matrix.sh @@ -31,10 +31,16 @@ get_dist() { for os in "${container_os_array[@]}"; do for php in "${php_array[@]}"; do for build in "${build_array[@]}"; do - dist="$(get_dist "$os")" - dist_version="$(get_dist_version "$os")" - os_base="$(get_container_base "$os")" - container_os_json_array+=("{\"container\": \"$os\", \"container-base\": \"$os_base\", \"php-version\": \"$php\", \"dist\": \"$dist\", \"dist-version\": \"$dist_version\", \"build\": \"$build\"}") + for asan in "" "asan"; do + # Skip ASAN for PHP < 8.0 + if [[ -n "$asan" && ! "$php" =~ ^8\. ]]; then + continue + fi + dist="$(get_dist "$os")" + dist_version="$(get_dist_version "$os")" + os_base="$(get_container_base "$os")" + container_os_json_array+=("{\"container\": \"$os\", \"container-base\": \"$os_base\", \"php-version\": \"$php\", \"dist\": \"$dist\", \"dist-version\": \"$dist_version\", \"build\": \"$build\", \"asan\": \"$asan\"}") + done done done done @@ -43,7 +49,13 @@ done for os in "${runner_os_array[@]}"; do for php in "${php_array[@]}"; do for build in "${build_array[@]}"; do - runner_os_json_array+=("{\"os\": \"$os\", \"php-version\": \"$php\", \"build\": \"$build\"}") + for asan in "" "asan"; do + # Skip ASAN for PHP < 8.0 + if [[ -n "$asan" && ! "$php" =~ ^8\. ]]; then + continue + fi + runner_os_json_array+=("{\"os\": \"$os\", \"php-version\": \"$php\", \"build\": \"$build\", \"asan\": \"$asan\"}") + done done done done @@ -52,11 +64,17 @@ done for os in "${container_os_array[@]}"; do for php in "${php_array[@]}"; do for build in "${build_array[@]}"; do - for debug in debug release; do - dist="$(get_dist "$os")" - dist_version="$(get_dist_version "$os")" - os_base="$(get_container_base "$os")" - test_container_os_json_array+=("{\"container\": \"$os\", \"container-base\": \"$os_base\", \"php-version\": \"$php\", \"dist\": \"$dist\", \"dist-version\": \"$dist_version\", \"build\": \"$build\", \"debug\": \"$debug\"}") + for asan in "" "asan"; do + # Skip ASAN for PHP < 8.0 + if [[ -n "$asan" && ! "$php" =~ ^8\. ]]; then + continue + fi + for debug in debug release; do + dist="$(get_dist "$os")" + dist_version="$(get_dist_version "$os")" + os_base="$(get_container_base "$os")" + test_container_os_json_array+=("{\"container\": \"$os\", \"container-base\": \"$os_base\", \"php-version\": \"$php\", \"dist\": \"$dist\", \"dist-version\": \"$dist_version\", \"build\": \"$build\", \"asan\": \"$asan\", \"debug\": \"$debug\"}") + done done done done @@ -66,8 +84,14 @@ done for os in "${runner_os_array[@]}"; do for php in "${php_array[@]}"; do for build in "${build_array[@]}"; do - for debug in debug release; do - test_runner_os_json_array+=("{\"os\": \"$os\", \"php-version\": \"$php\", \"build\": \"$build\", \"debug\": \"$debug\"}") + for asan in "" "asan"; do + # Skip ASAN for PHP < 8.0 + if [[ -n "$asan" && ! "$php" =~ ^8\. ]]; then + continue + fi + for debug in debug release; do + test_runner_os_json_array+=("{\"os\": \"$os\", \"php-version\": \"$php\", \"build\": \"$build\", \"asan\": \"$asan\", \"debug\": \"$debug\"}") + done done done done diff --git a/scripts/install.sh b/scripts/install.sh index d0c2979..e47fe9a 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -470,6 +470,8 @@ for arg in "$@"; do debug="$arg" elif [[ "$arg" =~ nts|zts ]]; then build="$arg" + elif [[ "$arg" =~ asan ]]; then + asan="asan" fi done @@ -487,6 +489,9 @@ PHP_PKG_SUFFIX= if [ "${build:?}" = "zts" ]; then PHP_PKG_SUFFIX="-zts" fi +if [ "${asan:-}" = "asan" ]; then + PHP_PKG_SUFFIX="$PHP_PKG_SUFFIX-asan" +fi if [ "$debug" = "debug" ]; then PHP_PKG_SUFFIX="$PHP_PKG_SUFFIX-dbgsym" fi