Browse Source

Merge pull request #202 from stesie/issue-183

Export public methods on derived classes to V8
Stefan Siegl 9 years ago
parent
commit
82ffaa2c35

+ 7 - 0
php_v8js_macros.h

@@ -85,6 +85,13 @@ extern "C" {
 #define V8JS_FLAG_FORCE_ARRAY	(1<<1)
 #define V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS	(1<<2)
 
+
+/* These are not defined by Zend */
+#define ZEND_WAKEUP_FUNC_NAME    "__wakeup"
+#define ZEND_SLEEP_FUNC_NAME     "__sleep"
+#define ZEND_SET_STATE_FUNC_NAME "__set_state"
+
+
 /* Convert zval into V8 value */
 v8::Handle<v8::Value> zval_to_v8js(zval *, v8::Isolate * TSRMLS_DC);
 

+ 30 - 0
tests/issue_183_001.phpt

@@ -0,0 +1,30 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (protected)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+	protected function hello()
+	{
+		print("Hello World\n");
+	}
+}
+
+$JS = <<< EOT
+PHP.hello();
+EOT;
+
+$v8 = new Foo();
+$v8->executeString($JS);
+
+?>
+===EOF===
+--EXPECTF--
+Fatal error: Uncaught exception 'V8JsScriptException' with message 'V8Js::compileString():1: TypeError: %s' in %s
+Stack trace:
+#0 %s: V8Js->executeString('PHP.hello();')
+#1 {main}
+  thrown in %s on line 16

+ 30 - 0
tests/issue_183_002.phpt

@@ -0,0 +1,30 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (private)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+	private function hello()
+	{
+		print("Hello World\n");
+	}
+}
+
+$JS = <<< EOT
+PHP.hello();
+EOT;
+
+$v8 = new Foo();
+$v8->executeString($JS);
+
+?>
+===EOF===
+--EXPECTF--
+Fatal error: Uncaught exception 'V8JsScriptException' with message 'V8Js::compileString():1: TypeError: %s' in %s
+Stack trace:
+#0 %s: V8Js->executeString('PHP.hello();')
+#1 {main}
+  thrown in %s on line 16

+ 57 - 0
tests/issue_183_003.phpt

@@ -0,0 +1,57 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (V8Js methods)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+    public function hello()
+    {
+	print("Hello World\n");
+    }
+}
+
+$JS = <<< EOT
+var_dump(typeof PHP.hello);
+var_dump(typeof PHP.executeString);
+var_dump(typeof PHP.compileString);
+var_dump(typeof PHP.executeScript);
+var_dump(typeof PHP.checkString);
+var_dump(typeof PHP.getPendingException);
+var_dump(typeof PHP.setModuleNormaliser);
+var_dump(typeof PHP.setModuleLoader);
+var_dump(typeof PHP.registerExtension);
+var_dump(typeof PHP.getExtensions);
+var_dump(typeof PHP.setTimeLimit);
+var_dump(typeof PHP.setMemoryLimit);
+
+try {
+    PHP.setTimeLimit(100);
+}
+catch(e) {
+    var_dump('caught');
+}
+EOT;
+
+$v8 = new Foo();
+$v8->executeString($JS);
+
+?>
+===EOF===
+--EXPECTF--
+string(8) "function"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(9) "undefined"
+string(6) "caught"
+===EOF===

+ 44 - 0
tests/issue_183_004.phpt

@@ -0,0 +1,44 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (overridden V8Js methods)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+    public function hello()
+    {
+        print("Hello World\n");
+    }
+
+    public function executeString($script, $identifier = NULL, $flags = NULL, $time_limit = NULL, $memory_limit = NULL)
+    {
+        var_dump("executeString");
+        return parent::executeString($script);
+    }
+}
+
+$JS = <<< EOT
+var_dump(typeof PHP.hello);
+var_dump(typeof PHP.executeString);
+
+try {
+    PHP.executeString('print("blar")');
+}
+catch(e) {
+    var_dump('caught');
+}
+EOT;
+
+$v8 = new Foo();
+$v8->executeString($JS);
+
+?>
+===EOF===
+--EXPECTF--
+string(13) "executeString"
+string(8) "function"
+string(9) "undefined"
+string(6) "caught"
+===EOF===

