0
0
mirror of https://github.com/phpv8/v8js.git synced 2025-03-11 22:18:44 +00:00

Merge pull request #91 from stesie/hack-fatal-error-unwind

Handle PHP Fatal Errors inside JS->PHP callbacks
This commit is contained in:
Patrick Reilly 2014-04-14 10:00:17 -07:00
commit 16447f8fce
5 changed files with 142 additions and 2 deletions

View File

@ -259,6 +259,12 @@ ZEND_BEGIN_MODULE_GLOBALS(v8js)
bool timer_stop;
std::map<char *, v8::Handle<v8::Object> > modules_loaded;
// fatal error unwinding
bool fatal_error_abort;
int error_num;
char *error_message;
jmp_buf *unwind_env;
ZEND_END_MODULE_GLOBALS(v8js)
extern zend_v8js_globals v8js_globals;

View File

@ -0,0 +1,38 @@
--TEST--
Test V8::executeString() : Fatal Error with recursive executeString calls
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$js = new V8Js();
$js->baz = function() {
$bar = null;
$bar->bar();
};
$js->bar = function() {
global $js;
$js->executeString("PHP.baz();");
};
$js->foo = function() {
global $js;
$js->executeString("PHP.bar();");
};
$js->nofail = function() {
echo "foo\n";
};
$js->executeString("PHP.nofail();");
$js->executeString("PHP.nofail(); PHP.foo();");
?>
===EOF===
--EXPECTF--
foo
foo
Fatal error: Call to a member function bar() on a non-object in %s/fatal_error_recursive.php on line 7

View File

@ -0,0 +1,26 @@
--TEST--
Test V8::executeString() : Fatal Error rethrowing
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$js = new V8Js();
$js->foo = function() {
$bar = null;
$bar->bar();
};
$script = <<<END
PHP.foo();
END;
$js->executeString($script);
?>
===EOF===
--EXPECTF--
Fatal error: Call to a member function bar() on a non-object in %s/fatal_error_rethrow.php on line 7

11
v8js.cc
View File

@ -1134,6 +1134,12 @@ static PHP_METHOD(V8Js, executeString)
php_v8js_timer_pop(TSRMLS_C);
}
/* Check for fatal error marker possibly set by php_v8js_error_handler; just
* rethrow the error since we're now out of V8. */
if(V8JSG(fatal_error_abort)) {
zend_error(V8JSG(error_num), "%s", V8JSG(error_message));
}
char exception_string[64];
if (c->time_limit_hit) {
@ -1868,6 +1874,11 @@ static PHP_GINIT_FUNCTION(v8js)
new(&v8js_globals->timer_mutex) std::mutex;
new(&v8js_globals->timer_stack) std::stack<php_v8js_timer_ctx *>;
new(&v8js_globals->modules_loaded) std::map<char *, v8::Handle<v8::Object>>;
v8js_globals->fatal_error_abort = 0;
v8js_globals->error_num = 0;
v8js_globals->error_message = 0;
v8js_globals->unwind_env = NULL;
#endif
}
/* }}} */

View File

@ -30,6 +30,29 @@ extern "C" {
#include <stdexcept>
#include <limits>
/* Callback for PHP's zend_error_cb; catching any fatal PHP error.
* The callback is installed in the lowest (stack wise) php_v8js_call_php_func
* frame. Just store the error message and jump right back there and fall
* back into V8 context. */
static void php_v8js_error_handler(int error_num, const char *error_filename,
const uint error_lineno, const char *format,
va_list args) /* {{{ */
{
char *buffer;
int buffer_len;
buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, args);
V8JSG(fatal_error_abort) = true;
V8JSG(error_num) = error_num;
V8JSG(error_message) = buffer;
longjmp(*V8JSG(unwind_env), 1);
}
/* }}} */
static void php_v8js_weak_object_callback(const v8::WeakCallbackData<v8::Object, zval> &data);
/* Callback for PHP methods and functions */
@ -43,6 +66,13 @@ static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_funct
char *error;
int error_len, i, flags = V8JS_FLAG_NONE;
#if PHP_V8_API_VERSION <= 3023008
/* Until V8 3.23.8 Isolate could only take one external pointer. */
php_v8js_ctx *ctx = (php_v8js_ctx *) isolate->GetData();
#else
php_v8js_ctx *ctx = (php_v8js_ctx *) isolate->GetData(0);
#endif
/* Set parameter limits */
min_num_args = method_ptr->common.required_num_args;
max_num_args = method_ptr->common.num_args;
@ -125,12 +155,41 @@ static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_funct
fcc.called_scope = ce;
fcc.object_ptr = value;
/* Call the method */
zend_call_function(&fci, &fcc TSRMLS_CC);
jmp_buf env;
int val = 0;
void (*old_error_handler)(int, const char *, const uint, const char*, va_list);
/* If this is the first level call from V8 back to PHP, install a
* handler for fatal errors; we must fall back through V8 to keep
* it from crashing. */
if (V8JSG(unwind_env) == NULL) {
old_error_handler = zend_error_cb;
zend_error_cb = php_v8js_error_handler;
val = setjmp (env);
V8JSG(unwind_env) = &env;
}
if (!val) {
/* Call the method */
zend_call_function(&fci, &fcc TSRMLS_CC);
}
if (old_error_handler != NULL) {
zend_error_cb = old_error_handler;
V8JSG(unwind_env) = NULL;
}
}
isolate->Enter();
if (V8JSG(fatal_error_abort)) {
v8::V8::TerminateExecution(isolate);
info.GetReturnValue().Set(V8JS_NULL);
return;
}
failure:
/* Cleanup */
if (argc) {