Skip to content

Commit 82ffaa2

Browse files
committed
Merge pull request #202 from stesie/issue-183
Export public methods on derived classes to V8
2 parents bd442be + 3808f69 commit 82ffaa2

11 files changed

+317
-8
lines changed

php_v8js_macros.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ extern "C" {
8585
#define V8JS_FLAG_FORCE_ARRAY (1<<1)
8686
#define V8JS_FLAG_PROPAGATE_PHP_EXCEPTIONS (1<<2)
8787

88+
89+
/* These are not defined by Zend */
90+
#define ZEND_WAKEUP_FUNC_NAME "__wakeup"
91+
#define ZEND_SLEEP_FUNC_NAME "__sleep"
92+
#define ZEND_SET_STATE_FUNC_NAME "__set_state"
93+
94+
8895
/* Convert zval into V8 value */
8996
v8::Handle<v8::Value> zval_to_v8js(zval *, v8::Isolate * TSRMLS_DC);
9097

tests/issue_183_001.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (protected)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
protected function hello()
11+
{
12+
print("Hello World\n");
13+
}
14+
}
15+
16+
$JS = <<< EOT
17+
PHP.hello();
18+
EOT;
19+
20+
$v8 = new Foo();
21+
$v8->executeString($JS);
22+
23+
?>
24+
===EOF===
25+
--EXPECTF--
26+
Fatal error: Uncaught exception 'V8JsScriptException' with message 'V8Js::compileString():1: TypeError: %s' in %s
27+
Stack trace:
28+
#0 %s: V8Js->executeString('PHP.hello();')
29+
#1 {main}
30+
thrown in %s on line 16

tests/issue_183_002.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (private)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
private function hello()
11+
{
12+
print("Hello World\n");
13+
}
14+
}
15+
16+
$JS = <<< EOT
17+
PHP.hello();
18+
EOT;
19+
20+
$v8 = new Foo();
21+
$v8->executeString($JS);
22+
23+
?>
24+
===EOF===
25+
--EXPECTF--
26+
Fatal error: Uncaught exception 'V8JsScriptException' with message 'V8Js::compileString():1: TypeError: %s' in %s
27+
Stack trace:
28+
#0 %s: V8Js->executeString('PHP.hello();')
29+
#1 {main}
30+
thrown in %s on line 16

tests/issue_183_003.phpt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (V8Js methods)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
public function hello()
11+
{
12+
print("Hello World\n");
13+
}
14+
}
15+
16+
$JS = <<< EOT
17+
var_dump(typeof PHP.hello);
18+
var_dump(typeof PHP.executeString);
19+
var_dump(typeof PHP.compileString);
20+
var_dump(typeof PHP.executeScript);
21+
var_dump(typeof PHP.checkString);
22+
var_dump(typeof PHP.getPendingException);
23+
var_dump(typeof PHP.setModuleNormaliser);
24+
var_dump(typeof PHP.setModuleLoader);
25+
var_dump(typeof PHP.registerExtension);
26+
var_dump(typeof PHP.getExtensions);
27+
var_dump(typeof PHP.setTimeLimit);
28+
var_dump(typeof PHP.setMemoryLimit);
29+
30+
try {
31+
PHP.setTimeLimit(100);
32+
}
33+
catch(e) {
34+
var_dump('caught');
35+
}
36+
EOT;
37+
38+
$v8 = new Foo();
39+
$v8->executeString($JS);
40+
41+
?>
42+
===EOF===
43+
--EXPECTF--
44+
string(8) "function"
45+
string(9) "undefined"
46+
string(9) "undefined"
47+
string(9) "undefined"
48+
string(9) "undefined"
49+
string(9) "undefined"
50+
string(9) "undefined"
51+
string(9) "undefined"
52+
string(9) "undefined"
53+
string(9) "undefined"
54+
string(9) "undefined"
55+
string(9) "undefined"
56+
string(6) "caught"
57+
===EOF===