+ 19 - 0
tests/issue_183_005.phpt

@@ -0,0 +1,19 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (__sleep)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+    public function __sleep()
+    {
+	var_dump("foo");
+    }
+}
+
+?>
+===EOF===
+--EXPECTF--
+Fatal error: Cannot override final method V8Js::__sleep() in %s

+ 19 - 0
tests/issue_183_006.phpt

@@ -0,0 +1,19 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes (__wakeup)
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+    public function __wakeup()
+    {
+	var_dump("foo");
+    }
+}
+
+?>
+===EOF===
+--EXPECTF--
+Fatal error: Cannot override final method V8Js::__wakeup() in %s

+ 27 - 0
tests/issue_183_basic.phpt

@@ -0,0 +1,27 @@
+--TEST--
+Test V8::executeString() : Method access on derived classes
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo extends \V8Js
+{
+	public function hello()
+	{
+		print("Hello World\n");
+	}
+}
+
+$JS = <<< EOT
+PHP.hello();
+EOT;
+
+$v8 = new Foo();
+$v8->executeString($JS);
+
+?>
+===EOF===
+--EXPECT--
+Hello World
+===EOF===

+ 81 - 2
v8js_class.cc

@@ -30,6 +30,7 @@ extern "C" {
 #include "v8js_v8.h"
 #include "v8js_exceptions.h"
 #include "v8js_v8object_class.h"
+#include "v8js_object_export.h"
 #include "v8js_timer.h"
 
 #include <functional>
@@ -45,6 +46,9 @@ static zend_class_entry *php_ce_v8js;
 static zend_object_handlers v8js_object_handlers;
 /* }}} */
 
+/* Forward declare v8js_methods, actually "static" but not possible in C++ */
+extern const zend_function_entry v8js_methods[];
+
 typedef struct _v8js_script {
 	char *name;
 	v8js_ctx *ctx;
@@ -319,6 +323,10 @@ static void v8js_fatal_error_handler(const char *location, const char *message)
 }
 /* }}} */
 
+#define IS_MAGIC_FUNC(mname) \
+	((key_len == sizeof(mname)) && \
+	!strncasecmp(key, mname, key_len - 1))
+
 /* {{{ proto void V8Js::__construct([string object_name [, array variables [, array extensions [, bool report_uncaught_exceptions]]])
    __construct for V8Js */
 static PHP_METHOD(V8Js, __construct)
@@ -480,7 +488,78 @@ static PHP_METHOD(V8Js, __construct)
 		}
 	}
 
