mirror of
https://github.com/phpv8/v8js.git
synced 2024-11-12 22:18:41 +00:00
72cb2da9ba
The V8::TerminateExecution does *not* immediately terminate execution as its name might suggest. It just marks the given isolate as "to terminate" and the execution thread checks - from time to time - whether to terminate. For v8 itself this is not problematic as the call is thought to stop long-running scripts executed in the browser context. As v8js exposes this function to JavaScript with the exit() method, this behaviour is confusing. In order to stop code execution right at the exit() call, v8js enters an endless loop and waits for v8 to terminate execution within it.
427 lines
13 KiB
C++
427 lines
13 KiB
C++
/*
|
||
+----------------------------------------------------------------------+
|
||
| PHP Version 5 |
|
||
+----------------------------------------------------------------------+
|
||
| Copyright (c) 1997-2013 The PHP Group |
|
||
+----------------------------------------------------------------------+
|
||
| http://www.opensource.org/licenses/mit-license.php MIT License |
|
||
+----------------------------------------------------------------------+
|
||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||
| Author: Patrick Reilly <preilly@php.net> |
|
||
+----------------------------------------------------------------------+
|
||
*/
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
#include "config.h"
|
||
#endif
|
||
|
||
#include "php_v8js_macros.h"
|
||
|
||
extern "C" {
|
||
#include "zend_exceptions.h"
|
||
}
|
||
|
||
/* Forward declarations */
|
||
void v8js_commonjs_normalise_identifier(char *base, char *identifier, char *normalised_path, char *module_name);
|
||
|
||
/* global.exit - terminate execution */
|
||
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();
|
||
}
|
||
/* }}} */
|
||
|
||
/* global.sleep - sleep for passed seconds */
|
||
V8JS_METHOD(sleep) /* {{{ */
|
||
{
|
||
php_sleep(info[0]->Int32Value());
|
||
}
|
||
/* }}} */
|
||
|
||
/* global.print - php print() */
|
||
V8JS_METHOD(print) /* {{{ */
|
||
{
|
||
v8::Isolate *isolate = info.GetIsolate();
|
||
int ret = 0;
|
||
V8JS_TSRMLS_FETCH();
|
||
|
||
for (int i = 0; i < info.Length(); i++) {
|
||
v8::String::Utf8Value str(info[i]);
|
||
const char *cstr = ToCString(str);
|
||
ret = PHPWRITE(cstr, strlen(cstr));
|
||
}
|
||
info.GetReturnValue().Set(V8JS_INT(ret));
|
||
}
|
||
/* }}} */
|
||
|
||
static void v8js_dumper(v8::Isolate *isolate, v8::Local<v8::Value> var, int level TSRMLS_DC) /* {{{ */
|
||
{
|
||
if (level > 1) {
|
||
php_printf("%*c", (level - 1) * 2, ' ');
|
||
}
|
||
|
||
if (var.IsEmpty())
|
||
{
|
||
php_printf("<empty>\n");
|
||
return;
|
||
}
|
||
if (var->IsNull() || var->IsUndefined() /* PHP compat */)
|
||
{
|
||
php_printf("NULL\n");
|
||
return;
|
||
}
|
||
if (var->IsInt32())
|
||
{
|
||
php_printf("int(%ld)\n", (long) var->IntegerValue());
|
||
return;
|
||
}
|
||
if (var->IsUint32())
|
||
{
|
||
php_printf("int(%lu)\n", (unsigned long) var->IntegerValue());
|
||
return;
|
||
}
|
||
if (var->IsNumber())
|
||
{
|
||
php_printf("float(%f)\n", var->NumberValue());
|
||
return;
|
||
}
|
||
if (var->IsBoolean())
|
||
{
|
||
php_printf("bool(%s)\n", var->BooleanValue() ? "true" : "false");
|
||
return;
|
||
}
|
||
|
||
v8::TryCatch try_catch; /* object.toString() can throw an exception */
|
||
v8::Local<v8::String> details = var->ToDetailString();
|
||
if (try_catch.HasCaught()) {
|
||
details = V8JS_SYM("<toString threw exception>");
|
||
}
|
||
v8::String::Utf8Value str(details);
|
||
const char *valstr = ToCString(str);
|
||
size_t valstr_len = details->ToString()->Utf8Length();
|
||
|
||
if (var->IsString())
|
||
{
|
||
php_printf("string(%zu) \"", valstr_len, valstr);
|
||
PHPWRITE(valstr, valstr_len);
|
||
php_printf("\"\n");
|
||
}
|
||
else if (var->IsDate())
|
||
{
|
||
// fake the fields of a PHP DateTime
|
||
php_printf("Date(%s)\n", valstr);
|
||
}
|
||
else if (var->IsRegExp())
|
||
{
|
||
php_printf("regexp(%s)\n", valstr);
|
||
}
|
||
else if (var->IsArray())
|
||
{
|
||
v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(var);
|
||
uint32_t length = array->Length();
|
||
|
||
php_printf("array(%d) {\n", length);
|
||
|
||
for (unsigned i = 0; i < length; i++) {
|
||
php_printf("%*c[%d] =>\n", level * 2, ' ', i);
|
||
v8js_dumper(isolate, array->Get(i), level + 1 TSRMLS_CC);
|
||
}
|
||
|
||
if (level > 1) {
|
||
php_printf("%*c", (level - 1) * 2, ' ');
|
||
}
|
||
|
||
ZEND_PUTS("}\n");
|
||
}
|
||
else if (var->IsObject())
|
||
{
|
||
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(var);
|
||
V8JS_GET_CLASS_NAME(cname, object);
|
||
int hash = object->GetIdentityHash();
|
||
|
||
if (var->IsFunction())
|
||
{
|
||
v8::String::Utf8Value csource(object->ToString());
|
||
php_printf("object(Closure)#%d {\n%*c%s\n", hash, level * 2 + 2, ' ', ToCString(csource));
|
||
}
|
||
else
|
||
{
|
||
v8::Local<v8::Array> keys = object->GetOwnPropertyNames();
|
||
uint32_t length = keys->Length();
|
||
|
||
if (strcmp(ToCString(cname), "Array") == 0 ||
|
||
strcmp(ToCString(cname), "V8Object") == 0) {
|
||
php_printf("array");
|
||
} else {
|
||
php_printf("object(%s)#%d", ToCString(cname), hash);
|
||
}
|
||
php_printf(" (%d) {\n", length);
|
||
|
||
for (unsigned i = 0; i < length; i++) {
|
||
v8::Local<v8::String> key = keys->Get(i)->ToString();
|
||
v8::String::Utf8Value kname(key);
|
||
php_printf("%*c[\"%s\"] =>\n", level * 2, ' ', ToCString(kname));
|
||
v8js_dumper(isolate, object->Get(key), level + 1 TSRMLS_CC);
|
||
}
|
||
}
|
||
|
||
if (level > 1) {
|
||
php_printf("%*c", (level - 1) * 2, ' ');
|
||
}
|
||
|
||
ZEND_PUTS("}\n");
|
||
}
|
||
else /* null, undefined, etc. */
|
||
{
|
||
php_printf("<%s>\n", valstr);
|
||
}
|
||
}
|
||
/* }}} */
|
||
|
||
/* global.var_dump - Dump JS values */
|
||
V8JS_METHOD(var_dump) /* {{{ */
|
||
{
|
||
v8::Isolate *isolate = info.GetIsolate();
|
||
V8JS_TSRMLS_FETCH();
|
||
|
||
for (int i = 0; i < info.Length(); i++) {
|
||
v8js_dumper(isolate, info[i], 1 TSRMLS_CC);
|
||
}
|
||
|
||
info.GetReturnValue().Set(V8JS_NULL);
|
||
}
|
||
/* }}} */
|
||
|
||
V8JS_METHOD(require)
|
||
{
|
||
v8::Isolate *isolate = info.GetIsolate();
|
||
V8JS_TSRMLS_FETCH();
|
||
|
||
// Get the extension context
|
||
v8::Handle<v8::External> data = v8::Handle<v8::External>::Cast(info.Data());
|
||
v8js_ctx *c = static_cast<v8js_ctx*>(data->Value());
|
||
|
||
// Check that we have a module loader
|
||
if (c->module_loader == NULL) {
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("No module loader")));
|
||
return;
|
||
}
|
||
|
||
v8::String::Utf8Value module_id_v8(info[0]);
|
||
|
||
// Make sure to duplicate the module name string so it doesn't get freed by the V8 garbage collector
|
||
char *module_id = estrdup(ToCString(module_id_v8));
|
||
char *normalised_path = (char *)emalloc(PATH_MAX);
|
||
char *module_name = (char *)emalloc(PATH_MAX);
|
||
|
||
v8js_commonjs_normalise_identifier(c->modules_base.back(), module_id, normalised_path, module_name);
|
||
efree(module_id);
|
||
|
||
char *normalised_module_id = (char *)emalloc(strlen(normalised_path)+1+strlen(module_name)+1);
|
||
*normalised_module_id = 0;
|
||
|
||
if (strlen(normalised_path) > 0) {
|
||
strcat(normalised_module_id, normalised_path);
|
||
strcat(normalised_module_id, "/");
|
||
}
|
||
|
||
strcat(normalised_module_id, module_name);
|
||
efree(module_name);
|
||
|
||
// Check for module cyclic dependencies
|
||
for (std::vector<char *>::iterator it = c->modules_stack.begin(); it != c->modules_stack.end(); ++it)
|
||
{
|
||
if (!strcmp(*it, normalised_module_id)) {
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module cyclic dependency")));
|
||
return;
|
||
}
|
||
}
|
||
|
||
// If we have already loaded and cached this module then use it
|
||
if (c->modules_loaded.count(normalised_module_id) > 0) {
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
|
||
v8::Persistent<v8::Object> newobj;
|
||
newobj.Reset(isolate, c->modules_loaded[normalised_module_id]);
|
||
info.GetReturnValue().Set(newobj);
|
||
return;
|
||
}
|
||
|
||
// Callback to PHP to load the module code
|
||
|
||
zval *module_code;
|
||
zval *normalised_path_zend;
|
||
|
||
MAKE_STD_ZVAL(normalised_path_zend);
|
||
ZVAL_STRING(normalised_path_zend, normalised_module_id, 1);
|
||
|
||
zval **params[1] = {&normalised_path_zend};
|
||
if (FAILURE == call_user_function_ex(EG(function_table), NULL, c->module_loader, &module_code, 1, params, 0, NULL TSRMLS_CC)) {
|
||
zval_ptr_dtor(&normalised_path_zend);
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module loader callback failed")));
|
||
return;
|
||
}
|
||
zval_ptr_dtor(&normalised_path_zend);
|
||
|
||
// Check if an exception was thrown
|
||
if (EG(exception)) {
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
|
||
// Clear the PHP exception and throw it in V8 instead
|
||
zend_clear_exception(TSRMLS_C);
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module loader callback exception")));
|
||
return;
|
||
}
|
||
|
||
// Convert the return value to string
|
||
if (Z_TYPE_P(module_code) != IS_STRING) {
|
||
convert_to_string(module_code);
|
||
}
|
||
|
||
// Check that some code has been returned
|
||
if (Z_STRLEN_P(module_code)==0) {
|
||
zval_ptr_dtor(&module_code);
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module loader callback did not return code")));
|
||
return;
|
||
}
|
||
|
||
// Create a template for the global object and set the built-in global functions
|
||
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
|
||
global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(isolate, V8JS_MN(print)), v8::ReadOnly);
|
||
global->Set(V8JS_SYM("sleep"), v8::FunctionTemplate::New(isolate, V8JS_MN(sleep)), v8::ReadOnly);
|
||
global->Set(V8JS_SYM("require"), v8::FunctionTemplate::New(isolate, V8JS_MN(require), v8::External::New(isolate, c)), v8::ReadOnly);
|
||
|
||
// Add the exports object in which the module can return its API
|
||
v8::Local<v8::ObjectTemplate> exports_template = v8::ObjectTemplate::New();
|
||
v8::Local<v8::Object> exports = exports_template->NewInstance();
|
||
global->Set(V8JS_SYM("exports"), exports);
|
||
|
||
// Add the module object in which the module can have more fine-grained control over what it can return
|
||
v8::Handle<v8::ObjectTemplate> module_template = v8::ObjectTemplate::New();
|
||
v8::Handle<v8::Object> module = module_template->NewInstance();
|
||
module->Set(V8JS_SYM("id"), V8JS_STR(normalised_module_id));
|
||
global->Set(V8JS_SYM("module"), module);
|
||
|
||
// Each module gets its own context so different modules do not affect each other
|
||
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate, v8::Context::New(isolate, NULL, global));
|
||
|
||
// Catch JS exceptions
|
||
v8::TryCatch try_catch;
|
||
|
||
v8::Locker locker(isolate);
|
||
v8::Isolate::Scope isolate_scope(isolate);
|
||
|
||
v8::HandleScope handle_scope(isolate);
|
||
|
||
// Enter the module context
|
||
v8::Context::Scope scope(context);
|
||
// Set script identifier
|
||
v8::Local<v8::String> sname = V8JS_SYM("require");
|
||
|
||
v8::Local<v8::String> source = V8JS_STRL(Z_STRVAL_P(module_code), Z_STRLEN_P(module_code));
|
||
zval_ptr_dtor(&module_code);
|
||
|
||
// Create and compile script
|
||
v8::Local<v8::Script> script = v8::Script::Compile(source, sname);
|
||
|
||
// The script will be empty if there are compile errors
|
||
if (script.IsEmpty()) {
|
||
efree(normalised_module_id);
|
||
efree(normalised_path);
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module script compile failed")));
|
||
return;
|
||
}
|
||
|
||
// Add this module and path to the stack
|
||
c->modules_stack.push_back(normalised_module_id);
|
||
|
||
c->modules_base.push_back(normalised_path);
|
||
|
||
// Run script
|
||
v8::Local<v8::Value> result = script->Run();
|
||
|
||
// Remove this module and path from the stack
|
||
c->modules_stack.pop_back();
|
||
c->modules_base.pop_back();
|
||
|
||
efree(normalised_path);
|
||
|
||
// Script possibly terminated, return immediately
|
||
if (!try_catch.CanContinue()) {
|
||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module script compile failed")));
|
||
efree(normalised_module_id);
|
||
return;
|
||
}
|
||
|
||
// Handle runtime JS exceptions
|
||
if (try_catch.HasCaught()) {
|
||
// Rethrow the exception back to JS
|
||
info.GetReturnValue().Set(try_catch.ReThrow());
|
||
efree(normalised_module_id);
|
||
return;
|
||
}
|
||
|
||
v8::Handle<v8::Object> newobj;
|
||
|
||
// Cache the module so it doesn't need to be compiled and run again
|
||
// Ensure compatibility with CommonJS implementations such as NodeJS by playing nicely with module.exports and exports
|
||
if (module->Has(V8JS_SYM("exports")) && !module->Get(V8JS_SYM("exports"))->IsUndefined()) {
|
||
// If module.exports has been set then we cache this arbitrary value...
|
||
newobj = module->Get(V8JS_SYM("exports"))->ToObject();
|
||
} else {
|
||
// ...otherwise we cache the exports object itself
|
||
newobj = exports;
|
||
}
|
||
|
||
c->modules_loaded[normalised_module_id].Reset(isolate, newobj);
|
||
info.GetReturnValue().Set(newobj);
|
||
efree(normalised_module_id);
|
||
}
|
||
|
||
void v8js_register_methods(v8::Handle<v8::ObjectTemplate> global, v8js_ctx *c) /* {{{ */
|
||
{
|
||
v8::Isolate *isolate = c->isolate;
|
||
global->Set(V8JS_SYM("exit"), v8::FunctionTemplate::New(isolate, V8JS_MN(exit)), v8::ReadOnly);
|
||
global->Set(V8JS_SYM("sleep"), v8::FunctionTemplate::New(isolate, V8JS_MN(sleep)), v8::ReadOnly);
|
||
global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(isolate, V8JS_MN(print)), v8::ReadOnly);
|
||
global->Set(V8JS_SYM("var_dump"), v8::FunctionTemplate::New(isolate, V8JS_MN(var_dump)), v8::ReadOnly);
|
||
|
||
c->modules_base.push_back("");
|
||
global->Set(V8JS_SYM("require"), v8::FunctionTemplate::New(isolate, V8JS_MN(require), v8::External::New(isolate, c)), v8::ReadOnly);
|
||
}
|
||
/* }}} */
|
||
|
||
/*
|
||
* Local variables:
|
||
* tab-width: 4
|
||
* c-basic-offset: 4
|
||
* End:
|
||
* vim600: noet sw=4 ts=4 fdm=marker
|
||
* vim<600: noet sw=4 ts=4
|
||
*/
|