Browse Source

Refactor CommonJS modules functionality to store state in the extension globals and context as appropriate.

Simon Best 12 years ago
parent
commit
8ae7606338
5 changed files with 212 additions and 157 deletions
  1. 1 1
      config.m4
  2. 50 0
      php_v8js_macros.h
  3. 2 41
      v8js.cc
  4. 111 0
      v8js_commonjs.cc
  5. 48 115
      v8js_methods.cc

+ 1 - 1
config.m4

@@ -67,7 +67,7 @@ LDFLAGS=$old_LDFLAGS
     AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
     AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
   fi
   fi
   
   
-  PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc, $ext_shared, , "-std=c++0x")
+  PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc v8js_commonjs.cc, $ext_shared, , "-std=c++0x")
 
 
   PHP_ADD_MAKEFILE_FRAGMENT
   PHP_ADD_MAKEFILE_FRAGMENT
 fi
 fi

+ 50 - 0
php_v8js_macros.h

@@ -22,8 +22,20 @@
 #ifndef PHP_V8JS_MACROS_H
 #ifndef PHP_V8JS_MACROS_H
 #define PHP_V8JS_MACROS_H
 #define PHP_V8JS_MACROS_H
 
 
+extern "C" {
+#include "php.h"
+#include "php_v8js.h"
+}
+
 #include <v8.h>
 #include <v8.h>
 
 
+#include <chrono>
+#include <stack>
+#include <thread>
+
+#include <map>
+#include <vector>
+
 /* V8Js Version */
 /* V8Js Version */
 #define V8JS_VERSION "0.1.3"
 #define V8JS_VERSION "0.1.3"
 
 
@@ -107,9 +119,47 @@ struct php_v8js_ctx {
   bool memory_limit_hit;
   bool memory_limit_hit;
   v8::Persistent<v8::FunctionTemplate> global_template;
   v8::Persistent<v8::FunctionTemplate> global_template;
   zval *module_loader;
   zval *module_loader;
+  std::vector<char *> modules_stack;
+  std::vector<char *> modules_base;
 };
 };
 /* }}} */
 /* }}} */
 
 
+// Timer context
+struct php_v8js_timer_ctx
+{
+  long time_limit;
+  long memory_limit;
+  std::chrono::time_point<std::chrono::high_resolution_clock> time_point;
+  php_v8js_ctx *v8js_ctx;
+};
+
+/* Module globals */
+ZEND_BEGIN_MODULE_GLOBALS(v8js)
+  int v8_initialized;
+  HashTable *extensions;
+  int disposed_contexts; /* Disposed contexts since last time V8 did GC */
+
+  /* Ini globals */
+  char *v8_flags; /* V8 command line flags */
+  int max_disposed_contexts; /* Max disposed context allowed before forcing V8 GC */
+
+  // Timer thread globals
+  std::stack<php_v8js_timer_ctx *> timer_stack;
+  std::thread *timer_thread;
+  std::mutex timer_mutex;
+  bool timer_stop;
+
+  std::map<char *, v8::Handle<v8::Object> > modules_loaded;
+ZEND_END_MODULE_GLOBALS(v8js)
+
+extern zend_v8js_globals v8js_globals;
+
+#ifdef ZTS
+# define V8JSG(v) TSRMG(v8js_globals_id, zend_v8js_globals *, v)
+#else
+# define V8JSG(v) (v8js_globals.v)
+#endif
+
 /* Register builtin methods into passed object */
 /* Register builtin methods into passed object */
 void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate>, php_v8js_ctx *c);
 void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate>, php_v8js_ctx *c);
 
 

+ 2 - 41
v8js.cc

@@ -25,59 +25,20 @@
 #include "config.h"
 #include "config.h"
 #endif
 #endif
 
 
