0
0
mirror of https://github.com/phpv8/v8js.git synced 2024-09-19 21:15:19 +00:00

Merge pull request #17 from stesie/fix-object-creation-leak-master

v8js leaks memory if objects are returned to javascript (fix)
This commit is contained in:
Patrick Reilly 2013-07-11 14:22:20 -07:00
commit 347442c471

View File

@ -32,6 +32,8 @@ extern "C" {
#include "php_v8js_macros.h" #include "php_v8js_macros.h"
#include <v8.h> #include <v8.h>
#include <map>
#include <stdexcept>
/* Callback for PHP methods and functions */ /* Callback for PHP methods and functions */
static void php_v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */ static void php_v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
@ -283,10 +285,23 @@ static void php_v8js_property_query(v8::Local<v8::String> property, const v8::Pr
#define PHP_V8JS_CALLBACK(mptr) \ #define PHP_V8JS_CALLBACK(mptr) \
v8::FunctionTemplate::New(php_v8js_php_callback, v8::External::New(mptr))->GetFunction() v8::FunctionTemplate::New(php_v8js_php_callback, v8::External::New(mptr))->GetFunction()
static void php_v8js_weak_object_callback(v8::Isolate *isolate, v8::Persistent<v8::Object> *object, zval *value)
{
if (READY_TO_DESTROY(value)) {
zval_dtor(value);
FREE_ZVAL(value);
} else {
Z_DELREF_P(value);
}
v8::V8::AdjustAmountOfExternalAllocatedMemory(-1024);
object->Dispose();
}
static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *isolate TSRMLS_DC) /* {{{ */ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *isolate TSRMLS_DC) /* {{{ */
{ {
v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(); v8::Handle<v8::Object> newobj;
v8::Local<v8::Object> newobj;
int i; int i;
char *key = NULL; char *key = NULL;
ulong index; ulong index;
@ -310,13 +325,33 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
/* Object methods */ /* Object methods */
if (ce) { if (ce) {
new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length)); v8::Handle<v8::FunctionTemplate> new_tpl;
new_tpl->InstanceTemplate()->SetInternalFieldCount(2); bool cached_tpl = true;
static std::map<const char *, v8::Persistent<v8::FunctionTemplate> > tpl_map;
if (ce == zend_ce_closure) { try {
new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback); new_tpl = tpl_map.at(ce->name);
newobj = new_tpl->InstanceTemplate()->NewInstance(); }
} else { catch (const std::out_of_range &) {
cached_tpl = false;
/* No cached v8::FunctionTemplate available as of yet, create one. */
new_tpl = v8::FunctionTemplate::New();
new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
new_tpl->InstanceTemplate()->SetInternalFieldCount(2);
if (ce == zend_ce_closure) {
/* Got a closure, mustn't cache ... */
new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback);
} else {
/* Add new v8::FunctionTemplate to tpl_map, as long as it is not a closure. */
tpl_map[ce->name] = v8::Persistent<v8::FunctionTemplate>::New(isolate, new_tpl);
}
}
if (ce != zend_ce_closure) {
/* Attach object methods to the instance template. */
zend_hash_internal_pointer_reset_ex(&ce->function_table, &pos); zend_hash_internal_pointer_reset_ex(&ce->function_table, &pos);
for (;; zend_hash_move_forward_ex(&ce->function_table, &pos)) { for (;; zend_hash_move_forward_ex(&ce->function_table, &pos)) {
if (zend_hash_get_current_key_ex(&ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING || if (zend_hash_get_current_key_ex(&ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING ||
@ -331,7 +366,7 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
(method_ptr->common.fn_flags & ZEND_ACC_CLONE) == 0 /* ..or __clone() functions */ (method_ptr->common.fn_flags & ZEND_ACC_CLONE) == 0 /* ..or __clone() functions */
) { ) {
/* Override native toString() with __tostring() if it is set in passed object */ /* Override native toString() with __tostring() if it is set in passed object */
if (IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME)) { if (!cached_tpl && IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME)) {
new_tpl->InstanceTemplate()->Set(V8JS_SYM("toString"), PHP_V8JS_CALLBACK(method_ptr)); new_tpl->InstanceTemplate()->Set(V8JS_SYM("toString"), PHP_V8JS_CALLBACK(method_ptr));
/* TODO: __set(), __unset() disabled as JS is not allowed to modify the passed PHP object yet. /* TODO: __set(), __unset() disabled as JS is not allowed to modify the passed PHP object yet.
* __sleep(), __wakeup(), __set_state() are always ignored */ * __sleep(), __wakeup(), __set_state() are always ignored */
@ -352,33 +387,49 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
invoke_ptr = method_ptr; invoke_ptr = method_ptr;
} else if (IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) { } else if (IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
isset_ptr = method_ptr; isset_ptr = method_ptr;
} else { } else if (!cached_tpl) {
new_tpl->InstanceTemplate()->Set(V8JS_STR(method_ptr->common.function_name), PHP_V8JS_CALLBACK(method_ptr), v8::ReadOnly); new_tpl->InstanceTemplate()->Set(V8JS_STR(method_ptr->common.function_name), PHP_V8JS_CALLBACK(method_ptr), v8::ReadOnly);
} }
} }
} }
/* Only register getter, etc. when they're set in PHP side */ if (!cached_tpl) {
if (call_ptr || get_ptr || isset_ptr) /* Only register getter, etc. when they're set in PHP side */
{ if (call_ptr || get_ptr || isset_ptr)
/* Set __get() handler which acts also as __call() proxy */ {
new_tpl->InstanceTemplate()->SetNamedPropertyHandler( /* Set __get() handler which acts also as __call() proxy */
php_v8js_property_getter, /* getter */ new_tpl->InstanceTemplate()->SetNamedPropertyHandler(
0, /* setter */ php_v8js_property_getter, /* getter */
isset_ptr ? php_v8js_property_query : 0, /* query */ 0, /* setter */
0, /* deleter */ isset_ptr ? php_v8js_property_query : 0, /* query */
0, /* enumerator */ 0, /* deleter */
V8JS_BOOL(call_ptr ? true : false) 0, /* enumerator */
); V8JS_BOOL(call_ptr ? true : false)
);
}
/* __invoke() handler */
if (invoke_ptr) {
new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_property_caller, V8JS_SYM(ZEND_INVOKE_FUNC_NAME));
}
} }
}
/* __invoke() handler */ // Since we got to decrease the reference count again, in case v8 garbage collector
if (invoke_ptr) { // decides to dispose the JS object, we add a weak persistent handle and register
new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_property_caller, V8JS_SYM(ZEND_INVOKE_FUNC_NAME)); // a callback function that removes the reference.
} v8::Persistent<v8::Object> persist_newobj = v8::Persistent<v8::Object>::New(isolate, new_tpl->GetFunction()->NewInstance());
persist_newobj.MakeWeak(value, php_v8js_weak_object_callback);
newobj = new_tpl->InstanceTemplate()->NewInstance(); // Just tell v8 that we're allocating some external memory
// (for the moment we just always tell 1k instead of trying to find out actual values)
v8::V8::AdjustAmountOfExternalAllocatedMemory(1024);
newobj = persist_newobj;
if (ce != zend_ce_closure) {
// These unfortunately cannot be attached to the template, hence we have to put them
// on each and every object instance manually.
if (call_ptr) { if (call_ptr) {
newobj->SetHiddenValue(V8JS_SYM(ZEND_CALL_FUNC_NAME), PHP_V8JS_CALLBACK(call_ptr)); newobj->SetHiddenValue(V8JS_SYM(ZEND_CALL_FUNC_NAME), PHP_V8JS_CALLBACK(call_ptr));
} }
@ -399,6 +450,8 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
newobj->SetAlignedPointerInInternalField(0, (void *) value); newobj->SetAlignedPointerInInternalField(0, (void *) value);
newobj->SetAlignedPointerInInternalField(1, (void *) isolate); newobj->SetAlignedPointerInInternalField(1, (void *) isolate);
} else { } else {
v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise
new_tpl->SetClassName(V8JS_SYM("Array")); new_tpl->SetClassName(V8JS_SYM("Array"));
newobj = new_tpl->InstanceTemplate()->NewInstance(); newobj = new_tpl->InstanceTemplate()->NewInstance();
} }