فهرست منبع

Merge remote-tracking branch 'origin/master' into php7

Conflicts:
	README.md
	v8js.cc
	v8js_class.cc
	v8js_exceptions.cc
	v8js_object_export.cc
	v8js_v8.cc
	v8js_v8object_class.cc
Stefan Siegl 9 سال پیش
والد
کامیت
5cba44ccd0

+ 5 - 0
Makefile.frag

@@ -3,6 +3,11 @@ ifneq (,$(realpath $(EXTENSION_DIR)/json.so))
 PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/json.so
 endif
 
+# add pthreads extension, if available
+ifneq (,$(realpath $(EXTENSION_DIR)/pthreads.so))
+PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/pthreads.so
+endif
+
 testv8: all
 	$(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
 

+ 4 - 4
README.Win32.md

@@ -52,7 +52,7 @@ svn co http://src.chromium.org/svn/trunk/deps/third_party/cygwin@66844 third_par
 svn co http://googletest.googlecode.com/svn/trunk testing/gtest --revision 643
 svn co http://googlemock.googlecode.com/svn/trunk testing/gmock --revision 410
 
-python build\gyp_v8 -Dcomponent=shared_library
+python build\gyp_v8 -Dcomponent=shared_library -Dv8_use_snapshot=0
 "\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.com" /build Release build/All.sln
 ```
 
@@ -97,9 +97,9 @@ mkdir vc12\x86\deps\lib
   and unpack to below `\php-sdk\phpdev\vc12\x86`
 * from `\v8\build\Release\lib` copy `icui18n.lib`, `icuuc.lib` and `v8.lib`
   to deps\lib folder
-* from `\v8\include copy` all v8*.h files to deps\include folder
-* within the PHP source code folder create a sub-directory named `pecl`
-* download V8Js and unpack it into a seperate directory below the `pecl` folder
+* from `\v8\include` copy all v8*.h files to deps\include folder
+* download V8Js and unpack it into a separate directory below `ext` folder
+* make sure `config.w32` file inside that folder defines version of the compiled v8
 
 (still in "VS2013 x86 Native Tools Command Prompt")
 

+ 28 - 2
README.md

@@ -17,7 +17,7 @@ of this repository.
 Minimum requirements
 --------------------
 
-- V8 Javascript Engine library (libv8) master <https://github.com/v8/v8/> (trunk)
+- V8 Javascript Engine library (libv8) master <https://github.com/v8/v8-git-mirror> (trunk)
 
 	V8 is Google's open source Javascript engine.
 	V8 is written in C++ and is used in Google Chrome, the open source browser from Google.
@@ -26,7 +26,7 @@ Minimum requirements
 
 - PHP 7.0.0+
 
-  This embedded implementation of the V8 engine uses thread locking so it should work with ZTS enabled.
+  This embedded implementation of the V8 engine uses thread locking so it works with ZTS enabled.
 
 
 Compiling latest version
@@ -55,6 +55,7 @@ class V8Js
 
     const FLAG_NONE = 1;
     const FLAG_FORCE_ARRAY = 2;
+    const FLAG_PROPAGATE_PHP_EXCEPTIONS = 4;
 
     const DEBUG_AUTO_BREAK_NEVER = 1;
     const DEBUG_AUTO_BREAK_ONCE = 2;
@@ -304,3 +305,28 @@ PHP Objects implementing ArrayAccess, Countable
 The above rule that PHP objects are generally converted to JavaScript objects also applies to PHP objects of `ArrayObject` type or other classes, that implement both the `ArrayAccess` and the `Countable` interface -- even so they behave like PHP arrays.
 
 This behaviour can be changed by enabling the php.ini flag `v8js.use_array_access`.  If set, objects of PHP classes that implement the aforementioned interfaces are converted to JavaScript Array-like objects.  This is by-index access of this object results in immediate calls to the `offsetGet` or `offsetSet` PHP methods (effectively this is live-binding of JavaScript against the PHP object).  Such an Array-esque object also supports calling every attached public method of the PHP object + methods of JavaScript's native Array.prototype methods (as long as they are not overloaded by PHP methods).
+
+Exceptions
+==========
+
+If the JavaScript code throws (without catching), causes errors or doesn't
+compile, `V8JsScriptException` exceptions are thrown unless the `V8Js` object
+is constructed with `report_uncaught_exceptions` set `FALSE`.
+
+PHP exceptions that occur due to calls from JavaScript code by default are
+*not* re-thrown into JavaScript context but cause the JavaScript execution to
+be stopped immediately and then are reported at the location calling the JS code.
+
+This behaviour can be changed by setting the `FLAG_PROPAGATE_PHP_EXCEPTIONS`
+flag.  If it is set, PHP exception (objects) are converted to JavaScript
+objects obeying the above rules and re-thrown in JavaScript context.  If they
+are not caught by JavaScript code the execution stops and a
+`V8JsScriptException` is thrown, which has the original PHP exception accessible
+via `getPrevious` method.
+
+V8Js versions 0.2.4 and before did not stop JS code execution on PHP exceptions,
+but silently ignored them (even so succeeding PHP calls from within the same piece
+of JS code were not executed by the PHP engine).  This behaviour is considered as
+a bug and hence was fixed with 0.2.5 release.  Nevertheless there is a 
+compatibility php.ini switch (`v8js.compat_php_exceptions`) which turns previous
+behaviour back on.

+ 124 - 28
package.xml

@@ -16,11 +16,11 @@
   <email>[email protected]</email>
   <active>yes</active>
  </lead>
- <date>2015-07-26</date>
- <time>00:04:15</time>
+ <date>2015-09-26</date>
+ <time>12:31:47</time>
  <version>
-  <release>0.2.1</release>
-  <api>0.2.1</api>
+  <release>0.2.6</release>
+  <api>0.2.6</api>
  </version>
  <stability>
   <release>beta</release>
@@ -28,10 +28,7 @@
  </stability>
  <license uri="http://www.php.net/license">The MIT License (MIT)</license>
  <notes>
-- adapt to latest v8 API (v8 versions from 3.24.6 up to latest 4.6 branch supported now)
-- fixed FLAG_FORCE_ARRAY behaviour regarding property assignments
-- properly stop (and restart) timer thread (for memory &amp; cpu limits)
-- fixed crash on failed module bootstrapping
+- Fix reference counting issue on PHP-&gt;JS-&gt;PHP exception propagation
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
@@ -69,8 +66,15 @@
    <file baseinstalldir="/" md5sum="72f2ffb206047d5918d4eb5f32284e3c" name="tests/checkstring_compile.phpt" role="test" />
    <file baseinstalldir="/" md5sum="0e6c4098d0f370b2fa8f433ab6026c6a" name="tests/closures_basic.phpt" role="test" />
    <file baseinstalldir="/" md5sum="1f5c7e8895220923d0203653fbebfc6f" name="tests/closures_dynamic.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="50f7ba3626131cf015e26b7dc296d20d" name="tests/commonjs_caching_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="9bcac28a73d4d274c0e62802fd7af96b" name="tests/commonjs_caching_002.phpt" role="test" />
    <file baseinstalldir="/" md5sum="90c628544fa6f401221237511a9a4fb7" name="tests/commonjs_modules.phpt" role="test" />
    <file baseinstalldir="/" md5sum="24e2a74c0d15b94cbcdc926d1e19af0c" name="tests/commonjs_multiassign.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="1d7a8f251186c47ce92fe7b1fbb0abc0" name="tests/commonjs_normalise_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="3d705ba0a7c22a73be170c9bae2303ba" name="tests/commonjs_normalise_002.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="8e0e5d784e6f7f896dcc94acbf909bbc" name="tests/commonjs_normalise_003.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="b573c7fa2e53c8d20eb88dfb747811fc" name="tests/commonjs_normalise_004.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="e9eb81a3065e2858d79fb772e837982c" name="tests/commonjs_normalise_005.phpt" role="test" />
    <file baseinstalldir="/" md5sum="6980e6a4c02cf3de87c0eab762fe2a69" name="tests/compile_string.phpt" role="test" />
    <file baseinstalldir="/" md5sum="177659c1f2be8fb1b018341f896b7cd6" name="tests/compile_string_isolate.phpt" role="test" />
    <file baseinstalldir="/" md5sum="bf4fed6b841034477cb61e3303fb1362" name="tests/construct.phpt" role="test" />
@@ -88,7 +92,7 @@
    <file baseinstalldir="/" md5sum="7da1f96584a7ed1edd19c73dd80f01d6" name="tests/exception.phpt" role="test" />
    <file baseinstalldir="/" md5sum="1942e8949e4b323d6ef92aef92334103" name="tests/exception_clearing.phpt" role="test" />
    <file baseinstalldir="/" md5sum="446308298f2562a0dd7779486fa561aa" name="tests/exception_propagation_1.phpt" role="test" />
-   <file baseinstalldir="/" md5sum="74026d3872c248782bf98dcc8e05ef41" name="tests/exception_propagation_2.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="fafb380c87c0241f18fc4b5d318b282f" name="tests/exception_propagation_2.phpt" role="test" />
    <file baseinstalldir="/" md5sum="f90813f9ac47107b4630461816ad6221" name="tests/exception_propagation_3.phpt" role="test" />
    <file baseinstalldir="/" md5sum="cf4ef4abb30a47214e7f368b378f54a8" name="tests/exception_start_column.phpt" role="test" />
    <file baseinstalldir="/" md5sum="8ed403ca3798d987b0f29e0132c7686f" name="tests/execute_flags.phpt" role="test" />
@@ -111,7 +115,9 @@
    <file baseinstalldir="/" md5sum="7d240e23d061f59599109cc679084da4" name="tests/has_property_after_dispose.phpt" role="test" />
    <file baseinstalldir="/" md5sum="1443aef2fda8793abd79c06a29639797" name="tests/inheritance_basic.phpt" role="test" />
    <file baseinstalldir="/" md5sum="afdb74aca312497cce114a8d9dba6ee9" name="tests/issue_116-v8function-injection.phpt" role="test" />
-   <file baseinstalldir="/" md5sum="422d9e9af28d9c7e8042bbf9496fc04c" name="tests/issue_127_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="6d4e573daaf2ca5c177230541c31fc96" name="tests/issue_127_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="3ff639cdb2f80e8b0a256aa12fce9c5d" name="tests/issue_156_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="858c759b267b903dcdd65b5f208dc07f" name="tests/issue_160_basic.phpt" role="test" />
    <file baseinstalldir="/" md5sum="9f1e697d63231a03da06de97c14a5076" name="tests/js-construct-basic.phpt" role="test" />
    <file baseinstalldir="/" md5sum="0e951523a9abae08b531ecd3193a2581" name="tests/js-construct-direct-call.phpt" role="test" />
    <file baseinstalldir="/" md5sum="7733d7eb9693e1c799a3d071a7804b13" name="tests/js-construct-protected-ctor.phpt" role="test" />
@@ -129,6 +135,13 @@
    <file baseinstalldir="/" md5sum="ed1d6d0aafe39f41545c375507507564" name="tests/object_passback.phpt" role="test" />
    <file baseinstalldir="/" md5sum="95e8658755180e9dc7533c0ed4d61bb2" name="tests/object_prototype.phpt" role="test" />
    <file baseinstalldir="/" md5sum="732770da7b148dcb702894dcb9462674" name="tests/object_reuse.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="e49bb15778697eea7d717de4cfc0c385" name="tests/php_exceptions_001.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="bc07bbc424d078d3d0590540a67648ba" name="tests/php_exceptions_002.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="3913092b6336924271fd9315a87d0973" name="tests/php_exceptions_003.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="66c0769781344af35f63d5d1b7a50852" name="tests/php_exceptions_004.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="610889e09e1579075ef973ef41883e8f" name="tests/php_exceptions_005.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="59e80f305f84f2effbac279261f8c5a6" name="tests/php_exceptions_006.phpt" role="test" />
+   <file baseinstalldir="/" md5sum="c513770deccefc87636a30aa4329f779" name="tests/php_exceptions_basic.phpt" role="test" />
    <file baseinstalldir="/" md5sum="3cc6c105f2413e4270eba038e6a2dffd" name="tests/property_exists.phpt" role="test" />
    <file baseinstalldir="/" md5sum="065d238a0c45902ce226622847e5e341" name="tests/property_visibility-delete.phpt" role="test" />
    <file baseinstalldir="/" md5sum="4aa538bcaa3ab56b2fad8b6105ec1717" name="tests/property_visibility-enumerate.phpt" role="test" />
@@ -158,39 +171,40 @@
    <file baseinstalldir="/" md5sum="35ce3816ae00e697fca26142c46e0c79" name="tests/v8_write_property.phpt" role="test" />
    <file baseinstalldir="/" md5sum="ae504a63e5ff800e3aa7d529835d0e8e" name="tests/variable_passing.phpt" role="test" />
    <file baseinstalldir="/" md5sum="1bd7738aeeb5cf80d80561554f59f2ed" name="tests/var_dump.phpt" role="test" />
-   <file baseinstalldir="/" md5sum="19150f213fb00790c51f22989b39ff55" name="config.m4" role="src" />
+   <file baseinstalldir="/" md5sum="63c4b2873ccc935571ae7fbb1baeab7b" name="config.m4" role="src" />
    <file baseinstalldir="/" md5sum="987d834d2edc84ead98dc1fddba2ad73" name="config.w32" role="src" />
    <file baseinstalldir="/" md5sum="cea72666538d5b0b80a64ccdbda24919" name="CREDITS" role="doc" />
    <file baseinstalldir="/" md5sum="9f5b5f41204bcde55d9df87d5a970b30" name="LICENSE" role="doc" />
    <file baseinstalldir="/" md5sum="25260e0bc3111b01f700fad13544d6a9" name="Makefile.frag" role="src" />
    <file baseinstalldir="/" md5sum="31e331386def7ce98943694151c0d5cb" name="Makefile.travis" role="src" />
    <file baseinstalldir="/" md5sum="0e23fa6446e52a3b1cff8b18a6e0bd79" name="php_v8js.h" role="src" />
-   <file baseinstalldir="/" md5sum="b81ab8ff4f87d883363b02fe358da87c" name="php_v8js_macros.h" role="src" />
-   <file baseinstalldir="/" md5sum="8d36541e788d9f2de7d19d4e167a1b3b" name="README.Linux.md" role="doc" />
+   <file baseinstalldir="/" md5sum="0d986531818b0e31633f2db3a242afb7" name="php_v8js_macros.h" role="src" />
+   <file baseinstalldir="/" md5sum="ec19e63ca9310bfc4dc4dbd357c779ae" name="README.Linux.md" role="doc" />
    <file baseinstalldir="/" md5sum="4a65a3f9995d325a2c2ccb23224ea503" name="README.MacOS.md" role="doc" />
-   <file baseinstalldir="/" md5sum="00cc5dd1c69120c7156abd08efafacba" name="README.md" role="doc" />
-   <file baseinstalldir="/" md5sum="f7baf040ec2145f7eeccd5540ebb085f" name="README.Win32.md" role="doc" />
+   <file baseinstalldir="/" md5sum="177459a9628e3c8c31b305f20c970f8d" name="README.md" role="doc" />
+   <file baseinstalldir="/" md5sum="9839870e001306943797003e8828d855" name="README.Win32.md" role="doc" />
    <file baseinstalldir="/" md5sum="542f52c54898f33ac53b173970cba305" name="test.php" role="php" />
    <file baseinstalldir="/" md5sum="65294fadb5ed766094b1f587fc20ad37" name="TODO" role="doc" />
-   <file baseinstalldir="/" md5sum="d1e8223596fac67062055261b6cf1180" name="v8js.cc" role="src" />
+   <file baseinstalldir="/" md5sum="cc54d77b4d0044d7b143989f2dc12b94" name="v8js.cc" role="src" />
    <file baseinstalldir="/" md5sum="358c628b2627319e40fd7e5092f19872" name="v8js_array_access.cc" role="src" />
    <file baseinstalldir="/" md5sum="7baf3fe5b77d1374b39a1d8332e05df4" name="v8js_array_access.h" role="src" />
-   <file baseinstalldir="/" md5sum="5504a5f186152a48ef3b3819bd53a676" name="v8js_class.cc" role="src" />
-   <file baseinstalldir="/" md5sum="afd1c86428a25cc71c35a6f9e3ea04bb" name="v8js_class.h" role="src" />
-   <file baseinstalldir="/" md5sum="795e65a077e963de413eb6947edd1f94" name="v8js_commonjs.cc" role="src" />
-   <file baseinstalldir="/" md5sum="6b3d174805b6c97bdec88b9479e8ce6c" name="v8js_convert.cc" role="src" />
+   <file baseinstalldir="/" md5sum="6c213918edf4f46ac630498e92bb99f6" name="v8js_class.cc" role="src" />
+   <file baseinstalldir="/" md5sum="444a6fda6259076cd2a419cf59ab2c42" name="v8js_class.h" role="src" />
+   <file baseinstalldir="/" md5sum="88b49988a5ef55edbd7ba085e7857f64" name="v8js_commonjs.cc" role="src" />
+   <file baseinstalldir="/" md5sum="32a5d1a65f64ec37ec294f496fc11ff1" name="v8js_commonjs.h" role="src" />
+   <file baseinstalldir="/" md5sum="c061344705c42fb705bffb2959fc1001" name="v8js_convert.cc" role="src" />
    <file baseinstalldir="/" md5sum="ede2cf80141b1831c7e7ab50dc57236f" name="v8js_debug.cc" role="src" />
    <file baseinstalldir="/" md5sum="cbdb6ed29c9ece278aa2aeab75dbe61f" name="v8js_debug.h" role="src" />
-   <file baseinstalldir="/" md5sum="1867ebcefbc577aed051f40d9ccc58ad" name="v8js_exceptions.cc" role="src" />
-   <file baseinstalldir="/" md5sum="587370513a019c34f2ddaac3b1f4cbf8" name="v8js_exceptions.h" role="src" />
-   <file baseinstalldir="/" md5sum="3b5845c5f4109366257a9e35975703ac" name="v8js_methods.cc" role="src" />
-   <file baseinstalldir="/" md5sum="55bfab106f9a92e8909aa6fba344037c" name="v8js_object_export.cc" role="src" />
+   <file baseinstalldir="/" md5sum="40b66c44650a8127618c7fc48bf4b0b2" name="v8js_exceptions.cc" role="src" />
+   <file baseinstalldir="/" md5sum="9d13bf5f413c2d76664670e847e1a801" name="v8js_exceptions.h" role="src" />
+   <file baseinstalldir="/" md5sum="9f3ad8c136cdc3ebc2bdf993491f9ad8" name="v8js_methods.cc" role="src" />
+   <file baseinstalldir="/" md5sum="d688b8eb822736d49f7282d22546d6bc" name="v8js_object_export.cc" role="src" />
    <file baseinstalldir="/" md5sum="281fb591fbebc3d23e04196cdb3ec64a" name="v8js_object_export.h" role="src" />
-   <file baseinstalldir="/" md5sum="767d38eec0b407fba5e30398444e180e" name="v8js_timer.cc" role="src" />
+   <file baseinstalldir="/" md5sum="d96c0e1eeaf1693813236f7e5da61e09" name="v8js_timer.cc" role="src" />
    <file baseinstalldir="/" md5sum="49f609c8cea6033f2ad1e6c9c829a571" name="v8js_timer.h" role="src" />
-   <file baseinstalldir="/" md5sum="41b036ea855a1c8682a93d50dd834a67" name="v8js_v8.cc" role="src" />
-   <file baseinstalldir="/" md5sum="72cc4af7af63c62138d4156faf784ec9" name="v8js_v8.h" role="src" />
-   <file baseinstalldir="/" md5sum="842fb2dea63c473cdfaf501321a6a530" name="v8js_v8object_class.cc" role="src" />
+   <file baseinstalldir="/" md5sum="b3ba6b76f92683c55b45bce351af887e" name="v8js_v8.cc" role="src" />
+   <file baseinstalldir="/" md5sum="0c4829d52ff46116c381b1b66ec27541" name="v8js_v8.h" role="src" />
+   <file baseinstalldir="/" md5sum="82908f4e741755efa2aedfb486945a40" name="v8js_v8object_class.cc" role="src" />
    <file baseinstalldir="/" md5sum="8a80d71ff40dfa833d3b58ac94475a9f" name="v8js_v8object_class.h" role="src" />
    <file baseinstalldir="/" md5sum="29be67d9bf8bfb1642d1219356109063" name="v8js_variables.cc" role="src" />
   </dir>
@@ -331,5 +345,87 @@
 - fixed crash on failed module bootstrapping
    </notes>
   </release>
+  <release>
+   <version>
+    <release>0.2.2</release>
+    <api>0.2.2</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-08-26</date>
+   <license uri="http://www.php.net/license">The MIT License (MIT)</license>
+   <notes>
+- Fix CommonJS module caching
+- Fix use-after-free issue on CommonJS module reuse
+- Fix memory leaks in CommonJS module loader
+- Fix memory leak regarding lost script resources (compileScript call et al)
+- Improve V8Function call performance
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.2.3</release>
+    <api>0.2.3</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-08-27</date>
+   <license uri="http://www.php.net/license">The MIT License (MIT)</license>
+   <notes>
+- Fix FLAG_FORCE_ARRAY affecting V8Function objects
+- Fix memory leak with repeated calls of methods on exported PHP objects
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.2.4</release>
+    <api>0.2.4</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-09-01</date>
+   <license uri="http://www.php.net/license">The MIT License (MIT)</license>
+   <notes>
+- Fix memory leak with repeated Array exports from PHP to JS
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.2.5</release>
+    <api>0.2.5</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-09-23</date>
+   <license uri="http://www.php.net/license">The MIT License (MIT)</license>
+   <notes>
+- Stop JS execution on PHP exceptions (instead of continuing silently)
+- Allow propagation of PHP exceptions to JS context (disabled by default)
+- Add v8js.compat_php_exceptions php.ini switch to switch previous behaviour back on
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.2.6</release>
+    <api>0.2.6</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-09-26</date>
+   <license uri="http://www.php.net/license">The MIT License (MIT)</license>
+   <notes>
+- Fix reference counting issue on PHP-&gt;JS-&gt;PHP exception propagation
+   </notes>
+  </release>
  </changelog>
 </package>

+ 37 - 10
php_v8js_macros.h

@@ -52,7 +52,7 @@ extern "C" {
 #endif
 
 /* V8Js Version */
-#define PHP_V8JS_VERSION "0.2.1"
+#define PHP_V8JS_VERSION "0.2.6"
 
 /* Hidden field name used to link JS wrappers with underlying PHP object */
 #define PHPJS_OBJECT_KEY "phpjs::object"
@@ -69,13 +69,10 @@ extern "C" {
 # define V8JS_CONST (char *)
 #endif
 
-/* Global flags */
-#define V8JS_GLOBAL_SET_FLAGS(isolate,flags)	V8JS_GLOBAL(isolate)->SetHiddenValue(V8JS_SYM("__php_flags__"), V8JS_INT(flags))
-#define V8JS_GLOBAL_GET_FLAGS(isolate)			V8JS_GLOBAL(isolate)->GetHiddenValue(V8JS_SYM("__php_flags__"))->IntegerValue();
-
 /* Options */
 #define V8JS_FLAG_NONE			(1<<0)
 #define V8JS_FLAG_FORCE_ARRAY	(1<<1)
+#define V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS	(1<<2)
 
 #define V8JS_DEBUG_AUTO_BREAK_NEVER		0
 #define V8JS_DEBUG_AUTO_BREAK_ONCE		1
@@ -104,16 +101,13 @@ struct v8js_timer_ctx;
 
 /* Module globals */
 ZEND_BEGIN_MODULE_GLOBALS(v8js)
+  // Thread-local cache whether V8 has been initialized so far
   int v8_initialized;
-#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-  v8::Platform *v8_platform;
-#endif
-  HashTable *extensions;
 
   /* Ini globals */
-  char *v8_flags; /* V8 command line flags */
   bool use_date; /* Generate JS Date objects instead of PHP DateTime */
   bool use_array_access; /* Convert ArrayAccess, Countable objects to array-like objects */
+  bool compat_php_exceptions; /* Don't stop JS execution on PHP exception */
 
   // Timer thread globals
   std::deque<v8js_timer_ctx *> timer_stack;
@@ -130,6 +124,39 @@ ZEND_EXTERN_MODULE_GLOBALS(v8js)
 
 #define V8JSG(v) ZEND_MODULE_GLOBALS_ACCESSOR(v8js, v)
 
+/*
+ *  Process-wide globals
+ *
+ * The zend_v8js_globals structure declared above is created once per thread
+ * (in a ZTS environment).  If a multi-threaded PHP process uses V8 there is
+ * some stuff shared among all of these threads
+ *
+ *  - whether V8 has been initialized at all
+ *  - the V8 backend platform
+ *  - loaded extensions
+ *  - V8 "command line" flags
+ *
+ * In a ZTS-enabled environment access to all of these variables must happen
+ * while holding a mutex lock.
+ */
+struct _v8js_process_globals {
+#ifdef ZTS
+	int v8_initialized;
+	std::mutex lock;
+#endif
+
+	HashTable *extensions;
+
+	/* V8 command line flags */
+	char *v8_flags;
+
+#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
+	v8::Platform *v8_platform;
+#endif
+};
+
+extern struct _v8js_process_globals v8js_process_globals;
+
 /* Register builtin methods into passed object */
 void v8js_register_methods(v8::Handle<v8::ObjectTemplate>, v8js_ctx *c);
 

+ 6 - 2
tests/exception_propagation_2.phpt

@@ -14,6 +14,9 @@ class Foo {
 		$this->v8->foo = $this;
 		$this->v8->executeString('fooobar', 'throw_0');
 		var_dump($this->v8->getPendingException());
+		// the exception is not cleared before the next executeString call,
+		// hence the next *exiting* executeString will throw.
+		// In this case this is the executeString call in bar() function.
 		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch1');
 		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch2');
 	}
@@ -21,6 +24,8 @@ class Foo {
 	public function bar()
 	{
 		echo "To Bar!\n";
+		// This executeString call throws a PHP exception, not propagated
+		// to JS, hence immediately triggering the top-level catch handler.
 		$this->v8->executeString('throw new Error();', 'throw_1');
 	}
 }
@@ -71,7 +76,7 @@ object(V8JsScriptException)#%d (13) {
       ["file"]=>
       string(%d) "%s"
       ["line"]=>
-      int(24)
+      int(29)
       ["function"]=>
       string(11) "__construct"
       ["class"]=>
@@ -100,6 +105,5 @@ object(V8JsScriptException)#%d (13) {
     at throw_0:1:1"
 }
 To Bar!
-Error caught!
 PHP Exception: throw_0:1: ReferenceError: fooobar is not defined
 ===EOF===

+ 33 - 0
tests/issue_156_001.phpt

@@ -0,0 +1,33 @@
+--TEST--
+Test V8::executeString() : Backwards compatibility for issue #156
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--INI--
+v8js.compat_php_exceptions = 1
+--FILE--
+<?php
+
+$v8 = new V8Js();
+
+$v8->throwPHPException = function () {
+    echo "throwing PHP exception now ...\n";
+    throw new \Exception('foo');
+};
+
+$JS = <<< EOT
+PHP.throwPHPException();
+print("... old behaviour was to not stop JS execution on PHP exceptions\\n");
+EOT;
+
+try {
+    $v8->executeString($JS, 'issue_156_001.js');
+} catch(Exception $e) {
+    var_dump($e->getMessage());
+}
+?>
+===EOF===
+--EXPECT--
+throwing PHP exception now ...
+... old behaviour was to not stop JS execution on PHP exceptions
+string(3) "foo"
+===EOF===

+ 23 - 0
tests/issue_160_basic.phpt

@@ -0,0 +1,23 @@
+--TEST--
+Test V8::executeString() : Issue #160 V8Function affected by V8Js::FLAG_FORCE_ARRAY
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+$v8 = new V8Js();
+
+$JS = <<<EOT
+(function(foo) { print(foo); });
+EOT;
+
+$func = $v8->executeString($JS, 'test', V8Js::FLAG_FORCE_ARRAY);
+
+var_dump($func);
+$func("Test-Foo Func Call\n");
+?>
+===EOF===
+--EXPECTF--
+object(V8Function)#%d (0) {
+}
+Test-Foo Func Call
+===EOF===

+ 50 - 0
tests/php_exceptions_001.phpt

@@ -0,0 +1,50 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (repeated)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function throwException() {
+	throw new \Exception("Test-Exception");
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+try {
+    PHP.foo.throwException();
+    // the exception should abort further execution,
+    // hence the print must not pop up
+    print("after throwException\\n");
+} catch(e) {
+    // JS should not catch in default mode
+    print("JS caught exception");
+}
+EOT;
+
+for($i = 0; $i < 5; $i ++) {
+    var_dump($i);
+    try {
+        $v8->executeString($JS);
+    } catch (Exception $e) {
+        var_dump($e->getMessage());
+    }
+}
+?>
+===EOF===
+--EXPECTF--
+int(0)
+string(14) "Test-Exception"
+int(1)
+string(14) "Test-Exception"
+int(2)
+string(14) "Test-Exception"
+int(3)
+string(14) "Test-Exception"
+int(4)
+string(14) "Test-Exception"
+===EOF===

+ 67 - 0
tests/php_exceptions_002.phpt

@@ -0,0 +1,67 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (multi-level)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function throwException() {
+	throw new \Exception("Test-Exception");
+    }
+
+    function recurse($i) {
+        echo "recurse[$i] ...\n";
+        global $work;
+        $work($i);
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$work = $v8->executeString(<<<EOT
+var work = function(level) {
+  if(level--) {
+    PHP.foo.recurse(level);
+  }
+  else {
+    PHP.foo.throwException();
+  }
+};
+work;
+EOT
+);
+
+for($i = 0; $i < 5; $i ++) {
+    var_dump($i);
+    try {
+        $work($i);
+    } catch (Exception $e) {
+        var_dump($e->getMessage());
+    }
+}
+?>
+===EOF===
+--EXPECT--
+int(0)
+string(14) "Test-Exception"
+int(1)
+recurse[0] ...
+string(14) "Test-Exception"
+int(2)
+recurse[1] ...
+recurse[0] ...
+string(14) "Test-Exception"
+int(3)
+recurse[2] ...
+recurse[1] ...
+recurse[0] ...
+string(14) "Test-Exception"
+int(4)
+recurse[3] ...
+recurse[2] ...
+recurse[1] ...
+recurse[0] ...
+string(14) "Test-Exception"
+===EOF===

+ 36 - 0
tests/php_exceptions_003.phpt

@@ -0,0 +1,36 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (basic JS propagation)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function throwException() {
+	throw new \Exception("Test-Exception");
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+try {
+    PHP.foo.throwException();
+    // the exception should abort further execution,
+    // hence the print must not pop up
+    print("after throwException\\n");
+} catch(e) {
+    print("JS caught exception!\\n");
+    var_dump(e.getMessage());
+}
+EOT;
+
+$v8->executeString($JS, 'php_exceptions_003', V8Js::FLAG_PROPAGATE_PHP_EXCEPTIONS);
+
+?>
+===EOF===
+--EXPECTF--
+JS caught exception!
+string(14) "Test-Exception"
+===EOF===

+ 36 - 0
tests/php_exceptions_004.phpt

@@ -0,0 +1,36 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (PHP->JS->PHP back propagation)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function throwException() {
+	throw new \Exception("Test-Exception");
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+PHP.foo.throwException();
+// the exception should abort further execution,
+// hence the print must not pop up
+print("after throwException\\n");
+EOT;
+
+try {
+    $v8->executeString($JS, 'php_exceptions_004', V8Js::FLAG_PROPAGATE_PHP_EXCEPTIONS);
+}
+catch(V8JsScriptException $e) {
+    echo "Got V8JsScriptException\n";
+    var_dump($e->getPrevious()->getMessage());
+}
+?>
+===EOF===
+--EXPECTF--
+Got V8JsScriptException
+string(14) "Test-Exception"
+===EOF===

+ 43 - 0
tests/php_exceptions_005.phpt

@@ -0,0 +1,43 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (JS throw PHP-exception)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function getException() {
+        return new \Exception("Test-Exception");
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+var ex = PHP.foo.getException();
+print("after getException\\n");
+throw ex;
+print("after throw\\n");
+EOT;
+
+try {
+    $v8->executeString($JS, 'php_exceptions_005');
+}
+catch(V8JsScriptException $e) {
+    echo "Got V8JsScriptException\n";
+    var_dump($e->getMessage());
+    var_dump($e->getPrevious()->getMessage());
+}
+?>
+===EOF===
+--EXPECTF--
+after getException
+Got V8JsScriptException
+string(%d) "php_exceptions_005:3: exception 'Exception' with message 'Test-Exception' in %s
+Stack trace:
+#0 [internal function]: Foo->getException()
+#1 %s: V8Js->executeString('var ex = PHP.fo...', 'php_exceptions_...')
+#2 {main}"
+string(14) "Test-Exception"
+===EOF===

+ 40 - 0
tests/php_exceptions_006.phpt

@@ -0,0 +1,40 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (JS throws normal PHP-object)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function getNonExceptionObject() {
+        return new \Foo();
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+var ex = PHP.foo.getNonExceptionObject();
+print("after getNonExceptionObject\\n");
+throw ex;
+print("after throw\\n");
+EOT;
+
+try {
+    $v8->executeString($JS, 'php_exceptions_006');
+}
+catch(V8JsScriptException $e) {
+    echo "Got V8JsScriptException\n";
+    var_dump($e->getMessage());
+    // previous exception should be NULL, as it is *not* a php exception
+    var_dump($e->getPrevious());
+}
+?>
+===EOF===
+--EXPECTF--
+after getNonExceptionObject
+Got V8JsScriptException
+string(34) "php_exceptions_006:3: [object Foo]"
+NULL
+===EOF===

+ 42 - 0
tests/php_exceptions_basic.phpt

@@ -0,0 +1,42 @@
+--TEST--
+Test V8::executeString() : PHP Exception handling (basic)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+    function throwException() {
+	throw new \Exception("Test-Exception");
+    }
+}
+
+$v8 = new V8Js();
+$v8->foo = new \Foo();
+
+$JS = <<< EOT
+try {
+    PHP.foo.throwException();
+    // the exception should abort further execution,
+    // hence the print must not pop up
+    print("after throwException\\n");
+} catch(e) {
+    // JS should not catch in default mode
+    print("JS caught exception");
+}
+EOT;
+
+try {
+    $v8->executeString($JS);
+} catch (Exception $e) {
+    var_dump($e->getMessage());
+    var_dump($e->getFile());
+    var_dump($e->getLine());
+}
+?>
+===EOF===
+--EXPECTF--
+string(14) "Test-Exception"
+string(%d) "%sphp_exceptions_basic.php"
+int(5)
+===EOF===

+ 65 - 0
tests/pthreads_001.phpt

@@ -0,0 +1,65 @@
+--TEST--
+Test V8::executeString() : Pthreads test #1
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . '/skipif.inc');
+if(!class_exists('Thread')) {
+    die('skip pthreads extension required');
+}
+?>
+--FILE--
+<?php
+
+class Workhorse extends Thread
+{
+    protected $val;
+
+    public function __construct($val)
+    {
+        $this->val = $val;
+    }
+
+    public function run()
+    {
+        $v8 = new V8Js();
+        $v8->val = $this->val;
+        $v8->sync_var_dump = function($value) {
+            $this->synchronized(function($thread) use ($value) {
+                    while(!$thread->readyToPrint) {
+                        $thread->wait();
+                    }
+                    var_dump($value);
+                    $thread->notify();
+                }, $this);
+        };
+
+        $v8->executeString('PHP.sync_var_dump(PHP.val);');
+    }
+}
+
+$foo = new Workhorse('foo');
+$bar = new Workhorse('bar');
+
+$foo->start();
+$bar->start();
+
+$bar->synchronized(function($thread) {
+    $thread->readyToPrint = true;
+    $thread->notify();
+    $thread->wait();
+}, $bar);
+
+$foo->synchronized(function($thread) {
+    $thread->readyToPrint = true;
+    $thread->notify();
+    $thread->wait();
+}, $foo);
+
+$foo->join();
+$bar->join();
+?>
+===EOF===
+--EXPECT--
+string(3) "bar"
+string(3) "foo"
+===EOF===

+ 74 - 43
v8js.cc

@@ -28,55 +28,77 @@ extern "C" {
 #include "v8js_v8object_class.h"
 
 ZEND_DECLARE_MODULE_GLOBALS(v8js)
+struct _v8js_process_globals v8js_process_globals;
 
 /* {{{ INI Settings */
 
 static ZEND_INI_MH(v8js_OnUpdateV8Flags) /* {{{ */
 {
+	bool immutable = false;
+
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+
+	if(v8js_process_globals.v8_initialized) {
+		v8js_process_globals.lock.unlock();
+		immutable = true;
+	}
+
+	v8js_process_globals.lock.unlock();
+#else
+	immutable = V8JSG(v8_initialized);
+#endif
+
+	if(immutable) {
+		/* V8 already has been initialized -> cannot be changed anymore */
+		return FAILURE;
+	}
+
 	if (new_value) {
-		if (V8JSG(v8_flags)) {
-			free(V8JSG(v8_flags));
-			V8JSG(v8_flags) = NULL;
+		if (v8js_process_globals.v8_flags) {
+			free(v8js_process_globals.v8_flags);
+			v8js_process_globals.v8_flags = NULL;
 		}
 		if (!new_value->val[0]) {
 			return FAILURE;
 		}
-		V8JSG(v8_flags) = zend_strndup(new_value->val, new_value->len);
+		v8js_process_globals.v8_flags = zend_strndup(new_value->val, new_value->len);
 	}
 
 	return SUCCESS;
 }
 
-static ZEND_INI_MH(v8js_OnUpdateUseDate) /* {{{ */
+static bool v8js_ini_to_bool(const zend_string *new_value) /* {{{ */
 {
-	bool value;
 	if (new_value->len == 2 && strcasecmp("on", new_value->val) == 0) {
-		value = (bool) 1;
+		return true;
     } else if (new_value->len == 3 && strcasecmp("yes", new_value->val) == 0) {
-		value = (bool) 1;
+		return true;
 	} else if (new_value->len == 4 && strcasecmp("true", new_value->val) == 0) {
-		value = (bool) 1;
+		return true;
 	} else {
-		value = (bool) atoi(new_value->val);
+		return (bool) atoi(new_value->val);
 	}
-	V8JSG(use_date) = value;
+}
+/* }}} */
+
+static ZEND_INI_MH(v8js_OnUpdateUseDate) /* {{{ */
+{
+	V8JSG(use_date) = v8js_ini_to_bool(new_value);
 	return SUCCESS;
 }
 /* }}} */
 
 static ZEND_INI_MH(v8js_OnUpdateUseArrayAccess) /* {{{ */
 {
-	bool value;
-	if (new_value->len == 2 && strcasecmp("on", new_value->val) == 0) {
-		value = (bool) 1;
-    } else if (new_value->len == 3 && strcasecmp("yes", new_value->val) == 0) {
-		value = (bool) 1;
-	} else if (new_value->len == 4 && strcasecmp("true", new_value->val) == 0) {
-		value = (bool) 1;
-	} else {
-		value = (bool) atoi(new_value->val);
-	}
-	V8JSG(use_array_access) = value;
+	V8JSG(use_array_access) = v8js_ini_to_bool(new_value);
+	return SUCCESS;
+}
+/* }}} */
+
+static ZEND_INI_MH(v8js_OnUpdateCompatExceptions) /* {{{ */
+{
+	V8JSG(compat_php_exceptions) = v8js_ini_to_bool(new_value);
 	return SUCCESS;
 }
 /* }}} */
@@ -85,6 +107,7 @@ ZEND_INI_BEGIN() /* {{{ */
 	ZEND_INI_ENTRY("v8js.flags", NULL, ZEND_INI_ALL, v8js_OnUpdateV8Flags)
 	ZEND_INI_ENTRY("v8js.use_date", "0", ZEND_INI_ALL, v8js_OnUpdateUseDate)
 	ZEND_INI_ENTRY("v8js.use_array_access", "0", ZEND_INI_ALL, v8js_OnUpdateUseArrayAccess)
+	ZEND_INI_ENTRY("v8js.compat_php_exceptions", "0", ZEND_INI_ALL, v8js_OnUpdateCompatExceptions)
 ZEND_INI_END()
 /* }}} */
 
@@ -119,6 +142,35 @@ PHP_MINIT_FUNCTION(v8js)
 static PHP_MSHUTDOWN_FUNCTION(v8js)
 {
 	UNREGISTER_INI_ENTRIES();
+
+	bool v8_initialized;
+
+#ifdef ZTS
+	v8_initialized = v8js_process_globals.v8_initialized;
+#else
+	v8_initialized = V8JSG(v8_initialized);
+#endif
+
+	if(v8_initialized) {
+		v8::V8::Dispose();
+#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
+		v8::V8::ShutdownPlatform();
+		// @fixme call virtual destructor somehow
+		//delete v8js_process_globals.v8_platform;
+#endif
+	}
+
+	if (v8js_process_globals.v8_flags) {
+		free(v8js_process_globals.v8_flags);
+		v8js_process_globals.v8_flags = NULL;
+	}
+
+	if (v8js_process_globals.extensions) {
+		zend_hash_destroy(v8js_process_globals.extensions);
+		free(v8js_process_globals.extensions);
+		v8js_process_globals.extensions = NULL;
+	}
+
 	return SUCCESS;
 }
 /* }}} */
@@ -174,9 +226,7 @@ static PHP_GINIT_FUNCTION(v8js)
 	  run the destructors manually.
 	*/
 #ifdef ZTS
-	v8js_globals->extensions = NULL;
 	v8js_globals->v8_initialized = 0;
-	v8js_globals->v8_flags = NULL;
 
 	v8js_globals->timer_thread = NULL;
 	v8js_globals->timer_stop = false;
@@ -192,29 +242,10 @@ static PHP_GINIT_FUNCTION(v8js)
  */
 static PHP_GSHUTDOWN_FUNCTION(v8js)
 {
-	if (v8js_globals->extensions) {
-		zend_hash_destroy(v8js_globals->extensions);
-		free(v8js_globals->extensions);
-		v8js_globals->extensions = NULL;
-	}
-
-	if (v8js_globals->v8_flags) {
-		free(v8js_globals->v8_flags);
-		v8js_globals->v8_flags = NULL;
-	}
-
 #ifdef ZTS
 	v8js_globals->timer_stack.~deque();
 	v8js_globals->timer_mutex.~mutex();
 #endif
-
-	if (v8js_globals->v8_initialized) {
-		v8::V8::Dispose();
-#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-		v8::V8::ShutdownPlatform();
-		delete v8js_globals->v8_platform;
-#endif
-	}
 }
 /* }}} */
 

+ 55 - 9
v8js_class.cc

@@ -2,12 +2,13 @@
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
-  | Copyright (c) 1997-2013 The PHP Group                                |
+  | Copyright (c) 1997-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | http://www.opensource.org/licenses/mit-license.php  MIT License      |
   +----------------------------------------------------------------------+
   | Author: Jani Taskinen <[email protected]>                         |
   | Author: Patrick Reilly <[email protected]>                             |
+  | Author: Stefan Siegl <[email protected]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -103,6 +104,22 @@ static void v8js_free_storage(zend_object *object TSRMLS_DC) /* {{{ */
 	c->object_name.~Persistent();
 	c->global_template.Reset();
 	c->global_template.~Persistent();
+	c->array_tmpl.Reset();
+	c->array_tmpl.~Persistent();
+
+	/* Clear persistent call_impl & method_tmpls templates */
+	for (std::map<v8js_tmpl_t *, v8js_tmpl_t>::iterator it = c->call_impls.begin();
+		 it != c->call_impls.end(); ++it) {
+		// No need to free it->first, as it is stored in c->template_cache and freed below
+		it->second.Reset();
+	}
+	c->call_impls.~map();
+
+	for (std::map<zend_function *, v8js_tmpl_t>::iterator it = c->method_tmpls.begin();
+		 it != c->method_tmpls.end(); ++it) {
+		it->second.Reset();
+	}
+	c->method_tmpls.~map();
 
 	/* Clear persistent handles in template cache */
 	for (std::map<const zend_string *,v8js_tmpl_t>::iterator it = c->template_cache.begin();
@@ -196,6 +213,7 @@ static zend_object* v8js_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */
 	new(&c->object_name) v8::Persistent<v8::String>();
 	new(&c->context) v8::Persistent<v8::Context>();
 	new(&c->global_template) v8::Persistent<v8::FunctionTemplate>();
+	new(&c->array_tmpl) v8::Persistent<v8::FunctionTemplate>();
 
 	new(&c->modules_stack) std::vector<char*>();
 	new(&c->modules_base) std::vector<char*>();
@@ -206,6 +224,8 @@ static zend_object* v8js_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */
 
 	new(&c->weak_closures) std::map<v8js_tmpl_t *, v8js_persistent_obj_t>();
 	new(&c->weak_objects) std::map<zend_object *, v8js_persistent_obj_t>();
+	new(&c->call_impls) std::map<v8js_tmpl_t *, v8js_tmpl_t>();
+	new(&c->method_tmpls) std::map<zend_function *, v8js_tmpl_t>();
 
 	new(&c->v8js_v8objects) std::list<v8js_v8object *>();
 	new(&c->script_objects) std::vector<v8js_script *>();
@@ -480,7 +500,7 @@ static void v8js_compile_script(zval *this_ptr, zend_string *str, zend_string *i
 
 	/* Compile errors? */
 	if (script.IsEmpty()) {
-		v8js_throw_script_exception(&try_catch TSRMLS_CC);
+		v8js_throw_script_exception(c->isolate, &try_catch TSRMLS_CC);
 		return;
 	}
 	res = (v8js_script *)emalloc(sizeof(v8js_script));
@@ -774,10 +794,17 @@ static int v8js_register_extension(zend_string *name, zend_string *source, zval
 {
 	v8js_jsext *jsext = NULL;
 
-	if (!V8JSG(extensions)) {
-		V8JSG(extensions) = (HashTable *) malloc(sizeof(HashTable));
-		zend_hash_init(V8JSG(extensions), 1, NULL, v8js_jsext_dtor, 1);
-	} else if (zend_hash_exists(V8JSG(extensions), name)) {
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+#endif
+
+	if (!v8js_process_globals.extensions) {
+		v8js_process_globals.extensions = (HashTable *) malloc(sizeof(HashTable));
+		zend_hash_init(v8js_process_globals.extensions, 1, NULL, v8js_jsext_dtor, 1);
+	} else if (zend_hash_exists(v8js_process_globals.extensions, name)) {
+#ifdef ZTS
+		v8js_process_globals.lock.unlock();
+#endif
 		return FAILURE;
 	}
 
@@ -789,6 +816,9 @@ static int v8js_register_extension(zend_string *name, zend_string *source, zval
 		if (v8js_create_ext_strarr(&jsext->deps, jsext->deps_count, Z_ARRVAL_P(deps_arr)) == FAILURE) {
 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid dependency array passed");
 			v8js_jsext_free_storage(jsext);
+#ifdef ZTS
+			v8js_process_globals.lock.unlock();
+#endif
 			return FAILURE;
 		}
 	}
@@ -805,11 +835,18 @@ static int v8js_register_extension(zend_string *name, zend_string *source, zval
 
 	jsext->extension = new v8::Extension(ZSTR_VAL(jsext->name), ZSTR_VAL(jsext->source), jsext->deps_count, jsext->deps);
 
-	if (!zend_hash_add_ptr(V8JSG(extensions), jsext->name, jsext)) {
+	if (!zend_hash_add_ptr(v8js_process_globals.extensions, jsext->name, jsext)) {
 		v8js_jsext_free_storage(jsext);
+#ifdef ZTS
+		v8js_process_globals.lock.unlock();
+#endif
 		return FAILURE;
 	}
 
+#ifdef ZTS
+	v8js_process_globals.lock.unlock();
+#endif
+
 	jsext->extension->set_auto_enable(auto_enable ? true : false);
 	v8::RegisterExtension(jsext->extension);
 
@@ -857,8 +894,12 @@ static PHP_METHOD(V8Js, getExtensions)
 
 	array_init(return_value);
 
-	if (V8JSG(extensions)) {
-		ZEND_HASH_FOREACH_KEY_VAL(V8JSG(extensions), index, key, val) {
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+#endif
+
+	if (v8js_process_globals.extensions) {
+		ZEND_HASH_FOREACH_KEY_VAL(v8js_process_globals.extensions, index, key, val) {
 			if (key) {
 				jsext = (v8js_jsext *) Z_PTR_P(val);
 				array_init(&ext);
@@ -873,6 +914,10 @@ static PHP_METHOD(V8Js, getExtensions)
 			}
 		} ZEND_HASH_FOREACH_END();
 	}
+
+#ifdef ZTS
+	v8js_process_globals.lock.unlock();
+#endif
 }
 /* }}} */
 
@@ -1039,6 +1084,7 @@ PHP_MINIT_FUNCTION(v8js_class) /* {{{ */
 
 	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("FLAG_NONE"),			V8JS_FLAG_NONE			TSRMLS_CC);
 	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("FLAG_FORCE_ARRAY"),	V8JS_FLAG_FORCE_ARRAY	TSRMLS_CC);
+	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("FLAG_PROPAGATE_PHP_EXCEPTIONS"), V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS TSRMLS_CC);
 
 #ifdef ENABLE_DEBUGGER_SUPPORT
 	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("DEBUG_AUTO_BREAK_NEVER"),	V8JS_DEBUG_AUTO_BREAK_NEVER			TSRMLS_CC);

+ 6 - 1
v8js_class.h

@@ -39,12 +39,15 @@ struct v8js_ctx {
   int in_execution;
   v8::Isolate *isolate;
 
+  long flags;
+
   long time_limit;
   bool time_limit_hit;
   long memory_limit;
   bool memory_limit_hit;
 
-  v8::Persistent<v8::FunctionTemplate> global_template;
+  v8js_tmpl_t global_template;
+  v8js_tmpl_t array_tmpl;
 
   zval module_loader;
   std::vector<char *> modules_stack;
@@ -54,6 +57,8 @@ struct v8js_ctx {
 
   std::map<zend_object *, v8js_persistent_obj_t> weak_objects;
   std::map<v8js_tmpl_t *, v8js_persistent_obj_t> weak_closures;
+  std::map<v8js_tmpl_t *, v8js_tmpl_t> call_impls;
+  std::map<zend_function *, v8js_tmpl_t> method_tmpls;
 
   std::list<v8js_v8object *> v8js_v8objects;
 

+ 1 - 1
v8js_convert.cc

@@ -235,7 +235,7 @@ int v8js_to_zval(v8::Handle<v8::Value> jsValue, zval *return_value, int flags, v
 			RETVAL_ZVAL(&zval_object, 1, 0);
 			return SUCCESS;
 		}
-		if ((flags & V8JS_FLAG_FORCE_ARRAY) || jsValue->IsArray()) {
+		if ((flags & V8JS_FLAG_FORCE_ARRAY && !jsValue->IsFunction()) || jsValue->IsArray()) {
 			array_init(return_value);
 			return v8js_get_properties_hash(jsValue, Z_ARRVAL_P(return_value), flags, isolate TSRMLS_CC);
 		} else {

+ 21 - 4
v8js_exceptions.cc

@@ -2,12 +2,13 @@
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
-  | Copyright (c) 1997-2013 The PHP Group                                |
+  | Copyright (c) 1997-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | http://www.opensource.org/licenses/mit-license.php  MIT License      |
   +----------------------------------------------------------------------+
   | Author: Jani Taskinen <[email protected]>                         |
   | Author: Patrick Reilly <[email protected]>                             |
+  | Author: Stefan Siegl <[email protected]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -38,7 +39,7 @@ zend_class_entry *php_ce_v8js_memory_limit_exception;
 
 /* {{{ Class: V8JsScriptException */
 
-void v8js_create_script_exception(zval *return_value, v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
+void v8js_create_script_exception(zval *return_value, v8::Isolate *isolate, v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
 {
 	v8::String::Utf8Value exception(try_catch->Exception());
 	const char *exception_string = ToCString(exception);
@@ -81,6 +82,22 @@ void v8js_create_script_exception(zval *return_value, v8::TryCatch *try_catch TS
 			const char* stacktrace_string = ToCString(stacktrace);
 			PHPV8_EXPROP(_string, JsTrace, stacktrace_string);
 		}
+
+		if(try_catch->Exception()->IsObject()) {
+			v8::Local<v8::Value> php_ref = try_catch->Exception()->ToObject()->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY));
+
+			if(!php_ref.IsEmpty()) {
+				assert(php_ref->IsExternal());
+				zval *php_exception = reinterpret_cast<zval *>(v8::External::Cast(*php_ref)->Value());
+
+				zend_class_entry *exception_ce = zend_exception_get_default(TSRMLS_C);
+				if (Z_TYPE_P(php_exception) == IS_OBJECT && instanceof_function(Z_OBJCE_P(php_exception), exception_ce TSRMLS_CC)) {
+					Z_ADDREF_P(php_exception);
+					zend_exception_set_previous(Z_OBJ_P(return_value), Z_OBJ_P(php_exception));
+				}
+			}
+		}
+
 	}
 
 	PHPV8_EXPROP(_string, message, message_string);
@@ -89,7 +106,7 @@ void v8js_create_script_exception(zval *return_value, v8::TryCatch *try_catch TS
 }
 /* }}} */
 
-void v8js_throw_script_exception(v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
+void v8js_throw_script_exception(v8::Isolate *isolate, v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
 {
 	v8::String::Utf8Value exception(try_catch->Exception());
 	const char *exception_string = ToCString(exception);
@@ -98,7 +115,7 @@ void v8js_throw_script_exception(v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
 	if (try_catch->Message().IsEmpty()) {
 		zend_throw_exception(php_ce_v8js_script_exception, (char *) exception_string, 0 TSRMLS_CC);
 	} else {
-		v8js_create_script_exception(&zexception, try_catch TSRMLS_CC);
+		v8js_create_script_exception(&zexception, isolate, try_catch TSRMLS_CC);
 		zend_throw_exception_object(&zexception TSRMLS_CC);
 	}
 }

+ 4 - 3
v8js_exceptions.h

@@ -2,12 +2,13 @@
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
-  | Copyright (c) 1997-2013 The PHP Group                                |
+  | Copyright (c) 1997-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | http://www.opensource.org/licenses/mit-license.php  MIT License      |
   +----------------------------------------------------------------------+
   | Author: Jani Taskinen <[email protected]>                         |
   | Author: Patrick Reilly <[email protected]>                             |
+  | Author: Stefan Siegl <[email protected]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -19,8 +20,8 @@ extern zend_class_entry *php_ce_v8js_script_exception;
 extern zend_class_entry *php_ce_v8js_time_limit_exception;
 extern zend_class_entry *php_ce_v8js_memory_limit_exception;
 
-void v8js_create_script_exception(zval *return_value, v8::TryCatch *try_catch TSRMLS_DC);
-void v8js_throw_script_exception(v8::TryCatch *try_catch TSRMLS_DC);
+void v8js_create_script_exception(zval *return_value, v8::Isolate *isolate, v8::TryCatch *try_catch TSRMLS_DC);
+void v8js_throw_script_exception(v8::Isolate *isolate, v8::TryCatch *try_catch TSRMLS_DC);
 
 PHP_MINIT_FUNCTION(v8js_exceptions);
 

+ 1 - 14
v8js_methods.cc

@@ -26,20 +26,7 @@ extern "C" {
 V8JS_METHOD(exit) /* {{{ */
 {
 	v8::Isolate *isolate = info.GetIsolate();
-
-	/* Unfortunately just calling TerminateExecution on the isolate is not
-	 * enough, since v8 just marks the thread as "to be aborted" and doesn't
-	 * immediately do so.  Hence we enter an endless loop after signalling
-	 * termination, so we definitely don't execute JS code after the exit()
-	 * statement. */
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope handle_scope(isolate);
-
-	v8::Local<v8::String> source = V8JS_STR("for(;;);");
-	v8::Local<v8::Script> script = v8::Script::Compile(source);
-	v8::V8::TerminateExecution(isolate);
-	script->Run();
+	v8js_terminate_execution(isolate);
 }
 /* }}} */
 

+ 63 - 22
v8js_object_export.cc

@@ -21,6 +21,7 @@ extern "C" {
 #include "ext/standard/php_string.h"
 #include "zend_interfaces.h"
 #include "zend_closures.h"
+#include "zend_exceptions.h"
 }
 
 #include "php_v8js_macros.h"
@@ -33,13 +34,13 @@ static void v8js_weak_object_callback(const v8::WeakCallbackData<v8::Object, zen
 /* Callback for PHP methods and functions */
 static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, v8::Isolate *isolate, const v8::FunctionCallbackInfo<v8::Value>& info TSRMLS_DC) /* {{{ */
 {
-	v8::Handle<v8::Value> return_value;
+	v8::Handle<v8::Value> return_value = V8JS_NULL;
 	zend_fcall_info fci;
 	zend_fcall_info_cache fcc;
 	zval fname, retval, **argv = NULL;
 	unsigned int argc = info.Length(), min_num_args = 0, max_num_args = 0;
 	char *error;
-	int error_len, i, flags = V8JS_FLAG_NONE;
+	int error_len, i;
 
 	v8js_ctx *ctx = (v8js_ctx *) isolate->GetData(0);
 
@@ -84,7 +85,6 @@ static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, v
 
 	/* Convert parameters passed from V8 */
 	if (argc) {
-		flags = V8JS_GLOBAL_GET_FLAGS(isolate);
 		fci.params = (zval *) safe_emalloc(argc, sizeof(zval), 0);
 		for (i = 0; i < argc; i++) {
 			v8::Local<v8::Value> php_object;
@@ -97,7 +97,7 @@ static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, v
 				ZVAL_OBJ(&fci.params[i], object);
 				Z_ADDREF_P(&fci.params[i]);
 			} else {
-				if (v8js_to_zval(info[i], &fci.params[i], flags, isolate TSRMLS_CC) == FAILURE) {
+				if (v8js_to_zval(info[i], &fci.params[i], ctx->flags, isolate TSRMLS_CC) == FAILURE) {
 					error_len = spprintf(&error, 0, "converting parameter #%d passed to %s() failed", i + 1, method_ptr->common.function_name);
 					return_value = V8JS_THROW(isolate, Error, error, error_len);
 					efree(error);
@@ -132,7 +132,7 @@ static void v8js_call_php_func(zend_object *object, zend_function *method_ptr, v
 		isolate->Enter();
 	}
 	zend_catch {
-		v8::V8::TerminateExecution(isolate);
+		v8js_terminate_execution(isolate);
 		V8JSG(fatal_error_abort) = 1;
 	}
 	zend_end_try();
@@ -146,7 +146,19 @@ failure:
 		efree(fci.params);
 	}
 
-	return_value = zval_to_v8js(&retval, isolate TSRMLS_CC);
+	if(EG(exception) && !V8JSG(compat_php_exceptions)) {
+		if(ctx->flags & V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS) {
+			zval tmp_zv;
+			ZVAL_OBJ(&tmp_zv, EG(exception));
+			return_value = isolate->ThrowException(zval_to_v8js(&tmp_zv, isolate TSRMLS_CC));
+			zend_clear_exception(TSRMLS_C);
+		} else {
+			v8js_terminate_execution(isolate);
+		}
+	} else {
+		return_value = zval_to_v8js(&retval, isolate TSRMLS_CC);
+	}
+
 	zval_dtor(&retval);
 	zval_dtor(&fname);
 
@@ -505,6 +517,7 @@ template<typename T>
 v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<T> &info, property_op_t callback_type, v8::Local<v8::Value> set_value) /* {{{ */
 {
 	v8::Isolate *isolate = info.GetIsolate();
+	v8js_ctx *ctx = (v8js_ctx *) isolate->GetData(0);
 	v8::String::Utf8Value cstr(property);
 	const char *name = ToCString(cstr);
 	uint name_len = property->Utf8Length();
@@ -524,9 +537,8 @@ v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> property
 	zval zobject;
 	ZVAL_OBJ(&zobject, object);
 
-	v8::Local<v8::FunctionTemplate> tmpl =
-		v8::Local<v8::FunctionTemplate>::New
-		(isolate, *reinterpret_cast<v8js_tmpl_t *>(self->GetAlignedPointerFromInternalField(0)));
+	v8js_tmpl_t *tmpl_ptr = reinterpret_cast<v8js_tmpl_t *>(self->GetAlignedPointerFromInternalField(0));
+	v8::Local<v8::FunctionTemplate> tmpl = v8::Local<v8::FunctionTemplate>::New(isolate, *tmpl_ptr);
 	ce = scope = object->ce;
 
 	/* First, check the (case-insensitive) method table */
@@ -560,14 +572,35 @@ v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> property
 					// Fake __call implementation
 					// (only use this if method_ptr==NULL, which means
 					//  there is no actual PHP __call() implementation)
-					v8::Local<v8::Function> cb =
-						v8::FunctionTemplate::New(isolate,
-							v8js_fake_call_impl, V8JS_NULL,
-							v8::Signature::New(isolate, tmpl))->GetFunction();
+					v8::Local<v8::FunctionTemplate> ft;
+					try {
+						ft = v8::Local<v8::FunctionTemplate>::New
+							(isolate, ctx->call_impls.at(tmpl_ptr));
+					}
+					catch (const std::out_of_range &) {
+						ft = v8::FunctionTemplate::New(isolate,
+								v8js_fake_call_impl, V8JS_NULL,
+								v8::Signature::New(isolate, tmpl));
+						v8js_tmpl_t *persistent_ft = &ctx->call_impls[tmpl_ptr];
+						persistent_ft->Reset(isolate, ft);
+					}
+					v8::Local<v8::Function> cb = ft->GetFunction();
 					cb->SetName(property);
 					ret_value = cb;
 				} else {
-					ret_value = PHP_V8JS_CALLBACK(isolate, method_ptr, tmpl);
+					v8::Local<v8::FunctionTemplate> ft;
+					try {
+						ft = v8::Local<v8::FunctionTemplate>::New
+							(isolate, ctx->method_tmpls.at(method_ptr));
+					}
+					catch (const std::out_of_range &) {
+						ft = v8::FunctionTemplate::New(isolate, v8js_php_callback,
+								v8::External::New((isolate), method_ptr),
+								v8::Signature::New((isolate), tmpl));
+						v8js_tmpl_t *persistent_ft = &ctx->method_tmpls[method_ptr];
+						persistent_ft->Reset(isolate, ft);
+					}
+					ret_value = ft->GetFunction();
 				}
 			}
 		} else if (callback_type == V8JS_PROP_QUERY) {
@@ -623,9 +656,7 @@ v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> property
 			}
 
 		} else if (callback_type == V8JS_PROP_SETTER) {
-			int flags = V8JS_GLOBAL_GET_FLAGS(isolate);
-
-			if (v8js_to_zval(set_value, &php_value, flags, isolate TSRMLS_CC) != SUCCESS) {
+			if (v8js_to_zval(set_value, &php_value, ctx->flags, isolate TSRMLS_CC) != SUCCESS) {
 				ret_value = v8::Handle<v8::Value>();
 			}
 			else {
@@ -833,12 +864,22 @@ static v8::Handle<v8::Object> v8js_wrap_array_to_object(v8::Isolate *isolate, zv
 	zend_string *key;
 	ulong index;
 
-	// @todo re-use template likewise
-	v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(isolate, 0);
+	v8js_ctx *ctx = (v8js_ctx *) isolate->GetData(0);
+	v8::Local<v8::FunctionTemplate> new_tpl;
+
+	if(ctx->array_tmpl.IsEmpty()) {
+		new_tpl = v8::FunctionTemplate::New(isolate, 0);
+
+		/* Call it Array, but it is not a native array, especially it doesn't have
+		 * have the typical Array.prototype functions. */
+		new_tpl->SetClassName(V8JS_SYM("Array"));
 
-	/* Call it Array, but it is not a native array, especially it doesn't have
-	 * have the typical Array.prototype functions. */
-	new_tpl->SetClassName(V8JS_SYM("Array"));
+		/* Store for later re-use */
+		ctx->array_tmpl.Reset(isolate, new_tpl);
+	}
+	else {
+		new_tpl = v8::Local<v8::FunctionTemplate>::New(isolate, ctx->array_tmpl);
+	}
 
 	v8::Handle<v8::Object> newobj = new_tpl->InstanceTemplate()->NewInstance();
 

+ 2 - 2
v8js_timer.cc

@@ -54,7 +54,7 @@ static void v8js_timer_interrupt_handler(v8::Isolate *isolate, void *data) { /*
 
 		if (timer_ctx->memory_limit > 0 && hs.used_heap_size() > timer_ctx->memory_limit) {
 			timer_ctx->killed = true;
-			v8js_terminate_execution(c TSRMLS_CC);
+			v8::V8::TerminateExecution(c->isolate);
 			c->memory_limit_hit = true;
 		}
 	}
@@ -79,7 +79,7 @@ void v8js_timer_thread(zend_v8js_globals *globals) /* {{{ */
 			}
 			else if(timer_ctx->time_limit > 0 && now > timer_ctx->time_point) {
 				timer_ctx->killed = true;
-				v8js_terminate_execution(c TSRMLS_CC);
+				v8::V8::TerminateExecution(c->isolate);
 				c->time_limit_hit = true;
 			}
 			else if (timer_ctx->memory_limit > 0) {

+ 49 - 14
v8js_v8.cc

@@ -2,12 +2,13 @@
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
-  | Copyright (c) 1997-2013 The PHP Group                                |
+  | Copyright (c) 1997-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | http://www.opensource.org/licenses/mit-license.php  MIT License      |
   +----------------------------------------------------------------------+
   | Author: Jani Taskinen <[email protected]>                         |
   | Author: Patrick Reilly <[email protected]>                             |
+  | Author: Stefan Siegl <[email protected]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -36,26 +37,42 @@ extern "C" {
 
 void v8js_v8_init(TSRMLS_D) /* {{{ */
 {
-	/* Run only once */
+	/* Run only once; thread-local test first */
 	if (V8JSG(v8_initialized)) {
 		return;
 	}
 
+	/* Set thread-local flag, that V8 was initialized. */
+	V8JSG(v8_initialized) = 1;
+
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+
+	if(v8js_process_globals.v8_initialized) {
+		/* V8 already has been initialized by another thread */
+		v8js_process_globals.lock.unlock();
+		return;
+	}
+#endif
+
 #if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-	V8JSG(v8_platform) = v8::platform::CreateDefaultPlatform();
-	v8::V8::InitializePlatform(V8JSG(v8_platform));
+	v8js_process_globals.v8_platform = v8::platform::CreateDefaultPlatform();
+	v8::V8::InitializePlatform(v8js_process_globals.v8_platform);
 #endif
 
 	/* Set V8 command line flags (must be done before V8::Initialize()!) */
-	if (V8JSG(v8_flags)) {
-		v8::V8::SetFlagsFromString(V8JSG(v8_flags), strlen(V8JSG(v8_flags)));
+	if (v8js_process_globals.v8_flags) {
+		v8::V8::SetFlagsFromString(v8js_process_globals.v8_flags,
+								   strlen(v8js_process_globals.v8_flags));
 	}
 
 	/* Initialize V8 */
 	v8::V8::Initialize();
 
-	/* Run only once */
-	V8JSG(v8_initialized) = 1;
+#ifdef ZTS
+	v8js_process_globals.v8_initialized = 1;
+	v8js_process_globals.lock.unlock();
+#endif
 }
 /* }}} */
 
@@ -77,7 +94,7 @@ void v8js_v8_call(v8js_ctx *c, zval **return_value,
 	v8::TryCatch try_catch;
 
 	/* Set flags for runtime use */
-	V8JS_GLOBAL_SET_FLAGS(isolate, flags);
+	c->flags = flags;
 
 	/* Check if timezone has been changed and notify V8 */
 	tz = getenv("TZ");
@@ -176,13 +193,13 @@ void v8js_v8_call(v8js_ctx *c, zval **return_value,
 
 			/* Report immediately if report_uncaught is true */
 			if (c->report_uncaught) {
-				v8js_throw_script_exception(&try_catch TSRMLS_CC);
+				v8js_throw_script_exception(c->isolate, &try_catch TSRMLS_CC);
 				return;
 			}
 
 			/* Exception thrown from JS, preserve it for future execution */
 			if (result.IsEmpty()) {
-				v8js_create_script_exception(&c->pending_exception, &try_catch TSRMLS_CC);
+				v8js_create_script_exception(&c->pending_exception, c->isolate, &try_catch TSRMLS_CC);
 				return;
 			}
 		}
@@ -199,10 +216,28 @@ void v8js_v8_call(v8js_ctx *c, zval **return_value,
 }
 /* }}} */
 
-void v8js_terminate_execution(v8js_ctx *c TSRMLS_DC) /* {{{ */
+void v8js_terminate_execution(v8::Isolate *isolate) /* {{{ */
 {
-	// Forcefully terminate the current thread of V8 execution in the isolate
-	v8::V8::TerminateExecution(c->isolate);
+	if(v8::V8::IsExecutionTerminating(isolate)) {
+		/* Execution already terminating, needn't trigger it again and
+		 * especially must not execute the spinning loop (which would cause
+		 * crashes in V8 itself, at least with 4.2 and 4.3 version lines). */
+		return;
+	}
+
+	/* Unfortunately just calling TerminateExecution on the isolate is not
+	 * enough, since v8 just marks the thread as "to be aborted" and doesn't
+	 * immediately do so.  Hence we enter an endless loop after signalling
+	 * termination, so we definitely don't execute JS code after the exit()
+	 * statement. */
+	v8::Locker locker(isolate);
+	v8::Isolate::Scope isolate_scope(isolate);
+	v8::HandleScope handle_scope(isolate);
+
+	v8::Local<v8::String> source = V8JS_STR("for(;;);");
+	v8::Local<v8::Script> script = v8::Script::Compile(source);
+	v8::V8::TerminateExecution(isolate);
+	script->Run();
 }
 /* }}} */
 

+ 6 - 3
v8js_v8.h

@@ -49,15 +49,15 @@ void v8js_v8_init(TSRMLS_D);
 void v8js_v8_call(v8js_ctx *c, zval **return_value,
 				  long flags, long time_limit, long memory_limit,
 				  std::function< v8::Local<v8::Value>(v8::Isolate *) >& v8_call TSRMLS_DC);
-void v8js_terminate_execution(v8js_ctx *c TSRMLS_DC);
+void v8js_terminate_execution(v8::Isolate *isolate);
 
 /* Fetch V8 object properties */
 int v8js_get_properties_hash(v8::Handle<v8::Value> jsValue, HashTable *retval, int flags, v8::Isolate *isolate TSRMLS_DC);
 
-#define V8JS_CTX_PROLOGUE(ctx) \
+#define V8JS_CTX_PROLOGUE_EX(ctx, ret) \
 	if (!V8JSG(v8_initialized)) { \
 		zend_error(E_ERROR, "V8 not initialized"); \
-		return; \
+		return ret; \
 	} \
 	\
 	v8::Isolate *isolate = (ctx)->isolate; \
@@ -67,6 +67,9 @@ int v8js_get_properties_hash(v8::Handle<v8::Value> jsValue, HashTable *retval, i
 	v8::Local<v8::Context> v8_context = v8::Local<v8::Context>::New(isolate, (ctx)->context); \
 	v8::Context::Scope context_scope(v8_context);
 
+#define V8JS_CTX_PROLOGUE(ctx) \
+	V8JS_CTX_PROLOGUE_EX(ctx,)
+
 #define V8JS_BEGIN_CTX(ctx, object) \
 	v8js_ctx *(ctx); \
 	(ctx) = Z_V8JS_CTX_OBJ_P(object); \

+ 7 - 46
v8js_v8object_class.cc

@@ -60,13 +60,7 @@ static int v8js_v8object_has_property(zval *object, zval *member, int has_set_ex
 		return retval;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
-
+	V8JS_CTX_PROLOGUE_EX(obj->ctx, retval);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (Z_TYPE_P(member) == IS_STRING && v8obj->IsObject() && !v8obj->IsFunction())
@@ -125,13 +119,7 @@ static zval *v8js_v8object_read_property(zval *object, zval *member, int type, v
 		return retval;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
-
+	V8JS_CTX_PROLOGUE_EX(obj->ctx, retval);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (Z_TYPE_P(member) == IS_STRING && v8obj->IsObject() && !v8obj->IsFunction())
@@ -165,13 +153,7 @@ static void v8js_v8object_write_property(zval *object, zval *member, zval *value
 		return;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
-
+	V8JS_CTX_PROLOGUE(obj->ctx);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (v8obj->IsObject() && !v8obj->IsFunction()) {
@@ -190,13 +172,7 @@ static void v8js_v8object_unset_property(zval *object, zval *member, void **cach
 		return;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
-
+	V8JS_CTX_PROLOGUE(obj->ctx);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (v8obj->IsObject() && !v8obj->IsFunction()) {
@@ -235,12 +211,7 @@ static HashTable *v8js_v8object_get_properties(zval *object TSRMLS_DC) /* {{{ */
 		return NULL;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
+	V8JS_CTX_PROLOGUE_EX(obj->ctx, NULL);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (v8js_get_properties_hash(v8obj, obj->properties, obj->flags, isolate TSRMLS_CC) == SUCCESS) {
@@ -269,12 +240,7 @@ static zend_function *v8js_v8object_get_method(zend_object **object_ptr, zend_st
 		return NULL;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
+	V8JS_CTX_PROLOGUE_EX(obj->ctx, NULL);
 	v8::Local<v8::String> jsKey = V8JS_ZSTR(method);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
@@ -360,12 +326,7 @@ static int v8js_v8object_get_closure(zval *object, zend_class_entry **ce_ptr, ze
 		return FAILURE;
 	}
 
-	v8::Isolate *isolate = obj->ctx->isolate;
-	v8::Locker locker(isolate);
-	v8::Isolate::Scope isolate_scope(isolate);
-	v8::HandleScope local_scope(isolate);
-	v8::Local<v8::Context> temp_context = v8::Context::New(isolate);
-	v8::Context::Scope temp_scope(temp_context);
+	V8JS_CTX_PROLOGUE_EX(obj->ctx, FAILURE);
 	v8::Local<v8::Value> v8obj = v8::Local<v8::Value>::New(isolate, obj->v8obj);
 
 	if (!v8obj->IsFunction()) {