Jelajahi Sumber

Merge pull request #156 from stesie/php-exception-behaviour

PHP->JS exception propagation
Stefan Siegl 9 tahun lalu
induk
melakukan
83f51e5021

+ 26 - 0
README.md

@@ -52,6 +52,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;
@@ -301,3 +302,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.

+ 2 - 4
php_v8js_macros.h

@@ -80,13 +80,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
@@ -124,6 +121,7 @@ ZEND_BEGIN_MODULE_GLOBALS(v8js)
   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;

+ 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===

+ 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===

+ 18 - 0
v8js.cc

@@ -82,10 +82,28 @@ static ZEND_INI_MH(v8js_OnUpdateUseArrayAccess) /* {{{ */
 }
 /* }}} */
 
+static ZEND_INI_MH(v8js_OnUpdateCompatExceptions) /* {{{ */
+{
+	bool value;
+	if (new_value_length==2 && strcasecmp("on", new_value)==0) {
+		value = (bool) 1;
+    } else if (new_value_length==3 && strcasecmp("yes", new_value)==0) {
+		value = (bool) 1;
+	} else if (new_value_length==4 && strcasecmp("true", new_value)==0) {
+		value = (bool) 1;
+	} else {
+		value = (bool) atoi(new_value);
+	}
+	V8JSG(compat_php_exceptions) = value;
+	return SUCCESS;
+}
+/* }}} */
+
 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()
 /* }}} */
 

+ 4 - 2
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]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -517,7 +518,7 @@ static void v8js_compile_script(zval *this_ptr, const char *str, int str_len, co
 
 	/* 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));
@@ -1097,6 +1098,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);

+ 2 - 0
v8js_class.h

@@ -40,6 +40,8 @@ struct v8js_ctx {
   int in_execution;
   v8::Isolate *isolate;
 
+  long flags;
+
   long time_limit;
   bool time_limit_hit;
   long memory_limit;

+ 20 - 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,21 @@ 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)) {
+					zend_exception_set_previous(return_value, php_exception TSRMLS_CC);
+				}
+			}
+		}
+
 	}
 
 	PHPV8_EXPROP(_string, message, message_string);
@@ -89,7 +105,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);
@@ -99,7 +115,7 @@ void v8js_throw_script_exception(v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
 		zend_throw_exception(php_ce_v8js_script_exception, (char *) exception_string, 0 TSRMLS_CC);
 	} else {
 		MAKE_STD_ZVAL(zexception);
-		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);
 }
 /* }}} */
 

+ 19 - 10
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, zva
 /* Callback for PHP methods and functions */
 static void v8js_call_php_func(zval *value, zend_class_entry *ce, 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_ptr = NULL, **argv = NULL;
 	zend_uint 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(zval *value, zend_class_entry *ce, zend_function
 
 	/* Convert parameters passed from V8 */
 	if (argc) {
-		flags = V8JS_GLOBAL_GET_FLAGS(isolate);
 		fci.params = (zval ***) safe_emalloc(argc, sizeof(zval **), 0);
 		argv = (zval **) safe_emalloc(argc, sizeof(zval *), 0);
 		for (i = 0; i < argc; i++) {
@@ -98,7 +98,7 @@ static void v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function
 				Z_ADDREF_P(argv[i]);
 			} else {
 				MAKE_STD_ZVAL(argv[i]);
-				if (v8js_to_zval(info[i], argv[i], flags, isolate TSRMLS_CC) == FAILURE) {
+				if (v8js_to_zval(info[i], argv[i], ctx->flags, isolate TSRMLS_CC) == FAILURE) {
 					fci.param_count++;
 					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);
@@ -134,7 +134,7 @@ static void v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function
 		isolate->Enter();
 	}
 	zend_catch {
-		v8::V8::TerminateExecution(isolate);
+		v8js_terminate_execution(isolate);
 		V8JSG(fatal_error_abort) = 1;
 	}
 	zend_end_try();
@@ -149,11 +149,19 @@ failure:
 		efree(fci.params);
 	}
 
-	if (retval_ptr != NULL) {
+	if(EG(exception) && !V8JSG(compat_php_exceptions)) {
+		if(ctx->flags & V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS) {
+			return_value = isolate->ThrowException(zval_to_v8js(EG(exception), isolate TSRMLS_CC));
+			zend_clear_exception(TSRMLS_C);
+		} else {
+			v8js_terminate_execution(isolate);
+		}
+	} else if (retval_ptr != NULL) {
 		return_value = zval_to_v8js(retval_ptr, isolate TSRMLS_CC);
+	}
+
+	if (retval_ptr != NULL) {
 		zval_ptr_dtor(&retval_ptr);
-	} else {
-		return_value = V8JS_NULL;
 	}
 
 	info.GetReturnValue().Set(return_value);
@@ -526,6 +534,8 @@ inline v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> p
 	const char *method_name;
 	uint method_name_len;
 
+	v8js_ctx *ctx = (v8js_ctx *) isolate->GetData(0);
+
 	v8::Local<v8::Object> self = info.Holder();
 	v8::Local<v8::Value> ret_value;
 	v8::Local<v8::Function> cb;
@@ -669,9 +679,8 @@ inline v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> p
 				zval_ptr_dtor(&php_value);
 			}
 		} else if (callback_type == V8JS_PROP_SETTER) {
-			int flags = V8JS_GLOBAL_GET_FLAGS(isolate);
 			MAKE_STD_ZVAL(php_value);
-			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 {

+ 2 - 2
v8js_timer.cc

@@ -55,7 +55,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;
 		}
 	}
@@ -80,7 +80,7 @@ void v8js_timer_thread(TSRMLS_D) /* {{{ */
 			}
 			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) {

+ 26 - 7
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]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -77,7 +78,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");
@@ -175,14 +176,14 @@ 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()) {
 				MAKE_STD_ZVAL(c->pending_exception);
-				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 +200,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();
 }
 /* }}} */
 

+ 1 - 1
v8js_v8.h

@@ -45,7 +45,7 @@ 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);