From eb66b9452193d8b2dc3758b8a9896688018765d7 Mon Sep 17 00:00:00 2001 From: Igor Zaytsev Date: Fri, 11 May 2012 19:14:10 +0400 Subject: [PATCH 01/10] Fix 'taking address of temporary' warning. --- V8Context.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/V8Context.cpp b/V8Context.cpp index f6c1564..e2c51f3 100644 --- a/V8Context.cpp +++ b/V8Context.cpp @@ -219,6 +219,20 @@ class V8FunctionData : public V8ObjectData { bool returns_list; }; +class PerlFunctionData; + +Handle MakeFunction(V8Context* context, PerlFunctionData* fd) { + Handle wrap(External::Wrap(fd)); + + return Handle::Cast( + context->make_function->Call( + context->context->Global(), + 1, + &wrap + ) + ); +} + class PerlFunctionData : public PerlObjectData { private: SV *rv; @@ -229,18 +243,8 @@ class PerlFunctionData : public PerlObjectData { public: PerlFunctionData(V8Context* context_, SV *cv) - : PerlObjectData( - context_, - Handle::Cast( - context_->make_function->Call( - context_->context->Global(), - 1, - &External::Wrap(this) - ) - ), - cv - ) - , rv(cv ? newRV_noinc(cv) : NULL) + : PerlObjectData(context_, MakeFunction(context_, this), cv) + , rv(cv ? newRV_noinc(cv) : NULL) { } static Handle v8invoke(const Arguments& args) { From 60d823d83d01daeacf14a611aa991e95b1de4d0f Mon Sep 17 00:00:00 2001 From: Igor Zaytsev Date: Tue, 18 Sep 2012 19:41:24 +0400 Subject: [PATCH 02/10] Fix test for new V8 version. V8 3.12 consumes less memory per object. --- t/zzmem_plojb2.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/zzmem_plojb2.t b/t/zzmem_plojb2.t index 3bd64b8..5778fc5 100644 --- a/t/zzmem_plojb2.t +++ b/t/zzmem_plojb2.t @@ -19,7 +19,7 @@ sub new { package main; $context->eval('var DATA = []'); -for (1..100000) { +for (1..200000) { print STDERR "$_\r"; $context->eval('(function(data) { DATA.push(data); })')->(Test->new($_)); } From 65d200fe08d7423661a351f1331668944f4a0adf Mon Sep 17 00:00:00 2001 From: Igor Zaytsev Date: Thu, 25 Oct 2012 00:12:49 +0400 Subject: [PATCH 03/10] JS functions => to Perl package functions Support for Package::function() calls in addition to $object->method() cals. v8method() and v8closure() XS calls have been generalized into v8closure(). Call type is determined in runtime. --- V8Context.cpp | 152 ++++++++++++++++++++++++++++---------------------- t/jsobj.t | 39 +++++++++++++ 2 files changed, 123 insertions(+), 68 deletions(-) diff --git a/V8Context.cpp b/V8Context.cpp index e2c51f3..5a13942 100644 --- a/V8Context.cpp +++ b/V8Context.cpp @@ -720,77 +720,93 @@ my_gv_setsv(pTHX_ GV* const gv, SV* const sv){ #define DVAR dVAR; #endif -#define SETUP_V8_CALL(ARGS_OFFSET) \ - DVAR \ - dXSARGS; \ -\ - bool die = false; \ - int count = 1; \ -\ - { \ - /* We have to do all this inside a block so that all the proper \ - * destuctors are called if we need to croak. If we just croak in the \ - * middle of the block, v8 will segfault at program exit. */ \ - TryCatch try_catch; \ - HandleScope scope; \ - V8FunctionData* data = (V8FunctionData*)sv_object_data((SV*)cv); \ - if (data->context) { \ - V8Context *self = data->context; \ - Handle ctx = self->context; \ - Context::Scope context_scope(ctx); \ - Handle argv[items - ARGS_OFFSET]; \ -\ - for (I32 i = ARGS_OFFSET; i < items; i++) { \ - argv[i - ARGS_OFFSET] = self->sv2v8(ST(i)); \ - } +inline bool call_is_method(OP* o) { + OP* cvop, *aop; + aop = cUNOPx(PL_op)->op_first; -#define CONVERT_V8_RESULT(POP) \ - if (try_catch.HasCaught()) { \ - set_perl_error(try_catch); \ - die = true; \ - } \ - else { \ - if (data->returns_list && GIMME_V == G_ARRAY && result->IsArray()) { \ - Handle array = Handle::Cast(result); \ - if (GIMME_V == G_ARRAY) { \ - count = array->Length(); \ - EXTEND(SP, count - items); \ - for (int i = 0; i < count; i++) { \ - ST(i) = sv_2mortal(self->v82sv(array->Get(Integer::New(i)))); \ - } \ - } \ - else { \ - ST(0) = sv_2mortal(newSViv(array->Length())); \ - } \ - } \ - else { \ - ST(0) = sv_2mortal(self->v82sv(result)); \ - } \ - } \ - } \ - else {\ - die = true; \ - sv_setpv(ERRSV, "Fatal error: V8 context is no more"); \ - sv_utf8_upgrade(ERRSV); \ - } \ - } \ -\ - if (die) \ - croak(NULL); \ -\ -XSRETURN(count); + if (!aop) + return false; -XS(v8closure) { - SETUP_V8_CALL(0) - Handle result = Handle::Cast(data->object)->Call(ctx->Global(), items, argv); - CONVERT_V8_RESULT() + if (!aop->op_sibling) + aop = cUNOPx(aop)->op_first; + + aop = aop->op_sibling; + for (cvop = aop; cvop->op_sibling; cvop = cvop->op_sibling) ; + + return cvop->op_type == OP_METHOD || cvop->op_type == OP_METHOD_NAMED; } -XS(v8method) { - SETUP_V8_CALL(1) - V8ObjectData* This = (V8ObjectData*)SvIV((SV*)SvRV(ST(0))); - Handle result = Handle::Cast(data->object)->Call(This->object, items - 1, argv); - CONVERT_V8_RESULT(POPs); +XS(v8closure) { + DVAR + dXSARGS; + + bool die = false; + int count = 1; + + { + /* We have to do all this inside a block so that all the proper + * destuctors are called if we need to croak. If we just croak in the + * middle of the block, v8 will segfault at program exit. */ + TryCatch try_catch; + HandleScope scope; + V8FunctionData* data = (V8FunctionData*)sv_object_data((SV*)cv); + if (data->context) { + V8Context *self = data->context; + Handle ctx = self->context; + Context::Scope context_scope(ctx); + Handle object; + Handle argv[items]; + Handle *argv_ptr; + + for (I32 i = 0; i < items; i++) { + argv[i] = self->sv2v8(ST(i)); + } + + if (call_is_method(PL_op)) { + object = (*argv)->ToObject(); + argv_ptr = argv + 1; + } + else { + object = ctx->Global(); + argv_ptr = argv; + } + + Handle result = Handle::Cast(data->object)->Call(object, items, argv_ptr); + + if (try_catch.HasCaught()) { + set_perl_error(try_catch); + die = true; + } + else { + if (data->returns_list && GIMME_V == G_ARRAY && result->IsArray()) { + Handle array = Handle::Cast(result); + if (GIMME_V == G_ARRAY) { + count = array->Length(); + EXTEND(SP, count - items); + for (int i = 0; i < count; i++) { + ST(i) = sv_2mortal(self->v82sv(array->Get(Integer::New(i)))); + } + } + else { + ST(0) = sv_2mortal(newSViv(array->Length())); + } + } + else { + ST(0) = sv_2mortal(self->v82sv(result)); + } + } + } + else { + die = true; + sv_setpv(ERRSV, "Fatal error: V8 context is no more"); + sv_utf8_upgrade(ERRSV); + } + } + + if (die) + croak(NULL); + + XSRETURN(count); } SV* @@ -830,7 +846,7 @@ V8Context::object2blessed(Handle obj) { Local fn = Local::Cast(property); - CV *code = newXS(NULL, v8method, __FILE__); + CV *code = newXS(NULL, v8closure, __FILE__); V8ObjectData *data = new V8FunctionData(this, fn, (SV*)code); GV* gv = (GV*)*hv_fetch(stash, *String::AsciiValue(name), name->Length(), TRUE); diff --git a/t/jsobj.t b/t/jsobj.t index a2a1d13..426ec9c 100644 --- a/t/jsobj.t +++ b/t/jsobj.t @@ -2,6 +2,7 @@ use Test::More; use JavaScript::V8; +use Storable qw/ freeze thaw /; use Data::Dumper; use utf8; @@ -56,6 +57,23 @@ Counter.prototype.previousValues.__perlReturnsList = true; Counter.prototype.__perlPackage = "Counter"; +Counter.prototype.STORABLE_freeze = function(obj, cloning) { + return JSON.stringify(obj); +} + +Counter.prototype.STORABLE_attach = function(klass, cloning, serialized) { + var clone = new Counter(); + var data = JSON.parse(serialized); + for (var k in data) + if (data.hasOwnProperty(k)) + clone[k] = data[k]; + return clone; +} + +Counter.prototype.methodVsFunctionTest = function(arg) { + return [this, arg]; +} + new Counter; END @@ -136,4 +154,25 @@ like $@, qr{SomeError.*at counter\.js:\d+}, 'js method error propagates to perl' is_deeply [$c->previousValues], [1, 77, 78], 'method in list context'; } + +{ + my $context = JavaScript::V8::Context->new( enable_blessing => 1, enable_wantarray => 1 ); + + $context->eval($COUNTER_SRC); + + my $c = $context->eval('var c = new Counter(); c.set(77); c.inc(); c.inc(); c'); + is_deeply [thaw(freeze($c))->previousValues], [$c->previousValues], 'freeze/thaw work'; + + $context->eval('Z = 1'); + + my $pkg = ref $c; + # this is bound to global + is((eval "${pkg}::methodVsFunctionTest(1)")->[0]{Z}, 1, 'js function called as an package function'); + is((eval "${pkg}::methodVsFunctionTest(1)")->[1], 1); + + # this is bound to $c + is $c->methodVsFunctionTest(1)->[0], $c; + is $c->methodVsFunctionTest(1)->[1], 1; +} + done_testing; From 55453e2b9fd762128d90f3b0419566babd091fac Mon Sep 17 00:00:00 2001 From: Igor Zaytsev Date: Sat, 27 Oct 2012 00:13:58 +0400 Subject: [PATCH 04/10] Allow custom to_js() conversion callbacks --- V8Context.cpp | 91 ++++++++++++++++++++++++++++++++++++++++++--------- V8Context.h | 9 +++-- t/plobj.t | 39 ++++++++++++++++++++++ 3 files changed, 122 insertions(+), 17 deletions(-) diff --git a/V8Context.cpp b/V8Context.cpp index 5a13942..6986029 100644 --- a/V8Context.cpp +++ b/V8Context.cpp @@ -331,6 +331,7 @@ V8Context::V8Context( make_function = Persistent::New(Handle::Cast(script->Run())); string_wrap = Persistent::New(String::New("wrap")); + string_to_js = Persistent::New(String::New("to_js")); number++; } @@ -548,16 +549,60 @@ V8Context::v82sv(Handle value) { } void -V8Context::fill_prototype(Handle prototype, HV* stash) { +V8Context::fill_prototype_isa(Handle prototype, HV* stash) { + if (AV *isa = mro_get_linear_isa(stash)) { + for (int i = 0; i <= av_len(isa); i++) { + SV **sv = av_fetch(isa, i, 0); + HV *stash = gv_stashsv(*sv, 0); + fill_prototype_stash(prototype, stash); + } + } +} + +void +V8Context::fill_prototype_stash(Handle prototype, HV* stash) { HE *he; while (he = hv_iternext(stash)) { SV *key = HeSVKEY_force(he); - Local name = String::New(SvPV_nolen(key)); + char *key_str = SvPV_nolen(key); + Local name = String::New(key_str); if (prototype->Has(name)) continue; - prototype->Set(name, (new PerlMethodData(this, SvPV_nolen(key)))->object); + PerlFunctionData* pfd + = name->Equals(string_to_js) // we want to_js() to be called as a package function + ? new PerlFunctionData(this, (SV*)GvCV(gv_fetchmethod(stash, key_str))) + : new PerlMethodData(this, key_str); + + prototype->Set(name, pfd->object); + } +} + +// parse string returned by $self->to_js() into function +void +V8Context::fixup_prototype(Handle prototype) { + Handle val = prototype->Get(string_to_js); + + if (val.IsEmpty() || !val->IsFunction()) + return; + + TryCatch try_catch; + + Handle to_js = Handle::Cast(val)->Call(context->Global(), 0, NULL); + Handle