tests/issue_183_004.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (overridden V8Js methods)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
public function hello()
11+
{
12+
print("Hello World\n");
13+
}
14+
15+
public function executeString($script, $identifier = NULL, $flags = NULL, $time_limit = NULL, $memory_limit = NULL)
16+
{
17+
var_dump("executeString");
18+
return parent::executeString($script);
19+
}
20+
}
21+
22+
$JS = <<< EOT
23+
var_dump(typeof PHP.hello);
24+
var_dump(typeof PHP.executeString);
25+
26+
try {
27+
PHP.executeString('print("blar")');
28+
}
29+
catch(e) {
30+
var_dump('caught');
31+
}
32+
EOT;
33+
34+
$v8 = new Foo();
35+
$v8->executeString($JS);
36+
37+
?>
38+
===EOF===
39+
--EXPECTF--
40+
string(13) "executeString"
41+
string(8) "function"
42+
string(9) "undefined"
43+
string(6) "caught"
44+
===EOF===

tests/issue_183_005.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (__sleep)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
public function __sleep()
11+
{
12+
var_dump("foo");
13+
}
14+
}
15+
16+
?>
17+
===EOF===
18+
--EXPECTF--
19+
Fatal error: Cannot override final method V8Js::__sleep() in %s

tests/issue_183_006.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes (__wakeup)
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
public function __wakeup()
11+
{
12+
var_dump("foo");
13+
}
14+
}
15+
16+
?>
17+
===EOF===
18+
--EXPECTF--
19+
Fatal error: Cannot override final method V8Js::__wakeup() in %s

tests/issue_183_basic.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Test V8::executeString() : Method access on derived classes
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
class Foo extends \V8Js
9+
{
10+
public function hello()
11+
{
12+
print("Hello World\n");
13+
}
14+
}
15+
16+
$JS = <<< EOT
17+
PHP.hello();
18+
EOT;
19+
20+
$v8 = new Foo();
21+
$v8->executeString($JS);
22+
23+
?>
24+
===EOF===
25+
--EXPECT--
26+
Hello World
27+
===EOF===

v8js_class.cc

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extern "C" {
3030
#include "v8js_v8.h"
3131
#include "v8js_exceptions.h"
3232
#include "v8js_v8object_class.h"
33+
#include "v8js_object_export.h"
3334
#include "v8js_timer.h"
3435

3536
#include <functional>
@@ -45,6 +46,9 @@ static zend_class_entry *php_ce_v8js;
4546
static zend_object_handlers v8js_object_handlers;
4647
/* }}} */
4748

49+
/* Forward declare v8js_methods, actually "static" but not possible in C++ */
50+
extern const zend_function_entry v8js_methods[];
51+
4852
typedef struct _v8js_script {
4953
char *name;
5054
v8js_ctx *ctx;
@@ -319,6 +323,10 @@ static void v8js_fatal_error_handler(const char *location, const char *message)
319323
}
320324
/* }}} */
321325

326+
#define IS_MAGIC_FUNC(mname) \
327+
((key_len == sizeof(mname)) && \
328+
!strncasecmp(key, mname, key_len - 1))
329+
322330
/* {{{ proto void V8Js::__construct([string object_name [, array variables [, array extensions [, bool report_uncaught_exceptions]]])
323331
__construct for V8Js */
324332
static PHP_METHOD(V8Js, __construct)
@@ -480,7 +488,78 @@ static PHP_METHOD(V8Js, __construct)
480488
}
481489
}
482490

