Skip to content
This repository was archived by the owner on Mar 29, 2024. It is now read-only.

Commit 1a18e77

Browse files
authored
Merge pull request #1 from pinepain/fix_templates_recursion
Fix templates recursion
2 parents 87803c8 + 090ca9c commit 1a18e77

15 files changed

+260
-62
lines changed

README.md

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ provides an accurate native V8 C++ API implementation available from PHP.
2626
- multiple isolates and contexts at the same time;
2727
- it works;
2828

29-
With this extension almost all what native V8 C++ API provides can be used. It provides a way to pass php scalars,
29+
With this extension almost all that native V8 C++ API provides can be used. It provides a way to pass php scalars,
3030
objects and function to V8 runtime and specify interaction with passed values (objects and functions only, as scalars
3131
become js scalars too). While specific functionality will be done in PHP userland rather then in C/C++ this extension,
32-
it let get into V8 hacking faster, reduces time costs and let have more maintainable solution. And it doesn't make any
33-
assumptions for you so you are the boss, it does exactly what you ask for.
32+
it lets you get into V8 hacking faster, reduces time costs and gives you a more maintainable solution. And it doesn't
33+
make any assumptions for you, so you stay in control, it does exactly what you ask it to do.
3434

3535
With php-v8 you can even implement nodejs in PHP. Not sure whether anyone should/will do this anyway, but it's doable.
3636