+#include "php_v8js_macros.h"
+
 extern "C" {
 extern "C" {
-#include "php.h"
 #include "php_ini.h"
 #include "php_ini.h"
 #include "ext/standard/info.h"
 #include "ext/standard/info.h"
 #include "ext/standard/php_string.h"
 #include "ext/standard/php_string.h"
 #include "ext/standard/php_smart_str.h"
 #include "ext/standard/php_smart_str.h"
 #include "zend_exceptions.h"
 #include "zend_exceptions.h"
-#include "php_v8js.h"
 }
 }
 
 
-#include <v8.h>
-#include "php_v8js_macros.h"
-
-#include <chrono>
-#include <stack>
-#include <thread>
-
 /* Forward declarations */
 /* Forward declarations */
 static void php_v8js_throw_script_exception(v8::TryCatch * TSRMLS_DC);
 static void php_v8js_throw_script_exception(v8::TryCatch * TSRMLS_DC);
 static void php_v8js_create_script_exception(zval *, v8::TryCatch * TSRMLS_DC);
 static void php_v8js_create_script_exception(zval *, v8::TryCatch * TSRMLS_DC);
 
 
-// Timer context
-struct php_v8js_timer_ctx
-{
-	long time_limit;
-	long memory_limit;
-	std::chrono::time_point<std::chrono::high_resolution_clock> time_point;
-	php_v8js_ctx *v8js_ctx;
-};
-
-/* Module globals */
-ZEND_BEGIN_MODULE_GLOBALS(v8js)
-	int v8_initialized;
-	HashTable *extensions;
-	int disposed_contexts; /* Disposed contexts since last time V8 did GC */
-
-	/* Ini globals */
-	char *v8_flags; /* V8 command line flags */
-	int max_disposed_contexts; /* Max disposed context allowed before forcing V8 GC */
-
-	// Timer thread globals
-	std::stack<php_v8js_timer_ctx *> timer_stack;
-	std::thread *timer_thread;
-	std::mutex timer_mutex;
-	bool timer_stop;
-ZEND_END_MODULE_GLOBALS(v8js)
-
-#ifdef ZTS
-# define V8JSG(v) TSRMG(v8js_globals_id, zend_v8js_globals *, v)
-#else
-# define V8JSG(v) (v8js_globals.v)
-#endif
-
 ZEND_DECLARE_MODULE_GLOBALS(v8js)
 ZEND_DECLARE_MODULE_GLOBALS(v8js)
 
 
 /* {{{ INI Settings */
 /* {{{ INI Settings */

+ 111 - 0
v8js_commonjs.cc

@@ -0,0 +1,111 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2012 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Simon Best <[email protected]>                            |
+  +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include "php.h"
+#include "zend_exceptions.h"
+}
+
+#include "php_v8js_macros.h"
+
+void php_v8js_commonjs_split_terms(char *identifier, std::vector<char *> &terms)
+{
+    char *term = (char *)malloc(PATH_MAX), *ptr = term;
+
+    // Initialise the term string
+    *term = 0;
+
+    while (*identifier > 0) {
+        if (*identifier == '/') {
+            if (strlen(term) > 0) {
+                // Terminate term string and add to terms vector
+                *ptr++ = 0;
+                terms.push_back(strdup(term));
+
+                // Reset term string
+                memset(term, 0, strlen(term));
+                ptr = term;
+            }
+        } else {
+            *ptr++ = *identifier;
+        }
+
+        identifier++;
+    }
+
+    if (strlen(term) > 0) {
+        // Terminate term string and add to terms vector
+        *ptr++ = 0;
+        terms.push_back(strdup(term));
+    }
+
+    if (term > 0) {
+        free(term);
+    }
+}
+
+void php_v8js_commonjs_normalise_identifier(char *base, char *identifier, char *normalised_path, char *module_name)
+{
+    std::vector<char *> id_terms, terms;
+    php_v8js_commonjs_split_terms(identifier, id_terms);
+
+    // If we have a relative module identifier then include the base terms
+    if (!strcmp(id_terms.front(), ".") || !strcmp(id_terms.front(), "..")) {
+        php_v8js_commonjs_split_terms(base, terms);
+    }
+
+    terms.insert(terms.end(), id_terms.begin(), id_terms.end());
+
+    std::vector<char *> normalised_terms;
+
+    for (std::vector<char *>::iterator it = terms.begin(); it != terms.end(); it++) {
+        char *term = *it;
+
+        if (!strcmp(term, "..")) {
+            // Ignore parent term (..) if it's the first normalised term
+            if (normalised_terms.size() > 0) {
+                // Remove the parent normalized term
+                normalised_terms.pop_back();
+            }
+        } else if (strcmp(term, ".")) {
+            // Add the term if it's not the current term (.)
+            normalised_terms.push_back(term);
+        }
+    }
+
+    // Initialise the normalised path string
+    *normalised_path = 0;
+    *module_name = 0;
+
+    strcat(module_name, normalised_terms.back());
+    normalised_terms.pop_back();
+
+    for (std::vector<char *>::iterator it = normalised_terms.begin(); it != normalised_terms.end(); it++) {
+        char *term = *it;
+
+        if (strlen(normalised_path) > 0) {
+            strcat(normalised_path, "/");
+        }
+
+        strcat(normalised_path, term);
+    }
+}