491+
/* Add pointer to zend object */
492+
php_obj->SetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY), v8::External::New(isolate, getThis()));
493+
494+
/* Export public methods */
495+
zend_function *method_ptr;
496+
char *key = NULL;
497+
uint key_len;
498+
499+
zend_hash_internal_pointer_reset_ex(&c->std.ce->function_table, &pos);
500+
for (;; zend_hash_move_forward_ex(&c->std.ce->function_table, &pos)) {
501+
if (zend_hash_get_current_key_ex(&c->std.ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING ||
502+
zend_hash_get_current_data_ex(&c->std.ce->function_table, (void **) &method_ptr, &pos) == FAILURE
503+
) {
504+
break;
505+
}
483506

507+
if ((method_ptr->common.fn_flags & ZEND_ACC_PUBLIC) == 0) {
508+
/* Allow only public methods */
509+
continue;
510+
}
511+
512+
if ((method_ptr->common.fn_flags & (ZEND_ACC_CTOR|ZEND_ACC_DTOR|ZEND_ACC_CLONE)) != 0) {
513+
/* no __construct, __destruct(), or __clone() functions */
514+
continue;
515+
}
516+
517+
/* hide (do not export) other PHP magic functions */
518+
if (IS_MAGIC_FUNC(ZEND_CALLSTATIC_FUNC_NAME) ||
519+
IS_MAGIC_FUNC(ZEND_SLEEP_FUNC_NAME) ||
520+
IS_MAGIC_FUNC(ZEND_WAKEUP_FUNC_NAME) ||
521+
IS_MAGIC_FUNC(ZEND_SET_STATE_FUNC_NAME) ||
522+
IS_MAGIC_FUNC(ZEND_GET_FUNC_NAME) ||
523+
IS_MAGIC_FUNC(ZEND_SET_FUNC_NAME) ||
524+
IS_MAGIC_FUNC(ZEND_UNSET_FUNC_NAME) ||
525+
IS_MAGIC_FUNC(ZEND_CALL_FUNC_NAME) ||
526+
IS_MAGIC_FUNC(ZEND_INVOKE_FUNC_NAME) ||
527+
IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME) ||
528+
IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
529+
continue;
530+
}
531+
532+
const zend_function_entry *fe;
533+
for (fe = v8js_methods; fe->fname; fe ++) {
534+
if (strcmp(fe->fname, method_ptr->common.function_name) == 0) {
535+
break;
536+
}
537+
}
538+
539+
if(fe->fname) {
540+
/* Method belongs to \V8Js class itself, never export to V8, even if
541+
* it is overriden in a derived class. */
542+
continue;
543+
}
544+
545+
v8::Local<v8::String> method_name = V8JS_STR(method_ptr->common.function_name);
546+
v8::Local<v8::FunctionTemplate> ft;
547+
548+
try {
549+
ft = v8::Local<v8::FunctionTemplate>::New
550+
(isolate, c->method_tmpls.at(method_ptr));
551+
}
552+
catch (const std::out_of_range &) {
553+
ft = v8::FunctionTemplate::New(isolate, v8js_php_callback,
554+
v8::External::New((isolate), method_ptr));
555+
// @fixme add/check Signature v8::Signature::New((isolate), tmpl));
556+
v8js_tmpl_t *persistent_ft = &c->method_tmpls[method_ptr];
557+
persistent_ft->Reset(isolate, ft);
558+
}
559+
560+
561+
php_obj->ForceSet(method_name, ft->GetFunction());
562+
}
484563
}
485564
/* }}} */
486565

@@ -1056,7 +1135,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_setmemorylimit, 0, 0, 1)
10561135
ZEND_END_ARG_INFO()
10571136

10581137

1059-
static const zend_function_entry v8js_methods[] = { /* {{{ */
1138+
const zend_function_entry v8js_methods[] = { /* {{{ */
10601139
PHP_ME(V8Js, __construct, arginfo_v8js_construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
10611140
PHP_ME(V8Js, __sleep, arginfo_v8js_sleep, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
10621141
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
11071186
/* Global PHP JS object */
11081187
v8::Local<v8::String> object_name_js = v8::Local<v8::String>::New(isolate, c->object_name);
11091188
v8::Local<v8::Object> jsobj = V8JS_GLOBAL(isolate)->Get(object_name_js)->ToObject();
1110-
1189+
11111190
/* Delete value from PHP JS object */
11121191
jsobj->Delete(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)));
11131192

v8js_object_export.cc

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ static void v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function
169169
/* }}} */
170170

171171
/* Callback for PHP methods and functions */
172-
static void v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
172+
void v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
173173
{
174174
v8::Isolate *isolate = info.GetIsolate();
175175
v8::Local<v8::Object> self = info.Holder();
@@ -291,11 +291,6 @@ static void v8js_weak_closure_callback(const v8::WeakCallbackData<v8::Object, v8
291291
ctx->weak_closures.erase(persist_tpl_);
292292
};
293293

294-
/* These are not defined by Zend */
295-
#define ZEND_WAKEUP_FUNC_NAME "__wakeup"
296-
#define ZEND_SLEEP_FUNC_NAME "__sleep"
297-
#define ZEND_SET_STATE_FUNC_NAME "__set_state"
298-
299294
#define IS_MAGIC_FUNC(mname) \
300295
((key_len == sizeof(mname)) && \
301296
!strncasecmp(key, mname, key_len - 1))

0 commit comments

Comments
 (0)