@@ -55,8 +55,8 @@ $result = $script->Run($context);
5555
echo $result->ToString($context)->Value(), PHP_EOL;
5656
```
5757

58-
which will output `Hello, World!`. See how it shorter and readable from that C++ version? And it also doesn't limit you
59-
from V8 API utilizing to implement more amazing stuff.
58+
which will output `Hello, World!`. See how it's shorter and readable from that C++ version? And it also doesn't limit
59+
you from V8 API utilizing to implement more amazing stuff.
6060

6161

6262
## Installation
@@ -103,25 +103,6 @@ $ sudo make install
103103
- To track memory usage you may want to use `smem`, `pmem` and even `lsof` to see what shared object are loaded
104104
and `free` to display free and used memory in the system.
105105

106-
107-
## Edge cases:
108-
109-
### Templates recursion:
110-
111-
When you set property on any `Template` (`ObjectTemplate` or `FunctionTemplate`) it shouldn't lead to recursion during
112-
template instantiation while it leads to segfault and for now there are no reasonable way to avoid this on extension
113-
level (probably, some wrapper around `ObjectTemplate` and `FunctionTemplate` will solve this.
114-
115-
Known issues demo:
116-
117-
```php
118-
$isolate = new V8\Isolate();
119-
120-
$template = new V8\ObjectTemplate($isolate);
121-
122-
$template->Set('self', $template); // leads to segfault
123-
```
124-
125106
## License
126107

127108
Copyright (c) 2015-2016 Bogdan Padalko <pinepain@gmail.com>

src/php_v8_function_template.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ static void php_v8_function_template_free(zend_object *object) {
104104
}
105105
}
106106

107+
delete php_v8_function_template->node;
108+
107109
if (php_v8_function_template->gc_data) {
108110
efree(php_v8_function_template->gc_data);
109111
}
@@ -122,6 +124,8 @@ static zend_object * php_v8_function_template_ctor(zend_class_entry *ce) {
122124
php_v8_function_template->persistent = new v8::Persistent<v8::FunctionTemplate>();
123125
php_v8_function_template->callbacks = new php_v8_callbacks_t();
124126

127+
php_v8_function_template->node = new phpv8::TemplateNode();
128+
125129
php_v8_function_template->std.handlers = &php_v8_function_template_object_handlers;
126130

127131
return &php_v8_function_template->std;

src/php_v8_function_template.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
typedef struct _php_v8_function_template_t php_v8_function_template_t;
1919

20+
#include "php_v8_template.h"
2021
#include "php_v8_exceptions.h"
2122
#include "php_v8_isolate.h"
2223
#include <v8.h>
@@ -61,6 +62,8 @@ struct _php_v8_function_template_t {
6162
zval *gc_data;
6263
int gc_data_count;
6364

65+
phpv8::TemplateNode *node;
66+
6467
zend_object std;
6568
};
6669

src/php_v8_object_template.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ static void php_v8_object_template_free(zend_object *object) {
9898
}
9999
}
100100

101+
delete php_v8_object_template->node;
102+
101103
if (php_v8_object_template->gc_data) {
102104
efree(php_v8_object_template->gc_data);
103105
}
@@ -116,6 +118,8 @@ static zend_object * php_v8_object_template_ctor(zend_class_entry *ce) {
116118
php_v8_object_template->persistent = new v8::Persistent<v8::ObjectTemplate>();
117119
php_v8_object_template->callbacks = new php_v8_callbacks_t();
118120

121+
php_v8_object_template->node = new phpv8::TemplateNode();
122+
119123
php_v8_object_template->std.handlers = &php_v8_object_template_object_handlers;
120124

121125
return &php_v8_object_template->std;

src/php_v8_object_template.h

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
typedef struct _php_v8_object_template_t php_v8_object_template_t;
1919

20+
#include "php_v8_template.h"
2021
#include "php_v8_exceptions.h"
2122
#include "php_v8_template.h"
2223
#include "php_v8_isolate.h"
@@ -53,20 +54,21 @@ extern php_v8_object_template_t * php_v8_object_template_fetch_object(zend_objec
5354
PHP_V8_COPY_POINTER_TO_ISOLATE((to_php_v8_val), (from_php_v8_val));
5455

5556

56-
5757
struct _php_v8_object_template_t {
58-
php_v8_isolate_t *php_v8_isolate;
58+
php_v8_isolate_t *php_v8_isolate;
59+
60+
uint32_t isolate_handle;
5961

60-
uint32_t isolate_handle;
62+
bool is_weak;
63+
v8::Persistent<v8::ObjectTemplate> *persistent;
64+
php_v8_callbacks_t *callbacks;
6165

62-
bool is_weak;
63-
v8::Persistent<v8::ObjectTemplate> *persistent;
64-
php_v8_callbacks_t *callbacks;
66+
zval *gc_data;
67+
int gc_data_count;
6568

66-
zval *gc_data;
67-
int gc_data_count;
69+
phpv8::TemplateNode *node;
6870

69-
zend_object std;
71+
zend_object std;
7072
};
7173

7274

src/php_v8_template.cc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ void php_v8_function_template_SetNativeDataProperty(INTERNAL_FUNCTION_PARAMETERS
102102
php_v8_template_SetNativeDataProperty(isolate, local_template, php_v8_template, INTERNAL_FUNCTION_PARAM_PASSTHRU);
103103
}
104104

105+
template<typename M, typename N>
106+
static inline bool php_v8_template_node_set(M *parent, N *child) {
107+
if (parent->node->isSelf(child->node)) {
108+
PHP_V8_THROW_EXCEPTION("Can't set: recursion detected");
109+
return false;
110+
}
111+
112+
if (parent->node->isParent(child->node)) {
113+
PHP_V8_THROW_EXCEPTION("Can't set: recursion detected");
114+
return false;
115+
}
116+
117+
parent->node->addChild(child->node);
118+
return true;
119+
}
105120

106121
template<class T, typename N>
107122
void php_v8_template_Set(v8::Isolate *isolate, v8::Local<T> local_template, N* php_v8_template, INTERNAL_FUNCTION_PARAMETERS) {
@@ -135,15 +150,19 @@ void php_v8_template_Set(v8::Isolate *isolate, v8::Local<T> local_template, N* p
135150

136151
PHP_V8_DATA_ISOLATES_CHECK(php_v8_template, php_v8_object_template_to_set);
137152

138-
local_template->Set(local_name, php_v8_object_template_get_local(isolate, php_v8_object_template_to_set), static_cast<v8::PropertyAttribute>(attributes));
153+
if (php_v8_template_node_set(php_v8_template, php_v8_object_template_to_set)) {
154+
local_template->Set(local_name, php_v8_object_template_get_local(isolate, php_v8_object_template_to_set), static_cast<v8::PropertyAttribute>(attributes));
155+
}
139156

140157
} else if (instanceof_function(Z_OBJCE_P(php_v8_value_zv), php_v8_function_template_class_entry)) {
141158
// set function template
142159
PHP_V8_FETCH_FUNCTION_TEMPLATE_WITH_CHECK(php_v8_value_zv, php_v8_function_template_to_set);
143160

144161
PHP_V8_DATA_ISOLATES_CHECK(php_v8_template, php_v8_function_template_to_set);
145162

146-
local_template->Set(local_name, php_v8_function_template_get_local(isolate, php_v8_function_template_to_set), static_cast<v8::PropertyAttribute>(attributes));
163+
if (php_v8_template_node_set(php_v8_template, php_v8_function_template_to_set)) {
164+
local_template->Set(local_name, php_v8_function_template_get_local(isolate, php_v8_function_template_to_set), static_cast<v8::PropertyAttribute>(attributes));
165+
}
147166
} else {
148167
// should never get here
149168
PHP_V8_THROW_EXCEPTION("Unknown type to set");

src/php_v8_template.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#ifndef PHP_V8_TEMPLATE_H
1616
#define PHP_V8_TEMPLATE_H
1717

18+
namespace phpv8 {
19+
class TemplateNode;
20+
}
21+
1822
extern "C" {
1923
#include "php.h"
2024

@@ -23,6 +27,8 @@ extern "C" {
2327
#endif
2428
}
2529

30+
#include <set>
31+
2632
extern zend_class_entry* php_v8_template_ce;
2733

2834
extern void php_v8_object_template_Set(INTERNAL_FUNCTION_PARAMETERS);
@@ -37,6 +43,61 @@ extern void php_v8_function_template_SetNativeDataProperty(INTERNAL_FUNCTION_PAR
3743
#define PHP_V8_TEMPLATE_STORE_ISOLATE(to_zval, from_isolate_zv) zend_update_property(php_v8_template_ce, (to_zval), ZEND_STRL("isolate"), (from_isolate_zv));
3844
#define PHP_V8_TEMPLATE_READ_ISOLATE(from_zval) zend_read_property(php_v8_template_ce, (from_zval), ZEND_STRL("isolate"), 0, &rv)
3945

46+
namespace phpv8 {
47+
class TemplateNode {
48+
public:
49+
std::set<TemplateNode *> children;
50+
std::set<TemplateNode *> parents;
51+
52+
bool isSelf(TemplateNode *node) {
53+
return this == node;
54+
}
55+
56+
bool isParent(TemplateNode *node) {
57+
if (parents.find(node) != parents.end()) {
58+
return true;
59+
}
60+
61+
for (TemplateNode *parent : parents) {
62+
if (parent->isParent(node)) {
63+
return true;
64+
}
65+
}
66+
67+
return false;
68+
}
69+
70+
//bool isChild(TemplateNode *node) {
71+
// if (children.find(node) != children.end()) {
72+
// return true;
73+
// }
74+
//
75+
// for (TemplateNode *child : children) {
76+
// if (child->isChild(node)) {
77+
// return true;
78+
// }
79+
// }
80+
//
81+
// return false;
82+
//}
83+
84+
void addChild(TemplateNode *node) {
85+
children.insert(node);
86+
node->parents.insert(this);
87+
}
88+
89+
~TemplateNode() {
90+
for (TemplateNode *parent : parents) {
91+
parent->children.erase(this);
92+
}
93+
94+
for (TemplateNode *child : children) {
95+
child->parents.erase(this);
96+
}
97+
}
98+
};
99+
}
100+
40101

41102
PHP_MINIT_FUNCTION(php_v8_template);
42103

tests/.testsuite.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ public function dump_object_methods($object, array $arguments = [], FilterInterf
267267
echo $rc->getName(), $info, $access, $m->getName(), '():', $final_res, PHP_EOL;
268268
}
269269
}
270+
271+
public function need_more_time() {
272+
// NOTE: this check is a bit fragile but should fits our need
273+
return isset($_ENV['TRAVIS']) && isset($_ENV['TEST_PHP_ARGS']) && $_ENV['TEST_PHP_ARGS'] == '-m';
274+
}
270275
}
271276

272277
interface FilterInterface
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
V8\ObjectTemplate - recursive 2
3+
--SKIPIF--
4+
<?php if (!extension_loaded("v8")) print "skip"; ?>
5+
--FILE--
6+
<?php
7+
8+
use V8\Exceptions\GenericException;
9+
10+
/** @var \Phpv8Testsuite $helper */
11+
$helper = require '.testsuite.php';
12+
13+
require '.v8-helpers.php';
14+
$v8_helper = new PhpV8Helpers($helper);
15+
16+
// Tests:
17+
18+
$isolate = new \V8\Isolate();
19+
20+
$template1 = new \V8\ObjectTemplate($isolate);
21+
$template2 = new \V8\ObjectTemplate($isolate);
22+
$template3 = new \V8\ObjectTemplate($isolate);
23+
24+
$template1->Set(new \V8\StringValue($isolate, 'that2'), $template2);
25+
$template2->Set(new \V8\StringValue($isolate, 'that3'), $template3);
26+
27+
try {
28+
$template3->Set(new \V8\StringValue($isolate, 'that1'), $template2);
29+
} catch (GenericException $e) {
30+
$helper->exception_export($e);
31+
}
32+
33+
34+
$context = new \V8\Context($isolate);
35+
$context->GlobalObject()->Set($context, new \V8\StringValue($isolate, 'test'), $template1->NewInstance($context));
36+
37+
?>
38+
--EXPECT--
39+
V8\Exceptions\GenericException: Can't set: recursion detected

tests/V8ObjectTemplate_recursive_global.phpt renamed to tests/003-V8ObjectTemplate_recursive_global.phpt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
V8\ObjectTemplate
33
--SKIPIF--
44
<?php if (!extension_loaded("v8")) print "skip"; ?>
5-
<?php print "skip this test is known to fail and it hangs on travis"; ?>
65
--FILE--
76
<?php
7+
8+
use V8\Exceptions\GenericException;
9+
810
/** @var \Phpv8Testsuite $helper */
911
$helper = require '.testsuite.php';
1012

@@ -16,12 +18,16 @@ $v8_helper = new PhpV8Helpers($helper);
1618
$isolate = new \V8\Isolate();
1719

1820
$template = new \V8\ObjectTemplate($isolate);
19-
$template->Set(new \V8\StringValue($isolate, 'self'), $template);
21+
22+
try {
23+
$template->Set(new \V8\StringValue($isolate, 'self'), $template);
24+
} catch (GenericException $e) {
25+
$helper->exception_export($e);
26+
}
2027

2128
$context = new \V8\Context($isolate, [], $template);
2229

2330

2431
?>
25-
--XFAIL--
26-
Recursive templates are known to segfault
2732
--EXPECT--
33+
V8\Exceptions\GenericException: Can't set: recursion detected
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
--TEST--
2-
V8\ObjectTemplate
2+
V8\ObjectTemplate::Set() - recursive self
33
--SKIPIF--
44
<?php if (!extension_loaded("v8")) print "skip"; ?>
5-
<?php print "skip this test is known to fail and it hangs on travis"; ?>
65
--FILE--
76
<?php
7+
8+
use V8\Exceptions\GenericException;
9+
810
/** @var \Phpv8Testsuite $helper */
911
$helper = require '.testsuite.php';
1012

@@ -16,12 +18,16 @@ $v8_helper = new PhpV8Helpers($helper);
1618
$isolate = new \V8\Isolate();
1719

1820
$template = new \V8\ObjectTemplate($isolate);
19-
$template->Set(new \V8\StringValue($isolate, 'self'), $template);
21+
22+
try {
23+
$template->Set(new \V8\StringValue($isolate, 'self'), $template);
24+
} catch (GenericException $e) {
25+
$helper->exception_export($e);
26+
}
2027

2128
$context = new \V8\Context($isolate);
2229
$context->GlobalObject()->Set($context, new \V8\StringValue($isolate, 'test'), $template->NewInstance($context));
2330

2431
?>
25-
--XFAIL--
26-
Recursive templates are known to segfault
2732
--EXPECT--
33+
V8\Exceptions\GenericException: Can't set: recursion detected

0 commit comments

Comments
 (0)