+ 48 - 115
v8js_methods.cc

@@ -23,15 +23,14 @@
 #include "config.h"
 #include "config.h"
 #endif
 #endif
 
 
+#include "php_v8js_macros.h"
+
 extern "C" {
 extern "C" {
-#include "php.h"
 #include "zend_exceptions.h"
 #include "zend_exceptions.h"
 }
 }
 
 
-#include "php_v8js_macros.h"
-#include <v8.h>
-#include <map>
-#include <vector>
+/* Forward declarations */
+void php_v8js_commonjs_normalise_identifier(char *base, char *identifier, char *normalised_path, char *module_name);
 
 
 /* global.exit - terminate execution */
 /* global.exit - terminate execution */
 V8JS_METHOD(exit) /* {{{ */
 V8JS_METHOD(exit) /* {{{ */
@@ -165,106 +164,33 @@ V8JS_METHOD(var_dump) /* {{{ */
 	for (int i = 0; i < args.Length(); i++) {
 	for (int i = 0; i < args.Length(); i++) {
 		_php_v8js_dumper(args[i], 1 TSRMLS_CC);
 		_php_v8js_dumper(args[i], 1 TSRMLS_CC);
 	}
 	}
-	
+
 	return V8JS_NULL;
 	return V8JS_NULL;
 }
 }
 /* }}} */
 /* }}} */
 
 
