diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e60dcfee..bd067a2a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,38 +12,29 @@ permissions: contents: read jobs: - build: + self-built-v8-cache-warmup: strategy: # set in accordance with number of v8-versions, so caching can kick in properly max-parallel: 2 matrix: - operating-system: + operating-system: # &self-built-v8-operating-systems - ubuntu-latest # - windows-latest -# - macos-latest - php-versions: -# - '8.1' -# - '8.2' - - '8.3' - - '8.4' - v8-versions: + - macos-latest + v8-versions: # &self-built-v8-v8-versions - 10.9.194 # - 11.9.172 - 12.9.203 -# - 13.1.104 + - 13.5.212 runs-on: ${{ matrix.operating-system }} steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - coverage: none + - name: Prepare cache folder v8 ${{ matrix.v8-versions }} + run: | + sudo mkdir -p /opt/v8/self-built/{lib,include} + sudo chown -R $(id -u):$(id -g) /opt/v8/self-built - name: Restore cache v8 ${{ matrix.v8-versions }} build id: v8-build-cache @@ -61,25 +52,45 @@ jobs: if: steps.v8-build-cache.outputs.cache-hit != 'true' run: | # Store extra tools somewhere undisturbing + set -x cd "$(mktemp -d)" - fetch v8 + ARCH=$(uname -m) + if [[ "$ARCH" == "x86_64" ]]; then + V8CONFIG="x64.release" + ARCH_SHORT="x64" + elif [[ "$ARCH" == "arm64" ]]; then + V8CONFIG="arm64.release" + ARCH_SHORT="arm64" + else + echo "Unknown architecture: $ARCH" >&2 + exit 1 + fi + fetch --nohooks --no-history v8 cd v8 - - git checkout ${{ matrix.v8-versions }} - gclient sync -D + git fetch --tag origin refs/tags/${{ matrix.v8-versions }} > /dev/null 2>&1 + git checkout ${{ matrix.v8-versions }} > /dev/null 2>&1 + gclient sync -D > /dev/null 2>&1 # Setup GN # Warnings are no errors - @see https://issues.chromium.org/issues/42203398#comment9 - tools/dev/v8gen.py -vv x64.release -- is_component_build=true use_custom_libcxx=false treat_warnings_as_errors=false + if [[ "${{ runner.os }}" == "macOS" ]]; then + # Run gn gen with args as v8gen does not override target_cpu properly + gn gen out.gn/$V8CONFIG --args='target_cpu="'$ARCH_SHORT'" v8_target_cpu="'$ARCH_SHORT'" is_component_build=true use_custom_libcxx=true treat_warnings_as_errors=false' + else + tools/dev/v8gen.py -vv $V8CONFIG -- is_component_build=true use_custom_libcxx=true treat_warnings_as_errors=false + fi # Build - ninja -C out.gn/x64.release/ + ninja -C out.gn/$V8CONFIG/ - # Install to /opt/v8/self-built - sudo mkdir -p /opt/v8/self-built/{lib,include} - sudo cp out.gn/x64.release/lib*.so out.gn/x64.release/*_blob.bin out.gn/x64.release/icudtl.dat /opt/v8/self-built/lib/ - sudo cp -R include/* /opt/v8/self-built/include/ + if [[ "${{ runner.os }}" == "macOS" ]]; then + LIB_EXT=dylib + else + LIB_EXT=so + fi + cp out.gn/$V8CONFIG/lib*.${LIB_EXT} out.gn/$V8CONFIG/*_blob.bin out.gn/$V8CONFIG/icudtl.dat /opt/v8/self-built/lib/ + cp -R include/* /opt/v8/self-built/include/ # Go back to origin cd "${GITHUB_WORKSPACE}" @@ -91,6 +102,44 @@ jobs: path: /opt/v8/self-built key: ${{ steps.v8-build-cache.outputs.cache-primary-key }} + self-built-v8: + needs: self-built-v8-cache-warmup + + strategy: + matrix: + operating-system: # *self-built-v8-operating-systems + - ubuntu-latest +# - windows-latest + - macos-latest + v8-versions: # *self-built-v8-v8-versions + - 10.9.194 +# - 11.9.172 + - 12.9.203 + - 13.5.212 + php-versions: +# - '8.1' + - '8.2' + - '8.3' + - '8.4' + + runs-on: ${{ matrix.operating-system }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + + - name: Download cache v8 ${{ matrix.v8-versions }} build + uses: actions/cache/restore@v4 + with: + path: /opt/v8/self-built + key: ${{ runner.os }}-${{ matrix.v8-versions }}-v8-build + - name: Build extension run: | phpize @@ -102,12 +151,12 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: phpt-test-results + name: phpt-test-results-on-${{ runner.os }}-${{ matrix.v8-versions }}-${{ matrix.php-versions }} path: | php_test_results*.txt tests/*.out - alpine: + alpine-package-manager-apk: runs-on: ubuntu-latest steps: @@ -120,7 +169,7 @@ jobs: - name: Install dependencies run: | cat /etc/alpine-release - apk add php83-dev nodejs-dev g++ make + apk add php83-dev nodejs-dev nodejs g++ make file shell: alpine.sh --root {0} - name: Build extension @@ -135,7 +184,52 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: phpt-test-results + name: phpt-test-results-on-alpine + path: | + php_test_results*.txt + tests/*.out + + macos-package-manager-brew: + strategy: + matrix: + php-versions: + - '8.2' + - '8.3' + - '8.4' + + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Install dependencies + run: | + brew install v8 + # Symlink icudtl.dat to the default location + ln -sf /opt/homebrew/Cellar/v8/$(brew list --versions v8 | awk '{print $2}')/libexec/icudtl.dat /opt/homebrew/lib/icudtl.dat + + - name: Build extension + run: | + phpize + ./configure --with-v8js=/opt/homebrew CPPFLAGS="-DV8_COMPRESS_POINTERS -DV8_ENABLE_SANDBOX" + make + make test + + - name: Archive test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: phpt-test-results-on-macos-brew-${{ matrix.php-versions }} path: | php_test_results*.txt tests/*.out diff --git a/README.Linux.md b/README.Linux.md index 6bc69625..37f6c2f1 100644 --- a/README.Linux.md +++ b/README.Linux.md @@ -89,23 +89,27 @@ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH=`pwd`/depot_tools:"$PATH" # Download v8 -fetch v8 +fetch --nohooks --no-history v8 +gclient sync -D --no-history cd v8 # (optional) If you'd like to build a certain version: +git fetch --tag origin refs/tags/12.0.267.36 git checkout 12.0.267.36 gclient sync -D +ARCH=$(uname -m) + # Setup GN -tools/dev/v8gen.py -vv x64.release -- is_component_build=true use_custom_libcxx=false +tools/dev/v8gen.py -vv ${ARCH}.release -- is_component_build=true use_custom_libcxx=false # Build -ninja -C out.gn/x64.release/ +ninja -C out.gn/${ARCH}.release/ # Install to /opt/v8/ sudo mkdir -p /opt/v8/{lib,include} -sudo cp out.gn/x64.release/lib*.so out.gn/x64.release/*_blob.bin \ - out.gn/x64.release/icudtl.dat /opt/v8/lib/ +sudo cp out.gn/${ARCH}.release/lib*.so out.gn/${ARCH}.release/*_blob.bin \ + out.gn/${ARCH}.release/icudtl.dat /opt/v8/lib/ sudo cp -R include/* /opt/v8/include/ ``` diff --git a/README.MacOS.md b/README.MacOS.md index 9a11c82a..098ff2fd 100644 --- a/README.MacOS.md +++ b/README.MacOS.md @@ -16,8 +16,24 @@ cd /tmp git clone https://github.com/phpv8/v8js.git cd v8js phpize -./configure --with-v8js=/opt/homebrew CPPFLAGS="-DV8_COMPRESS_POINTERS" +./configure --with-v8js=/opt/homebrew CPPFLAGS="-DV8_COMPRESS_POINTERS -DV8_ENABLE_SANDBOX" make -j4 make test make install ``` + +V8Js' build system assumes that the `icudtl.dat` file is located next to the `libv8.so` +library file and compiles the path into the library itself. If for whatever reason the +`icudtl.dat` file is stored at a different place during runtime, you need to set the +php.ini variable `v8js.icudtl_dat_path` to point to the file. Otherwise locale-aware +features of V8 will not work as expected. + +To avoid having to configure `v8js.icudtl_dat_path` manually, you can symlink or copy the ICU data file into the default library location. For Homebrew users, run: + +In case of a brew installed v8, run: + +``` +ln -sf /opt/homebrew/Cellar/v8/$(brew list --versions v8 | awk '{print $2}')/libexec/icudtl.dat /opt/homebrew/lib/icudtl.dat +``` + +This ensures V8Js will find `icudtl.dat` automatically and timezone/i18n support will work out of the box. diff --git a/config.m4 b/config.m4 index 835eedf9..edb11c12 100644 --- a/config.m4 +++ b/config.m4 @@ -118,6 +118,7 @@ if test "$PHP_V8JS" != "no"; then CPPFLAGS="$CPPFLAGS -I$V8_INCLUDE_DIR -std=$ac_cv_v8_cstd" LDFLAGS="$LDFLAGS -L$V8_LIBRARY_DIR" + LIBS="-L$V8_LIBRARY_DIR $LIBS" if test "$libname" = "v8"; then AC_MSG_CHECKING([for libv8_libplatform]) diff --git a/tests/set_memory_limit_001.phpt b/tests/set_memory_limit_001.phpt index b20b753e..d35a2cdc 100644 --- a/tests/set_memory_limit_001.phpt +++ b/tests/set_memory_limit_001.phpt @@ -13,20 +13,23 @@ if (getenv("SKIP_SLOW_TESTS")) { $JS = <<< EOT var jsfunc = function() { - var text = "abcdefghijklmnopqrstuvwyxz0123456789"; + var text = "abcdefghijklmnopqrstuvwyxz0123456789"; // 36 bytes var memory = ""; - for (var i = 0; i < 100; ++i) { - for (var j = 0; j < 10000; ++j) { + // should generate 360 MB + for (var i = 0; i < 10_000; ++i) { + for (var j = 0; j < 1000; ++j) { memory += text; - } - sleep(0); + } + sleep(0); } + + return memory; }; jsfunc; EOT; $v8 = new V8Js(); -$v8->setMemoryLimit(10000000); +$v8->setMemoryLimit(10_000_000); $func = $v8->executeString($JS); var_dump($func); diff --git a/tests/set_memory_limit_002.phpt b/tests/set_memory_limit_002.phpt new file mode 100644 index 00000000..78bbd9db --- /dev/null +++ b/tests/set_memory_limit_002.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test V8::setMemoryLimit() : Memory limit can be set but does not trigger when not exceeded +--SKIPIF-- + +--FILE-- +setMemoryLimit(10_000_000); + +$func = $v8->executeString($JS); +var_dump($func); + +try { + $func(); +} catch (V8JsMemoryLimitException $e) { + print get_class($e); print PHP_EOL; + print $e->getMessage(); print PHP_EOL; +} +?> +===EOF=== +--EXPECTF-- +object(V8Function)#%d (0) { +} +===EOF=== diff --git a/tests/set_memory_limit_003.phpt b/tests/set_memory_limit_003.phpt index 179dfe2e..e10669fb 100644 --- a/tests/set_memory_limit_003.phpt +++ b/tests/set_memory_limit_003.phpt @@ -14,14 +14,17 @@ if (getenv("SKIP_SLOW_TESTS")) { $JS = <<< EOT var jsfunc = function() { PHP.imposeMemoryLimit(); - var text = "abcdefghijklmnopqrstuvwyxz0123456789"; + var text = "abcdefghijklmnopqrstuvwyxz0123456789"; // 36 bytes var memory = ""; - for (var i = 0; i < 100; ++i) { - for (var j = 0; j < 10000; ++j) { + // should generate 360 MB + for (var i = 0; i < 10_000; ++i) { + for (var j = 0; j < 1000; ++j) { memory += text; - } - sleep(0); + } + sleep(0); } + + return memory; }; jsfunc; EOT; @@ -29,7 +32,7 @@ EOT; $v8 = new V8Js(); $v8->imposeMemoryLimit = function() use ($v8) { - $v8->setMemoryLimit(10000000); + $v8->setMemoryLimit(10_000_000); }; $func = $v8->executeString($JS); diff --git a/tests/time_limit.phpt b/tests/time_limit.phpt index 7d5dc994..60e9b73a 100644 --- a/tests/time_limit.phpt +++ b/tests/time_limit.phpt @@ -12,8 +12,12 @@ if (getenv("SKIP_SLOW_TESTS")) { 2000) { // 2 seconds safety valve + break; + } var encoded = encodeURI(text); } EOT; diff --git a/v8js_array_access.cc b/v8js_array_access.cc index 36752136..e6c0e0d2 100644 --- a/v8js_array_access.cc +++ b/v8js_array_access.cc @@ -59,7 +59,12 @@ static zval v8js_array_access_dispatch(zend_object *object, const char *method_n V8JS_INTERCEPTED v8js_array_access_getter(uint32_t index, const v8::PropertyCallbackInfo& info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -83,7 +88,12 @@ V8JS_INTERCEPTED v8js_array_access_setter(uint32_t index, v8::Local v const V8JS_SETTER_PROPERTY_CALLBACK_INFO &info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -157,7 +167,12 @@ static bool v8js_array_access_isset_p(zend_object *object, int index) /* {{{ */ static void v8js_array_access_length(v8::Local property, const v8::PropertyCallbackInfo& info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -169,7 +184,12 @@ static void v8js_array_access_length(v8::Local property, const v8::P V8JS_INTERCEPTED v8js_array_access_deleter(uint32_t index, const v8::PropertyCallbackInfo& info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -187,7 +207,12 @@ V8JS_INTERCEPTED v8js_array_access_deleter(uint32_t index, const v8::PropertyCal V8JS_INTERCEPTED v8js_array_access_query(uint32_t index, const v8::PropertyCallbackInfo& info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -206,7 +231,13 @@ V8JS_INTERCEPTED v8js_array_access_query(uint32_t index, const v8::PropertyCallb void v8js_array_access_enumerator(const v8::PropertyCallbackInfo& info) /* {{{ */ { v8::Isolate *isolate = info.GetIsolate(); - v8::Local self = info.Holder(); + v8::Local self; + + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); @@ -240,7 +271,14 @@ V8JS_INTERCEPTED v8js_array_access_named_getter(v8::Local property_nam return V8JS_INTERCEPTED_YES; } - v8::Local ret_value = v8js_named_property_callback(info.GetIsolate(), info.Holder(), property, V8JS_PROP_GETTER); + v8::Local holder; + #if PHP_V8_API_VERSION >= 13000000 + holder = info.This(); + #else + holder = info.Holder(); + #endif + + v8::Local ret_value = v8js_named_property_callback(info.GetIsolate(), holder, property, V8JS_PROP_GETTER); if(ret_value.IsEmpty()) { v8::Local arr = v8::Array::New(isolate); diff --git a/v8js_object_export.cc b/v8js_object_export.cc index 8027d380..46163b90 100644 --- a/v8js_object_export.cc +++ b/v8js_object_export.cc @@ -212,7 +212,11 @@ static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, c return_value = v8js_propagate_exception(ctx); } else if (Z_TYPE(retval) == IS_OBJECT && Z_OBJ(retval) == object) { // special case: "return $this" + #if PHP_V8_API_VERSION >= 13000000 + return_value = info.This(); + #else return_value = info.Holder(); + #endif } else { return_value = zval_to_v8js(&retval, isolate); } @@ -227,7 +231,12 @@ static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, c /* Callback for PHP methods and functions */ void v8js_php_callback(const v8::FunctionCallbackInfo& info) /* {{{ */ { - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif zend_object *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); zend_function *method_ptr; @@ -364,7 +373,12 @@ static void v8js_named_property_enumerator(const v8::PropertyCallbackInfo v8_context = isolate->GetEnteredOrMicrotaskContext(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif v8::Local result = v8::Array::New(isolate, 0); uint32_t result_len = 0; @@ -466,7 +480,12 @@ static void v8js_invoke_callback(const v8::FunctionCallbackInfo& info v8::Isolate *isolate = info.GetIsolate(); v8::Local v8_context = isolate->GetEnteredOrMicrotaskContext(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif v8::Local cb = v8::Local::Cast(info.Data()); int argc = info.Length(), i; v8::Local *argv = static_cast *>(alloca(sizeof(v8::Local) * argc)); @@ -511,7 +530,12 @@ static void v8js_fake_call_impl(const v8::FunctionCallbackInfo& info) v8::Isolate *isolate = info.GetIsolate(); v8::Local v8_context = isolate->GetEnteredOrMicrotaskContext(); - v8::Local self = info.Holder(); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif v8::Local return_value = V8JS_NULL; char *error; @@ -859,7 +883,13 @@ v8::Local v8js_named_property_callback(v8::Isolate *isolate, v8::Loca static V8JS_INTERCEPTED v8js_named_property_getter(v8::Local property, const v8::PropertyCallbackInfo &info) /* {{{ */ { - v8::Local r = v8js_named_property_callback(info.GetIsolate(), info.Holder(), property, V8JS_PROP_GETTER); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif + v8::Local r = v8js_named_property_callback(info.GetIsolate(), self, property, V8JS_PROP_GETTER); if (r.IsEmpty()) { return V8JS_INTERCEPTED_NO; @@ -872,7 +902,13 @@ static V8JS_INTERCEPTED v8js_named_property_getter(v8::Local property, static V8JS_INTERCEPTED v8js_named_property_setter(v8::Local property, v8::Local value, const V8JS_SETTER_PROPERTY_CALLBACK_INFO &info) /* {{{ */ { - v8::Local r = v8js_named_property_callback(info.GetIsolate(), info.Holder(), property, V8JS_PROP_SETTER, value); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif + v8::Local r = v8js_named_property_callback(info.GetIsolate(), self, property, V8JS_PROP_SETTER, value); #if PHP_V8_HAS_INTERCEPTED return r.IsEmpty() ? v8::Intercepted::kNo : v8::Intercepted::kYes; #else @@ -883,7 +919,13 @@ static V8JS_INTERCEPTED v8js_named_property_setter(v8::Local property, static V8JS_INTERCEPTED v8js_named_property_query(v8::Local property, const v8::PropertyCallbackInfo &info) /* {{{ */ { - v8::Local r = v8js_named_property_callback(info.GetIsolate(), info.Holder(), property, V8JS_PROP_QUERY); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif + v8::Local r = v8js_named_property_callback(info.GetIsolate(), self, property, V8JS_PROP_QUERY); if (r.IsEmpty()) { return V8JS_INTERCEPTED_NO; } @@ -901,7 +943,13 @@ static V8JS_INTERCEPTED v8js_named_property_query(v8::Local property, static V8JS_INTERCEPTED v8js_named_property_deleter(v8::Local property, const v8::PropertyCallbackInfo &info) /* {{{ */ { - v8::Local r = v8js_named_property_callback(info.GetIsolate(), info.Holder(), property, V8JS_PROP_DELETER); + v8::Local self; + #if PHP_V8_API_VERSION >= 13000000 + self = info.This(); + #else + self = info.Holder(); + #endif + v8::Local r = v8js_named_property_callback(info.GetIsolate(), self, property, V8JS_PROP_DELETER); if (r.IsEmpty()) { return V8JS_INTERCEPTED_NO; }