Skip to content

Commit eda7490

Browse files
committed
Fix module caching, closes #107
Use v8::Persistent handle to keep module instances around. Objects cannot be shared between isolates anyhow, hence moved modules_loaded map from global V8JSG structure to php_v8js_ctx. Besides fixes a use-after-free on normalised_module_id.
1 parent 7228e31 commit eda7490

File tree

4 files changed

+52
-11
lines changed

4 files changed

+52
-11
lines changed

php_v8js_macros.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ struct php_v8js_ctx {
200200
zval *module_loader;
201201
std::vector<char *> modules_stack;
202202
std::vector<char *> modules_base;
203+
std::map<char *, v8js_persistent_obj_t> modules_loaded;
203204
std::map<const char *,v8js_tmpl_t> template_cache;
204205

205206
std::map<zval *, v8js_persistent_obj_t> weak_objects;
@@ -262,8 +263,6 @@ ZEND_BEGIN_MODULE_GLOBALS(v8js)
262263
std::mutex timer_mutex;
263264
bool timer_stop;
264265

265-
std::map<char *, v8::Handle<v8::Object> > modules_loaded;
266-
267266
// fatal error unwinding
268267
bool fatal_error_abort;
269268
int error_num;

tests/commonjs_multiassign.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Test V8Js::setModuleLoader : Assign result multiple times
3+
--SKIPIF--
4+
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
$JS = <<< EOT
9+
var foo = require("test");
10+
var bar = require("test");
11+
var_dump(foo.bar);
12+
var_dump(bar.bar);
13+
EOT;
14+
15+
$v8 = new V8Js();
16+
$v8->setModuleLoader(function($module) {
17+
return 'exports.bar = 23;';
18+
});
19+
20+
$v8->executeString($JS, 'module.js');
21+
?>
22+
===EOF===
23+
--EXPECT--
24+
int(23)
25+
int(23)
26+
===EOF===

v8js.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,13 @@ static void php_v8js_free_storage(void *object TSRMLS_DC) /* {{{ */
718718
}
719719
c->php_v8js_objects.~list();
720720

721+
/* Clear persistent handles in module cache */
722+
for (std::map<char *, v8js_persistent_obj_t>::iterator it = c->modules_loaded.begin();
723+
it != c->modules_loaded.end(); ++it) {
724+
it->second.Reset();
725+
}
726+
c->modules_loaded.~map();
727+
721728
c->isolate->Dispose();
722729

723730
if(c->tz != NULL) {
@@ -726,6 +733,7 @@ static void php_v8js_free_storage(void *object TSRMLS_DC) /* {{{ */
726733

727734
c->modules_stack.~vector();
728735
c->modules_base.~vector();
736+
729737
efree(object);
730738
}
731739
/* }}} */
@@ -753,6 +761,8 @@ static zend_object_value php_v8js_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */
753761

754762
new(&c->modules_stack) std::vector<char*>();
755763
new(&c->modules_base) std::vector<char*>();
764+
new(&c->modules_loaded) std::map<char *, v8js_persistent_obj_t>;
765+
756766
new(&c->template_cache) std::map<const char *,v8js_tmpl_t>();
757767
new(&c->accessor_list) std::vector<php_v8js_accessor_ctx *>();
758768

@@ -1957,11 +1967,11 @@ static PHP_GINIT_FUNCTION(v8js)
19571967
v8js_globals->extensions = NULL;
19581968
v8js_globals->v8_initialized = 0;
19591969
v8js_globals->v8_flags = NULL;
1970+
19601971
v8js_globals->timer_thread = NULL;
19611972
v8js_globals->timer_stop = false;
19621973
new(&v8js_globals->timer_mutex) std::mutex;
19631974
new(&v8js_globals->timer_stack) std::stack<php_v8js_timer_ctx *>;
1964-
new(&v8js_globals->modules_loaded) std::map<char *, v8::Handle<v8::Object>>;
19651975

19661976
v8js_globals->fatal_error_abort = 0;
19671977
v8js_globals->error_num = 0;
@@ -1990,7 +2000,6 @@ static PHP_GSHUTDOWN_FUNCTION(v8js)
19902000
#ifdef ZTS
19912001
v8js_globals->timer_stack.~stack();
19922002
v8js_globals->timer_mutex.~mutex();
1993-
v8js_globals->modules_loaded.~map();
19942003
#endif
19952004
}
19962005
/* }}} */

v8js_methods.cc

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,13 @@ V8JS_METHOD(require)
245245
}
246246

247247
// If we have already loaded and cached this module then use it
248-
if (V8JSG(modules_loaded).count(normalised_module_id) > 0) {
248+
if (c->modules_loaded.count(normalised_module_id) > 0) {
249249
efree(normalised_module_id);
250250
efree(normalised_path);
251251

252-
info.GetReturnValue().Set(V8JSG(modules_loaded)[normalised_module_id]);
252+
v8::Persistent<v8::Object> newobj;
253+
newobj.Reset(isolate, c->modules_loaded[normalised_module_id]);
254+
info.GetReturnValue().Set(newobj);
253255
return;
254256
}
255257

@@ -324,7 +326,7 @@ V8JS_METHOD(require)
324326
v8::Locker locker(isolate);
325327
v8::Isolate::Scope isolate_scope(isolate);
326328

327-
v8::EscapableHandleScope handle_scope(isolate);
329+
v8::HandleScope handle_scope(isolate);
328330

329331
// Enter the module context
330332
v8::Context::Scope scope(context);
@@ -357,33 +359,38 @@ V8JS_METHOD(require)
357359
c->modules_stack.pop_back();
358360
c->modules_base.pop_back();
359361

360-
efree(normalised_module_id);
361362
efree(normalised_path);
362363

363364
// Script possibly terminated, return immediately
364365
if (!try_catch.CanContinue()) {
365366
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module script compile failed")));
367+
efree(normalised_module_id);
366368
return;
367369
}
368370

369371
// Handle runtime JS exceptions
370372
if (try_catch.HasCaught()) {
371373
// Rethrow the exception back to JS
372374
info.GetReturnValue().Set(try_catch.ReThrow());
375+
efree(normalised_module_id);
373376
return;
374377
}
375378

379+
v8::Handle<v8::Object> newobj;
380+
376381
// Cache the module so it doesn't need to be compiled and run again
377382
// Ensure compatibility with CommonJS implementations such as NodeJS by playing nicely with module.exports and exports
378383
if (module->Has(V8JS_SYM("exports")) && !module->Get(V8JS_SYM("exports"))->IsUndefined()) {
379384
// If module.exports has been set then we cache this arbitrary value...
380-
V8JSG(modules_loaded)[normalised_module_id] = handle_scope.Escape(module->Get(V8JS_SYM("exports"))->ToObject());
385+
newobj = module->Get(V8JS_SYM("exports"))->ToObject();
381386
} else {
382387
// ...otherwise we cache the exports object itself
383-
V8JSG(modules_loaded)[normalised_module_id] = handle_scope.Escape(exports);
388+
newobj = exports;
384389
}
385390

386-
info.GetReturnValue().Set(V8JSG(modules_loaded)[normalised_module_id]);
391+
c->modules_loaded[normalised_module_id].Reset(isolate, newobj);
392+
info.GetReturnValue().Set(newobj);
393+
efree(normalised_module_id);
387394
}
388395

389396
void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global, php_v8js_ctx *c) /* {{{ */

0 commit comments

Comments
 (0)