-// TODO: Put this in php_v8js_context
-std::map<char *, v8::Handle<v8::Object> > modules_loaded;
-std::vector<char *> modules_stack;
-std::vector<char *> modules_base;
-
-void split_terms(char *identifier, std::vector<char *> &terms)
-{
-	char *term = (char *)malloc(PATH_MAX), *ptr = term;
-
-	// Initialise the term string
-	*term = 0;
-
-	while (*identifier > 0) {
-		if (*identifier == '/') {
-			if (strlen(term) > 0) {
-				// Terminate term string and add to terms vector
-				*ptr++ = 0;
-				terms.push_back(strdup(term));
-
-				// Reset term string
-				memset(term, 0, strlen(term));
-				ptr = term;
-			}
-		} else {
-			*ptr++ = *identifier;
-		}
-
-		identifier++;
-	}
-
-	if (strlen(term) > 0) {
-		// Terminate term string and add to terms vector
-		*ptr++ = 0;
-		terms.push_back(strdup(term));
-	}
-
-	if (term > 0) {
-		free(term);
-	}
-}
-
-void normalize_identifier(char *base, char *identifier, char *normalised_path, char *module_name)
+V8JS_METHOD(require)
 {
 {
-	std::vector<char *> id_terms, terms;
-	split_terms(identifier, id_terms);
-
-	// If we have a relative module identifier then include the base terms
-	if (!strcmp(id_terms.front(), ".") || !strcmp(id_terms.front(), "..")) {
-		split_terms(base, terms);
-	}
-
-	terms.insert(terms.end(), id_terms.begin(), id_terms.end());
-
-	std::vector<char *> normalised_terms;
-
-	for (std::vector<char *>::iterator it = terms.begin(); it != terms.end(); it++) {
-		char *term = *it;
-
-		if (!strcmp(term, "..")) {
-			normalised_terms.pop_back();
-		} else if (strcmp(term, ".")) {
-			normalised_terms.push_back(term);
-		}
-	}
-
-	// Initialise the normalised path string
-	*normalised_path = 0;
-	*module_name = 0;
-
-	strcat(module_name, normalised_terms.back());
-	normalised_terms.pop_back();
-
-	for (std::vector<char *>::iterator it = normalised_terms.begin(); it != normalised_terms.end(); it++) {
-		char *term = *it;
-
-		if (strlen(normalised_path) > 0) {
-			strcat(normalised_path, "/");
-		}
+	// Get the extension context
+	v8::Handle<v8::External> data = v8::Handle<v8::External>::Cast(args.Data());
+	php_v8js_ctx *c = static_cast<php_v8js_ctx*>(data->Value());
 
 
-		strcat(normalised_path, term);
+	// Check that we have a module loader
+	if (c->module_loader == NULL) {
+		return v8::ThrowException(v8::String::New("No module loader"));
 	}
 	}
-}
 
 
-V8JS_METHOD(require)
-{
 	v8::String::Utf8Value module_id_v8(args[0]);
 	v8::String::Utf8Value module_id_v8(args[0]);
 
 
 	// Make sure to duplicate the module name string so it doesn't get freed by the V8 garbage collector
 	// Make sure to duplicate the module name string so it doesn't get freed by the V8 garbage collector
-	char *module_id = strdup(ToCString(module_id_v8));
-	char *normalised_path = (char *)malloc(PATH_MAX);
-	char *module_name = (char *)malloc(PATH_MAX);
+	char *module_id = estrdup(ToCString(module_id_v8));
+	char *normalised_path = (char *)emalloc(PATH_MAX);
+	char *module_name = (char *)emalloc(PATH_MAX);
 
 
-	normalize_identifier(modules_base.back(), module_id, normalised_path, module_name);
+	php_v8js_commonjs_normalise_identifier(c->modules_base.back(), module_id, normalised_path, module_name);
+	efree(module_id);
 
 
-	char *normalised_module_id = (char *)malloc(strlen(module_id));
+	char *normalised_module_id = (char *)emalloc(strlen(module_id));
 	*normalised_module_id = 0;
 	*normalised_module_id = 0;
 
 
 	if (strlen(normalised_path) > 0) {
 	if (strlen(normalised_path) > 0) {
@@ -273,27 +199,23 @@ V8JS_METHOD(require)
 	}
 	}
 
 
 	strcat(normalised_module_id, module_name);
 	strcat(normalised_module_id, module_name);
+	efree(module_name);
 
 
 	// Check for module cyclic dependencies
 	// Check for module cyclic dependencies
-	for (std::vector<char *>::iterator it = modules_stack.begin(); it != modules_stack.end(); ++it)
+	for (std::vector<char *>::iterator it = c->modules_stack.begin(); it != c->modules_stack.end(); ++it)
     {
     {
     	if (!strcmp(*it, normalised_module_id)) {
     	if (!strcmp(*it, normalised_module_id)) {
+    		efree(normalised_path);
+
     		return v8::ThrowException(v8::String::New("Module cyclic dependency"));
     		return v8::ThrowException(v8::String::New("Module cyclic dependency"));
     	}
     	}
     }
     }
 
 
     // If we have already loaded and cached this module then use it
     // If we have already loaded and cached this module then use it
-	if (modules_loaded.count(normalised_module_id) > 0) {
-		return modules_loaded[normalised_module_id];
-	}
-
-	// Get the extension context
-	v8::Handle<v8::External> data = v8::Handle<v8::External>::Cast(args.Data());
-	php_v8js_ctx *c = static_cast<php_v8js_ctx*>(data->Value());
+	if (V8JSG(modules_loaded).count(normalised_module_id) > 0) {
+		efree(normalised_path);
 
 
-	// Check that we have a module loader
-	if (c->module_loader == NULL) {
-		return v8::ThrowException(v8::String::New("No module loader"));
+		return V8JSG(modules_loaded)[normalised_module_id];
 	}
 	}
 
 
 	// Callback to PHP to load the module code
 	// Callback to PHP to load the module code
@@ -306,11 +228,15 @@ V8JS_METHOD(require)
 	zval* params[] = { normalised_path_zend };
 	zval* params[] = { normalised_path_zend };
 
 
 	if (FAILURE == call_user_function(EG(function_table), NULL, c->module_loader, &module_code, 1, params TSRMLS_CC)) {
 	if (FAILURE == call_user_function(EG(function_table), NULL, c->module_loader, &module_code, 1, params TSRMLS_CC)) {
+		efree(normalised_path);
+
 		return v8::ThrowException(v8::String::New("Module loader callback failed"));
 		return v8::ThrowException(v8::String::New("Module loader callback failed"));
 	}
 	}
 
 
 	// Check if an exception was thrown
 	// Check if an exception was thrown
 	if (EG(exception)) {
 	if (EG(exception)) {
+		efree(normalised_path);
+
 		// Clear the PHP exception and throw it in V8 instead
 		// Clear the PHP exception and throw it in V8 instead
 		zend_clear_exception(TSRMLS_CC);
 		zend_clear_exception(TSRMLS_CC);
 		return v8::ThrowException(v8::String::New("Module loader callback exception"));
 		return v8::ThrowException(v8::String::New("Module loader callback exception"));
@@ -323,6 +249,8 @@ V8JS_METHOD(require)
 
 
 	// Check that some code has been returned
 	// Check that some code has been returned
 	if (!strlen(Z_STRVAL(module_code))) {
 	if (!strlen(Z_STRVAL(module_code))) {
+		efree(normalised_path);
+
 		return v8::ThrowException(v8::String::New("Module loader callback did not return code"));
 		return v8::ThrowException(v8::String::New("Module loader callback did not return code"));
 	}
 	}
 
 
@@ -349,11 +277,13 @@ V8JS_METHOD(require)
 	// Catch JS exceptions
 	// Catch JS exceptions
 	v8::TryCatch try_catch;
 	v8::TryCatch try_catch;
 
 
-	// Enter the module context
-	v8::Context::Scope scope(context);
+	v8::Locker locker(c->isolate);
+	v8::Isolate::Scope isolate_scope(c->isolate);
 
 
-	v8::HandleScope handle_scope;
+	v8::HandleScope handle_scope(c->isolate);
 
 
+	// Enter the module context
+	v8::Context::Scope scope(context);
 	// Set script identifier
 	// Set script identifier
 	v8::Local<v8::String> sname = V8JS_SYM("require");
 	v8::Local<v8::String> sname = V8JS_SYM("require");
 
 
@@ -364,19 +294,23 @@ V8JS_METHOD(require)
 
 
 	// The script will be empty if there are compile errors
 	// The script will be empty if there are compile errors
 	if (script.IsEmpty()) {
 	if (script.IsEmpty()) {
+		efree(normalised_path);
 		return v8::ThrowException(v8::String::New("Module script compile failed"));
 		return v8::ThrowException(v8::String::New("Module script compile failed"));
 	}
 	}
 
 
 	// Add this module and path to the stack
 	// Add this module and path to the stack
-	modules_stack.push_back(normalised_module_id);
-	modules_base.push_back(normalised_path);
+	c->modules_stack.push_back(normalised_module_id);
+
+	c->modules_base.push_back(normalised_path);
 
 
 	// Run script
 	// Run script
 	v8::Local<v8::Value> result = script->Run();
 	v8::Local<v8::Value> result = script->Run();
 
 
 	// Remove this module and path from the stack
 	// Remove this module and path from the stack
-	modules_stack.pop_back();
-	modules_base.pop_back();
+	c->modules_stack.pop_back();
+	c->modules_base.pop_back();
+
+	efree(normalised_path);
 
 
 	// Script possibly terminated, return immediately
 	// Script possibly terminated, return immediately
 	if (!try_catch.CanContinue()) {
 	if (!try_catch.CanContinue()) {
@@ -385,7 +319,6 @@ V8JS_METHOD(require)
 
 
 	// Handle runtime JS exceptions
 	// Handle runtime JS exceptions
 	if (try_catch.HasCaught()) {
 	if (try_catch.HasCaught()) {
-
 		// Rethrow the exception back to JS
 		// Rethrow the exception back to JS
 		return try_catch.ReThrow();
 		return try_catch.ReThrow();
 	}
 	}
@@ -394,13 +327,13 @@ V8JS_METHOD(require)
 	// Ensure compatibility with CommonJS implementations such as NodeJS by playing nicely with module.exports and exports
 	// Ensure compatibility with CommonJS implementations such as NodeJS by playing nicely with module.exports and exports
 	if (module->Has(v8::String::New("exports")) && !module->Get(v8::String::New("exports"))->IsUndefined()) {
 	if (module->Has(v8::String::New("exports")) && !module->Get(v8::String::New("exports"))->IsUndefined()) {
 		// If module.exports has been set then we cache this arbitrary value...
 		// If module.exports has been set then we cache this arbitrary value...
-		modules_loaded[normalised_module_id] = handle_scope.Close(module->Get(v8::String::New("exports"))->ToObject());
+		V8JSG(modules_loaded)[normalised_module_id] = handle_scope.Close(module->Get(v8::String::New("exports"))->ToObject());
 	} else {
 	} else {
 		// ...otherwise we cache the exports object itself
 		// ...otherwise we cache the exports object itself
-		modules_loaded[normalised_module_id] = handle_scope.Close(exports);		
+		V8JSG(modules_loaded)[normalised_module_id] = handle_scope.Close(exports);
 	}
 	}
 
 
-	return modules_loaded[normalised_module_id];
+	return V8JSG(modules_loaded)[normalised_module_id];
 }
 }
 
 
 void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global, php_v8js_ctx *c) /* {{{ */
 void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global, php_v8js_ctx *c) /* {{{ */
@@ -410,7 +343,7 @@ void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global, php_v8js_c
 	global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(V8JS_MN(print)), v8::ReadOnly);
 	global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(V8JS_MN(print)), v8::ReadOnly);
 	global->Set(V8JS_SYM("var_dump"), v8::FunctionTemplate::New(V8JS_MN(var_dump)), v8::ReadOnly);
 	global->Set(V8JS_SYM("var_dump"), v8::FunctionTemplate::New(V8JS_MN(var_dump)), v8::ReadOnly);
 
 
-	modules_base.push_back("");
+	c->modules_base.push_back("");
 	global->Set(V8JS_SYM("require"), v8::FunctionTemplate::New(V8JS_MN(require), v8::External::New(c)), v8::ReadOnly);
 	global->Set(V8JS_SYM("require"), v8::FunctionTemplate::New(V8JS_MN(require), v8::External::New(c)), v8::ReadOnly);
 }
 }
 /* }}} */
 /* }}} */