diff --git a/JavaScript-V8-Context.xsp b/JavaScript-V8-Context.xsp index 75ba725..a3f4617 100644 --- a/JavaScript-V8-Context.xsp +++ b/JavaScript-V8-Context.xsp @@ -2,7 +2,8 @@ %name{JavaScript::V8::Context} class V8Context { - %name{_new} V8Context(int time_limit, const char* flags, bool enable_blessing, const char* bless_prefix); + %name{_new} V8Context(int time_limit, const char* flags, bool enable_blessing, const char* bless_prefix) + %cleanup{% RETVAL->my_sv = SvRV(ST(0)); %}; ~V8Context(); diff --git a/V8Context.cpp b/V8Context.cpp index f6c1564..788ea86 100644 --- a/V8Context.cpp +++ b/V8Context.cpp @@ -1,4 +1,6 @@ #include "V8Context.h" +#include "V8Thread.h" +#include "V8Util.h" #include #include @@ -10,27 +12,14 @@ #define INT32_MIN (-0x7fffffff-1) #endif -#define L(...) fprintf(stderr, ##__VA_ARGS__) - using namespace v8; using namespace std; int V8Context::number = 0; void set_perl_error(const TryCatch& try_catch) { - Handle msg = try_catch.Message(); - - char message[1024]; - snprintf( - message, - 1024, - "%s at %s:%d", - *(String::Utf8Value(try_catch.Exception())), - !msg.IsEmpty() ? *(String::AsciiValue(msg->GetScriptResourceName())) : "EVAL", - !msg.IsEmpty() ? msg->GetLineNumber() : 0 - ); - - sv_setpv(ERRSV, message); + auto_ptr message = error_message(try_catch); + sv_setpv(ERRSV, message->c_str()); sv_utf8_upgrade(ERRSV); } @@ -137,7 +126,7 @@ SV* SvMap::find(Handle object) { ObjectData::ObjectData(V8Context* context_, Handle object_, SV* sv_) : context(context_) - , object(Persistent::New(object_)) + , object(Persistent::New(context_->isolate, object_)) , sv(sv_) { if (!sv) return; @@ -148,8 +137,12 @@ ObjectData::ObjectData(V8Context* context_, Handle object_, SV* sv_) } ObjectData::~ObjectData() { - if (context) context->remove_object(this); - object.Dispose(); + { + Isolate::Scope isolate_scope(context->isolate); + Locker locker(context->isolate); + object.Dispose(context->isolate); + } + context->remove_object(this); } PerlObjectData::PerlObjectData(V8Context* context_, Handle object_, SV* sv_) @@ -163,7 +156,7 @@ PerlObjectData::PerlObjectData(V8Context* context_, Handle object_, SV* add_size(calculate_size(sv)); ptr = PTR2IV(sv); - object.MakeWeak(this, PerlObjectData::destroy); + object.MakeWeak(context_->isolate, this, PerlObjectData::destroy); } size_t PerlObjectData::size() { @@ -196,7 +189,7 @@ int V8ObjectData::svt_free(pTHX_ SV* sv, MAGIC* mg) { return 0; }; -void PerlObjectData::destroy(Persistent object, void *data) { +void PerlObjectData::destroy(Isolate* isolate, Persistent object, void *data) { delete static_cast(data); } @@ -219,6 +212,20 @@ class V8FunctionData : public V8ObjectData { bool returns_list; }; +class PerlFunctionData; + +Handle MakeFunction(V8Context* context, PerlFunctionData* fd) { + Handle wrap(External::New(fd)); + + return Handle::Cast( + context->make_function->Call( + context->context->Global(), + 1, + &wrap + ) + ); +} + class PerlFunctionData : public PerlObjectData { private: SV *rv; @@ -229,22 +236,12 @@ 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) { - PerlFunctionData* data = static_cast(External::Unwrap(args[0])); + PerlFunctionData* data = static_cast(External::Cast(*(args[0]))->Value()); return data->invoke(args); } }; @@ -301,11 +298,15 @@ V8Context::V8Context( bless_prefix(bless_prefix_), enable_blessing(enable_blessing_) { + isolate = Isolate::New(); + Isolate::Scope isolate_scope(isolate); + Locker locker(isolate); + HandleScope handle_scope; V8::SetFlagsFromString(flags, strlen(flags)); - context = Context::New(); - + context = Persistent::New(isolate, Context::New(isolate)); Context::Scope context_scope(context); - HandleScope handle_scope; + + V8Thread::install(context->Global()); Local tmpl = FunctionTemplate::New(PerlFunctionData::v8invoke); context->Global()->Set( @@ -324,40 +325,47 @@ V8Context::V8Context( "})" ) ); - make_function = Persistent::New(Handle::Cast(script->Run())); + make_function = Persistent::New(isolate, Handle::Cast(script->Run())); - string_wrap = Persistent::New(String::New("wrap")); + string_wrap = Persistent::New(isolate, String::New("wrap")); + string_to_js = Persistent::New(isolate, String::New("to_js")); number++; } void V8Context::register_object(ObjectData* data) { seen_perl[data->ptr] = data; - data->object->SetHiddenValue(string_wrap, External::Wrap(data)); + data->object->SetHiddenValue(string_wrap, External::New(data)); + SvREFCNT_inc(my_sv); } void V8Context::remove_object(ObjectData* data) { - ObjectDataMap::iterator it = seen_perl.find(data->ptr); - if (it != seen_perl.end()) - seen_perl.erase(it); - data->object->DeleteHiddenValue(string_wrap); + { + Isolate::Scope isolate_scope(isolate); + Locker locker(isolate); + ObjectDataMap::iterator it = seen_perl.find(data->ptr); + if (it != seen_perl.end()) + seen_perl.erase(it); + data->object->DeleteHiddenValue(string_wrap); + } + SvREFCNT_dec(my_sv); } V8Context::~V8Context() { - for (ObjectDataMap::iterator it = seen_perl.begin(); it != seen_perl.end(); it++) { - it->second->context = NULL; - } - seen_perl.clear(); - + isolate->Enter(); + while (!V8::IdleNotification()); // force garbage collection for (ObjectMap::iterator it = prototypes.begin(); it != prototypes.end(); it++) { - it->second.Dispose(); + it->second.Dispose(isolate); } - context.Dispose(); - while(!V8::IdleNotification()); // force garbage collection + context.Dispose(isolate); + isolate->Exit(); + isolate->Dispose(); } void V8Context::bind(const char *name, SV *thing) { + Isolate::Scope isolate_scope(isolate); + Locker locker(isolate); HandleScope scope; Context::Scope context_scope(context); @@ -367,8 +375,9 @@ V8Context::bind(const char *name, SV *thing) { // I fucking hate pthreads, this lacks error handling, but hopefully works. class thread_canceller { public: - thread_canceller(int sec) - : sec_(sec) + thread_canceller(Isolate* isolate, int sec) + : isolate_(isolate) + , sec_(sec) { if (sec_) { pthread_cond_init(&cond_, NULL); @@ -401,7 +410,7 @@ class thread_canceller { ts.tv_nsec = tv.tv_usec * 1000; if (pthread_cond_timedwait(&me->cond_, &me->mutex_, &ts) == ETIMEDOUT) { - V8::TerminateExecution(); + V8::TerminateExecution(me->isolate_); } pthread_mutex_unlock(&me->mutex_); } @@ -410,10 +419,13 @@ class thread_canceller { pthread_cond_t cond_; pthread_mutex_t mutex_; int sec_; + Isolate* isolate_; }; SV* V8Context::eval(SV* source, SV* origin) { + Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); HandleScope handle_scope; TryCatch try_catch; Context::Scope context_scope(context); @@ -425,14 +437,14 @@ V8Context::eval(SV* source, SV* origin) { if (try_catch.HasCaught()) { set_perl_error(try_catch); - return &PL_sv_undef; + return newSV(0); } else { - thread_canceller canceller(time_limit_); + thread_canceller canceller(isolate, time_limit_); Handle val = script->Run(); if (val.IsEmpty()) { set_perl_error(try_catch); - return &PL_sv_undef; + return newSV(0); } else { sv_setsv(ERRSV,&PL_sv_undef); return v82sv(val); @@ -480,17 +492,17 @@ SV* V8Context::seen_v8(Handle object) { if (wrap.IsEmpty()) return NULL; - ObjectData* data = (ObjectData*)External::Unwrap(wrap); + ObjectData* data = (ObjectData*)External::Cast(*wrap)->Value(); return newRV(data->sv); } SV * V8Context::v82sv(Handle value, SvMap& seen) { if (value->IsUndefined()) - return &PL_sv_undef; + return newSV(0); if (value->IsNull()) - return &PL_sv_undef; + return newSV(0); if (value->IsInt32()) return newSViv(value->Int32Value()); @@ -534,7 +546,7 @@ V8Context::v82sv(Handle value, SvMap& seen) { } warn("Unknown v8 value in v82sv"); - return &PL_sv_undef; + return newSV(0); } SV * @@ -544,16 +556,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