+	/* Add pointer to zend object */
+	php_obj->SetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY), v8::External::New(isolate, getThis()));
+
+	/* Export public methods */
+	zend_function *method_ptr;
+	char *key = NULL;
+	uint key_len;
+
+	zend_hash_internal_pointer_reset_ex(&c->std.ce->function_table, &pos);
+	for (;; zend_hash_move_forward_ex(&c->std.ce->function_table, &pos)) {
+		if (zend_hash_get_current_key_ex(&c->std.ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING  ||
+			zend_hash_get_current_data_ex(&c->std.ce->function_table, (void **) &method_ptr, &pos) == FAILURE
+			) {
+			break;
+		}
 
+		if ((method_ptr->common.fn_flags & ZEND_ACC_PUBLIC) == 0) {
+			/* Allow only public methods */
+			continue;
+		}
+
+		if ((method_ptr->common.fn_flags & (ZEND_ACC_CTOR|ZEND_ACC_DTOR|ZEND_ACC_CLONE)) != 0) {
+			/* no __construct, __destruct(), or __clone() functions */
+			continue;
+		}
+
+		/* hide (do not export) other PHP magic functions */
+		if (IS_MAGIC_FUNC(ZEND_CALLSTATIC_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_SLEEP_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_WAKEUP_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_SET_STATE_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_GET_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_SET_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_UNSET_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_CALL_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_INVOKE_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME) ||
+			IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
+			continue;
+		}
+
+		const zend_function_entry *fe;
+		for (fe = v8js_methods; fe->fname; fe ++) {
+			if (strcmp(fe->fname, method_ptr->common.function_name) == 0) {
+				break;
+			}
+		}
+
+		if(fe->fname) {
+			/* Method belongs to \V8Js class itself, never export to V8, even if
+			 * it is overriden in a derived class. */
+			continue;
+		}
+
+		v8::Local<v8::String> method_name = V8JS_STR(method_ptr->common.function_name);
+		v8::Local<v8::FunctionTemplate> ft;
+
+		try {
+			ft = v8::Local<v8::FunctionTemplate>::New
+				(isolate, c->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));
+			// @fixme add/check Signature v8::Signature::New((isolate), tmpl));
+			v8js_tmpl_t *persistent_ft = &c->method_tmpls[method_ptr];
+			persistent_ft->Reset(isolate, ft);
+		}
+
+
+		php_obj->ForceSet(method_name, ft->GetFunction());
+	}
 }
 /* }}} */
 
@@ -1056,7 +1135,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_setmemorylimit, 0, 0, 1)
 ZEND_END_ARG_INFO()
 
 
-static const zend_function_entry v8js_methods[] = { /* {{{ */
+const zend_function_entry v8js_methods[] = { /* {{{ */
 	PHP_ME(V8Js,	__construct,			arginfo_v8js_construct,				ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
 	PHP_ME(V8Js,	__sleep,				arginfo_v8js_sleep,					ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
 	PHP_ME(V8Js,	__wakeup,				arginfo_v8js_sleep,					ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
@@ -1107,7 +1186,7 @@ static void v8js_unset_property(zval *object, zval *member ZEND_HASH_KEY_DC TSRM
 	/* Global PHP JS object */
 	v8::Local<v8::String> object_name_js = v8::Local<v8::String>::New(isolate, c->object_name);
 	v8::Local<v8::Object> jsobj = V8JS_GLOBAL(isolate)->Get(object_name_js)->ToObject();
-	
+
 	/* Delete value from PHP JS object */
 	jsobj->Delete(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)));
 

+ 1 - 6
v8js_object_export.cc

@@ -169,7 +169,7 @@ failure:
 /* }}} */
 
 /* Callback for PHP methods and functions */
-static void v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
+void v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
 {
 	v8::Isolate *isolate = info.GetIsolate();
 	v8::Local<v8::Object> self = info.Holder();
@@ -291,11 +291,6 @@ static void v8js_weak_closure_callback(const v8::WeakCallbackData<v8::Object, v8
 	ctx->weak_closures.erase(persist_tpl_);
 };
 
-/* These are not defined by Zend */
-#define ZEND_WAKEUP_FUNC_NAME    "__wakeup"
-#define ZEND_SLEEP_FUNC_NAME     "__sleep"
-#define ZEND_SET_STATE_FUNC_NAME "__set_state"
-
 #define IS_MAGIC_FUNC(mname) \
 	((key_len == sizeof(mname)) && \
 	!strncasecmp(key, mname, key_len - 1))

+ 2 - 0
v8js_object_export.h

@@ -30,4 +30,6 @@ v8::Local<v8::Value> v8js_named_property_callback(v8::Local<v8::String> property
 						      property_op_t callback_type,
 						      v8::Local<v8::Value> set_value = v8::Local<v8::Value>());
 
+void v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info);
+
 #endif /* V8JS_OBJECT_EXPORT_H */