From 2fca33f9d09aadc3883e250c71f78b691de02444 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Wed, 18 Mar 2015 21:50:59 +0000 Subject: [PATCH 001/105] Remove old ext files and spec & link to the new V8 The functionality will be added (that is, if I don't get bored) one thing at a time with the spec. If you want to test, point the libv8 gem (in Gemfile) to its trunk branch & my changes at stormbreakerbg/libv8 @ trunk. What works currently is getting V8 to initialize, say its version and create a new Isolate. --- ext/v8/accessor.cc | 181 ------ ext/v8/array.cc | 26 - ext/v8/backref.cc | 45 -- ext/v8/{rr.cc => class_builder.cc} | 31 +- ext/v8/class_builder.h | 40 ++ ext/v8/constants.cc | 34 -- ext/v8/constraints.cc | 52 -- ext/v8/context.cc | 130 ---- ext/v8/date.cc | 18 - ext/v8/exception.cc | 38 -- ext/v8/extconf.rb | 2 +- ext/v8/external.cc | 43 -- ext/v8/function.cc | 58 -- ext/v8/gc.cc | 43 -- ext/v8/handles.cc | 50 +- ext/v8/handles.h | 17 + ext/v8/heap.cc | 35 -- ext/v8/init.cc | 55 +- ext/v8/invocation.cc | 86 --- ext/v8/isolate.cc | 20 + ext/v8/isolate.h | 31 + ext/v8/locker.cc | 77 --- ext/v8/message.cc | 51 -- ext/v8/object.cc | 335 ----------- ext/v8/pointer.h | 71 +++ ext/v8/primitive.cc | 8 - ext/v8/rr.h | 931 +---------------------------- ext/v8/script.cc | 115 ---- ext/v8/signature.cc | 18 - ext/v8/stack.cc | 76 --- ext/v8/string.cc | 47 -- ext/v8/template.cc | 175 ------ ext/v8/trycatch.cc | 87 --- ext/v8/v8.cc | 120 ++-- ext/v8/v8.h | 25 + ext/v8/value.cc | 239 -------- spec/c/array_spec.rb | 19 - spec/c/constants_spec.rb | 22 - spec/c/exception_spec.rb | 28 - spec/c/external_spec.rb | 11 - spec/c/function_spec.rb | 48 -- spec/c/handles_spec.rb | 31 - spec/c/isolate_spec.rb | 7 + spec/c/locker_spec.rb | 36 -- spec/c/object_spec.rb | 47 -- spec/c/script_spec.rb | 30 - spec/c/string_spec.rb | 18 - spec/c/template_spec.rb | 31 - spec/c/trycatch_spec.rb | 52 -- 49 files changed, 337 insertions(+), 3453 deletions(-) delete mode 100644 ext/v8/accessor.cc delete mode 100644 ext/v8/array.cc delete mode 100644 ext/v8/backref.cc rename ext/v8/{rr.cc => class_builder.cc} (88%) create mode 100644 ext/v8/class_builder.h delete mode 100644 ext/v8/constants.cc delete mode 100644 ext/v8/constraints.cc delete mode 100644 ext/v8/context.cc delete mode 100644 ext/v8/date.cc delete mode 100644 ext/v8/exception.cc delete mode 100644 ext/v8/external.cc delete mode 100644 ext/v8/function.cc delete mode 100644 ext/v8/gc.cc create mode 100644 ext/v8/handles.h delete mode 100644 ext/v8/heap.cc delete mode 100644 ext/v8/invocation.cc create mode 100644 ext/v8/isolate.cc create mode 100644 ext/v8/isolate.h delete mode 100644 ext/v8/locker.cc delete mode 100644 ext/v8/message.cc delete mode 100644 ext/v8/object.cc create mode 100644 ext/v8/pointer.h delete mode 100644 ext/v8/primitive.cc delete mode 100644 ext/v8/script.cc delete mode 100644 ext/v8/signature.cc delete mode 100644 ext/v8/stack.cc delete mode 100644 ext/v8/string.cc delete mode 100644 ext/v8/template.cc delete mode 100644 ext/v8/trycatch.cc create mode 100644 ext/v8/v8.h delete mode 100644 ext/v8/value.cc delete mode 100644 spec/c/array_spec.rb delete mode 100644 spec/c/constants_spec.rb delete mode 100644 spec/c/exception_spec.rb delete mode 100644 spec/c/external_spec.rb delete mode 100644 spec/c/function_spec.rb delete mode 100644 spec/c/handles_spec.rb create mode 100644 spec/c/isolate_spec.rb delete mode 100644 spec/c/locker_spec.rb delete mode 100644 spec/c/object_spec.rb delete mode 100644 spec/c/script_spec.rb delete mode 100644 spec/c/string_spec.rb delete mode 100644 spec/c/template_spec.rb delete mode 100644 spec/c/trycatch_spec.rb diff --git a/ext/v8/accessor.cc b/ext/v8/accessor.cc deleted file mode 100644 index 31bef241..00000000 --- a/ext/v8/accessor.cc +++ /dev/null @@ -1,181 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Accessor::Info::Class; - - void Accessor::Init() { - ClassBuilder("AccessorInfo"). - defineMethod("This", &Info::This). - defineMethod("Holder", &Info::Holder). - defineMethod("Data", &Info::Data). - store(&Info::Class); - } - - Accessor::Accessor(VALUE getter, VALUE setter, VALUE data_) : get(getter), set(setter), query(Qnil), deleter(Qnil), enumerator(Qnil), data(data_) {} - - Accessor::Accessor(VALUE get, VALUE set, VALUE query, VALUE deleter, VALUE enumerator, VALUE data) { - this->get = get; - this->set = set; - this->query = query; - this->deleter = deleter; - this->enumerator = enumerator; - this->data = data; - } - - Accessor::Accessor(v8::Handle value) { - v8::Local wrapper = value->ToObject(); - this->get = unwrap(wrapper, 0); - this->set = unwrap(wrapper, 1); - this->query = unwrap(wrapper, 2); - this->deleter = unwrap(wrapper, 3); - this->enumerator = unwrap(wrapper, 4); - v8::Handle data = wrapper->Get(5); - if (!data.IsEmpty() && !data->IsNull() && !data->IsUndefined()) { - this->data = Value(data); - } - } - - Accessor::operator v8::Handle() { - v8::Local wrapper = v8::Object::New(); - wrap(wrapper, 0, this->get); - wrap(wrapper, 1, this->set); - wrap(wrapper, 2, this->query); - wrap(wrapper, 3, this->deleter); - wrap(wrapper, 4, this->enumerator); - if (RTEST(this->data)) { - wrapper->Set(5, Value(this->data)); - } - return wrapper; - } - - void Accessor::wrap(v8::Handle wrapper, int index, VALUE value) { - if (RTEST(value)) { - wrapper->Set(index, External::wrap(value)); - } - } - - VALUE Accessor::unwrap(v8::Handle wrapper, int index) { - v8::Handle value = wrapper->Get(index); - if (value.IsEmpty() || !value->IsExternal()) { - return Qnil; - } else { - v8::Handle external(v8::External::Cast(*value)); - return External::unwrap(external); - } - } - - - VALUE Accessor::Info::This(VALUE self) { - return Object(Info(self)->This()); - } - - VALUE Accessor::Info::Holder(VALUE self) { - return Object(Info(self)->Holder()); - } - - VALUE Accessor::Info::Data(VALUE self) { - return Accessor(Info(self)->Data()).data; - } - - v8::Handle Accessor::AccessorGetter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).get(property); - } - - void Accessor::AccessorSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info) { - Info(info).set(property, value); - } - v8::Handle Accessor::NamedPropertyGetter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).get(property); - } - v8::Handle Accessor::NamedPropertySetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info) { - return Info(info).set(property, value); - } - v8::Handle Accessor::NamedPropertyQuery(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).query(property); - } - v8::Handle Accessor::NamedPropertyDeleter(v8::Local property, const v8::AccessorInfo& info) { - return Info(info).remove(property); - } - v8::Handle Accessor::NamedPropertyEnumerator(const v8::AccessorInfo& info) { - return Info(info).enumerateNames(); - } - - v8::Handle Accessor::IndexedPropertyGetter(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).get(index); - } - v8::Handle Accessor::IndexedPropertySetter(uint32_t index, v8::Local value, const v8::AccessorInfo& info) { - return Info(info).set(index, value); - } - v8::Handle Accessor::IndexedPropertyQuery(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).query(index); - } - v8::Handle Accessor::IndexedPropertyDeleter(uint32_t index, const v8::AccessorInfo& info) { - return Info(info).remove(index); - } - v8::Handle Accessor::IndexedPropertyEnumerator(const v8::AccessorInfo& info) { - return Info(info).enumerateIndices(); - } - - Accessor::Info::Info(const v8::AccessorInfo& info) { - this->info = &info; - } - - Accessor::Info::Info(VALUE value) { - Data_Get_Struct(value, class v8::AccessorInfo, info); - } - - v8::Handle Accessor::Info::get(v8::Local property) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.get, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this)); - } - - v8::Handle Accessor::Info::set(v8::Local property, v8::Local value) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.set, rb_intern("call"), 3, (VALUE)String(property), (VALUE)Value(value), (VALUE)*this)); - } - - v8::Handle Accessor::Info::query(v8::Local property) { - Accessor accessor(info->Data()); - return v8::Integer::New(NUM2INT(rb_funcall(accessor.query, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this))); - } - - v8::Handle Accessor::Info::remove(v8::Local property) { - Accessor accessor(info->Data()); - return v8::Boolean::New(Bool(rb_funcall(accessor.deleter, rb_intern("call"), 2, (VALUE)String(property), (VALUE)*this))); - } - - v8::Handle Accessor::Info::enumerateNames() { - Accessor accessor(info->Data()); - return Array(rb_funcall(accessor.enumerator, rb_intern("call"), 1, (VALUE)*this)); - } - - v8::Handle Accessor::Info::get(uint32_t index) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.get, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this)); - } - - v8::Handle Accessor::Info::set(uint32_t index, v8::Local value) { - Accessor accessor(info->Data()); - return Value(rb_funcall(accessor.set, rb_intern("call"), 3, UINT2NUM(index), (VALUE)Value(value), (VALUE)*this)); - } - - v8::Handle Accessor::Info::query(uint32_t index) { - Accessor accessor(info->Data()); - return v8::Integer::New(NUM2INT(rb_funcall(accessor.query, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this))); - } - - v8::Handle Accessor::Info::remove(uint32_t index) { - Accessor accessor(info->Data()); - return v8::Boolean::New(Bool(rb_funcall(accessor.deleter, rb_intern("call"), 2, UINT2NUM(index), (VALUE)*this))); - } - - v8::Handle Accessor::Info::enumerateIndices() { - Accessor accessor(info->Data()); - return Array(rb_funcall(accessor.enumerator, rb_intern("call"), 1, (VALUE)*this)); - } - - Accessor::Info::operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, (void*)this->info); - } -} \ No newline at end of file diff --git a/ext/v8/array.cc b/ext/v8/array.cc deleted file mode 100644 index 9f9bfc13..00000000 --- a/ext/v8/array.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "rr.h" - -namespace rr { - -void Array::Init() { - ClassBuilder("Array", Object::Class). - defineSingletonMethod("New", &New). - defineMethod("Length", &Length). - defineMethod("CloneElementAt", &CloneElementAt). - store(&Class); -} - -VALUE Array::New(int argc, VALUE argv[], VALUE self) { - VALUE length; rb_scan_args(argc, argv, "01", &length); - return Array(v8::Array::New(RTEST(length) ? NUM2INT(length) : 0)); -} - -VALUE Array::Length(VALUE self) { - return UInt32(Array(self)->Length()); -} - -VALUE Array::CloneElementAt(VALUE self, VALUE index) { - return Object(Array(self)->CloneElementAt(UInt32(index))); -} - -} \ No newline at end of file diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc deleted file mode 100644 index 25421b30..00000000 --- a/ext/v8/backref.cc +++ /dev/null @@ -1,45 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Backref::Storage; - ID Backref::_new; - ID Backref::object; - - void Backref::Init() { - Storage = rb_eval_string("V8::Weak::Ref"); - rb_gc_register_address(&Storage); - _new = rb_intern("new"); - object = rb_intern("object"); - } - - Backref::Backref(VALUE initial) { - set(initial); - rb_gc_register_address(&storage); - } - - Backref::~Backref() { - rb_gc_unregister_address(&storage); - } - - VALUE Backref::set(VALUE data) { - this->storage = rb_funcall(Storage, _new, 1, data); - return data; - } - - VALUE Backref::get() { - return rb_funcall(storage, object, 0); - } - - v8::Handle Backref::toExternal() { - v8::Local wrapper = v8::External::New(this); - v8::Persistent::New(wrapper).MakeWeak(this, &release); - return wrapper; - } - - void Backref::release(v8::Persistent handle, void* data) { - handle.Dispose(); - Backref* backref = (Backref*)data; - delete backref; - } -} diff --git a/ext/v8/rr.cc b/ext/v8/class_builder.cc similarity index 88% rename from ext/v8/rr.cc rename to ext/v8/class_builder.cc index 2896d1a8..047f19ad 100644 --- a/ext/v8/rr.cc +++ b/ext/v8/class_builder.cc @@ -1,7 +1,9 @@ #include "rr.h" +#include "class_builder.h" namespace rr { - VALUE defineClass(const char *name, VALUE superclass = rb_cObject) { + + VALUE ClassBuilder::defineClass(const char *name, VALUE superclass) { VALUE V8 = rb_define_module("V8"); VALUE V8_C = rb_define_module_under(V8, "C"); VALUE klass = rb_define_class_under(V8_C, name, superclass); @@ -9,44 +11,46 @@ namespace rr { return klass; } - VALUE defineModule(const char *name) { + VALUE ClassBuilder::defineModule(const char *name) { VALUE V8 = rb_define_module("V8"); VALUE V8_C = rb_define_module_under(V8, "C"); return rb_define_module_under(V8_C, name); } - VALUE not_implemented(const char* message) { - Void(rb_raise(rb_eStandardError, "not yet implemented %s", message)); - } - ClassBuilder::ClassBuilder(const char* name, VALUE superclass) { - this->value = defineClass(name, superclass); + this->value = ClassBuilder::defineClass(name, superclass); } ClassBuilder::ClassBuilder(const char* name, const char* supername) { - VALUE superclass = defineClass(supername); - this->value = defineClass(name, superclass); + VALUE superclass = ClassBuilder::defineClass(supername); + this->value = ClassBuilder::defineClass(name, superclass); } + ClassBuilder& ClassBuilder::defineConst(const char* name, VALUE value) { rb_define_const(this->value, name, value); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, -1); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 0); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 1); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 2); return *this; } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, 3); return *this; @@ -55,29 +59,36 @@ namespace rr { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, -1); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 0); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 1); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 2); return *this; } + ClassBuilder& ClassBuilder::defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)) { rb_define_singleton_method(this->value, name, (VALUE (*)(...))impl, 3); return *this; } + ClassBuilder& ClassBuilder::defineEnumConst(const char* name, int value) { rb_define_const(this->value, name, INT2FIX(value)); return *this; } + ClassBuilder& ClassBuilder::store(VALUE* storage) { rb_gc_register_address(storage); *storage = this->value; return *this; } -} \ No newline at end of file + +} diff --git a/ext/v8/class_builder.h b/ext/v8/class_builder.h new file mode 100644 index 00000000..163b88f1 --- /dev/null +++ b/ext/v8/class_builder.h @@ -0,0 +1,40 @@ +#ifndef RR_CLASS_BUILDER +#define RR_CLASS_BUILDER + +namespace rr { + + class ClassBuilder { + public: + static VALUE defineClass(const char *name, VALUE superclass = rb_cObject); + static VALUE defineModule(const char *name); + + ClassBuilder() {}; + ClassBuilder(const char* name, VALUE superclass = rb_cObject); + ClassBuilder(const char* name, const char* supername); + + ClassBuilder& defineConst(const char* name, VALUE value); + + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); + ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); + + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); + ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); + + ClassBuilder& defineEnumConst(const char* name, int value); + + ClassBuilder& store(VALUE* storage); + + inline operator VALUE() {return this->value;} + protected: + VALUE value; + }; + +} + +#endif diff --git a/ext/v8/constants.cc b/ext/v8/constants.cc deleted file mode 100644 index 65ca1b03..00000000 --- a/ext/v8/constants.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include "rr.h" - -namespace rr { - VALUE Constants::_Undefined; - VALUE Constants::_Null; - VALUE Constants::_True; - VALUE Constants::_False; - void Constants::Init() { - ModuleBuilder("V8::C"). - defineSingletonMethod("Undefined", &Undefined). - defineSingletonMethod("Null", &Null). - defineSingletonMethod("True", &True). - defineSingletonMethod("False", &False); - - _Undefined = _Null = _True = _False = Qnil; - rb_gc_register_address(&_Undefined); - rb_gc_register_address(&_Null); - rb_gc_register_address(&_True); - rb_gc_register_address(&_False); - } - - VALUE Constants::Undefined(VALUE self) { - return cached(&_Undefined, v8::Undefined()); - } - VALUE Constants::Null(VALUE self) { - return cached(&_Null, v8::Null()); - } - VALUE Constants::True(VALUE self) { - return cached(&_True, v8::True()); - } - VALUE Constants::False(VALUE self) { - return cached(&_False, v8::False()); - } -} \ No newline at end of file diff --git a/ext/v8/constraints.cc b/ext/v8/constraints.cc deleted file mode 100644 index 5ba5e863..00000000 --- a/ext/v8/constraints.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "rr.h" - -namespace rr { - void ResourceConstraints::Init() { - ClassBuilder("ResourceConstraints"). - defineSingletonMethod("new", &initialize). - defineMethod("max_young_space_size", &max_young_space_size). - defineMethod("set_max_young_space_size", &set_max_young_space_size). - defineMethod("max_old_space_size", &max_old_space_size). - defineMethod("set_max_old_space_size", &set_max_old_space_size). - defineMethod("max_executable_size", &set_max_executable_size). - defineMethod("set_max_executable_size", &set_max_executable_size). - store(&Class); - ModuleBuilder("V8::C"). - defineSingletonMethod("SetResourceConstraints", &SetResourceConstraints); - } - - VALUE ResourceConstraints::SetResourceConstraints(VALUE self, VALUE constraints) { - Void(v8::SetResourceConstraints(ResourceConstraints(constraints))); - } - - VALUE ResourceConstraints::initialize(VALUE self) { - return ResourceConstraints(new v8::ResourceConstraints()); - } - VALUE ResourceConstraints::max_young_space_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_young_space_size()); - } - VALUE ResourceConstraints::set_max_young_space_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_young_space_size(NUM2INT(value))); - } - VALUE ResourceConstraints::max_old_space_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_old_space_size()); - } - VALUE ResourceConstraints::set_max_old_space_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_old_space_size(NUM2INT(value))); - } - VALUE ResourceConstraints::max_executable_size(VALUE self) { - return INT2FIX(ResourceConstraints(self)->max_executable_size()); - } - VALUE ResourceConstraints::set_max_executable_size(VALUE self, VALUE value) { - Void(ResourceConstraints(self)->set_max_executable_size(NUM2INT(value))); - } - - // What do these even mean? - // uint32_t* stack_limit() const { return stack_limit_; } - // // Sets an address beyond which the VM's stack may not grow. - // void set_stack_limit(uint32_t* value) { stack_limit_ = value; } - - template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ResourceConstraints, pointer); - } -} \ No newline at end of file diff --git a/ext/v8/context.cc b/ext/v8/context.cc deleted file mode 100644 index f5849f54..00000000 --- a/ext/v8/context.cc +++ /dev/null @@ -1,130 +0,0 @@ -#include "rr.h" - -namespace rr { - -void Context::Init() { - ClassBuilder("Context"). - defineSingletonMethod("New", &New). - defineSingletonMethod("GetCurrent", &GetCurrent). - defineSingletonMethod("GetEntered", &GetEntered). - defineSingletonMethod("GetCalling", &GetCalling). - defineSingletonMethod("InContext", &InContext). - defineMethod("Dispose", &Dispose). - defineMethod("Global", &Global). - defineMethod("DetachGlobal", &Global). - defineMethod("ReattachGlobal", &ReattachGlobal). - defineMethod("SetSecurityToken", &SetSecurityToken). - defineMethod("UseDefaultSecurityToken", &UseDefaultSecurityToken). - defineMethod("GetSecurityToken", &GetSecurityToken). - defineMethod("HasOutOfMemoryException", &HasOutOfMemoryException). - defineMethod("SetEmbedderData", &SetEmbedderData). - defineMethod("GetEmbedderData", &GetEmbedderData). - defineMethod("AllowCodeGenerationFromStrings", &AllowCodeGenerationFromStrings). - defineMethod("IsCodeGenerationFromStringsAllowed", &IsCodeGenerationFromStringsAllowed). - defineMethod("Enter", &Enter). - defineMethod("Exit", &Exit). - store(&Class); - ClassBuilder("ExtensionConfiguration"). - defineSingletonMethod("new", &ExtensionConfiguration::initialize). - store(&ExtensionConfiguration::Class); -} - -VALUE Context::Dispose(VALUE self) { - Void(Context(self).dispose()) -} - -VALUE Context::Global(VALUE self) { - return Object(Context(self)->Global()); -} - -VALUE Context::DetachGlobal(VALUE self) { - Void(Context(self)->DetachGlobal()); -} - -VALUE Context::ReattachGlobal(VALUE self, VALUE global) { - Void(Context(self)->ReattachGlobal(Object(global))); -} - -VALUE Context::GetEntered(VALUE self) { - return Context(v8::Context::GetEntered()); -} - -VALUE Context::GetCurrent(VALUE self) { - return Context(v8::Context::GetCurrent()); -} - -VALUE Context::GetCalling(VALUE self) { - return Context(v8::Context::GetCalling()); -} - -VALUE Context::SetSecurityToken(VALUE self, VALUE token) { - Void(Context(self)->SetSecurityToken(Value(token))); -} - -VALUE Context::UseDefaultSecurityToken(VALUE self) { - Void(Context(self)->UseDefaultSecurityToken()); -} - -VALUE Context::GetSecurityToken(VALUE self) { - return Value(Context(self)->GetSecurityToken()); -} - -VALUE Context::HasOutOfMemoryException(VALUE self) { - return Bool(Context(self)->HasOutOfMemoryException()); -} - -VALUE Context::InContext(VALUE self) { - return Bool(v8::Context::InContext()); -} - -VALUE Context::SetEmbedderData(VALUE self, VALUE index, VALUE data) { - Void(Context(self)->SetEmbedderData(NUM2INT(index), Value(data))); -} - -VALUE Context::GetEmbedderData(VALUE self, VALUE index) { - Void(Context(self)->GetEmbedderData(NUM2INT(index))); -} - -VALUE Context::AllowCodeGenerationFromStrings(VALUE self, VALUE allow) { - Void(Context(self)->AllowCodeGenerationFromStrings(RTEST(allow))); -} - -VALUE Context::IsCodeGenerationFromStringsAllowed(VALUE self) { - return Bool(Context(self)->IsCodeGenerationFromStringsAllowed()); -} - -VALUE ExtensionConfiguration::initialize(VALUE self, VALUE names) { - int length = RARRAY_LENINT(names); - const char* array[length]; - for (int i = 0; i < length; i++) { - array[i] = RSTRING_PTR(rb_ary_entry(names, i)); - } - return ExtensionConfiguration(new v8::ExtensionConfiguration(length, array)); -} - -VALUE Context::New(int argc, VALUE argv[], VALUE self) { - VALUE extension_configuration; VALUE global_template; VALUE global_object; - rb_scan_args(argc, argv, "03", &extension_configuration, &global_template, &global_object); - v8::Persistent context(v8::Context::New( - ExtensionConfiguration(extension_configuration), - *ObjectTemplate(global_template), - *Object(global_object) - )); - Context reference(context); - context.Dispose(); - return reference; -} - -VALUE Context::Enter(VALUE self) { - Void(Context(self)->Enter()); -} - -VALUE Context::Exit(VALUE self) { - Void(Context(self)->Exit()); -} - -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); -} - -} diff --git a/ext/v8/date.cc b/ext/v8/date.cc deleted file mode 100644 index 1c51b1b4..00000000 --- a/ext/v8/date.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "rr.h" - -namespace rr { - - void Date::Init() { - ClassBuilder("Date", Value::Class). - defineSingletonMethod("New", &New). - defineMethod("NumberValue", &NumberValue). - store(&Class); - } - - VALUE Date::New(VALUE self, VALUE time) { - return Value(v8::Date::New(NUM2DBL(time))); - } - VALUE Date::NumberValue(VALUE self) { - return rb_float_new(Date(self)->NumberValue()); - } -} \ No newline at end of file diff --git a/ext/v8/exception.cc b/ext/v8/exception.cc deleted file mode 100644 index 8fb23de5..00000000 --- a/ext/v8/exception.cc +++ /dev/null @@ -1,38 +0,0 @@ -#include "rr.h" - -namespace rr { - void Exception::Init() { - ModuleBuilder("V8::C"). - defineSingletonMethod("ThrowException", &ThrowException); - ClassBuilder("Exception"). - defineSingletonMethod("RangeError", &RangeError). - defineSingletonMethod("ReferenceError", &ReferenceError). - defineSingletonMethod("SyntaxError", &SyntaxError). - defineSingletonMethod("TypeError", &TypeError). - defineSingletonMethod("Error", &Error); - } - - VALUE Exception::ThrowException(VALUE self, VALUE exception) { - return Value(v8::ThrowException(Value(exception))); - } - - VALUE Exception::RangeError(VALUE self, VALUE message) { - return Value(v8::Exception::RangeError(String(message))); - } - - VALUE Exception::ReferenceError(VALUE self, VALUE message) { - return Value(v8::Exception::ReferenceError(String(message))); - } - - VALUE Exception::SyntaxError(VALUE self, VALUE message) { - return Value(v8::Exception::SyntaxError(String(message))); - } - - VALUE Exception::TypeError(VALUE self, VALUE message) { - return Value(v8::Exception::TypeError(String(message))); - } - - VALUE Exception::Error(VALUE self, VALUE message) { - return Value(v8::Exception::Error(String(message))); - } -} \ No newline at end of file diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index 1bc885b1..bcbe4cf6 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -16,7 +16,7 @@ $CFLAGS += " -O0 -ggdb3" end -LIBV8_COMPATIBILITY = '~> 3.16.14' +LIBV8_COMPATIBILITY = '~> 3.31.0' begin require 'rubygems' diff --git a/ext/v8/external.cc b/ext/v8/external.cc deleted file mode 100644 index c4f67013..00000000 --- a/ext/v8/external.cc +++ /dev/null @@ -1,43 +0,0 @@ -#include "rr.h" - -namespace rr { - -void External::Init() { - ClassBuilder("External", "Value"). - defineSingletonMethod("New", &New). - defineMethod("Value", &Value). - store(&Class); -} -VALUE External::New(VALUE self, VALUE data) { - return External(wrap(data)); -} - -v8::Handle External::wrap(VALUE data) { - Data* holder = new Data(data); - v8::Local ext = v8::External::New(holder); - v8::Persistent::New(ext).MakeWeak(holder, &release); - return ext; -} - -VALUE External::unwrap(v8::Handle external) { - Data* data = (Data*)(external->Value()); - return data->value; -} - -VALUE External::Value(VALUE self) { - return unwrap(External(self)); -} - -void External::release(v8::Persistent handle, void* parameter) { - handle.Dispose(); - Data* data = (Data*)parameter; - delete data; -} -External::Data::Data(VALUE data) { - this->value = data; - rb_gc_register_address(&value); -} -External::Data::~Data() { - rb_gc_unregister_address(&value); -} -} \ No newline at end of file diff --git a/ext/v8/function.cc b/ext/v8/function.cc deleted file mode 100644 index c4380c28..00000000 --- a/ext/v8/function.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "rr.h" - -namespace rr { - void Function::Init() { - ClassBuilder("Function", Object::Class). - defineMethod("NewInstance", &NewInstance). - defineMethod("Call", &Call). - defineMethod("SetName", &SetName). - defineMethod("GetName", &GetName). - defineMethod("GetInferredName", &GetInferredName). - defineMethod("GetScriptLineNumber", &GetScriptLineNumber). - defineMethod("GetScriptColumnNumber", &GetScriptColumnNumber). - defineMethod("GetScriptId", &GetScriptId). - defineMethod("GetScriptOrigin", &GetScriptOrigin). - store(&Class); - } - - VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { - VALUE args; - rb_scan_args(argc,argv,"01", &args); - if (RTEST(args)) { - return Object(Function(self)->NewInstance(RARRAY_LENINT(args), Value::array(args))); - } else { - return Object(Function(self)->NewInstance()); - } - } - VALUE Function::Call(VALUE self, VALUE receiver, VALUE argv) { - return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); - } - - VALUE Function::SetName(VALUE self, VALUE name) { - Void(Function(self)->SetName(String(name))); - } - - VALUE Function::GetName(VALUE self) { - return Value(Function(self)->GetName()); - } - - VALUE Function::GetInferredName(VALUE self) { - return Value(Function(self)->GetInferredName()); - } - - VALUE Function::GetScriptLineNumber(VALUE self) { - return INT2FIX(Function(self)->GetScriptLineNumber()); - } - - VALUE Function::GetScriptColumnNumber(VALUE self) { - return INT2FIX(Function(self)->GetScriptColumnNumber()); - } - - VALUE Function::GetScriptId(VALUE self) { - return Value(Function(self)->GetScriptId()); - } - - VALUE Function::GetScriptOrigin(VALUE self) { - return not_implemented("GetScriptOrigin"); - } -} \ No newline at end of file diff --git a/ext/v8/gc.cc b/ext/v8/gc.cc deleted file mode 100644 index 9412e9b8..00000000 --- a/ext/v8/gc.cc +++ /dev/null @@ -1,43 +0,0 @@ -#include "rr.h" - -namespace rr { - GC::Queue* queue; - - GC::Queue::Queue() : first(0), divider(0), last(0){ - first = new GC::Queue::Node(NULL); - divider = first; - last = first; - } - - void GC::Queue::Enqueue(void* reference) { - last->next = new Node(reference); - last = last->next; - while (first != divider) { - Node* tmp = first; - first = first->next; - delete tmp; - } - } - - void* GC::Queue::Dequeue() { - void* result = NULL; - if (divider != last) { - result = divider->next->value; - divider = divider->next; - } - return result; - } - - void GC::Finalize(void* phantom) { - queue->Enqueue(phantom); - } - void GC::Drain(v8::GCType type, v8::GCCallbackFlags flags) { - for(Phantom phantom = queue->Dequeue(); phantom.NotNull(); phantom = queue->Dequeue()) { - phantom.destroy(); - } - } - void GC::Init() { - queue = new GC::Queue(); - v8::V8::AddGCPrologueCallback(GC::Drain); - } -} \ No newline at end of file diff --git a/ext/v8/handles.cc b/ext/v8/handles.cc index d51c9092..f1c37fa8 100644 --- a/ext/v8/handles.cc +++ b/ext/v8/handles.cc @@ -2,33 +2,35 @@ namespace rr { -void Handles::Init() { - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "HandleScope", (VALUE (*)(...))&HandleScope, -1); -} + void Handles::Init() { + VALUE v8 = rb_define_module("V8"); + VALUE c = rb_define_module_under(v8, "C"); + rb_define_singleton_method(c, "HandleScope", (VALUE (*)(...))&HandleScope, -1); + } + + VALUE Handles::HandleScope(int argc, VALUE* argv, VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } -VALUE Handles::HandleScope(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; + VALUE isolate, block; + rb_scan_args(argc, argv, "10&", &isolate, &block); + + int state = 0; + VALUE result = SetupAndCall(Isolate(isolate), &state, block); + if (state != 0) { + rb_jump_tag(state); + } + return result; } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = SetupAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); + + VALUE Handles::SetupAndCall(Isolate isolate, int* state, VALUE block) { + v8::HandleScope handle_scope(isolate); + return rb_protect(&DoCall, block, state); } - return result; -} -VALUE Handles::SetupAndCall(int* state, VALUE code) { - v8::HandleScope scope; - return rb_protect(&DoCall, code, state); -} + VALUE Handles::DoCall(VALUE block) { + return rb_funcall(block, rb_intern("call"), 0); + } -VALUE Handles::DoCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); } - -} \ No newline at end of file diff --git a/ext/v8/handles.h b/ext/v8/handles.h new file mode 100644 index 00000000..3d57ebf1 --- /dev/null +++ b/ext/v8/handles.h @@ -0,0 +1,17 @@ +#ifndef RR_HANDLES +#define RR_HANDLES + +namespace rr { + + class Handles { + public: + static void Init(); + static VALUE HandleScope(int argc, VALUE* argv, VALUE self); + private: + static VALUE SetupAndCall(Isolate isolate, int* state, VALUE code); + static VALUE DoCall(VALUE code); + }; + +} + +#endif diff --git a/ext/v8/heap.cc b/ext/v8/heap.cc deleted file mode 100644 index cf930b69..00000000 --- a/ext/v8/heap.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "rr.h" - -namespace rr { - void HeapStatistics::Init() { - ClassBuilder("HeapStatistics"). - defineSingletonMethod("new", &initialize). - defineMethod("total_heap_size", &total_heap_size). - defineMethod("total_heap_size_executable", &total_heap_size_executable). - defineMethod("total_physical_size", &total_physical_size). - defineMethod("used_heap_size", &used_heap_size). - defineMethod("heap_size_limit", &heap_size_limit). - store(&Class); - } - VALUE HeapStatistics::initialize(VALUE self) { - return HeapStatistics(new v8::HeapStatistics()); - } - VALUE HeapStatistics::total_heap_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_heap_size()); - } - VALUE HeapStatistics::total_heap_size_executable(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_heap_size_executable()); - } - VALUE HeapStatistics::total_physical_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->total_physical_size()); - } - VALUE HeapStatistics::used_heap_size(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->used_heap_size()); - } - VALUE HeapStatistics::heap_size_limit(VALUE self) { - return SIZET2NUM(HeapStatistics(self)->heap_size_limit()); - } - template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::HeapStatistics, pointer); - } -} diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 94d6f01d..e09c605f 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -8,32 +8,33 @@ using namespace rr; extern "C" { void Init_init() { - v8::Locker lock(); - GC::Init(); V8::Init(); - Handles::Init(); - Accessor::Init(); - Context::Init(); - Invocation::Init(); - Signature::Init(); - Value::Init(); - Primitive::Init(); - String::Init(); - Object::Init(); - Array::Init(); - Function::Init(); - Date::Init(); - Constants::Init(); - External::Init(); - Script::Init(); - Template::Init(); - Stack::Init(); - Message::Init(); - TryCatch::Init(); - Exception::Init(); - Locker::Init(); - ResourceConstraints::Init(); - HeapStatistics::Init(); - Backref::Init(); + Isolate::Init(); + // v8::Locker lock(); + // GC::Init(); + // Handles::Init(); + // Accessor::Init(); + // Context::Init(); + // Invocation::Init(); + // Signature::Init(); + // Value::Init(); + // Primitive::Init(); + // String::Init(); + // Object::Init(); + // Array::Init(); + // Function::Init(); + // Date::Init(); + // Constants::Init(); + // External::Init(); + // Script::Init(); + // Template::Init(); + // Stack::Init(); + // Message::Init(); + // TryCatch::Init(); + // Exception::Init(); + // Locker::Init(); + // ResourceConstraints::Init(); + // HeapStatistics::Init(); + // Backref::Init(); } -} \ No newline at end of file +} diff --git a/ext/v8/invocation.cc b/ext/v8/invocation.cc deleted file mode 100644 index 497e5304..00000000 --- a/ext/v8/invocation.cc +++ /dev/null @@ -1,86 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Invocation::Arguments::Class; - - void Invocation::Init() { - Arguments::Init(); - } - - void Invocation::Arguments::Init() { - ClassBuilder("Arguments"). - defineMethod("Length", &Length). - defineMethod("[]", &Get). - defineMethod("Callee", &Callee). - defineMethod("This", &This). - defineMethod("Holder", &Holder). - defineMethod("IsConstructCall", &IsConstructCall). - defineMethod("Data", &Data). - store(&Invocation::Arguments::Class); - } - - Invocation::Invocation(VALUE code, VALUE data) { - this->code = code; - this->data = data; - } - Invocation::Invocation(v8::Handle value) { - v8::Local wrapper = value->ToObject(); - this->code = External::unwrap((v8::Handle)v8::External::Cast(*wrapper->Get(0))); - this->data = Value(wrapper->Get(1)); - } - Invocation::operator v8::InvocationCallback() { - return &Callback; - } - Invocation::operator v8::Handle() { - v8::Local wrapper = v8::Object::New(); - wrapper->Set(0, External::wrap(this->code)); - wrapper->Set(1, Value(this->data)); - return wrapper; - } - - v8::Handle Invocation::Callback(const v8::Arguments& args) { - return Arguments(args).Call(); - } - - Invocation::Arguments::Arguments(const v8::Arguments& args) { - this->args = &args; - } - - Invocation::Arguments::Arguments(VALUE value) { - Data_Get_Struct(value, class v8::Arguments, args); - } - - v8::Handle Invocation::Arguments::Call() { - Invocation invocation(args->Data()); - return Value(rb_funcall(invocation.code, rb_intern("call"), 1, Data_Wrap_Struct(Class, 0, 0, (void*)this->args))); - } - - VALUE Invocation::Arguments::Length(VALUE self) { - return INT2FIX(Arguments(self)->Length()); - } - - VALUE Invocation::Arguments::Get(VALUE self, VALUE index) { - return Value((*Arguments(self))[NUM2INT(index)]); - } - - VALUE Invocation::Arguments::Callee(VALUE self) { - return Function(Arguments(self)->Callee()); - } - - VALUE Invocation::Arguments::This(VALUE self) { - return Object(Arguments(self)->This()); - } - - VALUE Invocation::Arguments::Holder(VALUE self) { - return Object(Arguments(self)->Holder()); - } - - VALUE Invocation::Arguments::IsConstructCall(VALUE self) { - return Bool(Arguments(self)->IsConstructCall()); - } - - VALUE Invocation::Arguments::Data(VALUE self) { - return Invocation(Arguments(self)->Data()).data; - } -} \ No newline at end of file diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc new file mode 100644 index 00000000..18e6ff9d --- /dev/null +++ b/ext/v8/isolate.cc @@ -0,0 +1,20 @@ +#include "rr.h" + +namespace rr { + + void Isolate::Init() { + ClassBuilder("Isolate"). + defineSingletonMethod("New", &New). + store(&Class); + } + + VALUE Isolate::New(VALUE self) { + return Isolate(v8::Isolate::New()); + } + + template <> + void Pointer::unwrap(VALUE value) { + Data_Get_Struct(value, class v8::Isolate, pointer); + } + +} diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h new file mode 100644 index 00000000..d956cee5 --- /dev/null +++ b/ext/v8/isolate.h @@ -0,0 +1,31 @@ +#ifndef RR_ISOLATE +#define RR_ISOLATE + +namespace rr { + + class Isolate : public Pointer { + public: + static void Init(); + static VALUE New(VALUE self); + + inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} + inline Isolate(VALUE value) : Pointer(value) {} + + inline operator VALUE() { + return Data_Wrap_Struct(Class, 0, &release, pointer); + } + + static void release(v8::Isolate* isolate) { + // The isolates must be released with Dispose. + // Using the delete operator is not allowed. + + // TODO: Do we want to dispose of the isolate when the object itself + // is garbage-collected? + // Can the isolate be used without it having a reference in ruby world? + isolate->Dispose(); + } + }; + +} + +#endif diff --git a/ext/v8/locker.cc b/ext/v8/locker.cc deleted file mode 100644 index b9fbe788..00000000 --- a/ext/v8/locker.cc +++ /dev/null @@ -1,77 +0,0 @@ -#include "rr.h" - -namespace rr { - void Locker::Init() { - ClassBuilder("Locker"). - defineSingletonMethod("StartPreemption", &StartPreemption). - defineSingletonMethod("StopPreemption", &StopPreemption). - defineSingletonMethod("IsLocked", &IsLocked). - defineSingletonMethod("IsActive", &IsActive); - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "Locker", (VALUE (*)(...))&doLock, -1); - rb_define_singleton_method(c, "Unlocker",(VALUE (*)(...))&doUnlock, -1); - } - - VALUE Locker::StartPreemption(VALUE self, VALUE every_n_ms) { - Void(v8::Locker::StartPreemption(NUM2INT(every_n_ms))); - } - - VALUE Locker::StopPreemption(VALUE self) { - Void(v8::Locker::StopPreemption()); - } - - VALUE Locker::IsLocked(VALUE self) { - return Bool(v8::Locker::IsLocked(v8::Isolate::GetCurrent())); - } - - VALUE Locker::IsActive(VALUE self) { - return Bool(v8::Locker::IsActive()); - } - - VALUE Locker::doLock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupLockAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE Locker::setupLockAndCall(int* state, VALUE code) { - v8::Locker locker; - return rb_protect(&doLockCall, code, state); - } - - VALUE Locker::doLockCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); - } - - VALUE Locker::doUnlock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupUnlockAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE Locker::setupUnlockAndCall(int* state, VALUE code) { - v8::Unlocker unlocker; - return rb_protect(&doUnlockCall, code, state); - } - - VALUE Locker::doUnlockCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 0); - } -} diff --git a/ext/v8/message.cc b/ext/v8/message.cc deleted file mode 100644 index 0d732ed8..00000000 --- a/ext/v8/message.cc +++ /dev/null @@ -1,51 +0,0 @@ -#include "rr.h" - -namespace rr { - - void Message::Init() { - ClassBuilder("Message"). - defineMethod("Get", &Get). - defineMethod("GetSourceLine", &GetSourceLine). - defineMethod("GetScriptResourceName", &GetScriptResourceName). - defineMethod("GetScriptData", &GetScriptData). - defineMethod("GetStackTrace", &GetStackTrace). - defineMethod("GetLineNumber", &GetLineNumber). - defineMethod("GetStartPosition", &GetStartPosition). - defineMethod("GetEndPosition", &GetEndPosition). - defineMethod("GetStartColumn", &GetEndColumn). - defineSingletonMethod("kNoLineNumberInfo", &kNoLineNumberInfo). - defineSingletonMethod("kNoColumnInfo", &kNoColumnInfo). - store(&Class); - } - - VALUE Message::Get(VALUE self) { - return String(Message(self)->Get()); - } - VALUE Message::GetSourceLine(VALUE self) { - return String(Message(self)->GetSourceLine()); - } - VALUE Message::GetScriptResourceName(VALUE self) { - return Value(Message(self)->GetScriptResourceName()); - } - VALUE Message::GetScriptData(VALUE self) { - return Value(Message(self)->GetScriptData()); - } - VALUE Message::GetStackTrace(VALUE self) { - return Stack::Trace(Message(self)->GetStackTrace()); - } - VALUE Message::GetLineNumber(VALUE self) { - return INT2FIX(Message(self)->GetLineNumber()); - } - VALUE Message::GetStartPosition(VALUE self) { - return INT2FIX(Message(self)->GetStartPosition()); - } - VALUE Message::GetEndPosition(VALUE self) { - return INT2FIX(Message(self)->GetEndPosition()); - } - VALUE Message::GetStartColumn(VALUE self) { - return INT2FIX(Message(self)->GetStartColumn()); - } - VALUE Message::GetEndColumn(VALUE self) { - return INT2FIX(Message(self)->GetEndColumn()); - } -} \ No newline at end of file diff --git a/ext/v8/object.cc b/ext/v8/object.cc deleted file mode 100644 index e63ee3b7..00000000 --- a/ext/v8/object.cc +++ /dev/null @@ -1,335 +0,0 @@ -#include "rr.h" - -namespace rr { - -void Object::Init() { - ClassBuilder("Object", Value::Class). - defineSingletonMethod("New", &New). - defineMethod("Set", &Set). - defineMethod("ForceSet", &ForceSet). - defineMethod("Get", &Get). - defineMethod("GetPropertyAttributes", &GetPropertyAttributes). - defineMethod("Has", &Has). - defineMethod("Delete", &Delete). - defineMethod("ForceDelete", &ForceDelete). - defineMethod("SetAccessor", &SetAccessor). - defineMethod("GetPropertyNames", &GetPropertyNames). - defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). - defineMethod("GetPrototype", &GetPrototype). - defineMethod("SetPrototype", &SetPrototype). - defineMethod("FindInstanceInPrototypeChain", &FindInstanceInPrototypeChain). - defineMethod("ObjectProtoToString", &ObjectProtoToString). - defineMethod("GetConstructorName", &GetConstructorName). - defineMethod("InternalFieldCount", &InternalFieldCount). - defineMethod("GetInternalField", &GetInternalField). - defineMethod("SetInternalField", &SetInternalField). - defineMethod("HasOwnProperty", &HasOwnProperty). - defineMethod("HasRealNamedProperty", &HasRealNamedProperty). - defineMethod("HasRealIndexedProperty", &HasRealIndexedProperty). - defineMethod("HasRealNamedCallbackProperty", &HasRealNamedCallbackProperty). - defineMethod("GetRealNamedPropertyInPrototypeChain", &GetRealNamedPropertyInPrototypeChain). - defineMethod("GetRealNamedProperty", &GetRealNamedProperty). - defineMethod("HasNamedLookupInterceptor", &HasNamedLookupInterceptor). - defineMethod("HasIndexedLookupInterceptor", &HasIndexedLookupInterceptor). - defineMethod("TurnOnAccessCheck", &TurnOnAccessCheck). - defineMethod("GetIdentityHash", &GetIdentityHash). - defineMethod("SetHiddenValue", &SetHiddenValue). - defineMethod("GetHiddenValue", &GetHiddenValue). - defineMethod("DeleteHiddenValue", &DeleteHiddenValue). - defineMethod("IsDirty", &IsDirty). - defineMethod("Clone", &Clone). - defineMethod("CreationContext", &CreationContext). - defineMethod("SetIndexedPropertiesToPixelData", &SetIndexedPropertiesToPixelData). - defineMethod("GetIndexedPropertiesPixelData", &GetIndexedPropertiesPixelData). - defineMethod("HasIndexedPropertiesToPixelData", &HasIndexedPropertiesInPixelData). - defineMethod("GetIndexedPropertiesPixelDataLength", &GetIndexedPropertiesPixelDataLength). - defineMethod("SetIndexedPropertiesToExternalArrayData", &SetIndexedPropertiesToExternalArrayData). - defineMethod("HasIndexedPropertiesInExternalArrayData", &HasIndexedPropertiesInExternalArrayData). - defineMethod("GetIndexedPropertiesExternalArrayData", &GetIndexedPropertiesExternalArrayData). - defineMethod("GetIndexedPropertiesExternalArrayDataType", &GetIndexedPropertiesExternalArrayDataType). - defineMethod("GetIndexedPropertiesExternalArrayDataLength", &GetIndexedPropertiesExternalArrayDataLength). - defineMethod("IsCallable", &IsCallable). - defineMethod("CallAsFunction", &CallAsFunction). - defineMethod("CallAsConstructor", &CallAsConstructor). - store(&Class); - ClassBuilder("PropertyAttribute"). - defineEnumConst("None", v8::None). - defineEnumConst("ReadOnly", v8::ReadOnly). - defineEnumConst("DontEnum", v8::DontEnum). - defineEnumConst("DontDelete", v8::DontDelete); - ClassBuilder("AccessControl"). - defineEnumConst("DEFAULT", v8::DEFAULT). - defineEnumConst("ALL_CAN_READ", v8::ALL_CAN_READ). - defineEnumConst("ALL_CAN_WRITE", v8::ALL_CAN_WRITE). - defineEnumConst("PROHIBITS_OVERWRITING", v8::PROHIBITS_OVERWRITING); -} - - -VALUE Object::New(VALUE self) { - return Object(v8::Object::New()); -} - -//TODO: Allow setting of property attributes -VALUE Object::Set(VALUE self, VALUE key, VALUE value) { - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(Object(self)->Set(UInt32(key), Value(value))); - } else { - return Bool(Object(self)->Set(*Value(key), Value(value))); - } -} - -VALUE Object::ForceSet(VALUE self, VALUE key, VALUE value) { - return Bool(Object(self)->ForceSet(Value(key), Value(value))); -} - -VALUE Object::Get(VALUE self, VALUE key) { - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value(Object(self)->Get(UInt32(key))); - } else { - return Value(Object(self)->Get(*Value(key))); - } -} - -VALUE Object::GetPropertyAttributes(VALUE self, VALUE key) { - return PropertyAttribute(Object(self)->GetPropertyAttributes(Value(key))); -} - -VALUE Object::Has(VALUE self, VALUE key) { - Object obj(self); - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(obj->Has(UInt32(key))); - } else { - return Bool(obj->Has(*String(key))); - } -} - -VALUE Object::Delete(VALUE self, VALUE key) { - Object obj(self); - if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(obj->Delete(UInt32(key))); - } else { - return Bool(obj->Delete(*String(key))); - } -} - -VALUE Object::ForceDelete(VALUE self, VALUE key) { - return Bool(Object(self)->ForceDelete(Value(key))); -} - - -VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { - VALUE name; VALUE get; VALUE set; VALUE data; VALUE settings; VALUE attribs; - rb_scan_args(argc, argv, "24", &name, &get, &set, &data, &settings, &attribs); - Accessor access(get, set, data); - return Bool(Object(self)->SetAccessor( - String(name), - access.accessorGetter(), - access.accessorSetter(), - access, - AccessControl(settings), - PropertyAttribute(attribs)) - ); -} - -Object::operator VALUE() { - if (handle.IsEmpty()) { - return Qnil; - } - Backref* backref; - v8::Local key(v8::String::NewSymbol("rr::Backref")); - v8::Local external = handle->GetHiddenValue(key); - VALUE value; - if (external.IsEmpty()) { - value = downcast(); - backref = new Backref(value); - handle->SetHiddenValue(key, backref->toExternal()); - } else { - v8::Local wrapper = v8::External::Cast(*external); - backref = (Backref*)wrapper->Value(); - value = backref->get(); - if (!RTEST(value)) { - value = downcast(); - backref->set(value); - } - } - return value; -} - -VALUE Object::downcast() { - if (handle->IsFunction()) { - return Function((v8::Handle) v8::Function::Cast(*handle)); - } - if (handle->IsArray()) { - return Array((v8::Handle)v8::Array::Cast(*handle)); - } - if (handle->IsDate()) { - // return Date(handle); - } - if (handle->IsBooleanObject()) { - // return BooleanObject(handle); - } - if (handle->IsNumberObject()) { - // return NumberObject(handle); - } - if (handle->IsStringObject()) { - // return StringObject(handle); - } - if (handle->IsRegExp()) { - // return RegExp(handle); - } - return Ref::operator VALUE(); -} - -VALUE Object::GetPropertyNames(VALUE self) { - return Array(Object(self)->GetPropertyNames()); -} - -VALUE Object::GetOwnPropertyNames(VALUE self) { - return Array(Object(self)->GetOwnPropertyNames()); -} - -VALUE Object::GetPrototype(VALUE self) { - return Value(Object(self)->GetPrototype()); -} - -VALUE Object::SetPrototype(VALUE self, VALUE prototype) { - return Bool(Object(self)->SetPrototype(Value(prototype))); -} - -VALUE Object::FindInstanceInPrototypeChain(VALUE self, VALUE impl) { - return Object(Object(self)->FindInstanceInPrototypeChain(FunctionTemplate(impl))); -} - -VALUE Object::ObjectProtoToString(VALUE self) { - return String(Object(self)->ObjectProtoToString()); -} - -VALUE Object::GetConstructorName(VALUE self) { - return String(Object(self)->GetConstructorName()); -} - -VALUE Object::InternalFieldCount(VALUE self) { - return INT2FIX(Object(self)->InternalFieldCount()); -} - -VALUE Object::GetInternalField(VALUE self, VALUE idx) { - return Value(Object(self)->GetInternalField(NUM2INT(idx))); -} - -VALUE Object::SetInternalField(VALUE self, VALUE idx, VALUE value) { - Void(Object(self)->SetInternalField(NUM2INT(idx), Value(value))); -} - -VALUE Object::HasOwnProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasOwnProperty(String(key))); -} - -VALUE Object::HasRealNamedProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasRealNamedProperty(String(key))); -} - -VALUE Object::HasRealIndexedProperty(VALUE self, VALUE idx) { - return Bool(Object(self)->HasRealIndexedProperty(UInt32(idx))); -} - -VALUE Object::HasRealNamedCallbackProperty(VALUE self, VALUE key) { - return Bool(Object(self)->HasRealNamedCallbackProperty(String(key))); -} - -VALUE Object::GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key) { - return Value(Object(self)->GetRealNamedPropertyInPrototypeChain(String(key))); -} - -VALUE Object::GetRealNamedProperty(VALUE self, VALUE key) { - return Value(Object(self)->GetRealNamedProperty(String(key))); -} - -VALUE Object::HasNamedLookupInterceptor(VALUE self) { - return Bool(Object(self)->HasNamedLookupInterceptor()); -} - -VALUE Object::HasIndexedLookupInterceptor(VALUE self) { - return Bool(Object(self)->HasIndexedLookupInterceptor()); -} - -VALUE Object::TurnOnAccessCheck(VALUE self) { - Void(Object(self)->TurnOnAccessCheck()); -} - -VALUE Object::GetIdentityHash(VALUE self) { - return INT2FIX(Object(self)->GetIdentityHash()); -} - -VALUE Object::SetHiddenValue(VALUE self, VALUE key, VALUE value) { - return Bool(Object(self)->SetHiddenValue(String(key), Value(value))); -} - -VALUE Object::GetHiddenValue(VALUE self, VALUE key) { - return Value(Object(self)->GetHiddenValue(String(key))); -} - -VALUE Object::DeleteHiddenValue(VALUE self, VALUE key) { - return Bool(Object(self)->DeleteHiddenValue(String(key))); -} - -VALUE Object::IsDirty(VALUE self) { - return Bool(Object(self)->IsDirty()); -} - -VALUE Object::Clone(VALUE self) { - return Object(Object(self)->Clone()); -} - -VALUE Object::CreationContext(VALUE self) { - return Context(Object(self)->CreationContext()); -} - -VALUE Object::SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length) { - return not_implemented("SetIndexedPropertiesToPixelData"); -} - -VALUE Object::GetIndexedPropertiesPixelData(VALUE self) { - return not_implemented("GetIndexedPropertiesPixelData"); -} - -VALUE Object::HasIndexedPropertiesInPixelData(VALUE self) { - return Bool(Object(self)->HasIndexedPropertiesInPixelData()); -} - -VALUE Object::GetIndexedPropertiesPixelDataLength(VALUE self) { - return INT2FIX(Object(self)->GetIndexedPropertiesPixelDataLength()); -} - -VALUE Object::SetIndexedPropertiesToExternalArrayData(VALUE self) { - return not_implemented("SetIndexedPropertiesToExternalArrayData"); -} - -VALUE Object::HasIndexedPropertiesInExternalArrayData(VALUE self) { - return Bool(Object(self)->HasIndexedPropertiesInExternalArrayData()); -} - -VALUE Object::GetIndexedPropertiesExternalArrayData(VALUE self) { - return not_implemented("GetIndexedPropertiesExternalArrayData"); -} - -VALUE Object::GetIndexedPropertiesExternalArrayDataType(VALUE self) { - return not_implemented("GetIndexedPropertiesExternalArrayDataType"); -} - -VALUE Object::GetIndexedPropertiesExternalArrayDataLength(VALUE self) { - return INT2FIX(Object(self)->GetIndexedPropertiesExternalArrayDataLength()); -} - -VALUE Object::IsCallable(VALUE self) { - return Bool(Object(self)->IsCallable()); -} - -VALUE Object::CallAsFunction(VALUE self, VALUE recv, VALUE argv) { - return Value(Object(self)->CallAsFunction(Object(recv), RARRAY_LENINT(argv), Value::array(argv))); -} - -VALUE Object::CallAsConstructor(VALUE self, VALUE argv) { - return Value(Object(self)->CallAsConstructor(RARRAY_LENINT(argv), Value::array(argv))); -} - -} diff --git a/ext/v8/pointer.h b/ext/v8/pointer.h new file mode 100644 index 00000000..a2c38848 --- /dev/null +++ b/ext/v8/pointer.h @@ -0,0 +1,71 @@ +#ifndef RR_POINTER +#define RR_POINTER + +namespace rr { + + /** + * A pointer to V8 object managed by Ruby + * + * You deal with V8 objects as either pointers or handles. + * While handles are managed by the V8 garbage collector, pointers + * must be explicitly created and destroyed by your code. + * + * The pointer class provides a handly way to wrap V8 pointers + * into Ruby objects so that they will be deleted when the + * Ruby object is garbage collected. Automatic type coercion is + * used to make wrapping and unwrapping painless. + * + * To create Ruby VALUE: + * + * Pointer ptr(new v8::ScriptOrigin()); + * VALUE value = ptr; //automatically wraps in Data_Wrap_Struct + * + * Conversely, the pointer can be unwrapped from a struct + * created in this way and the underlying methods can be + * invoked: + * + * VALUE value = ...; + * Pointer ptr(value); + * ptr->CallMethod(); + */ + template + class Pointer { + public: + inline Pointer(T* t) : pointer(t) {}; + inline Pointer(VALUE v) { + if (RTEST(v)) { + this->unwrap(v); + } else { + this->pointer = NULL; + } + }; + + inline operator T*() { + return pointer; + } + + inline T* operator ->() { + return pointer; + } + + inline operator VALUE() { + return Data_Wrap_Struct(Class, 0, &release, pointer); + } + + void unwrap(VALUE value); + + static void release(T* pointer) { + delete pointer; + } + + static VALUE Class; + + protected: + T* pointer; + }; + + template VALUE Pointer::Class; + +} + +#endif diff --git a/ext/v8/primitive.cc b/ext/v8/primitive.cc deleted file mode 100644 index 6ea11860..00000000 --- a/ext/v8/primitive.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include "rr.h" - -namespace rr { - void Primitive::Init() { - ClassBuilder("Primitive", Value::Class). - store(&Class); - } -} \ No newline at end of file diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 6c76bc09..401cf47a 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -1,934 +1,21 @@ #ifndef THE_RUBY_RACER #define THE_RUBY_RACER -#include +#include +#include + #include #include #ifdef HAVE_RUBY_ENCODING_H #include "ruby/encoding.h" #endif -#if !defined(RARRAY_LENINT) -# define RARRAY_LENINT(v) (int)RARRAY_LEN(v) -#endif /* ! defined(RARRAY_LENINT) */ - -#if !defined(SIZET2NUM) -# if SIZEOF_SIZE_T == SIZEOF_LONG -# define SIZET2NUM(n) ULONG2NUM(n) -# else -# define SIZET2NUM(n) ULL2NUM(n) -# endif -#endif /* ! defined(SIZET2NUM) */ - -#if !defined(NUM2SIZET) -# if SIZEOF_SIZE_T == SIZEOF_LONG -# define NUM2SIZET(n) ((size_t)NUM2ULONG(n)) -# else -# define NUM2SIZET(n) ((size_t)NUM2ULL(n)) -# endif -#endif /* ! defined(NUM2SIZET) */ - -namespace rr { - -#define Void(expr) expr; return Qnil; -VALUE not_implemented(const char* message); - -class Equiv { -public: - Equiv(VALUE val) : value(val) {} - inline operator VALUE() {return value;} -protected: - VALUE value; -}; - -class Bool : public Equiv { -public: - Bool(VALUE val) : Equiv(val) {} - Bool(bool b) : Equiv(b ? Qtrue : Qfalse) {} - Bool(v8::Handle b) : Equiv(b->Value() ? Qtrue : Qfalse) {} - inline operator bool() {return RTEST(value);} -}; - -class UInt32 : public Equiv { -public: - UInt32(VALUE val) : Equiv(val) {} - UInt32(uint32_t ui) : Equiv(UINT2NUM(ui)) {} - inline operator uint32_t() {return RTEST(value) ? NUM2UINT(value) : 0;} -}; - -class GC { -public: - class Queue { - public: - Queue(); - void Enqueue(void* phantom); - void* Dequeue(); - private: - struct Node { - Node(void* val ) : value(val), next(NULL) { } - void* value; - Node* next; - }; - Node* first; // for producer only - Node* divider; - Node* last; - }; - static void Finalize(void* phantom); - static void Drain(v8::GCType type, v8::GCCallbackFlags flags); - static void Init(); -}; - -/** -* A V8 Enum -*/ - -template class Enum : public Equiv { -public: - Enum(VALUE value, T defaultValue = 0) : Equiv(value) { - this->defaultValue = defaultValue; - } - inline operator T() { - return (T)(RTEST(value) ? NUM2INT(value) : defaultValue); - } -private: - T defaultValue; -}; - -/** -* A pointer to V8 object managed by Ruby -* -* You deal with V8 objects as either pointers or handles. -* While handles are managed by the V8 garbage collector, pointers -* must be explicitly created and destroyed by your code. -* -* The pointer class provides a handly way to wrap V8 pointers -* into Ruby objects so that they will be deleted when the -* Ruby object is garbage collected. Automatic type coercion is -* used to make wrapping and unwrapping painless. -* -* To create Ruby VALUE: -* -* Pointer ptr(new v8::ScriptOrigin()); -* VALUE value = ptr; //automatically wraps in Data_Wrap_Struct -* -* Conversely, the pointer can be unwrapped from a struct -* created in this way and the underlying methods can be -* invoked: -* -* VALUE value = ...; -* Pointer ptr(value); -* ptr->CallMethod(); -*/ - -template class Pointer { -public: - inline Pointer(T* t) : pointer(t) {}; - inline Pointer(VALUE v) { - if (RTEST(v)) { - this->unwrap(v); - } else { - this->pointer = NULL; - } - }; - inline operator T*() {return pointer;} - inline T* operator ->() {return pointer;} - inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, &release, pointer); - } - void unwrap(VALUE value); - static void release(T* pointer) { - delete pointer; - } - static VALUE Class; -protected: - T* pointer; -}; -template VALUE Pointer::Class; - -/** -* A Reference to a V8 managed object -* -* Uses type coercion to quickly convert from a v8 handle -* to a ruby object and back again. Suppose we have a v8 handle -* that we want to return to Ruby. We can put it into a Ref: -* -* v8::Handle object = v8::Object::New(); -* VALUE val = Ref(object); -* -* this will create a `v8::Persistent` handle for the object -* so that it will not be garbage collected by v8. It then -* stuffs this new persistent handle into a Data_Wrap_Struct -* which can then be passed to Ruby code. When this struct -* is garbage collected by Ruby, it enqueues the corresponding -* v8 handle to be released during v8 gc. -* -* By the same token, you can use Refs to unwrap a Data_Wrap_Struct -* which has been generated in this fashion and call through to -* the underlying v8 methods. Suppose we are passed a VALUE `val` -* wrapping a v8::Object: -* -* Ref object(val); -* object->Get(v8::String::New("foo")); -* -*/ -template class Ref { -public: - Ref(VALUE value) { - this->value = value; - } - Ref(v8::Handle handle, const char* label = "v8::Handle") { - this->handle = handle; - } - virtual ~Ref() {} - /* - * Coerce a Ref into a Ruby VALUE - */ - virtual operator VALUE() const { - return handle.IsEmpty() ? Qnil : Data_Wrap_Struct(Class, 0, &Holder::enqueue, new Holder(handle)); - } - /* - * Coerce a Ref into a v8::Handle. - */ - virtual operator v8::Handle() const { - if (RTEST(this->value)) { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); - return holder->handle; - } else { - return v8::Handle(); - } - } - void dispose() { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); - holder->dispose(); - } - - /* - * Pointer de-reference operators, this lets you use a ref to - * call through to underlying v8 methods. e.g - * - * Ref(value)->ToString(); - */ - inline v8::Handle operator->() const { return *this;} - inline v8::Handle operator*() const {return *this;} - - template class array { - public: - inline array(VALUE ary) : argv(ary), vector(RARRAY_LENINT(argv)) {} - inline operator v8::Handle*() { - for (uint32_t i = 0; i < vector.size(); i++) { - vector[i] = C(rb_ary_entry(argv, i)); - } - return &vector[0]; - } - private: - VALUE argv; - std::vector< v8::Handle > vector; - }; - - class Holder { - friend class Ref; - public: - Holder(v8::Handle handle) { - this->disposed_p = false; - this->handle = v8::Persistent::New(handle); - } - virtual ~Holder() { - this->dispose(); - } - void dispose() { - if (!this->disposed_p) { - handle.Dispose(); - this->disposed_p = true; - } - } - protected: - v8::Persistent handle; - bool disposed_p; - - static void enqueue(Holder* holder) { - GC::Finalize(holder); - } - }; - - VALUE value; - v8::Handle handle; - static VALUE Class; -}; -template VALUE Ref::Class; - -class Backref { -public: - static void Init(); - Backref(VALUE value); - virtual ~Backref(); - VALUE get(); - VALUE set(VALUE value); - v8::Handle toExternal(); - static void release(v8::Persistent handle, void* data); -private: - VALUE storage; - static VALUE Storage; - static ID _new; - static ID object; -}; -class Handles { -public: - static void Init(); - static VALUE HandleScope(int argc, VALUE* argv, VALUE self); -private: - static VALUE SetupAndCall(int* state, VALUE code); - static VALUE DoCall(VALUE code); -}; - -class Phantom { -public: - inline Phantom(void* reference) : holder((Ref::Holder*)reference) {} - inline bool NotNull() { - return this->holder != NULL; - } - inline void destroy() { - delete holder; - } - Ref::Holder* holder; -}; - -class ExtensionConfiguration : public Pointer { -public: - static VALUE initialize(VALUE self, VALUE names); - inline ExtensionConfiguration(v8::ExtensionConfiguration* config) : Pointer(config) {} - inline ExtensionConfiguration(VALUE value) : Pointer(value) {} -}; - -class Context : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Dispose(VALUE self); - static VALUE Enter(VALUE self); - static VALUE Exit(VALUE self); - static VALUE Global(VALUE self); - static VALUE DetachGlobal(VALUE self); - static VALUE ReattachGlobal(VALUE self, VALUE global); - static VALUE GetEntered(VALUE self); - static VALUE GetCurrent(VALUE self); - static VALUE GetCalling(VALUE self); - static VALUE SetSecurityToken(VALUE self, VALUE token); - static VALUE UseDefaultSecurityToken(VALUE self); - static VALUE GetSecurityToken(VALUE self); - static VALUE HasOutOfMemoryException(VALUE self); - static VALUE InContext(VALUE self); - static VALUE SetEmbedderData(VALUE self, VALUE index, VALUE data); - static VALUE GetEmbedderData(VALUE self, VALUE index); - static VALUE AllowCodeGenerationFromStrings(VALUE self, VALUE allow); - static VALUE IsCodeGenerationFromStringsAllowed(VALUE self); - - inline Context(VALUE value) : Ref(value) {} - inline Context(v8::Handle cxt) : Ref(cxt) {} -}; - -class External: public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE data); - static VALUE Value(VALUE self); - - inline External(VALUE value) : Ref(value) {} - inline External(v8::Handle ext) : Ref(ext) {} - static v8::Handle wrap(VALUE data); - static VALUE unwrap(v8::Handle external); -private: - static void release(v8::Persistent object, void* parameter); - struct Data { - Data(VALUE data); - ~Data(); - VALUE value; - }; -}; - -class ScriptOrigin : public Pointer { -public: - inline ScriptOrigin(v8::ScriptOrigin* o) : Pointer(o) {}; - inline ScriptOrigin(VALUE value) : Pointer(value) {} - - static VALUE initialize(int argc, VALUE argv[], VALUE self); -}; - -class ScriptData : public Pointer { -public: - inline ScriptData(v8::ScriptData* d) : Pointer(d) {}; - inline ScriptData(VALUE value) : Pointer(value) {} - - static VALUE PreCompile(VALUE self, VALUE input, VALUE length); - static VALUE New(VALUE self, VALUE data, VALUE length); - static VALUE Length(VALUE self); - static VALUE Data(VALUE self); - static VALUE HasError(VALUE self); -}; - -class Script : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Run(VALUE self); - static VALUE RunWithTimeout(VALUE self, VALUE timeout); - - inline Script(VALUE value) : Ref(value) {} - inline Script(v8::Handle script) : Ref(script) {} -}; - -class Value : public Ref { -public: - static void Init(); - static VALUE IsUndefined(VALUE self); - static VALUE IsNull(VALUE self); - static VALUE IsTrue(VALUE self); - static VALUE IsFalse(VALUE self); - static VALUE IsString(VALUE self); - static VALUE IsFunction(VALUE self); - static VALUE IsArray(VALUE self); - static VALUE IsObject(VALUE self); - static VALUE IsBoolean(VALUE self); - static VALUE IsNumber(VALUE self); - static VALUE IsExternal(VALUE self); - static VALUE IsInt32(VALUE self); - static VALUE IsUint32(VALUE self); - static VALUE IsDate(VALUE self); - static VALUE IsBooleanObject(VALUE self); - static VALUE IsNumberObject(VALUE self); - static VALUE IsStringObject(VALUE self); - static VALUE IsNativeError(VALUE self); - static VALUE IsRegExp(VALUE self); - // VALUE ToBoolean(VALUE self); - // VALUE ToNumber(VALUE self); - static VALUE ToString(VALUE self); - static VALUE ToDetailString(VALUE self); - static VALUE ToObject(VALUE self); - // static VALUE ToInteger(VALUE self); - // static VALUE ToUint32(VALUE self); - // static VALUE ToInt32(VALUE self); - // static VALUE ToArrayIndex(VALUE self); - static VALUE BooleanValue(VALUE self); - static VALUE NumberValue(VALUE self); - static VALUE IntegerValue(VALUE self); - static VALUE Uint32Value(VALUE self); - static VALUE Int32Value(VALUE self); - - static VALUE Equals(VALUE self, VALUE other); - static VALUE StrictEquals(VALUE self, VALUE other); - inline Value(VALUE value) : Ref(value) {} - inline Value(v8::Handle value) : Ref(value) {} - virtual operator VALUE(); - virtual operator v8::Handle() const; - static VALUE Empty; -}; - -class Primitive: public Ref { -public: - static void Init(); - inline Primitive(VALUE value) : Ref(value) {} - inline Primitive(v8::Handle primitive) : Ref(primitive) {} -}; - -class String: public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE value); - static VALUE NewSymbol(VALUE self, VALUE string); - static VALUE Utf8Value(VALUE self); - static VALUE Concat(VALUE self, VALUE left, VALUE right); - - inline String(VALUE value) : Ref(value) {} - inline String(v8::Handle string) : Ref(string) {} - virtual operator v8::Handle() const; -}; - -class PropertyAttribute: public Enum { -public: - inline PropertyAttribute(VALUE value) : Enum(value, v8::None) {} -}; -class AccessControl: public Enum { -public: - inline AccessControl(VALUE value) : Enum(value, v8::DEFAULT) {} -}; - -class Accessor { -public: - static void Init(); - Accessor(VALUE get, VALUE set, VALUE data); - Accessor(VALUE get, VALUE set, VALUE query, VALUE deleter, VALUE enumerator, VALUE data); - Accessor(v8::Handle value); - - inline v8::AccessorGetter accessorGetter() {return &AccessorGetter;} - inline v8::AccessorSetter accessorSetter() {return RTEST(set) ? &AccessorSetter : 0;} - - inline v8::NamedPropertyGetter namedPropertyGetter() {return &NamedPropertyGetter;} - inline v8::NamedPropertySetter namedPropertySetter() {return RTEST(set) ? &NamedPropertySetter : 0;} - inline v8::NamedPropertyQuery namedPropertyQuery() {return RTEST(query) ? &NamedPropertyQuery : 0;} - inline v8::NamedPropertyDeleter namedPropertyDeleter() {return RTEST(deleter) ? &NamedPropertyDeleter : 0;} - inline v8::NamedPropertyEnumerator namedPropertyEnumerator() {return RTEST(enumerator) ? &NamedPropertyEnumerator : 0;} - - inline v8::IndexedPropertyGetter indexedPropertyGetter() {return &IndexedPropertyGetter;} - inline v8::IndexedPropertySetter indexedPropertySetter() {return RTEST(set) ? &IndexedPropertySetter : 0;} - inline v8::IndexedPropertyQuery indexedPropertyQuery() {return RTEST(query) ? &IndexedPropertyQuery : 0;} - inline v8::IndexedPropertyDeleter indexedPropertyDeleter() {return RTEST(deleter) ? &IndexedPropertyDeleter : 0;} - inline v8::IndexedPropertyEnumerator indexedPropertyEnumerator() {return RTEST(enumerator) ? &IndexedPropertyEnumerator : 0;} - - operator v8::Handle(); - - class Info { - public: - Info(const v8::AccessorInfo& info); - Info(VALUE value); - static VALUE This(VALUE self); - static VALUE Holder(VALUE self); - static VALUE Data(VALUE self); - operator VALUE(); - inline const v8::AccessorInfo* operator->() {return this->info;} - v8::Handle get(v8::Local property); - v8::Handle set(v8::Local property, v8::Local value); - v8::Handle query(v8::Local property); - v8::Handle remove(v8::Local property); - v8::Handle enumerateNames(); - v8::Handle get(uint32_t index); - v8::Handle set(uint32_t index, v8::Local value); - v8::Handle query(uint32_t index); - v8::Handle remove(uint32_t index); - v8::Handle enumerateIndices(); +#include "class_builder.h" +#include "pointer.h" - static VALUE Class; - private: - const v8::AccessorInfo* info; - }; - friend class Info; -private: - static v8::Handle AccessorGetter(v8::Local property, const v8::AccessorInfo& info); - static void AccessorSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); - - static v8::Handle NamedPropertyGetter(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertySetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyQuery(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyDeleter(v8::Local property, const v8::AccessorInfo& info); - static v8::Handle NamedPropertyEnumerator(const v8::AccessorInfo& info); - - static v8::Handle IndexedPropertyGetter(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertySetter(uint32_t index, v8::Local value, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyQuery(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyDeleter(uint32_t index, const v8::AccessorInfo& info); - static v8::Handle IndexedPropertyEnumerator(const v8::AccessorInfo& info); - - void wrap(v8::Handle wrapper, int index, VALUE value); - VALUE unwrap(v8::Handle wrapper, int index); - VALUE get; - VALUE set; - VALUE query; - VALUE deleter; - VALUE enumerator; - VALUE data; -}; - -class Invocation { -public: - static void Init(); - Invocation(VALUE code, VALUE data); - Invocation(v8::Handle wrapper); - operator v8::InvocationCallback(); - operator v8::Handle(); - static v8::Handle Callback(const v8::Arguments& args); - - class Arguments { - public: - static void Init(); - Arguments(const v8::Arguments& args); - Arguments(VALUE value); - inline const v8::Arguments* operator->() {return this->args;} - inline const v8::Arguments operator*() {return *this->args;} - v8::Handle Call(); - - static VALUE Length(VALUE self); - static VALUE Get(VALUE self, VALUE index); - static VALUE Callee(VALUE self); - static VALUE This(VALUE self); - static VALUE Holder(VALUE self); - static VALUE IsConstructCall(VALUE self); - static VALUE Data(VALUE self); - private: - const v8::Arguments* args; - static VALUE Class; - }; -private: - VALUE code; - VALUE data; - friend class Arguments; -}; - -class Object : public Ref { -public: - static void Init(); - static VALUE New(VALUE self); - static VALUE Set(VALUE self, VALUE key, VALUE value); - static VALUE ForceSet(VALUE self, VALUE key, VALUE value); - static VALUE Get(VALUE self, VALUE key); - static VALUE GetPropertyAttributes(VALUE self, VALUE key); - static VALUE Has(VALUE self, VALUE key); - static VALUE Delete(VALUE self, VALUE key); - static VALUE ForceDelete(VALUE self, VALUE key); - static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); - static VALUE GetPropertyNames(VALUE self); - static VALUE GetOwnPropertyNames(VALUE self); - static VALUE GetPrototype(VALUE self); - static VALUE SetPrototype(VALUE self, VALUE prototype); - static VALUE FindInstanceInPrototypeChain(VALUE self, VALUE impl); - static VALUE ObjectProtoToString(VALUE self); - static VALUE GetConstructorName(VALUE self); - static VALUE InternalFieldCount(VALUE self); - static VALUE GetInternalField(VALUE self, VALUE idx); - static VALUE SetInternalField(VALUE self, VALUE idx, VALUE value); - static VALUE HasOwnProperty(VALUE self, VALUE key); - static VALUE HasRealNamedProperty(VALUE self, VALUE key); - static VALUE HasRealIndexedProperty(VALUE self, VALUE idx); - static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE key); - static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key); - static VALUE GetRealNamedProperty(VALUE self, VALUE key); - static VALUE HasNamedLookupInterceptor(VALUE self); - static VALUE HasIndexedLookupInterceptor(VALUE self); - static VALUE TurnOnAccessCheck(VALUE self); - static VALUE GetIdentityHash(VALUE self); - static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); - static VALUE GetHiddenValue(VALUE self, VALUE key); - static VALUE DeleteHiddenValue(VALUE self, VALUE key); - static VALUE IsDirty(VALUE self); - static VALUE Clone(VALUE self); - static VALUE CreationContext(VALUE self); - static VALUE SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length); - static VALUE GetIndexedPropertiesPixelData(VALUE self); - static VALUE HasIndexedPropertiesInPixelData(VALUE self); - static VALUE GetIndexedPropertiesPixelDataLength(VALUE self); - static VALUE SetIndexedPropertiesToExternalArrayData(VALUE self); - static VALUE HasIndexedPropertiesInExternalArrayData(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayData(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayDataType(VALUE self); - static VALUE GetIndexedPropertiesExternalArrayDataLength(VALUE self); - static VALUE IsCallable(VALUE self); - static VALUE CallAsFunction(VALUE self, VALUE recv, VALUE argv); - static VALUE CallAsConstructor(VALUE self, VALUE argv); - - inline Object(VALUE value) : Ref(value) {} - inline Object(v8::Handle object) : Ref(object) {} - virtual operator VALUE(); - -protected: - VALUE downcast(); -}; - -class Array : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Length(VALUE self); - static VALUE CloneElementAt(VALUE self, VALUE index); - - inline Array(v8::Handle array) : Ref(array) {} - inline Array(VALUE value) : Ref(value) {} -}; - -class Function : public Ref { -public: - static void Init(); - static VALUE NewInstance(int argc, VALUE argv[], VALUE self); - static VALUE Call(VALUE self, VALUE receiver, VALUE argv); - static VALUE SetName(VALUE self, VALUE name); - static VALUE GetName(VALUE self); - static VALUE GetInferredName(VALUE self); - static VALUE GetScriptLineNumber(VALUE self); - static VALUE GetScriptColumnNumber(VALUE self); - static VALUE GetScriptId(VALUE self); - static VALUE GetScriptOrigin(VALUE self); - - inline Function(VALUE value) : Ref(value) {} - inline Function(v8::Handle function) : Ref(function) {} -}; - -class Date : public Ref { -public: - static void Init(); - static VALUE New(VALUE self, VALUE time); - static VALUE NumberValue(VALUE self); - - inline Date(VALUE value) : Ref(value) {} - inline Date(v8::Handle date) : Ref(date) {} -}; - -class Signature : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - - inline Signature(v8::Handle sig) : Ref(sig) {} - inline Signature(VALUE value) : Ref(value) {} -}; - -class Template : public Ref { -public: - static void Init(); - static VALUE Set(int argc, VALUE argv[], VALUE self); - inline Template(v8::Handle t) : Ref(t) {} - inline Template(VALUE value) : Ref(value) {} -}; - -class ObjectTemplate : public Ref { -public: - static void Init(); - static VALUE New(VALUE self); - static VALUE NewInstance(VALUE self); - static VALUE SetAccessor(int argc, VALUE argv[], VALUE self); - static VALUE SetNamedPropertyHandler(int argc, VALUE argv[], VALUE self); - static VALUE SetIndexedPropertyHandler(int argc, VALUE argv[], VALUE self); - static VALUE SetCallAsFunctionHandler(int argc, VALUE argv[], VALUE self); - static VALUE MarkAsUndetectable(VALUE self); - static VALUE SetAccessCheckCallbacks(int argc, VALUE argv[], VALUE self); - static VALUE InternalFieldCount(VALUE self); - static VALUE SetInternalFieldCount(VALUE self, VALUE count); - - inline ObjectTemplate(VALUE value) : Ref(value) {} - inline ObjectTemplate(v8::Handle t) : Ref(t) {} -}; - -class FunctionTemplate : public Ref { -public: - static void Init(); - static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE GetFunction(VALUE self); - static VALUE SetCallHandler(int argc, VALUE argv[], VALUE self); - static VALUE InstanceTemplate(VALUE self); - static VALUE Inherit(VALUE self, VALUE parent); - static VALUE PrototypeTemplate(VALUE self); - static VALUE SetClassName(VALUE self, VALUE name); - static VALUE SetHiddenPrototype(VALUE self, VALUE value); - static VALUE ReadOnlyPrototype(VALUE self); - static VALUE HasInstance(VALUE self, VALUE object); - - inline FunctionTemplate(VALUE value) : Ref(value) {} - inline FunctionTemplate(v8::Handle t) : Ref(t) {} -}; - -class Message : public Ref { -public: - static void Init(); - inline Message(v8::Handle message) : Ref(message) {} - inline Message(VALUE value) : Ref(value) {} - - static VALUE Get(VALUE self); - static VALUE GetSourceLine(VALUE self); - static VALUE GetScriptResourceName(VALUE self); - static VALUE GetScriptData(VALUE self); - static VALUE GetStackTrace(VALUE self); - static VALUE GetLineNumber(VALUE self); - static VALUE GetStartPosition(VALUE self); - static VALUE GetEndPosition(VALUE self); - static VALUE GetStartColumn(VALUE self); - static VALUE GetEndColumn(VALUE self); - static inline VALUE kNoLineNumberInfo(VALUE self) {return INT2FIX(v8::Message::kNoLineNumberInfo);} - static inline VALUE kNoColumnInfo(VALUE self) {return INT2FIX(v8::Message::kNoColumnInfo);} -}; - -class Stack { -public: - static void Init(); - - class Trace : public Ref { - public: - class StackTraceOptions : public Enum { - public: - inline StackTraceOptions(VALUE value) : Enum(value, v8::StackTrace::kOverview) {} - }; - public: - inline Trace(v8::Handle trace) : Ref(trace) {} - inline Trace(VALUE value) : Ref(value) {} - static inline VALUE kLineNumber(VALUE self) {return INT2FIX(v8::StackTrace::kLineNumber);} - static inline VALUE kColumnOffset(VALUE self) {return INT2FIX(v8::StackTrace::kColumnOffset);} - static inline VALUE kScriptName(VALUE self) {return INT2FIX(v8::StackTrace::kScriptName);} - static inline VALUE kFunctionName(VALUE self) {return INT2FIX(v8::StackTrace::kFunctionName);} - static inline VALUE kIsEval(VALUE self) {return INT2FIX(v8::StackTrace::kIsEval);} - static inline VALUE kIsConstructor(VALUE self) {return INT2FIX(v8::StackTrace::kIsConstructor);} - static inline VALUE kScriptNameOrSourceURL(VALUE self) {return INT2FIX(v8::StackTrace::kScriptNameOrSourceURL);} - static inline VALUE kOverview(VALUE self) {return INT2FIX(v8::StackTrace::kOverview);} - static inline VALUE kDetailed(VALUE self) {return INT2FIX(v8::StackTrace::kDetailed);} - - static VALUE GetFrame(VALUE self, VALUE index); - static VALUE GetFrameCount(VALUE self); - static VALUE AsArray(VALUE self); - static VALUE CurrentStackTrace(int argc, VALUE argv[], VALUE self); - }; - class Frame : public Ref { - public: - inline Frame(v8::Handle frame) : Ref(frame) {} - inline Frame(VALUE value) : Ref(value) {} - static VALUE GetLineNumber(VALUE self); - static VALUE GetColumn(VALUE self); - static VALUE GetScriptName(VALUE self); - static VALUE GetScriptNameOrSourceURL(VALUE self); - static VALUE GetFunctionName(VALUE self); - static VALUE IsEval(VALUE self); - static VALUE IsConstructor(VALUE self); - }; -}; - -class TryCatch { -public: - static void Init(); - TryCatch(v8::TryCatch*); - TryCatch(VALUE value); - operator VALUE(); - inline v8::TryCatch* operator->() {return this->impl;} - static VALUE HasCaught(VALUE self); - static VALUE CanContinue(VALUE self); - static VALUE ReThrow(VALUE self); - static VALUE Exception(VALUE self); - static VALUE StackTrace(VALUE self); - static VALUE Message(VALUE self); - static VALUE Reset(VALUE self); - static VALUE SetVerbose(VALUE self, VALUE value); - static VALUE SetCaptureMessage(VALUE self, VALUE value); -private: - static VALUE doTryCatch(int argc, VALUE argv[], VALUE self); - static VALUE setupAndCall(int* state, VALUE code); - static VALUE doCall(VALUE code); - static VALUE Class; - v8::TryCatch* impl; -}; - -class Locker { -public: - static void Init(); - static VALUE StartPreemption(VALUE self, VALUE every_n_ms); - static VALUE StopPreemption(VALUE self); - static VALUE IsLocked(VALUE self); - static VALUE IsActive(VALUE self); - static VALUE doLock(int argc, VALUE* argv, VALUE self); - static VALUE setupLockAndCall(int* state, VALUE code); - static VALUE doLockCall(VALUE code); - static VALUE doUnlock(int argc, VALUE* argv, VALUE self); - static VALUE setupUnlockAndCall(int* state, VALUE code); - static VALUE doUnlockCall(VALUE code); -}; - -class HeapStatistics : public Pointer { -public: - static void Init(); - static VALUE initialize(VALUE self); - static VALUE total_heap_size(VALUE self); - static VALUE total_heap_size_executable(VALUE self); - static VALUE total_physical_size(VALUE self); - static VALUE used_heap_size(VALUE self); - static VALUE heap_size_limit(VALUE self); - - inline HeapStatistics(v8::HeapStatistics* stats) : Pointer(stats) {} - inline HeapStatistics(VALUE value) : Pointer(value) {} -}; - -class ResourceConstraints : Pointer { -public: - static void Init(); - static VALUE initialize(VALUE self); - static VALUE max_young_space_size(VALUE self); - static VALUE set_max_young_space_size(VALUE self, VALUE value); - static VALUE max_old_space_size(VALUE self); - static VALUE set_max_old_space_size(VALUE self, VALUE value); - static VALUE max_executable_size(VALUE self); - static VALUE set_max_executable_size(VALUE self, VALUE value); - - static VALUE SetResourceConstraints(VALUE self, VALUE constraints); - - inline ResourceConstraints(v8::ResourceConstraints* o) : Pointer(o) {}; - inline ResourceConstraints(VALUE value) : Pointer(value) {} -}; - -class Exception { -public: - static void Init(); - static VALUE ThrowException(VALUE self, VALUE exception); - static VALUE RangeError(VALUE self, VALUE message); - static VALUE ReferenceError(VALUE self, VALUE message); - static VALUE SyntaxError(VALUE self, VALUE message); - static VALUE TypeError(VALUE self, VALUE message); - static VALUE Error(VALUE self, VALUE message); -}; - -class Constants { -public: - static void Init(); - static VALUE Undefined(VALUE self); - static VALUE Null(VALUE self); - static VALUE True(VALUE self); - static VALUE False(VALUE self); - -private: - template static VALUE cached(VALUE* storage, v8::Handle value) { - if (!RTEST(*storage)) { - *storage = R(value); - } - return *storage; - } - static VALUE _Undefined; - static VALUE _Null; - static VALUE _True; - static VALUE _False; -}; - -class V8 { -public: - static void Init(); - static VALUE IdleNotification(int argc, VALUE argv[], VALUE self); - static VALUE SetFlagsFromString(VALUE self, VALUE string); - static VALUE SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags); - static VALUE AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes); - static VALUE PauseProfiler(VALUE self); - static VALUE ResumeProfiler(VALUE self); - static VALUE IsProfilerPaused(VALUE self); - static VALUE GetCurrentThreadId(VALUE self); - static VALUE TerminateExecution(VALUE self, VALUE thread_id); - static VALUE IsExecutionTerminating(VALUE self); - static VALUE Dispose(VALUE self); - static VALUE LowMemoryNotification(VALUE self); - static VALUE ContextDisposedNotification(VALUE self); - - static VALUE SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self); - static VALUE GetHeapStatistics(VALUE self, VALUE statistics_ptr); - static VALUE GetVersion(VALUE self); -}; - -class ClassBuilder { -public: - ClassBuilder() {}; - ClassBuilder(const char* name, VALUE superclass = rb_cObject); - ClassBuilder(const char* name, const char* supername); - ClassBuilder& defineConst(const char* name, VALUE value); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); - ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE)); - ClassBuilder& defineSingletonMethod(const char* name, VALUE (*impl)(VALUE, VALUE, VALUE, VALUE)); - ClassBuilder& defineEnumConst(const char* name, int value); - ClassBuilder& store(VALUE* storage); - inline operator VALUE() {return this->value;} -protected: - VALUE value; -}; - -class ModuleBuilder : public ClassBuilder { -public: - inline ModuleBuilder(const char* name) { - this->value = rb_eval_string(name); - } -}; - -} +#include "v8.h" +#include "isolate.h" +#include "handles.h" #endif + diff --git a/ext/v8/script.cc b/ext/v8/script.cc deleted file mode 100644 index f93823c5..00000000 --- a/ext/v8/script.cc +++ /dev/null @@ -1,115 +0,0 @@ -#include "rr.h" -#include "pthread.h" -#include "unistd.h" - -namespace rr { - -void Script::Init() { - ClassBuilder("Script"). - defineSingletonMethod("New", &New). - defineMethod("Run", &Run). - defineMethod("RunWithTimeout", &RunWithTimeout). - store(&Class); - ClassBuilder("ScriptOrigin"). - defineSingletonMethod("new", &ScriptOrigin::initialize). - store(&ScriptOrigin::Class); - ClassBuilder("ScriptData"). - defineSingletonMethod("PreCompile", &ScriptData::PreCompile). - defineSingletonMethod("New", &ScriptData::New). - defineMethod("Length", &ScriptData::Length). - defineMethod("Data", &ScriptData::Data). - defineMethod("HasError", &ScriptData::HasError). - store(&ScriptData::Class); -} - -VALUE ScriptOrigin::initialize(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE line_offset; VALUE column_offset; - rb_scan_args(argc, argv, "12", &name, &line_offset, &column_offset); - v8::Handle loff = v8::Integer::New(RTEST(line_offset) ? NUM2INT(line_offset) : 0); - v8::Handle coff = v8::Integer::New(RTEST(column_offset) ? NUM2INT(column_offset) : 0); - return ScriptOrigin(new v8::ScriptOrigin(*String(name), loff, coff)); -} - -VALUE ScriptData::PreCompile(VALUE self, VALUE input, VALUE length) { -#ifdef HAVE_RUBY_ENCODING_H - if (!rb_equal(rb_enc_from_encoding(rb_utf8_encoding()), rb_obj_encoding(input))) { - rb_warn("ScriptData::Precompile only accepts UTF-8 encoded source, not: %s", RSTRING_PTR(rb_inspect(rb_obj_encoding(input)))); - } -#endif - return ScriptData(v8::ScriptData::PreCompile(RSTRING_PTR(input), NUM2INT(length))); -} -VALUE ScriptData::New(VALUE self, VALUE data, VALUE length) { - return ScriptData(v8::ScriptData::New(RSTRING_PTR(data), NUM2INT(length))); -} -VALUE ScriptData::Length(VALUE self) { - return ScriptData(self)->Length(); -} -VALUE ScriptData::Data(VALUE self) { - ScriptData data(self); -#ifdef HAVE_RUBY_ENCODING_H - return rb_enc_str_new(data->Data(), data->Length(), rb_enc_find("BINARY")); -#else - return rb_str_new(data->Data(), data->Length()); -#endif -} - -VALUE ScriptData::HasError(VALUE self) { - return ScriptData(self)->HasError(); -} - -VALUE Script::New(int argc, VALUE argv[], VALUE self) { - VALUE source; VALUE origin; VALUE pre_data; VALUE script_data; - rb_scan_args(argc, argv, "13", &source, &origin, &pre_data, &script_data); - if (argc == 2) { - VALUE filename = origin; - return Script(v8::Script::New(String(source), Value(filename))); - } else { - return Script(v8::Script::New(String(source), ScriptOrigin(origin), ScriptData(pre_data), String(script_data))); - } -} - -VALUE Script::Run(VALUE self) { - return Value(Script(self)->Run()); -} - -typedef struct { - v8::Isolate *isolate; - long timeout; -} timeout_data; - -void* breaker(void *d) { - timeout_data* data = (timeout_data*)d; - usleep(data->timeout*1000); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - v8::V8::TerminateExecution(data->isolate); - return NULL; -} - -VALUE Script::RunWithTimeout(VALUE self, VALUE timeout) { - pthread_t breaker_thread; - timeout_data data; - VALUE rval; - void *res; - - data.isolate = v8::Isolate::GetCurrent(); - data.timeout = NUM2LONG(timeout); - - pthread_create(&breaker_thread, NULL, breaker, &data); - - rval = Value(Script(self)->Run()); - - pthread_cancel(breaker_thread); - pthread_join(breaker_thread, &res); - - return rval; -} - -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ScriptData, pointer); -} - -template <> void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::ScriptOrigin, pointer); -} - -} //namespace rr diff --git a/ext/v8/signature.cc b/ext/v8/signature.cc deleted file mode 100644 index d227d275..00000000 --- a/ext/v8/signature.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "rr.h" - -namespace rr { - void Signature::Init() { - ClassBuilder("Signature"). - defineMethod("New", &New). - store(&Class); - } - - VALUE Signature::New(int argc, VALUE args[], VALUE self) { - VALUE receiver; VALUE argv; - rb_scan_args(argc, args, "02", &receiver, &argv); - FunctionTemplate recv(receiver); - int length = RARRAY_LENINT(argv); - FunctionTemplate::array types(argv); - return Signature(v8::Signature::New(recv, length, types)); - } -} \ No newline at end of file diff --git a/ext/v8/stack.cc b/ext/v8/stack.cc deleted file mode 100644 index 1f095583..00000000 --- a/ext/v8/stack.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "rr.h" - -namespace rr { - void Stack::Init() { - ClassBuilder("StackTrace"). - defineSingletonMethod("kLineNumber", &Trace::kLineNumber). - defineSingletonMethod("kColumnOffset", &Trace::kColumnOffset). - defineSingletonMethod("kScriptName", &Trace::kScriptName). - defineSingletonMethod("kFunctionName", &Trace::kFunctionName). - defineSingletonMethod("kIsEval", &Trace::kIsEval). - defineSingletonMethod("kIsConstructor", &Trace::kIsConstructor). - defineSingletonMethod("kScriptNameOrSourceURL", &Trace::kScriptNameOrSourceURL). - defineSingletonMethod("kOverview", &Trace::kOverview). - defineSingletonMethod("kDetailed", &Trace::kDetailed). - defineSingletonMethod("CurrentStackTrace", &Trace::CurrentStackTrace). - defineMethod("GetFrame", &Trace::GetFrame). - defineMethod("GetFrameCount", &Trace::GetFrameCount). - defineMethod("AsArray", &Trace::AsArray). - store(&Trace::Class); - ClassBuilder("StackFrame"). - defineMethod("GetLineNumber", &Frame::GetLineNumber). - defineMethod("GetColumn", &Frame::GetColumn). - defineMethod("GetScriptName", &Frame::GetScriptName). - defineMethod("GetScriptNameOrSourceURL", &Frame::GetScriptNameOrSourceURL). - defineMethod("GetFunctionName", &Frame::GetFunctionName). - defineMethod("IsEval", &Frame::IsEval). - defineMethod("IsConstructor", &Frame::IsConstructor). - store(&Frame::Class); - } - - VALUE Stack::Trace::GetFrame(VALUE self, VALUE index) { - return Frame(Trace(self)->GetFrame(NUM2UINT(index))); - } - - VALUE Stack::Trace::GetFrameCount(VALUE self) { - return INT2FIX(Trace(self)->GetFrameCount()); - } - - VALUE Stack::Trace::AsArray(VALUE self) { - return Array(Trace(self)->AsArray()); - } - - VALUE Stack::Trace::CurrentStackTrace(int argc, VALUE argv[], VALUE self) { - VALUE frame_limit; VALUE options; - rb_scan_args(argc, argv, "11", &frame_limit, &options); - return Trace(v8::StackTrace::CurrentStackTrace(NUM2INT(frame_limit), StackTraceOptions(options))); - } - - VALUE Stack::Frame::GetLineNumber(VALUE self) { - return INT2FIX(Frame(self)->GetLineNumber()); - } - - VALUE Stack::Frame::GetColumn(VALUE self) { - return INT2FIX(Frame(self)->GetColumn()); - } - - VALUE Stack::Frame::GetScriptName(VALUE self) { - return String(Frame(self)->GetScriptName()); - } - - VALUE Stack::Frame::GetScriptNameOrSourceURL(VALUE self) { - return String(Frame(self)->GetScriptNameOrSourceURL()); - } - - VALUE Stack::Frame::GetFunctionName(VALUE self) { - return String(Frame(self)->GetFunctionName()); - } - - VALUE Stack::Frame::IsEval(VALUE self) { - return Bool(Frame(self)->IsEval()); - } - - VALUE Stack::Frame::IsConstructor(VALUE self) { - return Bool(Frame(self)->IsConstructor()); - } -} \ No newline at end of file diff --git a/ext/v8/string.cc b/ext/v8/string.cc deleted file mode 100644 index a75ead40..00000000 --- a/ext/v8/string.cc +++ /dev/null @@ -1,47 +0,0 @@ -#include "rr.h" - -namespace rr { - -void String::Init() { - ClassBuilder("String", Primitive::Class). - defineSingletonMethod("New", &New). - defineSingletonMethod("NewSymbol", &NewSymbol). - defineSingletonMethod("Concat", &Concat). - defineMethod("Utf8Value", &Utf8Value). - store(&Class); -} - -VALUE String::New(VALUE StringClass, VALUE string) { - return String(v8::String::New(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} - -VALUE String::NewSymbol(VALUE self, VALUE string) { - return String(v8::String::NewSymbol(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} - -VALUE String::Utf8Value(VALUE self) { - String str(self); - #ifdef HAVE_RUBY_ENCODING_H - return rb_enc_str_new(*v8::String::Utf8Value(*str), str->Utf8Length(), rb_enc_find("utf-8")); - #else - return rb_str_new(*v8::String::Utf8Value(*str), str->Utf8Length()); - #endif -} - -VALUE String::Concat(VALUE self, VALUE left, VALUE right) { - return String(v8::String::Concat(String(left), String(right))); -} - -String::operator v8::Handle() const { - switch (TYPE(value)) { - case T_STRING: - return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value)); - case T_DATA: - return Ref::operator v8::Handle(); - default: - VALUE string = rb_funcall(value, rb_intern("to_s"), 0); - return v8::String::New(RSTRING_PTR(string), (int)RSTRING_LEN(string)); - } -} - -} //namespace rr \ No newline at end of file diff --git a/ext/v8/template.cc b/ext/v8/template.cc deleted file mode 100644 index e82642d6..00000000 --- a/ext/v8/template.cc +++ /dev/null @@ -1,175 +0,0 @@ -#include "rr.h" - -namespace rr { - void Template::Init() { - ClassBuilder("Template"). - defineMethod("Set", &Set); - ObjectTemplate::Init(); - FunctionTemplate::Init(); - } - - VALUE Template::Set(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE value; VALUE attributes; - rb_scan_args(argc, argv, "21", &name, &value, &attributes); - Void(Template(self)->Set(*String(name), *Value(value), PropertyAttribute(attributes))); - } - - void ObjectTemplate::Init() { - ClassBuilder("ObjectTemplate", "Template"). - defineSingletonMethod("New", &New). - defineMethod("NewInstance", &NewInstance). - defineMethod("SetAccessor", &SetAccessor). - defineMethod("SetNamedPropertyHandler", &SetNamedPropertyHandler). - defineMethod("SetIndexedPropertyHandler", &SetIndexedPropertyHandler). - defineMethod("SetCallAsFunctionHandler", &SetCallAsFunctionHandler). - defineMethod("MarkAsUndetectable", &MarkAsUndetectable). - defineMethod("SetAccessCheckCallbacks", &SetAccessCheckCallbacks). - defineMethod("InternalFieldCount", &InternalFieldCount). - defineMethod("SetInternalFieldCount", &SetInternalFieldCount). - store(&Class); - } - - VALUE ObjectTemplate::New(VALUE self) { - return ObjectTemplate(v8::ObjectTemplate::New()); - } - - VALUE ObjectTemplate::NewInstance(VALUE self) { - return Object(ObjectTemplate(self)->NewInstance()); - } - - VALUE ObjectTemplate::SetAccessor(int argc, VALUE argv[], VALUE self) { - VALUE name; VALUE get; VALUE set; VALUE data; VALUE settings; VALUE attribs; - rb_scan_args(argc, argv, "24", &name, &get, &set, &data, &settings, &attribs); - Accessor accessor(get, set, data); - ObjectTemplate(self)->SetAccessor( - String(name), - accessor.accessorGetter(), - accessor.accessorSetter(), - accessor, - AccessControl(settings), - PropertyAttribute(attribs) - ); - Void(); - } - - VALUE ObjectTemplate::SetNamedPropertyHandler(int argc, VALUE argv[], VALUE self) { - VALUE get; VALUE set; VALUE query; VALUE deleter; VALUE enumerator; VALUE data; - rb_scan_args(argc, argv, "15", &get, &set, &query, &deleter, &enumerator, &data); - Accessor accessor(get,set,query,deleter,enumerator,data); - ObjectTemplate(self)->SetNamedPropertyHandler( - accessor.namedPropertyGetter(), - accessor.namedPropertySetter(), - accessor.namedPropertyQuery(), - accessor.namedPropertyDeleter(), - accessor.namedPropertyEnumerator(), - accessor - ); - Void(); - } - - VALUE ObjectTemplate::SetIndexedPropertyHandler(int argc, VALUE argv[], VALUE self) { - VALUE get; VALUE set; VALUE query; VALUE deleter; VALUE enumerator; VALUE data; - rb_scan_args(argc, argv, "15", &get, &set, &query, &deleter, &enumerator, &data); - Accessor accessor(get,set,query,deleter,enumerator,data); - ObjectTemplate(self)->SetIndexedPropertyHandler( - accessor.indexedPropertyGetter(), - accessor.indexedPropertySetter(), - accessor.indexedPropertyQuery(), - accessor.indexedPropertyDeleter(), - accessor.indexedPropertyEnumerator(), - accessor - ); - Void(); - } - - VALUE ObjectTemplate::SetCallAsFunctionHandler(int argc, VALUE argv[], VALUE self) { - VALUE callback; VALUE data; - rb_scan_args(argc, argv, "11", &callback, &data); - Invocation invocation(callback, data); - Void(ObjectTemplate(self)->SetCallAsFunctionHandler(invocation, invocation)); - } - - VALUE ObjectTemplate::MarkAsUndetectable(VALUE self) { - Void(ObjectTemplate(self)->MarkAsUndetectable()); - } - - - VALUE ObjectTemplate::SetAccessCheckCallbacks(int argc, VALUE argv[], VALUE self) { - VALUE named_handler; VALUE indexed_handler; VALUE data; VALUE turned_on_by_default; - rb_scan_args(argc, argv, "22", &named_handler, &indexed_handler, &data, &turned_on_by_default); - return not_implemented("ObjectTemplate::SetAccessCheckCallbacks"); - } - - VALUE ObjectTemplate::InternalFieldCount(VALUE self) { - return INT2FIX(ObjectTemplate(self)->InternalFieldCount()); - } - - VALUE ObjectTemplate::SetInternalFieldCount(VALUE self, VALUE count) { - Void(ObjectTemplate(self)->SetInternalFieldCount(NUM2INT(count))); - } - - void FunctionTemplate::Init() { - ClassBuilder("FunctionTemplate", "Template"). - defineSingletonMethod("New", &New). - defineMethod("GetFunction", &GetFunction). - defineMethod("SetCallHandler", &SetCallHandler). - defineMethod("InstanceTemplate", &InstanceTemplate). - defineMethod("Inherit", &Inherit). - defineMethod("PrototypeTemplate", &PrototypeTemplate). - defineMethod("SetClassName", &SetClassName). - defineMethod("SetHiddenPrototype", &SetHiddenPrototype). - defineMethod("ReadOnlyPrototype", &ReadOnlyPrototype). - defineMethod("HasInstance", &HasInstance). - store(&Class); - } - - VALUE FunctionTemplate::New(int argc, VALUE argv[], VALUE self) { - VALUE code; VALUE data; VALUE signature; - rb_scan_args(argc, argv, "03", &code, &data, &signature); - if (RTEST(code)) { - Invocation invocation(code, data); - return FunctionTemplate(v8::FunctionTemplate::New(invocation, invocation, Signature(signature))); - } else { - return FunctionTemplate(v8::FunctionTemplate::New()); - } - } - - VALUE FunctionTemplate::GetFunction(VALUE self) { - return Function(FunctionTemplate(self)->GetFunction()); - } - - VALUE FunctionTemplate::SetCallHandler(int argc, VALUE argv[], VALUE self) { - VALUE code; VALUE data; - rb_scan_args(argc, argv, "11", &code, &data); - Invocation invocation(code, data); - Void(FunctionTemplate(self)->SetCallHandler(invocation, invocation)); - } - - VALUE FunctionTemplate::InstanceTemplate(VALUE self) { - return ObjectTemplate(FunctionTemplate(self)->InstanceTemplate()); - } - - VALUE FunctionTemplate::Inherit(VALUE self, VALUE parent) { - Void(FunctionTemplate(self)->Inherit(FunctionTemplate(parent))); - } - - VALUE FunctionTemplate::PrototypeTemplate(VALUE self) { - return ObjectTemplate(FunctionTemplate(self)->PrototypeTemplate()); - } - - VALUE FunctionTemplate::SetClassName(VALUE self, VALUE name) { - Void(FunctionTemplate(self)->SetClassName(String(name))); - } - - VALUE FunctionTemplate::SetHiddenPrototype(VALUE self, VALUE value) { - Void(FunctionTemplate(self)->SetHiddenPrototype(Bool(value))); - } - - VALUE FunctionTemplate::ReadOnlyPrototype(VALUE self) { - Void(FunctionTemplate(self)->ReadOnlyPrototype()); - } - - VALUE FunctionTemplate::HasInstance(VALUE self, VALUE object) { - return Bool(FunctionTemplate(self)->HasInstance(Value(object))); - } -} \ No newline at end of file diff --git a/ext/v8/trycatch.cc b/ext/v8/trycatch.cc deleted file mode 100644 index 1f092988..00000000 --- a/ext/v8/trycatch.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "rr.h" - -namespace rr { - VALUE TryCatch::Class; - - void TryCatch::Init() { - ClassBuilder("TryCatch"). - defineMethod("HasCaught", &HasCaught). - defineMethod("CanContinue", &CanContinue). - defineMethod("ReThrow", &ReThrow). - defineMethod("Exception", &Exception). - defineMethod("StackTrace", &StackTrace). - defineMethod("Message", &Message). - defineMethod("Reset", &Reset). - defineMethod("SetVerbose", &SetVerbose). - defineMethod("SetCaptureMessage", &SetCaptureMessage). - store(&Class); - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - rb_define_singleton_method(c, "TryCatch", (VALUE (*)(...))&doTryCatch, -1); - } - - TryCatch::TryCatch(v8::TryCatch* impl) { - this->impl = impl; - } - TryCatch::TryCatch(VALUE value) { - Data_Get_Struct(value, class v8::TryCatch, impl); - } - - TryCatch::operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, impl); - } - - VALUE TryCatch::HasCaught(VALUE self) { - return Bool(TryCatch(self)->HasCaught()); - } - VALUE TryCatch::CanContinue(VALUE self) { - return Bool(TryCatch(self)->CanContinue()); - } - VALUE TryCatch::ReThrow(VALUE self) { - return Value(TryCatch(self)->ReThrow()); - } - VALUE TryCatch::Exception(VALUE self) { - return Value(TryCatch(self)->Exception()); - } - VALUE TryCatch::StackTrace(VALUE self) { - return Value(TryCatch(self)->StackTrace()); - } - VALUE TryCatch::Message(VALUE self) { - return rr::Message(TryCatch(self)->Message()); - } - VALUE TryCatch::Reset(VALUE self) { - Void(TryCatch(self)->Reset()); - } - VALUE TryCatch::SetVerbose(VALUE self, VALUE value) { - Void(TryCatch(self)->SetVerbose(Bool(value))); - } - VALUE TryCatch::SetCaptureMessage(VALUE self, VALUE value) { - Void(TryCatch(self)->SetCaptureMessage(Bool(value))); - } - - VALUE TryCatch::doTryCatch(int argc, VALUE argv[], VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - int state = 0; - VALUE code; - rb_scan_args(argc,argv,"00&", &code); - VALUE result = setupAndCall(&state, code); - if (state != 0) { - rb_jump_tag(state); - } - return result; - } - - VALUE TryCatch::setupAndCall(int* state, VALUE code) { - v8::TryCatch trycatch; - rb_iv_set(code, "_v8_trycatch", TryCatch(&trycatch)); - VALUE result = rb_protect(&doCall, code, state); - rb_iv_set(code, "_v8_trycatch", Qnil); - return result; - } - - VALUE TryCatch::doCall(VALUE code) { - return rb_funcall(code, rb_intern("call"), 1, rb_iv_get(code, "_v8_trycatch")); - } -} \ No newline at end of file diff --git a/ext/v8/v8.cc b/ext/v8/v8.cc index 6357ca68..4bdc60d5 100644 --- a/ext/v8/v8.cc +++ b/ext/v8/v8.cc @@ -2,86 +2,48 @@ namespace rr { -void V8::Init() { - ClassBuilder("V8"). - defineSingletonMethod("IdleNotification", &IdleNotification). - defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString). - defineSingletonMethod("SetFlagsFromCommandLine", &SetFlagsFromCommandLine). - defineSingletonMethod("PauseProfiler", &PauseProfiler). - defineSingletonMethod("ResumeProfiler", &ResumeProfiler). - defineSingletonMethod("IsProfilerPaused", &IsProfilerPaused). - defineSingletonMethod("GetCurrentThreadId", &GetCurrentThreadId). - defineSingletonMethod("TerminateExecution", &TerminateExecution). - defineSingletonMethod("IsExecutionTerminating", &IsExecutionTerminating). - defineSingletonMethod("Dispose", &Dispose). - defineSingletonMethod("LowMemoryNotification", &LowMemoryNotification). - defineSingletonMethod("AdjustAmountOfExternalAllocatedMemory", &AdjustAmountOfExternalAllocatedMemory). - defineSingletonMethod("ContextDisposedNotification", &ContextDisposedNotification). - defineSingletonMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). - defineSingletonMethod("GetHeapStatistics", &GetHeapStatistics). - defineSingletonMethod("GetVersion", &GetVersion); -} + v8::Platform* V8::v8_platform = NULL; + + void V8::Init() { + // Initialize V8. + v8::V8::InitializeICU(); + v8_platform = v8::platform::CreateDefaultPlatform(); + v8::V8::InitializePlatform(v8_platform); + v8::V8::Initialize(); -VALUE V8::IdleNotification(int argc, VALUE argv[], VALUE self) { - VALUE hint; - rb_scan_args(argc, argv, "01", &hint); - if (RTEST(hint)) { - return Bool(v8::V8::IdleNotification(NUM2INT(hint))); - } else { - return Bool(v8::V8::IdleNotification()); + ClassBuilder("V8"). + // defineSingletonMethod("IdleNotification", &IdleNotification). + // defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString). + // defineSingletonMethod("SetFlagsFromCommandLine", &SetFlagsFromCommandLine). + // defineSingletonMethod("PauseProfiler", &PauseProfiler). + // defineSingletonMethod("ResumeProfiler", &ResumeProfiler). + // defineSingletonMethod("IsProfilerPaused", &IsProfilerPaused). + // defineSingletonMethod("GetCurrentThreadId", &GetCurrentThreadId). + // defineSingletonMethod("TerminateExecution", &TerminateExecution). + // defineSingletonMethod("IsExecutionTerminating", &IsExecutionTerminating). + defineSingletonMethod("Dispose", &Dispose). + // defineSingletonMethod("LowMemoryNotification", &LowMemoryNotification). + // defineSingletonMethod("AdjustAmountOfExternalAllocatedMemory", &AdjustAmountOfExternalAllocatedMemory). + // defineSingletonMethod("ContextDisposedNotification", &ContextDisposedNotification). + // defineSingletonMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). + // defineSingletonMethod("GetHeapStatistics", &GetHeapStatistics). + defineSingletonMethod("GetVersion", &GetVersion); } -} -VALUE V8::SetFlagsFromString(VALUE self, VALUE string) { - Void(v8::V8::SetFlagsFromString(RSTRING_PTR(string), (int)RSTRING_LEN(string))); -} -VALUE V8::SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags) { - int argc = RARRAY_LENINT(args); - char* argv[argc]; - for(int i = 0; i < argc; i++) { - argv[i] = RSTRING_PTR(rb_ary_entry(args, i)); + + VALUE V8::Dispose(VALUE self) { + v8::V8::Dispose(); + v8::V8::ShutdownPlatform(); + + if (v8_platform) { + delete v8_platform; + v8_platform = NULL; + } + + return Qnil; } - Void(v8::V8::SetFlagsFromCommandLine(&argc, argv, Bool(remove_flags))); -} -VALUE V8::AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes) { - return SIZET2NUM(v8::V8::AdjustAmountOfExternalAllocatedMemory(NUM2SIZET(change_in_bytes))); -} -VALUE V8::PauseProfiler(VALUE self) { - Void(v8::V8::PauseProfiler()); -} -VALUE V8::ResumeProfiler(VALUE self) { - Void(v8::V8::ResumeProfiler()); -} -VALUE V8::IsProfilerPaused(VALUE self) { - return Bool(v8::V8::IsProfilerPaused()); -} -VALUE V8::GetCurrentThreadId(VALUE self) { - return INT2FIX(v8::V8::GetCurrentThreadId()); -} -VALUE V8::TerminateExecution(VALUE self, VALUE thread_id) { - Void(v8::V8::TerminateExecution(NUM2INT(thread_id))); -} -VALUE V8::IsExecutionTerminating(VALUE self) { - return Bool(v8::V8::IsExecutionTerminating()); -} -VALUE V8::Dispose(VALUE self) { - Void(v8::V8::Dispose()); -} -VALUE V8::LowMemoryNotification(VALUE self) { - Void(v8::V8::LowMemoryNotification()); -} -VALUE V8::ContextDisposedNotification(VALUE self) { - return INT2FIX(v8::V8::ContextDisposedNotification()); -} -VALUE V8::SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self) { - VALUE should_capture; VALUE frame_limit; VALUE options; - rb_scan_args(argc, argv, "12", &should_capture, &frame_limit, &options); - int limit = RTEST(frame_limit) ? NUM2INT(frame_limit) : 10; - Void(v8::V8::SetCaptureStackTraceForUncaughtExceptions(Bool(should_capture), limit, Stack::Trace::StackTraceOptions(options))); -} -VALUE V8::GetHeapStatistics(VALUE self, VALUE statistics_ptr) { - Void(v8::V8::GetHeapStatistics(HeapStatistics(statistics_ptr))); -} -VALUE V8::GetVersion(VALUE self) { - return rb_str_new2(v8::V8::GetVersion()); + + VALUE V8::GetVersion(VALUE self) { + return rb_str_new2(v8::V8::GetVersion()); + } + } -} \ No newline at end of file diff --git a/ext/v8/v8.h b/ext/v8/v8.h new file mode 100644 index 00000000..a8f5ca55 --- /dev/null +++ b/ext/v8/v8.h @@ -0,0 +1,25 @@ +namespace rr { + class V8 { + public: + static v8::Platform* v8_platform; + + static void Init(); + // static VALUE IdleNotification(int argc, VALUE argv[], VALUE self); + // static VALUE SetFlagsFromString(VALUE self, VALUE string); + // static VALUE SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags); + // static VALUE AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes); + // static VALUE PauseProfiler(VALUE self); + // static VALUE ResumeProfiler(VALUE self); + // static VALUE IsProfilerPaused(VALUE self); + // static VALUE GetCurrentThreadId(VALUE self); + // static VALUE TerminateExecution(VALUE self, VALUE thread_id); + // static VALUE IsExecutionTerminating(VALUE self); + static VALUE Dispose(VALUE self); + // static VALUE LowMemoryNotification(VALUE self); + // static VALUE ContextDisposedNotification(VALUE self); + + // static VALUE SetCaptureStackTraceForUncaughtExceptions(int argc, VALUE argv[], VALUE self); + // static VALUE GetHeapStatistics(VALUE self, VALUE statistics_ptr); + static VALUE GetVersion(VALUE self); + }; +} diff --git a/ext/v8/value.cc b/ext/v8/value.cc deleted file mode 100644 index af2a955a..00000000 --- a/ext/v8/value.cc +++ /dev/null @@ -1,239 +0,0 @@ -#include "rr.h" - -namespace rr { - -VALUE Value::Empty; - -void Value::Init() { - Empty = rb_eval_string("Object.new"); - ClassBuilder("Value"). - defineConst("Empty", Empty). - defineMethod("IsUndefined", &IsUndefined). - defineMethod("IsNull", &IsNull). - defineMethod("IsTrue", &IsTrue). - defineMethod("IsFalse", &IsFalse). - defineMethod("IsString", &IsString). - defineMethod("IsFunction", &IsFunction). - defineMethod("IsArray", &IsArray). - defineMethod("IsObject", &IsObject). - defineMethod("IsBoolean", &IsBoolean). - defineMethod("IsNumber", &IsNumber). - defineMethod("IsExternal", &IsExternal). - defineMethod("IsInt32", &IsInt32). - defineMethod("IsUint32", &IsUint32). - defineMethod("IsDate", &IsDate). - defineMethod("IsBooleanObject", &IsBooleanObject). - defineMethod("IsNumberObject", &IsNumberObject). - defineMethod("IsStringObject", &IsStringObject). - defineMethod("IsNativeError", &IsNativeError). - defineMethod("IsRegExp", &IsRegExp). - defineMethod("ToString", &ToString). - defineMethod("ToDetailString", &ToDetailString). - defineMethod("ToObject", &ToObject). - defineMethod("BooleanValue", &BooleanValue). - defineMethod("NumberValue", &NumberValue). - defineMethod("IntegerValue", &IntegerValue). - defineMethod("Uint32Value", &Uint32Value). - defineMethod("IntegerValue", &IntegerValue). - defineMethod("Equals", &Equals). - defineMethod("StrictEquals", &StrictEquals) - .store(&Class); - rb_gc_register_address(&Empty); -} - - VALUE Value::IsUndefined(VALUE self) { - return Bool(Value(self)->IsUndefined()); - } - VALUE Value::IsNull(VALUE self) { - return Bool(Value(self)->IsNull()); - } - VALUE Value::IsTrue(VALUE self) { - return Bool(Value(self)->IsTrue()); - } - VALUE Value::IsFalse(VALUE self) { - return Bool(Value(self)->IsFalse()); - } - VALUE Value::IsString(VALUE self) { - return Bool(Value(self)->IsString()); - } - VALUE Value::IsFunction(VALUE self) { - return Bool(Value(self)->IsFunction()); - } - VALUE Value::IsArray(VALUE self) { - return Bool(Value(self)->IsArray()); - } - VALUE Value::IsObject(VALUE self) { - return Bool(Value(self)->IsObject()); - } - VALUE Value::IsBoolean(VALUE self) { - return Bool(Value(self)->IsBoolean()); - } - VALUE Value::IsNumber(VALUE self) { - return Bool(Value(self)->IsNumber()); - } - VALUE Value::IsExternal(VALUE self) { - return Bool(Value(self)->IsExternal()); - } - VALUE Value::IsInt32(VALUE self) { - return Bool(Value(self)->IsInt32()); - } - VALUE Value::IsUint32(VALUE self) { - return Bool(Value(self)->IsUint32()); - } - VALUE Value::IsDate(VALUE self) { - return Bool(Value(self)->IsDate()); - } - VALUE Value::IsBooleanObject(VALUE self) { - return Bool(Value(self)->IsBooleanObject()); - } - VALUE Value::IsNumberObject(VALUE self) { - return Bool(Value(self)->IsNumberObject()); - } - VALUE Value::IsStringObject(VALUE self) { - return Bool(Value(self)->IsStringObject()); - } - VALUE Value::IsNativeError(VALUE self) { - return Bool(Value(self)->IsNativeError()); - } - VALUE Value::IsRegExp(VALUE self) { - return Bool(Value(self)->IsRegExp()); - } - - // VALUE Value::ToBoolean(VALUE self) { - // return Boolean(Value(self)->ToBoolean()); - // } - - // VALUE Value::ToNumber(VALUE self) { - // return Number(Value(self)->ToNumber()); - // } - VALUE Value::ToString(VALUE self) { - return String(Value(self)->ToString()); - } - - VALUE Value::ToDetailString(VALUE self) { - return String(Value(self)->ToDetailString()); - } - - VALUE Value::ToObject(VALUE self) { - return Object(Value(self)->ToObject()); - } - - // VALUE Value::ToInteger(VALUE self) { - // return Integer(Value(self)->ToInteger()); - // } - - // VALUE Value::ToUint32(VALUE self) { - // return Uint32(Value(self)->ToUint32()); - // } - - // VALUE Value::ToInt32(VALUE self) { - // return Int32(Value(self)->ToInt32()); - // } - - -// VALUE Value::ToArrayIndex(VALUE self) { -// return Uint32(Value(self)->ToArrayIndex()); -// } - -VALUE Value::BooleanValue(VALUE self) { - return Bool(Value(self)->BooleanValue()); -} -VALUE Value::NumberValue(VALUE self) { - return rb_float_new(Value(self)->NumberValue()); -} -VALUE Value::IntegerValue(VALUE self) { - return INT2NUM(Value(self)->IntegerValue()); -} -VALUE Value::Uint32Value(VALUE self) { - return UINT2NUM(Value(self)->Uint32Value()); -} -VALUE Value::Int32Value(VALUE self) { - return INT2FIX(Value(self)->Int32Value()); -} - -VALUE Value::Equals(VALUE self, VALUE other) { - return Bool(Value(self)->Equals(Value(other))); -} - -VALUE Value::StrictEquals(VALUE self, VALUE other) { - return Bool(Value(self)->StrictEquals(Value(other))); -} - -Value::operator VALUE() { - if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) { - return Qnil; - } - if (handle->IsTrue()) { - return Qtrue; - } - if (handle->IsFalse()) { - return Qfalse; - } - if (handle->IsExternal()) { - return External((v8::Handle)v8::External::Cast(*handle)); - } - if (handle->IsUint32()) { - return UInt32(handle->Uint32Value()); - } - if (handle->IsInt32()) { - return INT2FIX(handle->Int32Value()); - } - if (handle->IsBoolean()) { - return handle->BooleanValue() ? Qtrue : Qfalse; - } - if (handle->IsNumber()) { - return rb_float_new(handle->NumberValue()); - } - if (handle->IsString()) { - return String(handle->ToString()); - } - if (handle->IsDate()) { - return Date((v8::Handle)v8::Date::Cast(*handle)); - } - if (handle->IsObject()) { - return Object(handle->ToObject()); - } - return Ref::operator VALUE(); -} - -Value::operator v8::Handle() const { - if (rb_equal(value,Empty)) { - return v8::Handle(); - } - switch (TYPE(value)) { - case T_FIXNUM: - return v8::Integer::New(NUM2INT(value)); - case T_FLOAT: - return v8::Number::New(NUM2DBL(value)); - case T_STRING: - return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value)); - case T_NIL: - return v8::Null(); - case T_TRUE: - return v8::True(); - case T_FALSE: - return v8::False(); - case T_DATA: - return Ref::operator v8::Handle(); - case T_OBJECT: - case T_CLASS: - case T_ICLASS: - case T_MODULE: - case T_REGEXP: - case T_MATCH: - case T_ARRAY: - case T_HASH: - case T_STRUCT: - case T_BIGNUM: - case T_FILE: - case T_SYMBOL: - case T_UNDEF: - case T_NODE: - default: - rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value))); - return v8::String::New("Undefined Conversion"); - } - - return v8::Undefined(); -} -} \ No newline at end of file diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb deleted file mode 100644 index aab69762..00000000 --- a/spec/c/array_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe V8::C::Array do - requires_v8_context - - it "can store and retrieve a value" do - o = V8::C::Object::New() - a = V8::C::Array::New() - a.Length().should eql 0 - a.Set(0, o) - a.Length().should eql 1 - a.Get(0).Equals(o).should be_true - end - - it "can be initialized with a length" do - a = V8::C::Array::New(5) - a.Length().should eql 5 - end -end diff --git a/spec/c/constants_spec.rb b/spec/c/constants_spec.rb deleted file mode 100644 index 46837bf6..00000000 --- a/spec/c/constants_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe V8::C do - requires_v8_context - - it "has constant methods for Undefined, Null, True and False" do - [:Undefined, :Null, :True, :False].each do |name| - constant = V8::C.send(name) - constant.should_not be_nil - V8::C.send(name).should be constant - end - end - - it "has a value for the Empty handle" do - V8::C::Value::Empty.should_not be_nil - V8::C::Value::Empty.should be V8::C::Value::Empty - end - - it "can access the V8 version" do - V8::C::V8::GetVersion().should match /^3\.16/ - end -end diff --git a/spec/c/exception_spec.rb b/spec/c/exception_spec.rb deleted file mode 100644 index 18512371..00000000 --- a/spec/c/exception_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe V8::C::Exception do - requires_v8_context - - it "can be thrown from Ruby" do - t = V8::C::FunctionTemplate::New(method(:explode)) - @cxt.Global().Set("explode", t.GetFunction()) - script = V8::C::Script::New(<<-JS, '') - (function() { - try { - explode() - } catch (e) { - return e.message - } - })() - JS - result = script.Run() - result.should_not be_nil - result.should be_kind_of(V8::C::String) - result.Utf8Value().should eql 'did not pay syntax' - end - - def explode(arguments) - error = V8::C::Exception::SyntaxError('did not pay syntax') - V8::C::ThrowException(error) - end -end diff --git a/spec/c/external_spec.rb b/spec/c/external_spec.rb deleted file mode 100644 index 2553028e..00000000 --- a/spec/c/external_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe V8::C::External do - requires_v8_context - - it "can store and retrieve a value" do - o = Object.new - external = V8::C::External::New(o) - external.Value().should be(o) - end -end diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb deleted file mode 100644 index 17835bc8..00000000 --- a/spec/c/function_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe V8::C::Function do - requires_v8_context - - it "can be called" do - fn = run '(function() {return "foo"})' - fn.Call(@cxt.Global(), []).Utf8Value().should eql "foo" - end - - it "can be called with arguments and context" do - fn = run '(function(one, two, three) {this.one = one; this.two = two; this.three = three})' - one = V8::C::Object::New() - two = V8::C::Object::New() - fn.Call(@cxt.Global(), [one, two, 3]) - @cxt.Global().Get("one").should eql one - @cxt.Global().Get("two").should eql two - @cxt.Global().Get("three").should eql 3 - end - - it "can be called as a constructor" do - fn = run '(function() {this.foo = "foo"})' - fn.NewInstance().Get(V8::C::String::New('foo')).Utf8Value().should eql "foo" - end - - it "can be called as a constructor with arguments" do - fn = run '(function(foo) {this.foo = foo})' - object = fn.NewInstance([V8::C::String::New("bar")]) - object.Get(V8::C::String::New('foo')).Utf8Value().should eql "bar" - end - - it "doesn't kill the world if invoking it throws a javascript exception" do - V8::C::TryCatch() do - fn = run '(function() { throw new Error("boom!")})' - fn.Call(@cxt.Global(), []) - fn.NewInstance([]) - end - end - - - def run(source) - source = V8::C::String::New(source.to_s) - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - result.kind_of?(V8::C::String) ? result.Utf8Value() : result - end -end diff --git a/spec/c/handles_spec.rb b/spec/c/handles_spec.rb deleted file mode 100644 index 4882403c..00000000 --- a/spec/c/handles_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe "setting up handles scopes" do - around(:each) do |example| - V8::C::Locker() do - cxt = V8::C::Context::New() - begin - cxt.Enter() - example.run - ensure - cxt.Exit() - end - end - end - - it "can allocate handle scopes" do - V8::C::HandleScope() do - V8::C::Object::New() - end.class.should eql V8::C::Object - end - - it "isn't the end of the world if a ruby exception is raised inside a HandleScope" do - begin - V8::C::HandleScope() do - raise "boom!" - end - rescue StandardError => e - e.message.should eql "boom!" - end - end -end diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb new file mode 100644 index 00000000..1ec1aea7 --- /dev/null +++ b/spec/c/isolate_spec.rb @@ -0,0 +1,7 @@ +require 'v8/init' + +describe V8::C::Isolate do + it "can create a new isolate" do + expect(V8::C::Isolate.New).to be + end +end diff --git a/spec/c/locker_spec.rb b/spec/c/locker_spec.rb deleted file mode 100644 index 3f7e163d..00000000 --- a/spec/c/locker_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe V8::C::Locker do - it "can lock and unlock the VM" do - V8::C::Locker::IsLocked().should be_false - V8::C::Locker() do - V8::C::Locker::IsLocked().should be_true - V8::C::Unlocker() do - V8::C::Locker::IsLocked().should be_false - end - end - V8::C::Locker::IsLocked().should be_false - end - - it "properly unlocks if an exception is thrown inside a lock block" do - begin - V8::C::Locker() do - raise "boom!" - end - rescue - V8::C::Locker::IsLocked().should be_false - end - end - - it "properly re-locks if an exception is thrown inside an un-lock block" do - V8::C::Locker() do - begin - V8::C::Unlocker() do - raise "boom!" - end - rescue - V8::C::Locker::IsLocked().should be_true - end - end - end -end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb deleted file mode 100644 index 58649896..00000000 --- a/spec/c/object_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -describe V8::C::Object do - requires_v8_context - - it "can store and retrieve a value" do - o = V8::C::Object::New() - key = V8::C::String::New("foo") - value = V8::C::String::New("bar") - o.Set(key, value) - o.Get(key).Utf8Value().should eql "bar" - end - - it "can retrieve all property names" do - o = V8::C::Object::New() - o.Set(V8::C::String::New("foo"), V8::C::String::New("bar")) - o.Set(V8::C::String::New("baz"), V8::C::String::New("bang")) - names = o.GetPropertyNames() - names.Length().should eql 2 - names.Get(0).Utf8Value().should eql "foo" - names.Get(1).Utf8Value().should eql "baz" - end - it "can set an accessor from ruby" do - o = V8::C::Object::New() - property = V8::C::String::New("statement") - callback_data = V8::C::String::New("I am Legend") - left = V8::C::String::New("Yo! ") - getter = proc do |name, info| - info.This().StrictEquals(o).should be_true - info.Holder().StrictEquals(o).should be_true - V8::C::String::Concat(left, info.Data()) - end - setter = proc do |name, value, info| - left = value - end - o.SetAccessor(property, getter, setter, callback_data) - o.Get(property).Utf8Value().should eql "Yo! I am Legend" - o.Set(property, V8::C::String::New("Bro! ")) - o.Get(property).Utf8Value().should eql "Bro! I am Legend" - end - it "always returns the same ruby object for the same V8 object" do - one = V8::C::Object::New() - two = V8::C::Object::New() - one.Set("two", two) - one.Get("two").should be two - end -end diff --git a/spec/c/script_spec.rb b/spec/c/script_spec.rb deleted file mode 100644 index db1a8b72..00000000 --- a/spec/c/script_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# encoding: UTF-8 -require 'spec_helper' - -describe V8::C::Script do - requires_v8_context - - it "can run a script and return a polymorphic result" do - source = V8::C::String::New("(new Array())") - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - result.should be_kind_of V8::C::Array - end - - it "can accept precompiled script data" do - source = "7 * 6" - name = V8::C::String::New("") - origin = V8::C::ScriptOrigin.new(name) - data = V8::C::ScriptData::PreCompile(source, source.length) - data.HasError().should be_false - script = V8::C::Script::New(V8::C::String::New(source), origin, data) - script.Run().should eql 42 - end - - it "can detect errors in the script data" do - source = "^ = ;" - data = V8::C::ScriptData::PreCompile(source, source.length) - data.HasError().should be_true - end -end diff --git a/spec/c/string_spec.rb b/spec/c/string_spec.rb deleted file mode 100644 index 75184e4f..00000000 --- a/spec/c/string_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe V8::C::String do - requires_v8_context - - it "can hold Unicode values outside the Basic Multilingual Plane" do - string = V8::C::String::New("\u{100000}") - string.Utf8Value().should eql "\u{100000}" - end - - it "can naturally translate ruby strings into v8 strings" do - V8::C::String::Concat(V8::C::String::New("Hello "), "World").Utf8Value().should eql "Hello World" - end - - it "can naturally translate ruby objects into v8 strings" do - V8::C::String::Concat(V8::C::String::New("forty two is "), 42).Utf8Value().should eql "forty two is 42" - end -end diff --git a/spec/c/template_spec.rb b/spec/c/template_spec.rb deleted file mode 100644 index f1da9b33..00000000 --- a/spec/c/template_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe V8::C::Template do - requires_v8_context - - describe V8::C::FunctionTemplate do - it "can be created with no arguments" do - t = V8::C::FunctionTemplate::New() - t.GetFunction().Call(@cxt.Global(),[]).StrictEquals(@cxt.Global()).should be_true - end - - it "can be created with a callback" do - receiver = V8::C::Object::New() - f = nil - callback = lambda do |arguments| - arguments.Length().should be(2) - arguments[0].Utf8Value().should eql 'one' - arguments[1].Utf8Value().should eql 'two' - arguments.Callee().StrictEquals(f).should be_true - arguments.This().StrictEquals(receiver).should be_true - arguments.Holder().StrictEquals(receiver).should be_true - arguments.IsConstructCall().should be_false - arguments.Data().Value().should be(42) - V8::C::String::New("result") - end - t = V8::C::FunctionTemplate::New(callback, V8::C::External::New(42)) - f = t.GetFunction() - f.Call(receiver, [V8::C::String::New('one'), V8::C::String::New('two')]).Utf8Value().should eql "result" - end - end -end diff --git a/spec/c/trycatch_spec.rb b/spec/c/trycatch_spec.rb deleted file mode 100644 index d9ec06e9..00000000 --- a/spec/c/trycatch_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe V8::C::External do - requires_v8_context - - it "can catch javascript exceptions" do - V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kDetailed) - V8::C::TryCatch() do |trycatch| - source = V8::C::String::New(<<-JS) - function one() { - two() - } - function two() { - three() - } - function three() { - boom() - } - function boom() { - throw new Error('boom!') - } - eval('one()') - JS - filename = V8::C::String::New("") - script = V8::C::Script::New(source, filename) - result = script.Run() - trycatch.HasCaught().should be_true - trycatch.CanContinue().should be_true - exception = trycatch.Exception() - exception.should_not be_nil - exception.IsNativeError().should be_true - trycatch.StackTrace().Utf8Value().should match /boom.*three.*two.*one/m - message = trycatch.Message(); - message.should_not be_nil - message.Get().Utf8Value().should eql "Uncaught Error: boom!" - message.GetSourceLine().Utf8Value().should eql " throw new Error('boom!')" - message.GetScriptResourceName().Utf8Value().should eql "" - message.GetLineNumber().should eql 11 - stack = message.GetStackTrace() - stack.should_not be_nil - stack.GetFrameCount().should eql 6 - frame = stack.GetFrame(0) - frame.GetLineNumber().should eql 11 - frame.GetColumn().should eql 15 - frame.GetScriptName().Utf8Value().should eql "" - frame.GetScriptNameOrSourceURL().Utf8Value().should eql "" - frame.IsEval().should be_false - stack.GetFrame(4).IsEval().should be_true - frame.IsConstructor().should be_false - end - end -end From 1f7192ca858cac08c483c859429c887cfe551b2c Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Wed, 18 Mar 2015 22:08:34 +0000 Subject: [PATCH 002/105] Add ability to create handle scopes --- ext/v8/init.cc | 2 +- spec/c/handles_spec.rb | 22 ++++++++++++++++++++++ spec/c/isolate_spec.rb | 2 +- spec/c/v8_spec.rb | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 spec/c/handles_spec.rb create mode 100644 spec/c/v8_spec.rb diff --git a/ext/v8/init.cc b/ext/v8/init.cc index e09c605f..e9bfee9c 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -10,9 +10,9 @@ extern "C" { void Init_init() { V8::Init(); Isolate::Init(); + Handles::Init(); // v8::Locker lock(); // GC::Init(); - // Handles::Init(); // Accessor::Init(); // Context::Init(); // Invocation::Init(); diff --git a/spec/c/handles_spec.rb b/spec/c/handles_spec.rb new file mode 100644 index 00000000..e8b31ee2 --- /dev/null +++ b/spec/c/handles_spec.rb @@ -0,0 +1,22 @@ +require 'v8/init' + +describe 'handles' do + describe '#HandleScope' do + let(:isolate) { V8::C::Isolate.New } + + it 'can allocate handle scopes' do + V8::C::HandleScope(isolate) do + end + end + + it 'isn\'t the end of the world if a ruby exception is raised inside a HandleScope' do + begin + V8::C::HandleScope(isolate) do + raise 'boom!' + end + rescue StandardError => e + expect(e.message).to eq 'boom!' + end + end + end +end diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 1ec1aea7..02124712 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -1,7 +1,7 @@ require 'v8/init' describe V8::C::Isolate do - it "can create a new isolate" do + it 'can create a new isolate' do expect(V8::C::Isolate.New).to be end end diff --git a/spec/c/v8_spec.rb b/spec/c/v8_spec.rb new file mode 100644 index 00000000..aab4a9b3 --- /dev/null +++ b/spec/c/v8_spec.rb @@ -0,0 +1,7 @@ +require 'v8/init' + +describe V8::C::V8 do + it 'can say its version' do + expect(V8::C::V8.GetVersion).to be_a String + end +end From d1e9088cf06afc2f695dd6b92d7cdf5e49e17e3c Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Fri, 20 Mar 2015 20:47:57 +0000 Subject: [PATCH 003/105] Port a ton of the original ext classes You can now create a JS object from Ruby-land without segfaulting --- ext/v8/backref.cc | 50 ++++++++++++ ext/v8/backref.h | 29 +++++++ ext/v8/bool.h | 19 +++++ ext/v8/context.cc | 64 +++++++++++++++ ext/v8/context.h | 39 ++++++++++ ext/v8/equiv.h | 17 ++++ ext/v8/init.cc | 7 +- ext/v8/isolate.cc | 25 ++++++ ext/v8/isolate.h | 5 +- ext/v8/object.cc | 99 ++++++++++++++++++++++++ ext/v8/object.h | 68 ++++++++++++++++ ext/v8/ref.h | 124 +++++++++++++++++++++++++++++ ext/v8/rr.h | 11 ++- ext/v8/uint32.h | 18 +++++ ext/v8/value.cc | 172 +++++++++++++++++++++++++++++++++++++++++ ext/v8/value.h | 51 ++++++++++++ spec/c/handles_spec.rb | 2 +- spec/c/isolate_spec.rb | 2 +- spec/c/object_spec.rb | 9 +++ spec/c/v8_spec.rb | 2 +- spec/c_spec_helper.rb | 39 ++++++++++ 21 files changed, 844 insertions(+), 8 deletions(-) create mode 100644 ext/v8/backref.cc create mode 100644 ext/v8/backref.h create mode 100644 ext/v8/bool.h create mode 100644 ext/v8/context.cc create mode 100644 ext/v8/context.h create mode 100644 ext/v8/equiv.h create mode 100644 ext/v8/object.cc create mode 100644 ext/v8/object.h create mode 100644 ext/v8/ref.h create mode 100644 ext/v8/uint32.h create mode 100644 ext/v8/value.cc create mode 100644 ext/v8/value.h create mode 100644 spec/c/object_spec.rb create mode 100644 spec/c_spec_helper.rb diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc new file mode 100644 index 00000000..7b87b23d --- /dev/null +++ b/ext/v8/backref.cc @@ -0,0 +1,50 @@ +#include "rr.h" + +namespace rr { + + VALUE Backref::Storage; + ID Backref::_new; + ID Backref::object; + + void Backref::Init() { + Storage = rb_eval_string("V8::Weak::Ref"); + rb_gc_register_address(&Storage); + _new = rb_intern("new"); + object = rb_intern("object"); + } + + Backref::Backref(VALUE initial) { + set(initial); + rb_gc_register_address(&storage); + } + + Backref::~Backref() { + rb_gc_unregister_address(&storage); + } + + VALUE Backref::set(VALUE data) { + this->storage = rb_funcall(Storage, _new, 1, data); + return data; + } + + VALUE Backref::get() { + return rb_funcall(storage, object, 0); + } + + v8::Handle Backref::toExternal() { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + + v8::Local wrapper = v8::External::New(isolate, this); + (new v8::Persistent(isolate, wrapper))->SetWeak(this, &release); + + return wrapper; + } + + void Backref::release(const v8::WeakCallbackData& data) { + // TODO: Since data.GetValue() is Local make sure + // the Persistent is disposed of (or will be) at this point. + + delete data.GetParameter(); + } + +} diff --git a/ext/v8/backref.h b/ext/v8/backref.h new file mode 100644 index 00000000..c3b5cac6 --- /dev/null +++ b/ext/v8/backref.h @@ -0,0 +1,29 @@ +#ifndef RR_BACKREF +#define RR_BACKREF + +namespace rr { + + class Backref { + public: + static void Init(); + + Backref(VALUE value); + + virtual ~Backref(); + + VALUE get(); + VALUE set(VALUE value); + + v8::Handle toExternal(); + + static void release(const v8::WeakCallbackData& data); + private: + VALUE storage; + static VALUE Storage; + static ID _new; + static ID object; + }; + +} + +#endif diff --git a/ext/v8/bool.h b/ext/v8/bool.h new file mode 100644 index 00000000..61195586 --- /dev/null +++ b/ext/v8/bool.h @@ -0,0 +1,19 @@ +#ifndef RR_BOOL +#define RR_BOOL + +namespace rr { + + class Bool : public Equiv { + public: + Bool(VALUE val) : Equiv(val) {} + Bool(bool b) : Equiv(b ? Qtrue : Qfalse) {} + Bool(v8::Handle b) : Equiv(b->Value() ? Qtrue : Qfalse) {} + + inline operator bool() { + return RTEST(value); + } + }; + +} + +#endif diff --git a/ext/v8/context.cc b/ext/v8/context.cc new file mode 100644 index 00000000..6ffc026f --- /dev/null +++ b/ext/v8/context.cc @@ -0,0 +1,64 @@ +#include "rr.h" + +namespace rr { + + void Context::Init() { + ClassBuilder("Context"). + defineSingletonMethod("New", &New). + + defineMethod("Dispose", &Dispose). + defineMethod("Enter", &Enter). + defineMethod("Exit", &Exit). + + store(&Class); + + // TODO + // ClassBuilder("ExtensionConfiguration"). + // defineSingletonMethod("new", &ExtensionConfiguration::initialize). + // store(&ExtensionConfiguration::Class); + } + + VALUE Context::New(int argc, VALUE argv[], VALUE self) { + VALUE isolate, extension_configuration, global_template, global_object; + rb_scan_args(argc, argv, "13", &isolate, &extension_configuration, &global_template, &global_object); + + // TODO: Is this needed? + // v8::Persistent context( + return Context(v8::Context::New( + Isolate(isolate) + // TODO + // , + // ExtensionConfiguration(extension_configuration), + // *ObjectTemplate(global_template), + // *Object(global_object) + )); + //); + + // TODO: Is this needed? + // Context reference(context); + // context.Reset(); + // + // return reference; + } + + VALUE Context::Dispose(VALUE self) { + Context(self).dispose(); + return Qnil; + } + + VALUE Context::Enter(VALUE self) { + Context(self)->Enter(); + return Qnil; + } + + VALUE Context::Exit(VALUE self) { + Context(self)->Exit(); + return Qnil; + } + + // TODO + // template <> void Pointer::unwrap(VALUE value) { + // Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); + // } + +} diff --git a/ext/v8/context.h b/ext/v8/context.h new file mode 100644 index 00000000..106d3767 --- /dev/null +++ b/ext/v8/context.h @@ -0,0 +1,39 @@ +#ifndef RR_CONTEXT +#define RR_CONTEXT + +namespace rr { + + class Context : public Ref { + public: + static void Init(); + + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE Dispose(VALUE self); + + static VALUE Enter(VALUE self); + static VALUE Exit(VALUE self); + + // TODO + // static VALUE Global(VALUE self); + // static VALUE DetachGlobal(VALUE self); + // static VALUE ReattachGlobal(VALUE self, VALUE global); + // static VALUE GetEntered(VALUE self); + // static VALUE GetCurrent(VALUE self); + // static VALUE GetCalling(VALUE self); + // static VALUE SetSecurityToken(VALUE self, VALUE token); + // static VALUE UseDefaultSecurityToken(VALUE self); + // static VALUE GetSecurityToken(VALUE self); + // static VALUE HasOutOfMemoryException(VALUE self); + // static VALUE InContext(VALUE self); + // static VALUE SetEmbedderData(VALUE self, VALUE index, VALUE data); + // static VALUE GetEmbedderData(VALUE self, VALUE index); + // static VALUE AllowCodeGenerationFromStrings(VALUE self, VALUE allow); + // static VALUE IsCodeGenerationFromStringsAllowed(VALUE self); + + inline Context(VALUE value) : Ref(value) {} + inline Context(v8::Handle cxt) : Ref(cxt) {} + }; + +} + +#endif diff --git a/ext/v8/equiv.h b/ext/v8/equiv.h new file mode 100644 index 00000000..c35455a7 --- /dev/null +++ b/ext/v8/equiv.h @@ -0,0 +1,17 @@ +#ifndef RR_EQUIV +#define RR_EQUIV + +namespace rr { + + class Equiv { + public: + Equiv(VALUE val) : value(val) {} + inline operator VALUE() { return value; } + + protected: + VALUE value; + }; + +} + +#endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index e9bfee9c..a620a774 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -11,13 +11,15 @@ extern "C" { V8::Init(); Isolate::Init(); Handles::Init(); + Context::Init(); + Backref::Init(); + Value::Init(); + Object::Init(); // v8::Locker lock(); // GC::Init(); // Accessor::Init(); - // Context::Init(); // Invocation::Init(); // Signature::Init(); - // Value::Init(); // Primitive::Init(); // String::Init(); // Object::Init(); @@ -35,6 +37,5 @@ extern "C" { // Locker::Init(); // ResourceConstraints::Init(); // HeapStatistics::Init(); - // Backref::Init(); } } diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 18e6ff9d..206361c8 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -5,6 +5,11 @@ namespace rr { void Isolate::Init() { ClassBuilder("Isolate"). defineSingletonMethod("New", &New). + defineSingletonMethod("GetCurrent", &GetCurrent). + + defineMethod("Enter", &Enter). + defineMethod("Exit", &Exit). + store(&Class); } @@ -12,6 +17,26 @@ namespace rr { return Isolate(v8::Isolate::New()); } + VALUE Isolate::Enter(VALUE self) { + Isolate(self)->Enter(); + return Qtrue; + } + + VALUE Isolate::Exit(VALUE self) { + Isolate(self)->Exit(); + return Qtrue; + } + + VALUE Isolate::GetCurrent(VALUE self) { + v8::Isolate* currentIsolate = v8::Isolate::GetCurrent(); + + if (!currentIsolate) { + return Qnil; + } + + return Isolate(currentIsolate); + } + template <> void Pointer::unwrap(VALUE value) { Data_Get_Struct(value, class v8::Isolate, pointer); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index d956cee5..73e161c6 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -7,6 +7,9 @@ namespace rr { public: static void Init(); static VALUE New(VALUE self); + static VALUE Enter(VALUE self); + static VALUE Exit(VALUE self); + static VALUE GetCurrent(VALUE self); inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} @@ -22,7 +25,7 @@ namespace rr { // TODO: Do we want to dispose of the isolate when the object itself // is garbage-collected? // Can the isolate be used without it having a reference in ruby world? - isolate->Dispose(); + // isolate->Dispose(); } }; diff --git a/ext/v8/object.cc b/ext/v8/object.cc new file mode 100644 index 00000000..26cf077b --- /dev/null +++ b/ext/v8/object.cc @@ -0,0 +1,99 @@ +#include "rr.h" + +namespace rr { + + void Object::Init() { + ClassBuilder("Object", Value::Class). + defineSingletonMethod("New", &New). + + defineMethod("Set", &Set). + defineMethod("Get", &Get). + + store(&Class); + } + + VALUE Object::New(VALUE self) { + return Object(v8::Object::New(v8::Isolate::GetCurrent())); + } + + // TODO: Allow setting of property attributes + VALUE Object::Set(VALUE self, VALUE key, VALUE value) { + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool(Object(self)->Set(UInt32(key), Value(value))); + } else { + return Bool(Object(self)->Set(*Value(key), Value(value))); + } + } + + VALUE Object::Get(VALUE self, VALUE key) { + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Value(Object(self)->Get(UInt32(key))); + } else { + return Value(Object(self)->Get(*Value(key))); + } + } + + Object::operator VALUE() { + if (handle.IsEmpty()) { + return Qnil; + } + + Backref* backref; + + v8::Local key(v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), "rr::Backref")); + v8::Local external = handle->GetHiddenValue(key); + + VALUE value; + + if (external.IsEmpty()) { + value = downcast(); + backref = new Backref(value); + + handle->SetHiddenValue(key, backref->toExternal()); + } else { + v8::External* wrapper = v8::External::Cast(*external); + backref = (Backref*)wrapper->Value(); + value = backref->get(); + + if (!RTEST(value)) { + value = downcast(); + backref->set(value); + } + } + + return value; + } + + VALUE Object::downcast() { + // TODO: Enable this when the methods are implemented + // if (handle->IsFunction()) { + // return Function((v8::Handle) v8::Function::Cast(*handle)); + // } + // + // if (handle->IsArray()) { + // return Array((v8::Handle)v8::Array::Cast(*handle)); + // } + // + // if (handle->IsDate()) { + // // return Date(handle); + // } + // + // if (handle->IsBooleanObject()) { + // // return BooleanObject(handle); + // } + // + // if (handle->IsNumberObject()) { + // // return NumberObject(handle); + // } + // + // if (handle->IsStringObject()) { + // // return StringObject(handle); + // } + // + // if (handle->IsRegExp()) { + // // return RegExp(handle); + // } + + return Ref::operator VALUE(); + } +} diff --git a/ext/v8/object.h b/ext/v8/object.h new file mode 100644 index 00000000..53eef5d5 --- /dev/null +++ b/ext/v8/object.h @@ -0,0 +1,68 @@ +#ifndef RR_OBJECT +#define RR_OBJECT + +namespace rr { + + class Object : public Ref { + public: + static void Init(); + static VALUE New(VALUE self); + static VALUE Set(VALUE self, VALUE key, VALUE value); + // static VALUE ForceSet(VALUE self, VALUE key, VALUE value); + static VALUE Get(VALUE self, VALUE key); + // static VALUE GetPropertyAttributes(VALUE self, VALUE key); + // static VALUE Has(VALUE self, VALUE key); + // static VALUE Delete(VALUE self, VALUE key); + // static VALUE ForceDelete(VALUE self, VALUE key); + // static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); + // static VALUE GetPropertyNames(VALUE self); + // static VALUE GetOwnPropertyNames(VALUE self); + // static VALUE GetPrototype(VALUE self); + // static VALUE SetPrototype(VALUE self, VALUE prototype); + // static VALUE FindInstanceInPrototypeChain(VALUE self, VALUE impl); + // static VALUE ObjectProtoToString(VALUE self); + // static VALUE GetConstructorName(VALUE self); + // static VALUE InternalFieldCount(VALUE self); + // static VALUE GetInternalField(VALUE self, VALUE idx); + // static VALUE SetInternalField(VALUE self, VALUE idx, VALUE value); + // static VALUE HasOwnProperty(VALUE self, VALUE key); + // static VALUE HasRealNamedProperty(VALUE self, VALUE key); + // static VALUE HasRealIndexedProperty(VALUE self, VALUE idx); + // static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE key); + // static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key); + // static VALUE GetRealNamedProperty(VALUE self, VALUE key); + // static VALUE HasNamedLookupInterceptor(VALUE self); + // static VALUE HasIndexedLookupInterceptor(VALUE self); + // static VALUE TurnOnAccessCheck(VALUE self); + // static VALUE GetIdentityHash(VALUE self); + // static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); + // static VALUE GetHiddenValue(VALUE self, VALUE key); + // static VALUE DeleteHiddenValue(VALUE self, VALUE key); + // static VALUE IsDirty(VALUE self); + // static VALUE Clone(VALUE self); + // static VALUE CreationContext(VALUE self); + // static VALUE SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length); + // static VALUE GetIndexedPropertiesPixelData(VALUE self); + // static VALUE HasIndexedPropertiesInPixelData(VALUE self); + // static VALUE GetIndexedPropertiesPixelDataLength(VALUE self); + // static VALUE SetIndexedPropertiesToExternalArrayData(VALUE self); + // static VALUE HasIndexedPropertiesInExternalArrayData(VALUE self); + // static VALUE GetIndexedPropertiesExternalArrayData(VALUE self); + // static VALUE GetIndexedPropertiesExternalArrayDataType(VALUE self); + // static VALUE GetIndexedPropertiesExternalArrayDataLength(VALUE self); + // static VALUE IsCallable(VALUE self); + // static VALUE CallAsFunction(VALUE self, VALUE recv, VALUE argv); + // static VALUE CallAsConstructor(VALUE self, VALUE argv); + + inline Object(VALUE value) : Ref(value) {} + inline Object(v8::Handle object) : Ref(object) {} + + virtual operator VALUE(); + + protected: + VALUE downcast(); + }; + +} + +#endif diff --git a/ext/v8/ref.h b/ext/v8/ref.h new file mode 100644 index 00000000..28b77e46 --- /dev/null +++ b/ext/v8/ref.h @@ -0,0 +1,124 @@ +#ifndef RR_REF +#define RR_REF + +namespace rr { + /** + * A Reference to a V8 managed object + * + * Uses type coercion to quickly convert from a v8 handle + * to a ruby object and back again. Suppose we have a v8 handle + * that we want to return to Ruby. We can put it into a Ref: + * + * v8::Handle object = v8::Object::New(); + * VALUE val = Ref(object); + * + * this will create a `v8::Persistent` handle for the object + * so that it will not be garbage collected by v8. It then + * stuffs this new persistent handle into a Data_Wrap_Struct + * which can then be passed to Ruby code. When this struct + * is garbage collected by Ruby, it enqueues the corresponding + * v8 handle to be released during v8 gc. + * + * By the same token, you can use Refs to unwrap a Data_Wrap_Struct + * which has been generated in this fashion and call through to + * the underlying v8 methods. Suppose we are passed a VALUE `val` + * wrapping a v8::Object: + * + * Ref object(val); + * object->Get(v8::String::New("foo")); + * + */ + template + class Ref { + public: + Ref(VALUE value) { + this->value = value; + } + + Ref(v8::Local handle) { + this->handle = handle; + } + + virtual ~Ref() {} + + /* + * Coerce a Ref into a Ruby VALUE + */ + virtual operator VALUE() const { + if (handle.IsEmpty()) { + return Qnil; + } + + return Data_Wrap_Struct(Class, 0, &Holder::enqueue, new Holder(handle)); + } + + /* + * Coerce a Ref into a v8::Local. + */ + virtual operator v8::Handle() const { + if (RTEST(this->value)) { + Holder* holder = NULL; + Data_Get_Struct(this->value, class Holder, holder); + + return v8::Local::New(v8::Isolate::GetCurrent(), *holder->handle); + } else { + return v8::Local(); + } + } + + void dispose() { + Holder* holder = NULL; + Data_Get_Struct(this->value, class Holder, holder); + holder->dispose(); + } + + /* + * Pointer de-reference operators, this lets you use a ref to + * call through to underlying v8 methods. e.g + * + * Ref(value)->ToString(); + */ + inline v8::Handle operator->() const { return *this; } + inline v8::Handle operator*() const { return *this; } + + class Holder { + friend class Ref; + public: + Holder(v8::Handle handle) { + this->disposed_p = false; + this->handle = new v8::Persistent(v8::Isolate::GetCurrent(), handle); + } + + virtual ~Holder() { + this->dispose(); + } + + void dispose() { + if (!this->disposed_p) { + handle->Reset(); + delete handle; + this->disposed_p = true; + } + } + + protected: + v8::Persistent* handle; + bool disposed_p; + + static void enqueue(Holder* holder) { + // TODO + // GC::Finalize(holder); + } + }; + + VALUE value; + v8::Handle handle; + static VALUE Class; + }; + + template + VALUE Ref::Class; + +} + +#endif diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 401cf47a..f004a6fc 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -12,10 +12,19 @@ #include "class_builder.h" #include "pointer.h" +#include "ref.h" #include "v8.h" #include "isolate.h" #include "handles.h" +#include "context.h" -#endif +#include "equiv.h" +#include "bool.h" +#include "uint32.h" +#include "value.h" +#include "backref.h" + +#include "object.h" +#endif diff --git a/ext/v8/uint32.h b/ext/v8/uint32.h new file mode 100644 index 00000000..c25986bd --- /dev/null +++ b/ext/v8/uint32.h @@ -0,0 +1,18 @@ +#ifndef RR_UINT32 +#define RR_UINT32 + +namespace rr { + + class UInt32 : public Equiv { + public: + UInt32(VALUE val) : Equiv(val) {} + UInt32(uint32_t ui) : Equiv(UINT2NUM(ui)) {} + + inline operator uint32_t() { + return RTEST(value) ? NUM2UINT(value) : 0; + } + }; + +} + +#endif diff --git a/ext/v8/value.cc b/ext/v8/value.cc new file mode 100644 index 00000000..844d98d0 --- /dev/null +++ b/ext/v8/value.cc @@ -0,0 +1,172 @@ +#include "rr.h" + +namespace rr { + + VALUE Value::Empty; + + void Value::Init() { + Empty = rb_eval_string("Object.new"); + + ClassBuilder("Value"). + defineConst("Empty", Empty). + + defineMethod("IsUndefined", &IsUndefined). + defineMethod("IsNull", &IsNull). + defineMethod("IsTrue", &IsTrue). + defineMethod("IsFalse", &IsFalse). + // defineMethod("IsString", &IsString). + // defineMethod("IsFunction", &IsFunction). + // defineMethod("IsArray", &IsArray). + defineMethod("IsObject", &IsObject). + // defineMethod("IsBoolean", &IsBoolean). + // defineMethod("IsNumber", &IsNumber). + // defineMethod("IsExternal", &IsExternal). + // defineMethod("IsInt32", &IsInt32). + // defineMethod("IsUint32", &IsUint32). + // defineMethod("IsDate", &IsDate). + // defineMethod("IsBooleanObject", &IsBooleanObject). + // defineMethod("IsNumberObject", &IsNumberObject). + // defineMethod("IsStringObject", &IsStringObject). + // defineMethod("IsNativeError", &IsNativeError). + // defineMethod("IsRegExp", &IsRegExp). + // defineMethod("ToString", &ToString). + // defineMethod("ToDetailString", &ToDetailString). + // defineMethod("ToObject", &ToObject). + // defineMethod("BooleanValue", &BooleanValue). + // defineMethod("NumberValue", &NumberValue). + // defineMethod("IntegerValue", &IntegerValue). + // defineMethod("Uint32Value", &Uint32Value). + // defineMethod("IntegerValue", &IntegerValue). + // defineMethod("Equals", &Equals). + // defineMethod("StrictEquals", &StrictEquals). + + store(&Class); + + rb_gc_register_address(&Empty); + } + + VALUE Value::IsUndefined(VALUE self) { + return Bool(Value(self)->IsUndefined()); + } + + VALUE Value::IsNull(VALUE self) { + return Bool(Value(self)->IsNull()); + } + + VALUE Value::IsTrue(VALUE self) { + return Bool(Value(self)->IsTrue()); + } + + VALUE Value::IsFalse(VALUE self) { + return Bool(Value(self)->IsFalse()); + } + + VALUE Value::IsObject(VALUE self) { + return Bool(Value(self)->IsObject()); + } + + VALUE Value::Equals(VALUE self, VALUE other) { + return Bool(Value(self)->Equals(Value(other))); + } + + VALUE Value::StrictEquals(VALUE self, VALUE other) { + return Bool(Value(self)->StrictEquals(Value(other))); + } + + Value::operator VALUE() { + if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) { + return Qnil; + } + + if (handle->IsTrue()) { + return Qtrue; + } + + if (handle->IsFalse()) { + return Qfalse; + } + + // TODO + // if (handle->IsExternal()) { + // return External((v8::Handle)v8::External::Cast(*handle)); + // } + + // TODO + // if (handle->IsUint32()) { + // return UInt32(handle->Uint32Value()); + // } + // + // if (handle->IsInt32()) { + // return INT2FIX(handle->Int32Value()); + // } + + if (handle->IsBoolean()) { + return handle->BooleanValue() ? Qtrue : Qfalse; + } + + // TODO + // if (handle->IsNumber()) { + // return rb_float_new(handle->NumberValue()); + // } + + // TODO + // if (handle->IsString()) { + // return String(handle->ToString()); + // } + // + // if (handle->IsDate()) { + // return Date((v8::Handle)v8::Date::Cast(*handle)); + // } + + if (handle->IsObject()) { + return Object(handle->ToObject()); + } + + return Ref::operator VALUE(); + } + + Value::operator v8::Handle() const { + if (rb_equal(value, Empty)) { + return v8::Handle(); + } + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + + switch (TYPE(value)) { + case T_FIXNUM: + return v8::Integer::New(isolate, NUM2INT(value)); + case T_FLOAT: + return v8::Number::New(isolate, NUM2DBL(value)); + case T_STRING: + return v8::String::NewFromUtf8(isolate, RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); + case T_NIL: + return v8::Null(isolate); + case T_TRUE: + return v8::True(isolate); + case T_FALSE: + return v8::False(isolate); + case T_DATA: + return Ref::operator v8::Handle(); + case T_OBJECT: + case T_CLASS: + case T_ICLASS: + case T_MODULE: + case T_REGEXP: + case T_MATCH: + case T_ARRAY: + case T_HASH: + case T_STRUCT: + case T_BIGNUM: + case T_FILE: + case T_SYMBOL: + case T_UNDEF: + case T_NODE: + default: + rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value))); + return v8::String::NewFromUtf8(isolate, "Undefined Conversion"); + } + + return v8::Undefined(isolate); + } + +} diff --git a/ext/v8/value.h b/ext/v8/value.h new file mode 100644 index 00000000..84ac9332 --- /dev/null +++ b/ext/v8/value.h @@ -0,0 +1,51 @@ +#ifndef RR_VALUE +#define RR_VALUE + +namespace rr { + + class Value : public Ref { + public: + static void Init(); + static VALUE IsUndefined(VALUE self); + static VALUE IsNull(VALUE self); + static VALUE IsTrue(VALUE self); + static VALUE IsFalse(VALUE self); + // static VALUE IsString(VALUE self); + // static VALUE IsFunction(VALUE self); + // static VALUE IsArray(VALUE self); + static VALUE IsObject(VALUE self); + static VALUE IsBoolean(VALUE self); + // static VALUE IsNumber(VALUE self); + // static VALUE IsExternal(VALUE self); + // static VALUE IsInt32(VALUE self); + // static VALUE IsUint32(VALUE self); + // static VALUE IsDate(VALUE self); + // static VALUE IsBooleanObject(VALUE self); + // static VALUE IsNumberObject(VALUE self); + // static VALUE IsStringObject(VALUE self); + // static VALUE IsNativeError(VALUE self); + // static VALUE IsRegExp(VALUE self); + // static VALUE ToString(VALUE self); + // static VALUE ToDetailString(VALUE self); + // static VALUE ToObject(VALUE self); + // static VALUE BooleanValue(VALUE self); + // static VALUE NumberValue(VALUE self); + // static VALUE IntegerValue(VALUE self); + // static VALUE Uint32Value(VALUE self); + // static VALUE Int32Value(VALUE self); + + static VALUE Equals(VALUE self, VALUE other); + static VALUE StrictEquals(VALUE self, VALUE other); + + inline Value(VALUE value) : Ref(value) {} + inline Value(v8::Handle value) : Ref(value) {} + + virtual operator VALUE(); + virtual operator v8::Handle() const; + + static VALUE Empty; + }; + +} + +#endif diff --git a/spec/c/handles_spec.rb b/spec/c/handles_spec.rb index e8b31ee2..604a1541 100644 --- a/spec/c/handles_spec.rb +++ b/spec/c/handles_spec.rb @@ -1,4 +1,4 @@ -require 'v8/init' +require 'c_spec_helper' describe 'handles' do describe '#HandleScope' do diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 02124712..41f075c7 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -1,4 +1,4 @@ -require 'v8/init' +require 'c_spec_helper' describe V8::C::Isolate do it 'can create a new isolate' do diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb new file mode 100644 index 00000000..c42f7a22 --- /dev/null +++ b/spec/c/object_spec.rb @@ -0,0 +1,9 @@ +require 'c_spec_helper' + +describe V8::C::Object do + requires_v8_context + + it 'can create a new object' do + expect(V8::C::Object.New).to be + end +end diff --git a/spec/c/v8_spec.rb b/spec/c/v8_spec.rb index aab4a9b3..65c0a189 100644 --- a/spec/c/v8_spec.rb +++ b/spec/c/v8_spec.rb @@ -1,4 +1,4 @@ -require 'v8/init' +require 'c_spec_helper' describe V8::C::V8 do it 'can say its version' do diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb new file mode 100644 index 00000000..bc6864d8 --- /dev/null +++ b/spec/c_spec_helper.rb @@ -0,0 +1,39 @@ +require 'v8/weak' +require 'v8/init' + +module V8ContextHelpers + module GroupMethods + def requires_v8_context + around(:each) do |example| + bootstrap_v8_context(&example) + end + end + end + + def bootstrap_v8_context + isolate = V8::C::Isolate.New + + begin + isolate.Enter + + # V8::C::Locker() do + V8::C::HandleScope(isolate) do + @cxt = V8::C::Context::New(isolate) + begin + @cxt.Enter + yield + ensure + @cxt.Exit + end + end + # end + ensure + isolate.Exit + end + end +end + +RSpec.configure do |c| + c.include V8ContextHelpers + c.extend V8ContextHelpers::GroupMethods +end From cf5c732880b783cfa15336328201657197b1bbf3 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Fri, 20 Mar 2015 21:37:49 +0000 Subject: [PATCH 004/105] Remove a TODO about the disposal of weak persistent handles They ARE disposed of automatically. Source: https://strongloop.com/strongblog/node-js-v0-12-c-apis-breaking/ --- ext/v8/backref.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc index 7b87b23d..980d314b 100644 --- a/ext/v8/backref.cc +++ b/ext/v8/backref.cc @@ -41,8 +41,7 @@ namespace rr { } void Backref::release(const v8::WeakCallbackData& data) { - // TODO: Since data.GetValue() is Local make sure - // the Persistent is disposed of (or will be) at this point. + // The Persistent handle is disposed of automatically. delete data.GetParameter(); } From d0f568cf18f8d618835e157295a2bcc85efd8863 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 21 Mar 2015 09:51:17 +0000 Subject: [PATCH 005/105] Add strings and some conversion options for values --- ext/v8/backref.cc | 2 +- ext/v8/context.cc | 25 +++++++------------- ext/v8/init.cc | 4 ++-- ext/v8/isolate.cc | 1 + ext/v8/isolate.h | 3 +++ ext/v8/pointer.h | 4 ++++ ext/v8/primitive.cc | 10 ++++++++ ext/v8/primitive.h | 16 +++++++++++++ ext/v8/rr.h | 8 +++++-- ext/v8/rr_string.cc | 49 ++++++++++++++++++++++++++++++++++++++ ext/v8/rr_string.h | 22 ++++++++++++++++++ ext/v8/value.cc | 53 ++++++++++++++++++++++++++++-------------- ext/v8/value.h | 11 +++++---- spec/c/isolate_spec.rb | 49 ++++++++++++++++++++++++++++++++++++++ spec/c/object_spec.rb | 48 ++++++++++++++++++++++++++++++++++++++ spec/c/string_spec.rb | 33 ++++++++++++++++++++++++++ spec/c_spec_helper.rb | 5 ++++ 17 files changed, 299 insertions(+), 44 deletions(-) create mode 100644 ext/v8/primitive.cc create mode 100644 ext/v8/primitive.h create mode 100644 ext/v8/rr_string.cc create mode 100644 ext/v8/rr_string.h create mode 100644 spec/c/string_spec.rb diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc index 980d314b..3e27bc4f 100644 --- a/ext/v8/backref.cc +++ b/ext/v8/backref.cc @@ -35,7 +35,7 @@ namespace rr { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Local wrapper = v8::External::New(isolate, this); - (new v8::Persistent(isolate, wrapper))->SetWeak(this, &release); + v8::Persistent(isolate, wrapper).SetWeak(this, &release); return wrapper; } diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 6ffc026f..5c9a6bfb 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -22,23 +22,14 @@ namespace rr { VALUE isolate, extension_configuration, global_template, global_object; rb_scan_args(argc, argv, "13", &isolate, &extension_configuration, &global_template, &global_object); - // TODO: Is this needed? - // v8::Persistent context( - return Context(v8::Context::New( - Isolate(isolate) - // TODO - // , - // ExtensionConfiguration(extension_configuration), - // *ObjectTemplate(global_template), - // *Object(global_object) - )); - //); - - // TODO: Is this needed? - // Context reference(context); - // context.Reset(); - // - // return reference; + return Context(v8::Context::New( + Isolate(isolate) + // TODO + // , + // ExtensionConfiguration(extension_configuration), + // *ObjectTemplate(global_template), + // *Object(global_object) + )); } VALUE Context::Dispose(VALUE self) { diff --git a/ext/v8/init.cc b/ext/v8/init.cc index a620a774..ca702c8d 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -15,13 +15,13 @@ extern "C" { Backref::Init(); Value::Init(); Object::Init(); + Primitive::Init(); + String::Init(); // v8::Locker lock(); // GC::Init(); // Accessor::Init(); // Invocation::Init(); // Signature::Init(); - // Primitive::Init(); - // String::Init(); // Object::Init(); // Array::Init(); // Function::Init(); diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 206361c8..b241659e 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -9,6 +9,7 @@ namespace rr { defineMethod("Enter", &Enter). defineMethod("Exit", &Exit). + defineMethod("Equals", &rr::Isolate::PointerEquals). store(&Class); } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 73e161c6..9824a4f2 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -6,11 +6,14 @@ namespace rr { class Isolate : public Pointer { public: static void Init(); + static VALUE New(VALUE self); static VALUE Enter(VALUE self); static VALUE Exit(VALUE self); static VALUE GetCurrent(VALUE self); + // TODO: Add a Dispose method + inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} diff --git a/ext/v8/pointer.h b/ext/v8/pointer.h index a2c38848..c705bdc9 100644 --- a/ext/v8/pointer.h +++ b/ext/v8/pointer.h @@ -60,6 +60,10 @@ namespace rr { static VALUE Class; + static inline VALUE PointerEquals(VALUE self, VALUE other) { + return Bool(Pointer(self).pointer == Pointer(other).pointer); + } + protected: T* pointer; }; diff --git a/ext/v8/primitive.cc b/ext/v8/primitive.cc new file mode 100644 index 00000000..1f3fe483 --- /dev/null +++ b/ext/v8/primitive.cc @@ -0,0 +1,10 @@ +#include "rr.h" + +namespace rr { + + void Primitive::Init() { + ClassBuilder("Primitive", Value::Class). + store(&Class); + } + +} diff --git a/ext/v8/primitive.h b/ext/v8/primitive.h new file mode 100644 index 00000000..6854912b --- /dev/null +++ b/ext/v8/primitive.h @@ -0,0 +1,16 @@ +#ifndef RR_PRIMITIVE +#define RR_PRIMITIVE + +namespace rr { + + class Primitive: public Ref { + public: + static void Init(); + + inline Primitive(VALUE value) : Ref(value) {} + inline Primitive(v8::Handle primitive) : Ref(primitive) {} + }; + +} + +#endif diff --git a/ext/v8/rr.h b/ext/v8/rr.h index f004a6fc..2ab59813 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -11,6 +11,9 @@ #endif #include "class_builder.h" + +#include "equiv.h" +#include "bool.h" #include "pointer.h" #include "ref.h" @@ -19,12 +22,13 @@ #include "handles.h" #include "context.h" -#include "equiv.h" -#include "bool.h" #include "uint32.h" #include "value.h" #include "backref.h" #include "object.h" +#include "primitive.h" +// This one is named v8_string to avoid name collisions with C's string.h +#include "rr_string.h" #endif diff --git a/ext/v8/rr_string.cc b/ext/v8/rr_string.cc new file mode 100644 index 00000000..eb76bebd --- /dev/null +++ b/ext/v8/rr_string.cc @@ -0,0 +1,49 @@ +#include "rr.h" + +namespace rr { + + void String::Init() { + ClassBuilder("String", Primitive::Class). + defineSingletonMethod("NewFromUtf8", &NewFromUtf8). + defineSingletonMethod("Concat", &Concat). + + defineMethod("Utf8Value", &Utf8Value). + + store(&Class); + } + + VALUE String::NewFromUtf8(VALUE StringClass, VALUE string) { + v8::Local v8_string = v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + + return String(v8_string); + } + + VALUE String::Utf8Value(VALUE self) { + String string(self); + + #ifdef HAVE_RUBY_ENCODING_H + return rb_enc_str_new(*v8::String::Utf8Value(*string), string->Utf8Length(), rb_enc_find("utf-8")); + #else + return rb_str_new(*v8::String::Utf8Value(*string), string->Utf8Length()); + #endif + } + + VALUE String::Concat(VALUE self, VALUE left, VALUE right) { + return String(v8::String::Concat(String(left), String(right))); + } + + String::operator v8::Handle() const { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + + switch (TYPE(value)) { + case T_STRING: + return v8::String::NewFromUtf8(isolate, RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); + case T_DATA: + return Ref::operator v8::Handle(); + default: + VALUE string = rb_funcall(value, rb_intern("to_s"), 0); + return v8::String::NewFromUtf8(isolate, RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + } + } + +} diff --git a/ext/v8/rr_string.h b/ext/v8/rr_string.h new file mode 100644 index 00000000..a57ac6c2 --- /dev/null +++ b/ext/v8/rr_string.h @@ -0,0 +1,22 @@ +#ifndef RR_STRING +#define RR_STRING + +namespace rr { + + class String: public Ref { + public: + static void Init(); + + static VALUE NewFromUtf8(VALUE self, VALUE value); + static VALUE Utf8Value(VALUE self); + static VALUE Concat(VALUE self, VALUE left, VALUE right); + + inline String(VALUE value) : Ref(value) {} + inline String(v8::Handle string) : Ref(string) {} + + virtual operator v8::Handle() const; + }; + +} + +#endif diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 844d98d0..02505857 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -14,22 +14,22 @@ namespace rr { defineMethod("IsNull", &IsNull). defineMethod("IsTrue", &IsTrue). defineMethod("IsFalse", &IsFalse). - // defineMethod("IsString", &IsString). + defineMethod("IsString", &IsString). // defineMethod("IsFunction", &IsFunction). // defineMethod("IsArray", &IsArray). defineMethod("IsObject", &IsObject). // defineMethod("IsBoolean", &IsBoolean). // defineMethod("IsNumber", &IsNumber). - // defineMethod("IsExternal", &IsExternal). - // defineMethod("IsInt32", &IsInt32). - // defineMethod("IsUint32", &IsUint32). + defineMethod("IsExternal", &IsExternal). + defineMethod("IsInt32", &IsInt32). + defineMethod("IsUint32", &IsUint32). // defineMethod("IsDate", &IsDate). // defineMethod("IsBooleanObject", &IsBooleanObject). // defineMethod("IsNumberObject", &IsNumberObject). // defineMethod("IsStringObject", &IsStringObject). // defineMethod("IsNativeError", &IsNativeError). // defineMethod("IsRegExp", &IsRegExp). - // defineMethod("ToString", &ToString). + defineMethod("ToString", &ToString). // defineMethod("ToDetailString", &ToDetailString). // defineMethod("ToObject", &ToObject). // defineMethod("BooleanValue", &BooleanValue). @@ -61,10 +61,30 @@ namespace rr { return Bool(Value(self)->IsFalse()); } + VALUE Value::IsString(VALUE self) { + return Bool(Value(self)->IsString()); + } + VALUE Value::IsObject(VALUE self) { return Bool(Value(self)->IsObject()); } + VALUE Value::IsExternal(VALUE self) { + return Bool(Value(self)->IsExternal()); + } + + VALUE Value::IsInt32(VALUE self) { + return Bool(Value(self)->IsInt32()); + } + + VALUE Value::IsUint32(VALUE self) { + return Bool(Value(self)->IsUint32()); + } + + VALUE Value::ToString(VALUE self) { + return String(Value(self)->ToString()); + } + VALUE Value::Equals(VALUE self, VALUE other) { return Bool(Value(self)->Equals(Value(other))); } @@ -91,14 +111,13 @@ namespace rr { // return External((v8::Handle)v8::External::Cast(*handle)); // } - // TODO - // if (handle->IsUint32()) { - // return UInt32(handle->Uint32Value()); - // } - // - // if (handle->IsInt32()) { - // return INT2FIX(handle->Int32Value()); - // } + if (handle->IsUint32()) { + return UInt32(handle->Uint32Value()); + } + + if (handle->IsInt32()) { + return INT2FIX(handle->Int32Value()); + } if (handle->IsBoolean()) { return handle->BooleanValue() ? Qtrue : Qfalse; @@ -109,11 +128,11 @@ namespace rr { // return rb_float_new(handle->NumberValue()); // } + if (handle->IsString()) { + return String(handle->ToString()); + } + // TODO - // if (handle->IsString()) { - // return String(handle->ToString()); - // } - // // if (handle->IsDate()) { // return Date((v8::Handle)v8::Date::Cast(*handle)); // } diff --git a/ext/v8/value.h b/ext/v8/value.h index 84ac9332..359ae0b4 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -6,26 +6,27 @@ namespace rr { class Value : public Ref { public: static void Init(); + static VALUE IsUndefined(VALUE self); static VALUE IsNull(VALUE self); static VALUE IsTrue(VALUE self); static VALUE IsFalse(VALUE self); - // static VALUE IsString(VALUE self); + static VALUE IsString(VALUE self); // static VALUE IsFunction(VALUE self); // static VALUE IsArray(VALUE self); static VALUE IsObject(VALUE self); static VALUE IsBoolean(VALUE self); // static VALUE IsNumber(VALUE self); - // static VALUE IsExternal(VALUE self); - // static VALUE IsInt32(VALUE self); - // static VALUE IsUint32(VALUE self); + static VALUE IsExternal(VALUE self); + static VALUE IsInt32(VALUE self); + static VALUE IsUint32(VALUE self); // static VALUE IsDate(VALUE self); // static VALUE IsBooleanObject(VALUE self); // static VALUE IsNumberObject(VALUE self); // static VALUE IsStringObject(VALUE self); // static VALUE IsNativeError(VALUE self); // static VALUE IsRegExp(VALUE self); - // static VALUE ToString(VALUE self); + static VALUE ToString(VALUE self); // static VALUE ToDetailString(VALUE self); // static VALUE ToObject(VALUE self); // static VALUE BooleanValue(VALUE self); diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 41f075c7..91c4269e 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -1,7 +1,56 @@ require 'c_spec_helper' describe V8::C::Isolate do + before(:each) { cleanup_isolates } + it 'can create a new isolate' do expect(V8::C::Isolate.New).to be end + + it 'can be tested for equality' do + isolate_one = V8::C::Isolate.New + isolate_two = V8::C::Isolate.New + + expect(isolate_one.Equals(isolate_one)).to eq true + expect(isolate_one.Equals(isolate_two)).to eq false + end + + describe 'GetCurrent' do + it 'can get the current isolate for the thread' do + isolate = V8::C::Isolate.New + isolate.Enter + + expect(V8::C::Isolate.GetCurrent.Equals(isolate)).to eq true + end + + it 'returns nil if there are no entered isolates' do + expect(V8::C::Isolate.GetCurrent).to be_nil + end + + it 'is different accross threads' do + V8::C::Isolate.New.Enter + + Thread.new do + expect(V8::C::Isolate.GetCurrent).to be_nil + end.join + end + end + + it 'can enter and exit multiple times' do + isolate_one = V8::C::Isolate.New + isolate_two = V8::C::Isolate.New + + isolate_one.Enter + isolate_one.Enter + isolate_two.Enter + + expect(V8::C::Isolate.GetCurrent.Equals(isolate_two)).to eq true + isolate_two.Exit + + expect(V8::C::Isolate.GetCurrent.Equals(isolate_one)).to eq true + isolate_one.Exit + + expect(V8::C::Isolate.GetCurrent.Equals(isolate_one)).to eq true + isolate_one.Exit + end end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index c42f7a22..99d5b87c 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -6,4 +6,52 @@ it 'can create a new object' do expect(V8::C::Object.New).to be end + + it 'can store and retrieve a value' do + o = V8::C::Object.New + key = V8::C::String.NewFromUtf8('foo') + value = V8::C::String.NewFromUtf8('bar') + + o.Set(key, value) + expect(o.Get(key).Utf8Value).to eq 'bar' + end + + # TODO: Enable this when the methods are implemented in the extension + # it 'can retrieve all property names' do + # o = V8::C::Object.New + # o.Set(V8::C::String.New('foo'), V8::C::String.New('bar')) + # o.Set(V8::C::String.New('baz'), V8::C::String.New('bang')) + # + # names = o.GetPropertyNames() + # names.Length().should eql 2 + # names.Get(0).Utf8Value().should eql 'foo' + # names.Get(1).Utf8Value().should eql 'baz' + # end + # + # it 'can set an accessor from ruby' do + # o = V8::C::Object.New + # property = V8::C::String.New('statement') + # callback_data = V8::C::String.New('I am Legend') + # left = V8::C::String.New('Yo! ') + # getter = proc do |name, info| + # info.This().StrictEquals(o).should be_true + # info.Holder().StrictEquals(o).should be_true + # V8::C::String::Concat(left, info.Data()) + # end + # setter = proc do |name, value, info| + # left = value + # end + # o.SetAccessor(property, getter, setter, callback_data) + # o.Get(property).Utf8Value().should eql 'Yo! I am Legend' + # o.Set(property, V8::C::String::New('Bro! ')) + # o.Get(property).Utf8Value().should eql 'Bro! I am Legend' + # end + + it 'always returns the same ruby object for the same V8 object' do + one = V8::C::Object.New + two = V8::C::Object.New + + one.Set('two', two) + expect(one.Get('two')).to be two + end end diff --git a/spec/c/string_spec.rb b/spec/c/string_spec.rb new file mode 100644 index 00000000..190e3c3f --- /dev/null +++ b/spec/c/string_spec.rb @@ -0,0 +1,33 @@ +require 'c_spec_helper' + +describe V8::C::String do + requires_v8_context + + it 'can create new strings' do + expect(V8::C::String.NewFromUtf8('test')).to be + end + + it 'can be converted to a ruby string' do + expect(V8::C::String.NewFromUtf8('test').Utf8Value).to eq 'test' + end + + it 'can hold Unicode values outside the Basic Multilingual Plane' do + string = V8::C::String.NewFromUtf8("\u{100000}") + expect(string.Utf8Value).to eq "\u{100000}" + end + + it 'can concatenate strings' do + string_one = V8::C::String.NewFromUtf8('Hello ') + string_two = V8::C::String.NewFromUtf8('World!') + + expect(V8::C::String.Concat(string_one, string_two).Utf8Value).to eq 'Hello World!' + end + + it 'can naturally translate ruby strings into v8 strings' do + expect(V8::C::String.Concat(V8::C::String.NewFromUtf8('Hello '), 'World').Utf8Value).to eq 'Hello World' + end + + it 'can naturally translate ruby objects into v8 strings' do + expect(V8::C::String.Concat(V8::C::String.NewFromUtf8('forty two is '), 42).Utf8Value).to eq 'forty two is 42' + end +end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index bc6864d8..f555295d 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -14,6 +14,7 @@ def bootstrap_v8_context isolate = V8::C::Isolate.New begin + cleanup_isolates isolate.Enter # V8::C::Locker() do @@ -31,6 +32,10 @@ def bootstrap_v8_context isolate.Exit end end + + def cleanup_isolates + V8::C::Isolate.GetCurrent.Exit while V8::C::Isolate.GetCurrent + end end RSpec.configure do |c| From 143bbb12320a3ff60e499bc75bf3a436aa86c691 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 21 Mar 2015 10:54:39 +0000 Subject: [PATCH 006/105] Add tests for V8::C::Value --- spec/c/string_spec.rb | 4 ++++ spec/c/value_spec.rb | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 spec/c/value_spec.rb diff --git a/spec/c/string_spec.rb b/spec/c/string_spec.rb index 190e3c3f..2d5de663 100644 --- a/spec/c/string_spec.rb +++ b/spec/c/string_spec.rb @@ -3,6 +3,10 @@ describe V8::C::String do requires_v8_context + it 'is a primitive' do + expect(V8::C::String.NewFromUtf8('test')).to be_a V8::C::Primitive + end + it 'can create new strings' do expect(V8::C::String.NewFromUtf8('test')).to be end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb new file mode 100644 index 00000000..9335d84b --- /dev/null +++ b/spec/c/value_spec.rb @@ -0,0 +1,44 @@ +require 'c_spec_helper' + +describe V8::C::Value do + requires_v8_context + + def to_value(value) + object = V8::C::Object.New + key = V8::C::String.NewFromUtf8('key') + + object.Set(key, value) + object.Get(key) + end + + it 'converts strings' do + expect(to_value(V8::C::String.NewFromUtf8('value')).Utf8Value).to eq 'value' + end + + it 'converts nil' do + expect(to_value(nil)).to eq nil + end + + it 'converts undefined to nil' do + object = V8::C::Object.New + key = V8::C::String.NewFromUtf8('key') + + expect(object.Get(key)).to eq nil + end + + it 'converts FixNums' do + expect(to_value(42)).to eq 42 + end + + it 'converts booleans' do + expect(to_value(true)).to eq true + expect(to_value(false)).to eq false + end + + it 'converts objects' do + object = V8::C::Object.New + object.Set(V8::C::String.NewFromUtf8('test'), 1) + + expect(to_value(object)).to eq object + end +end From 207158fcdb311407b9cd9cc76f466eecac7497ab Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 21 Mar 2015 20:27:06 +0000 Subject: [PATCH 007/105] Add Locker and Unlocker --- ext/v8/init.cc | 4 +-- ext/v8/locker.cc | 83 +++++++++++++++++++++++++++++++++++++++++++ ext/v8/locker.h | 26 ++++++++++++++ ext/v8/rr.h | 1 + spec/c/locker_spec.rb | 41 +++++++++++++++++++++ spec/c_spec_helper.rb | 25 ++++++------- 6 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 ext/v8/locker.cc create mode 100644 ext/v8/locker.h create mode 100644 spec/c/locker_spec.rb diff --git a/ext/v8/init.cc b/ext/v8/init.cc index ca702c8d..1e68a870 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -17,7 +17,8 @@ extern "C" { Object::Init(); Primitive::Init(); String::Init(); - // v8::Locker lock(); + Locker::Init(); + // GC::Init(); // Accessor::Init(); // Invocation::Init(); @@ -34,7 +35,6 @@ extern "C" { // Message::Init(); // TryCatch::Init(); // Exception::Init(); - // Locker::Init(); // ResourceConstraints::Init(); // HeapStatistics::Init(); } diff --git a/ext/v8/locker.cc b/ext/v8/locker.cc new file mode 100644 index 00000000..acebb322 --- /dev/null +++ b/ext/v8/locker.cc @@ -0,0 +1,83 @@ +#include "rr.h" + +namespace rr { + + void Locker::Init() { + ClassBuilder("Locker"). + defineSingletonMethod("IsLocked", &IsLocked). + defineSingletonMethod("IsActive", &IsActive); + + VALUE v8 = rb_define_module("V8"); + VALUE c = rb_define_module_under(v8, "C"); + + rb_define_singleton_method(c, "Locker", (VALUE (*)(...))&Lock, -1); + rb_define_singleton_method(c, "Unlocker",(VALUE (*)(...))&Unlock, -1); + } + + VALUE Locker::IsLocked(VALUE self, VALUE isolate) { + return Bool(v8::Locker::IsLocked(Isolate(isolate))); + } + + VALUE Locker::IsActive(VALUE self) { + return Bool(v8::Locker::IsActive()); + } + + VALUE Locker::Lock(int argc, VALUE* argv, VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } + + int state = 0; + + VALUE isolate, block; + rb_scan_args(argc, argv, "10&", &isolate, &block); + + VALUE result = setupLockAndCall(Isolate(isolate), &state, block); + + if (state != 0) { + rb_jump_tag(state); + } + + return result; + } + + VALUE Locker::setupLockAndCall(Isolate isolate, int* state, VALUE block) { + v8::Locker locker(isolate); + + return rb_protect(&doLockCall, block, state); + } + + VALUE Locker::doLockCall(VALUE block) { + return rb_funcall(block, rb_intern("call"), 0); + } + + VALUE Locker::Unlock(int argc, VALUE* argv, VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } + + int state = 0; + + VALUE isolate, block; + rb_scan_args(argc, argv, "10&", &isolate, &block); + + VALUE result = setupUnlockAndCall(Isolate(isolate), &state, block); + + if (state != 0) { + rb_jump_tag(state); + } + + return result; + } + + VALUE Locker::setupUnlockAndCall(Isolate isolate, int* state, VALUE block) { + v8::Unlocker unlocker(isolate); + + return rb_protect(&doUnlockCall, block, state); + } + + VALUE Locker::doUnlockCall(VALUE block) { + return rb_funcall(block, rb_intern("call"), 0); + } + +} diff --git a/ext/v8/locker.h b/ext/v8/locker.h new file mode 100644 index 00000000..a733d723 --- /dev/null +++ b/ext/v8/locker.h @@ -0,0 +1,26 @@ +#ifndef RR_LOCKER +#define RR_LOCKER + +namespace rr { + + class Locker { + public: + static void Init(); + + static VALUE IsLocked(VALUE self, VALUE isolate); + static VALUE IsActive(VALUE self); + + static VALUE Lock(int argc, VALUE* argv, VALUE self); + static VALUE Unlock(int argc, VALUE* argv, VALUE self); + + protected: + static VALUE setupLockAndCall(Isolate isolate, int* state, VALUE code); + static VALUE doLockCall(VALUE code); + + static VALUE setupUnlockAndCall(Isolate isolate, int* state, VALUE code); + static VALUE doUnlockCall(VALUE code); + }; + +} + +#endif diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 2ab59813..ddd478c3 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -19,6 +19,7 @@ #include "v8.h" #include "isolate.h" +#include "locker.h" #include "handles.h" #include "context.h" diff --git a/spec/c/locker_spec.rb b/spec/c/locker_spec.rb new file mode 100644 index 00000000..1f960c57 --- /dev/null +++ b/spec/c/locker_spec.rb @@ -0,0 +1,41 @@ +require 'c_spec_helper' + +describe V8::C::Locker do + let(:isolate) { V8::C::Isolate.New } + + it 'can lock and unlock the VM' do + expect(V8::C::Locker::IsLocked(isolate)).to eq false + + V8::C::Locker(isolate) do + expect(V8::C::Locker::IsLocked(isolate)).to eq true + + V8::C::Unlocker(isolate) do + expect(V8::C::Locker::IsLocked(isolate)).to eq false + end + end + + expect(V8::C::Locker::IsLocked(isolate)).to eq false + end + + it 'properly unlocks if an exception is thrown inside a lock block' do + begin + V8::C::Locker(isolate) do + raise 'boom!' + end + rescue + expect(V8::C::Locker::IsLocked(isolate)).to eq false + end + end + + it 'properly re-locks if an exception is thrown inside an un-lock block' do + V8::C::Locker(isolate) do + begin + V8::C::Unlocker(isolate) do + raise 'boom!' + end + rescue + expect(V8::C::Locker::IsLocked(isolate)).to eq true + end + end + end +end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index f555295d..4a45eab6 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -11,24 +11,21 @@ def requires_v8_context end def bootstrap_v8_context + cleanup_isolates + isolate = V8::C::Isolate.New - begin - cleanup_isolates + V8::C::Locker(isolate) do isolate.Enter - - # V8::C::Locker() do - V8::C::HandleScope(isolate) do - @cxt = V8::C::Context::New(isolate) - begin - @cxt.Enter - yield - ensure - @cxt.Exit - end + V8::C::HandleScope(isolate) do + @cxt = V8::C::Context::New(isolate) + begin + @cxt.Enter + yield + ensure + @cxt.Exit end - # end - ensure + end isolate.Exit end end From 7f48722d72a8f88b956828d643dc7c96b136d5f4 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 22 Mar 2015 10:05:49 +0000 Subject: [PATCH 008/105] Dispose of the persistent handles when they are GC'd by ruby --- ext/v8/init.cc | 2 -- ext/v8/ref.h | 11 ++++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 1e68a870..95c8a917 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -19,11 +19,9 @@ extern "C" { String::Init(); Locker::Init(); - // GC::Init(); // Accessor::Init(); // Invocation::Init(); // Signature::Init(); - // Object::Init(); // Array::Init(); // Function::Init(); // Date::Init(); diff --git a/ext/v8/ref.h b/ext/v8/ref.h index 28b77e46..311cb4d1 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -49,7 +49,7 @@ namespace rr { return Qnil; } - return Data_Wrap_Struct(Class, 0, &Holder::enqueue, new Holder(handle)); + return Data_Wrap_Struct(Class, 0, &Holder::destroy, new Holder(handle)); } /* @@ -105,8 +105,13 @@ namespace rr { v8::Persistent* handle; bool disposed_p; - static void enqueue(Holder* holder) { - // TODO + static void destroy(Holder* holder) { + holder->dispose(); + + // TODO: This previously enqueued the holder to be disposed of + // in `AddGCPrologueCallback`. Now that `AddGCPrologueCallback` depends + // on an active Isolate (and must be registered for each one) it + // might be better to just dispose of the object on the spot. // GC::Finalize(holder); } }; From a9268601505664947c472a6a3b645cbc8eda0248 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 4 Apr 2015 14:03:06 +0000 Subject: [PATCH 009/105] Lock inside the C methods and store isolate with every ref --- ext/v8/context.cc | 12 +++++- ext/v8/context.h | 2 +- ext/v8/handles.cc | 7 +++- ext/v8/init.cc | 1 - ext/v8/locker.cc | 83 ------------------------------------------ ext/v8/locker.h | 26 ------------- ext/v8/locks.h | 31 ++++++++++++++++ ext/v8/object.cc | 23 ++++++++---- ext/v8/object.h | 4 +- ext/v8/primitive.h | 2 +- ext/v8/ref.h | 53 ++++++++++++++++++--------- ext/v8/rr.h | 5 ++- ext/v8/rr_string.cc | 18 +++++---- ext/v8/rr_string.h | 4 +- ext/v8/value.cc | 37 ++++++++++++++----- ext/v8/value.h | 9 +++-- spec/c/isolate_spec.rb | 41 --------------------- spec/c/locker_spec.rb | 41 --------------------- spec/c/object_spec.rb | 17 +++++---- spec/c/string_spec.rb | 20 +++------- spec/c/value_spec.rb | 28 ++++++-------- spec/c_spec_helper.rb | 26 ++++--------- 22 files changed, 187 insertions(+), 303 deletions(-) delete mode 100644 ext/v8/locker.cc delete mode 100644 ext/v8/locker.h create mode 100644 ext/v8/locks.h delete mode 100644 spec/c/locker_spec.rb diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 5c9a6bfb..407e769a 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -38,12 +38,20 @@ namespace rr { } VALUE Context::Enter(VALUE self) { - Context(self)->Enter(); + Context context(self); + Locker lock(context.getIsolate()); + + context->Enter(); + return Qnil; } VALUE Context::Exit(VALUE self) { - Context(self)->Exit(); + Context context(self); + Locker lock(context.getIsolate()); + + context->Exit(); + return Qnil; } diff --git a/ext/v8/context.h b/ext/v8/context.h index 106d3767..449465af 100644 --- a/ext/v8/context.h +++ b/ext/v8/context.h @@ -31,7 +31,7 @@ namespace rr { // static VALUE IsCodeGenerationFromStringsAllowed(VALUE self); inline Context(VALUE value) : Ref(value) {} - inline Context(v8::Handle cxt) : Ref(cxt) {} + inline Context(v8::Handle ctx) : Ref(ctx->GetIsolate(), ctx) {} }; } diff --git a/ext/v8/handles.cc b/ext/v8/handles.cc index f1c37fa8..c771fe30 100644 --- a/ext/v8/handles.cc +++ b/ext/v8/handles.cc @@ -25,8 +25,13 @@ namespace rr { } VALUE Handles::SetupAndCall(Isolate isolate, int* state, VALUE block) { + Locker lock(isolate); v8::HandleScope handle_scope(isolate); - return rb_protect(&DoCall, block, state); + + { + Unlocker unlock(isolate); + return rb_protect(&DoCall, block, state); + } } VALUE Handles::DoCall(VALUE block) { diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 95c8a917..795329d1 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -17,7 +17,6 @@ extern "C" { Object::Init(); Primitive::Init(); String::Init(); - Locker::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/locker.cc b/ext/v8/locker.cc deleted file mode 100644 index acebb322..00000000 --- a/ext/v8/locker.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include "rr.h" - -namespace rr { - - void Locker::Init() { - ClassBuilder("Locker"). - defineSingletonMethod("IsLocked", &IsLocked). - defineSingletonMethod("IsActive", &IsActive); - - VALUE v8 = rb_define_module("V8"); - VALUE c = rb_define_module_under(v8, "C"); - - rb_define_singleton_method(c, "Locker", (VALUE (*)(...))&Lock, -1); - rb_define_singleton_method(c, "Unlocker",(VALUE (*)(...))&Unlock, -1); - } - - VALUE Locker::IsLocked(VALUE self, VALUE isolate) { - return Bool(v8::Locker::IsLocked(Isolate(isolate))); - } - - VALUE Locker::IsActive(VALUE self) { - return Bool(v8::Locker::IsActive()); - } - - VALUE Locker::Lock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - - int state = 0; - - VALUE isolate, block; - rb_scan_args(argc, argv, "10&", &isolate, &block); - - VALUE result = setupLockAndCall(Isolate(isolate), &state, block); - - if (state != 0) { - rb_jump_tag(state); - } - - return result; - } - - VALUE Locker::setupLockAndCall(Isolate isolate, int* state, VALUE block) { - v8::Locker locker(isolate); - - return rb_protect(&doLockCall, block, state); - } - - VALUE Locker::doLockCall(VALUE block) { - return rb_funcall(block, rb_intern("call"), 0); - } - - VALUE Locker::Unlock(int argc, VALUE* argv, VALUE self) { - if (!rb_block_given_p()) { - return Qnil; - } - - int state = 0; - - VALUE isolate, block; - rb_scan_args(argc, argv, "10&", &isolate, &block); - - VALUE result = setupUnlockAndCall(Isolate(isolate), &state, block); - - if (state != 0) { - rb_jump_tag(state); - } - - return result; - } - - VALUE Locker::setupUnlockAndCall(Isolate isolate, int* state, VALUE block) { - v8::Unlocker unlocker(isolate); - - return rb_protect(&doUnlockCall, block, state); - } - - VALUE Locker::doUnlockCall(VALUE block) { - return rb_funcall(block, rb_intern("call"), 0); - } - -} diff --git a/ext/v8/locker.h b/ext/v8/locker.h deleted file mode 100644 index a733d723..00000000 --- a/ext/v8/locker.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef RR_LOCKER -#define RR_LOCKER - -namespace rr { - - class Locker { - public: - static void Init(); - - static VALUE IsLocked(VALUE self, VALUE isolate); - static VALUE IsActive(VALUE self); - - static VALUE Lock(int argc, VALUE* argv, VALUE self); - static VALUE Unlock(int argc, VALUE* argv, VALUE self); - - protected: - static VALUE setupLockAndCall(Isolate isolate, int* state, VALUE code); - static VALUE doLockCall(VALUE code); - - static VALUE setupUnlockAndCall(Isolate isolate, int* state, VALUE code); - static VALUE doUnlockCall(VALUE code); - }; - -} - -#endif diff --git a/ext/v8/locks.h b/ext/v8/locks.h new file mode 100644 index 00000000..6563a260 --- /dev/null +++ b/ext/v8/locks.h @@ -0,0 +1,31 @@ +#ifndef RR_LOCKER +#define RR_LOCKER + +namespace rr { + + class Locker { + public: + Locker(v8::Isolate* _isolate): isolate(_isolate), locker(_isolate) { + isolate->Enter(); + } + + ~Locker() { + isolate->Exit(); + } + + protected: + v8::Isolate* isolate; + v8::Locker locker; + }; + + class Unlocker { + public: + Unlocker(v8::Isolate* isolate): unlocker(isolate) {} + + protected: + v8::Unlocker unlocker; + }; + +} + +#endif diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 26cf077b..26daf684 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -12,24 +12,33 @@ namespace rr { store(&Class); } - VALUE Object::New(VALUE self) { - return Object(v8::Object::New(v8::Isolate::GetCurrent())); + VALUE Object::New(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Object(isolate, v8::Object::New(isolate)); } // TODO: Allow setting of property attributes VALUE Object::Set(VALUE self, VALUE key, VALUE value) { + Object object(self); + Locker lock(object.getIsolate()); + if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(Object(self)->Set(UInt32(key), Value(value))); + return Bool(object->Set(UInt32(key), Value::rubyObjectToHandle(object.getIsolate(), value))); } else { - return Bool(Object(self)->Set(*Value(key), Value(value))); + return Bool(object->Set(*Value(key), Value::rubyObjectToHandle(object.getIsolate(), value))); } } VALUE Object::Get(VALUE self, VALUE key) { + Object object(self); + Locker lock(object.getIsolate()); + if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value(Object(self)->Get(UInt32(key))); + return Value::handleToRubyObject(object.getIsolate(), object->Get(UInt32(key))); } else { - return Value(Object(self)->Get(*Value(key))); + return Value::handleToRubyObject(object.getIsolate(), object->Get(*Value(key))); } } @@ -40,7 +49,7 @@ namespace rr { Backref* backref; - v8::Local key(v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), "rr::Backref")); + v8::Local key(v8::String::NewFromUtf8(getIsolate(), "rr::Backref")); v8::Local external = handle->GetHiddenValue(key); VALUE value; diff --git a/ext/v8/object.h b/ext/v8/object.h index 53eef5d5..80c70797 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -6,7 +6,7 @@ namespace rr { class Object : public Ref { public: static void Init(); - static VALUE New(VALUE self); + static VALUE New(VALUE self, VALUE isolate); static VALUE Set(VALUE self, VALUE key, VALUE value); // static VALUE ForceSet(VALUE self, VALUE key, VALUE value); static VALUE Get(VALUE self, VALUE key); @@ -55,7 +55,7 @@ namespace rr { // static VALUE CallAsConstructor(VALUE self, VALUE argv); inline Object(VALUE value) : Ref(value) {} - inline Object(v8::Handle object) : Ref(object) {} + inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} virtual operator VALUE(); diff --git a/ext/v8/primitive.h b/ext/v8/primitive.h index 6854912b..a3e42442 100644 --- a/ext/v8/primitive.h +++ b/ext/v8/primitive.h @@ -8,7 +8,7 @@ namespace rr { static void Init(); inline Primitive(VALUE value) : Ref(value) {} - inline Primitive(v8::Handle primitive) : Ref(primitive) {} + inline Primitive(v8::Isolate* isolate, v8::Handle primitive) : Ref(isolate, primitive) {} }; } diff --git a/ext/v8/ref.h b/ext/v8/ref.h index 311cb4d1..bdaae29c 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -33,9 +33,19 @@ namespace rr { public: Ref(VALUE value) { this->value = value; + Holder* holder = unwrapHolder(); + + if (holder) { + this->isolate = holder->isolate; + this->handle = v8::Local::New(holder->isolate, *holder->handle); + } else { + this->isolate = NULL; + this->handle = v8::Local(); + } } - Ref(v8::Local handle) { + Ref(v8::Isolate* isolate, v8::Local handle) { + this->isolate = isolate; this->handle = handle; } @@ -49,21 +59,18 @@ namespace rr { return Qnil; } - return Data_Wrap_Struct(Class, 0, &Holder::destroy, new Holder(handle)); + return Data_Wrap_Struct(Class, 0, &Holder::destroy, new Holder(isolate, handle)); } /* * Coerce a Ref into a v8::Local. */ virtual operator v8::Handle() const { - if (RTEST(this->value)) { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); + return handle; + } - return v8::Local::New(v8::Isolate::GetCurrent(), *holder->handle); - } else { - return v8::Local(); - } + v8::Isolate* getIsolate() const { + return isolate; } void dispose() { @@ -84,9 +91,10 @@ namespace rr { class Holder { friend class Ref; public: - Holder(v8::Handle handle) { + Holder(v8::Isolate* isolate, v8::Handle handle) { this->disposed_p = false; - this->handle = new v8::Persistent(v8::Isolate::GetCurrent(), handle); + this->isolate = isolate; + this->handle = new v8::Persistent(isolate, handle); } virtual ~Holder() { @@ -102,23 +110,34 @@ namespace rr { } protected: + v8::Isolate* isolate; v8::Persistent* handle; bool disposed_p; static void destroy(Holder* holder) { holder->dispose(); - // TODO: This previously enqueued the holder to be disposed of - // in `AddGCPrologueCallback`. Now that `AddGCPrologueCallback` depends - // on an active Isolate (and must be registered for each one) it - // might be better to just dispose of the object on the spot. - // GC::Finalize(holder); + // TODO: Use a nonblocking queue with `AddGCPrologueCallback` } }; + static VALUE Class; + + protected: + Holder* unwrapHolder() const { + if (RTEST(this->value)) { + Holder* holder = NULL; + Data_Get_Struct(this->value, class Holder, holder); + return holder; + } else { + return NULL; + } + } + VALUE value; + + v8::Isolate* isolate; v8::Handle handle; - static VALUE Class; }; template diff --git a/ext/v8/rr.h b/ext/v8/rr.h index ddd478c3..c85dfbd2 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -15,11 +15,12 @@ #include "equiv.h" #include "bool.h" #include "pointer.h" +#include "isolate.h" + #include "ref.h" #include "v8.h" -#include "isolate.h" -#include "locker.h" +#include "locks.h" #include "handles.h" #include "context.h" diff --git a/ext/v8/rr_string.cc b/ext/v8/rr_string.cc index eb76bebd..1b13bb4a 100644 --- a/ext/v8/rr_string.cc +++ b/ext/v8/rr_string.cc @@ -12,10 +12,12 @@ namespace rr { store(&Class); } - VALUE String::NewFromUtf8(VALUE StringClass, VALUE string) { - v8::Local v8_string = v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + VALUE String::NewFromUtf8(VALUE StringClass, VALUE rb_isolate, VALUE string) { + Isolate isolate(rb_isolate); - return String(v8_string); + v8::Local v8_string = v8::String::NewFromUtf8(isolate, RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + + return String(isolate, v8_string); } VALUE String::Utf8Value(VALUE self) { @@ -29,20 +31,20 @@ namespace rr { } VALUE String::Concat(VALUE self, VALUE left, VALUE right) { - return String(v8::String::Concat(String(left), String(right))); + String left_string(left); + + return String(left_string.getIsolate(), v8::String::Concat(left_string, String(right))); } String::operator v8::Handle() const { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - switch (TYPE(value)) { case T_STRING: - return v8::String::NewFromUtf8(isolate, RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); + return v8::String::NewFromUtf8(getIsolate(), RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); case T_DATA: return Ref::operator v8::Handle(); default: VALUE string = rb_funcall(value, rb_intern("to_s"), 0); - return v8::String::NewFromUtf8(isolate, RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); + return v8::String::NewFromUtf8(getIsolate(), RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); } } diff --git a/ext/v8/rr_string.h b/ext/v8/rr_string.h index a57ac6c2..698dd903 100644 --- a/ext/v8/rr_string.h +++ b/ext/v8/rr_string.h @@ -7,12 +7,12 @@ namespace rr { public: static void Init(); - static VALUE NewFromUtf8(VALUE self, VALUE value); + static VALUE NewFromUtf8(VALUE self, VALUE isolate, VALUE value); static VALUE Utf8Value(VALUE self); static VALUE Concat(VALUE self, VALUE left, VALUE right); inline String(VALUE value) : Ref(value) {} - inline String(v8::Handle string) : Ref(string) {} + inline String(v8::Isolate* isolate, v8::Handle string) : Ref(isolate, string) {} virtual operator v8::Handle() const; }; diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 02505857..22124b46 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -40,6 +40,9 @@ namespace rr { // defineMethod("Equals", &Equals). // defineMethod("StrictEquals", &StrictEquals). + defineMethod("ToRubyObject", &ToRubyObject). + defineSingletonMethod("FromRubyObject", &FromRubyObject). + store(&Class); rb_gc_register_address(&Empty); @@ -82,7 +85,9 @@ namespace rr { } VALUE Value::ToString(VALUE self) { - return String(Value(self)->ToString()); + Value value(self); + + return String(value.getIsolate(), value->ToString()); } VALUE Value::Equals(VALUE self, VALUE other) { @@ -93,7 +98,21 @@ namespace rr { return Bool(Value(self)->StrictEquals(Value(other))); } - Value::operator VALUE() { + VALUE Value::ToRubyObject(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return handleToRubyObject(value.getIsolate(), value); + } + + VALUE Value::FromRubyObject(VALUE selfClass, VALUE rb_isolate, VALUE value) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Value(isolate, rubyObjectToHandle(isolate, value)); + } + + VALUE Value::handleToRubyObject(v8::Isolate* isolate, v8::Handle handle) { if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) { return Qnil; } @@ -129,7 +148,7 @@ namespace rr { // } if (handle->IsString()) { - return String(handle->ToString()); + return String(isolate, handle->ToString()); } // TODO @@ -138,19 +157,19 @@ namespace rr { // } if (handle->IsObject()) { - return Object(handle->ToObject()); + return Object(isolate, handle->ToObject()); } - return Ref::operator VALUE(); + return Value(isolate, handle); } - Value::operator v8::Handle() const { + v8::Handle Value::rubyObjectToHandle(v8::Isolate* isolate, VALUE value) { + Locker lock(isolate); + if (rb_equal(value, Empty)) { return v8::Handle(); } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - switch (TYPE(value)) { case T_FIXNUM: return v8::Integer::New(isolate, NUM2INT(value)); @@ -165,7 +184,7 @@ namespace rr { case T_FALSE: return v8::False(isolate); case T_DATA: - return Ref::operator v8::Handle(); + return Value(value); case T_OBJECT: case T_CLASS: case T_ICLASS: diff --git a/ext/v8/value.h b/ext/v8/value.h index 359ae0b4..9d676e8e 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -35,14 +35,17 @@ namespace rr { // static VALUE Uint32Value(VALUE self); // static VALUE Int32Value(VALUE self); + static VALUE ToRubyObject(VALUE self); + static VALUE FromRubyObject(VALUE self, VALUE isolate, VALUE value); + static VALUE Equals(VALUE self, VALUE other); static VALUE StrictEquals(VALUE self, VALUE other); inline Value(VALUE value) : Ref(value) {} - inline Value(v8::Handle value) : Ref(value) {} + inline Value(v8::Isolate* isolate, v8::Handle value) : Ref(isolate, value) {} - virtual operator VALUE(); - virtual operator v8::Handle() const; + static VALUE handleToRubyObject(v8::Isolate* isolate, v8::Handle value); + static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); static VALUE Empty; }; diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 91c4269e..677a217d 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -1,8 +1,6 @@ require 'c_spec_helper' describe V8::C::Isolate do - before(:each) { cleanup_isolates } - it 'can create a new isolate' do expect(V8::C::Isolate.New).to be end @@ -14,43 +12,4 @@ expect(isolate_one.Equals(isolate_one)).to eq true expect(isolate_one.Equals(isolate_two)).to eq false end - - describe 'GetCurrent' do - it 'can get the current isolate for the thread' do - isolate = V8::C::Isolate.New - isolate.Enter - - expect(V8::C::Isolate.GetCurrent.Equals(isolate)).to eq true - end - - it 'returns nil if there are no entered isolates' do - expect(V8::C::Isolate.GetCurrent).to be_nil - end - - it 'is different accross threads' do - V8::C::Isolate.New.Enter - - Thread.new do - expect(V8::C::Isolate.GetCurrent).to be_nil - end.join - end - end - - it 'can enter and exit multiple times' do - isolate_one = V8::C::Isolate.New - isolate_two = V8::C::Isolate.New - - isolate_one.Enter - isolate_one.Enter - isolate_two.Enter - - expect(V8::C::Isolate.GetCurrent.Equals(isolate_two)).to eq true - isolate_two.Exit - - expect(V8::C::Isolate.GetCurrent.Equals(isolate_one)).to eq true - isolate_one.Exit - - expect(V8::C::Isolate.GetCurrent.Equals(isolate_one)).to eq true - isolate_one.Exit - end end diff --git a/spec/c/locker_spec.rb b/spec/c/locker_spec.rb deleted file mode 100644 index 1f960c57..00000000 --- a/spec/c/locker_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'c_spec_helper' - -describe V8::C::Locker do - let(:isolate) { V8::C::Isolate.New } - - it 'can lock and unlock the VM' do - expect(V8::C::Locker::IsLocked(isolate)).to eq false - - V8::C::Locker(isolate) do - expect(V8::C::Locker::IsLocked(isolate)).to eq true - - V8::C::Unlocker(isolate) do - expect(V8::C::Locker::IsLocked(isolate)).to eq false - end - end - - expect(V8::C::Locker::IsLocked(isolate)).to eq false - end - - it 'properly unlocks if an exception is thrown inside a lock block' do - begin - V8::C::Locker(isolate) do - raise 'boom!' - end - rescue - expect(V8::C::Locker::IsLocked(isolate)).to eq false - end - end - - it 'properly re-locks if an exception is thrown inside an un-lock block' do - V8::C::Locker(isolate) do - begin - V8::C::Unlocker(isolate) do - raise 'boom!' - end - rescue - expect(V8::C::Locker::IsLocked(isolate)).to eq true - end - end - end -end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 99d5b87c..7d62bc55 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -4,13 +4,13 @@ requires_v8_context it 'can create a new object' do - expect(V8::C::Object.New).to be + expect(V8::C::Object.New(@isolate)).to be end it 'can store and retrieve a value' do - o = V8::C::Object.New - key = V8::C::String.NewFromUtf8('foo') - value = V8::C::String.NewFromUtf8('bar') + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + value = V8::C::String.NewFromUtf8(@isolate, 'bar') o.Set(key, value) expect(o.Get(key).Utf8Value).to eq 'bar' @@ -48,10 +48,11 @@ # end it 'always returns the same ruby object for the same V8 object' do - one = V8::C::Object.New - two = V8::C::Object.New + key = V8::C::String.NewFromUtf8(@isolate, 'two') + one = V8::C::Object.New(@isolate) + two = V8::C::Object.New(@isolate) - one.Set('two', two) - expect(one.Get('two')).to be two + one.Set(key, two) + expect(one.Get(key)).to be two end end diff --git a/spec/c/string_spec.rb b/spec/c/string_spec.rb index 2d5de663..e7773287 100644 --- a/spec/c/string_spec.rb +++ b/spec/c/string_spec.rb @@ -4,34 +4,26 @@ requires_v8_context it 'is a primitive' do - expect(V8::C::String.NewFromUtf8('test')).to be_a V8::C::Primitive + expect(V8::C::String.NewFromUtf8(@isolate, 'test')).to be_a V8::C::Primitive end it 'can create new strings' do - expect(V8::C::String.NewFromUtf8('test')).to be + expect(V8::C::String.NewFromUtf8(@isolate, 'test')).to be end it 'can be converted to a ruby string' do - expect(V8::C::String.NewFromUtf8('test').Utf8Value).to eq 'test' + expect(V8::C::String.NewFromUtf8(@isolate, 'test').Utf8Value).to eq 'test' end it 'can hold Unicode values outside the Basic Multilingual Plane' do - string = V8::C::String.NewFromUtf8("\u{100000}") + string = V8::C::String.NewFromUtf8(@isolate, "\u{100000}") expect(string.Utf8Value).to eq "\u{100000}" end it 'can concatenate strings' do - string_one = V8::C::String.NewFromUtf8('Hello ') - string_two = V8::C::String.NewFromUtf8('World!') + string_one = V8::C::String.NewFromUtf8(@isolate, 'Hello ') + string_two = V8::C::String.NewFromUtf8(@isolate, 'World!') expect(V8::C::String.Concat(string_one, string_two).Utf8Value).to eq 'Hello World!' end - - it 'can naturally translate ruby strings into v8 strings' do - expect(V8::C::String.Concat(V8::C::String.NewFromUtf8('Hello '), 'World').Utf8Value).to eq 'Hello World' - end - - it 'can naturally translate ruby objects into v8 strings' do - expect(V8::C::String.Concat(V8::C::String.NewFromUtf8('forty two is '), 42).Utf8Value).to eq 'forty two is 42' - end end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb index 9335d84b..16b5a9cd 100644 --- a/spec/c/value_spec.rb +++ b/spec/c/value_spec.rb @@ -3,42 +3,38 @@ describe V8::C::Value do requires_v8_context - def to_value(value) - object = V8::C::Object.New - key = V8::C::String.NewFromUtf8('key') - - object.Set(key, value) - object.Get(key) + def convert(value) + V8::C::Value.FromRubyObject(@isolate, value).ToRubyObject end it 'converts strings' do - expect(to_value(V8::C::String.NewFromUtf8('value')).Utf8Value).to eq 'value' + expect(convert('value').Utf8Value).to eq 'value' end it 'converts nil' do - expect(to_value(nil)).to eq nil + expect(convert(nil)).to eq nil end it 'converts undefined to nil' do - object = V8::C::Object.New - key = V8::C::String.NewFromUtf8('key') + object = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'key') expect(object.Get(key)).to eq nil end it 'converts FixNums' do - expect(to_value(42)).to eq 42 + expect(convert(42)).to eq 42 end it 'converts booleans' do - expect(to_value(true)).to eq true - expect(to_value(false)).to eq false + expect(convert(true)).to eq true + expect(convert(false)).to eq false end it 'converts objects' do - object = V8::C::Object.New - object.Set(V8::C::String.NewFromUtf8('test'), 1) + object = V8::C::Object.New(@isolate) + object.Set(V8::C::String.NewFromUtf8(@isolate, 'test'), 1) - expect(to_value(object)).to eq object + expect(convert(object)).to eq object end end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index 4a45eab6..62397cbc 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -11,28 +11,18 @@ def requires_v8_context end def bootstrap_v8_context - cleanup_isolates + @isolate = V8::C::Isolate.New - isolate = V8::C::Isolate.New - - V8::C::Locker(isolate) do - isolate.Enter - V8::C::HandleScope(isolate) do - @cxt = V8::C::Context::New(isolate) - begin - @cxt.Enter - yield - ensure - @cxt.Exit - end + V8::C::HandleScope(@isolate) do + @cxt = V8::C::Context::New(@isolate) + begin + @cxt.Enter + yield + ensure + @cxt.Exit end - isolate.Exit end end - - def cleanup_isolates - V8::C::Isolate.GetCurrent.Exit while V8::C::Isolate.GetCurrent - end end RSpec.configure do |c| From 52e52c1a04d968e8828373adfb866d652250ad4d Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 4 Apr 2015 14:16:15 +0000 Subject: [PATCH 010/105] Remove #Enter, #Exit and ::GetCurrent from C::Isolate Also lock on some more places --- ext/v8/backref.cc | 4 +--- ext/v8/backref.h | 2 +- ext/v8/context.cc | 9 ++++--- ext/v8/isolate.cc | 23 ------------------ ext/v8/isolate.h | 3 --- ext/v8/object.cc | 6 ++++- ext/v8/rr_string.cc | 5 ++++ ext/v8/value.cc | 58 +++++++++++++++++++++++++++++++++++---------- 8 files changed, 63 insertions(+), 47 deletions(-) diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc index 3e27bc4f..60a67dab 100644 --- a/ext/v8/backref.cc +++ b/ext/v8/backref.cc @@ -31,9 +31,7 @@ namespace rr { return rb_funcall(storage, object, 0); } - v8::Handle Backref::toExternal() { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - + v8::Handle Backref::toExternal(v8::Isolate* isolate) { v8::Local wrapper = v8::External::New(isolate, this); v8::Persistent(isolate, wrapper).SetWeak(this, &release); diff --git a/ext/v8/backref.h b/ext/v8/backref.h index c3b5cac6..c835774f 100644 --- a/ext/v8/backref.h +++ b/ext/v8/backref.h @@ -14,7 +14,7 @@ namespace rr { VALUE get(); VALUE set(VALUE value); - v8::Handle toExternal(); + v8::Handle toExternal(v8::Isolate*); static void release(const v8::WeakCallbackData& data); private: diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 407e769a..4789c120 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -19,11 +19,14 @@ namespace rr { } VALUE Context::New(int argc, VALUE argv[], VALUE self) { - VALUE isolate, extension_configuration, global_template, global_object; - rb_scan_args(argc, argv, "13", &isolate, &extension_configuration, &global_template, &global_object); + VALUE rb_isolate, extension_configuration, global_template, global_object; + rb_scan_args(argc, argv, "13", &rb_isolate, &extension_configuration, &global_template, &global_object); + + Isolate isolate(rb_isolate); + Locker lock(isolate); return Context(v8::Context::New( - Isolate(isolate) + isolate // TODO // , // ExtensionConfiguration(extension_configuration), diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index b241659e..d989ee03 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -5,10 +5,7 @@ namespace rr { void Isolate::Init() { ClassBuilder("Isolate"). defineSingletonMethod("New", &New). - defineSingletonMethod("GetCurrent", &GetCurrent). - defineMethod("Enter", &Enter). - defineMethod("Exit", &Exit). defineMethod("Equals", &rr::Isolate::PointerEquals). store(&Class); @@ -18,26 +15,6 @@ namespace rr { return Isolate(v8::Isolate::New()); } - VALUE Isolate::Enter(VALUE self) { - Isolate(self)->Enter(); - return Qtrue; - } - - VALUE Isolate::Exit(VALUE self) { - Isolate(self)->Exit(); - return Qtrue; - } - - VALUE Isolate::GetCurrent(VALUE self) { - v8::Isolate* currentIsolate = v8::Isolate::GetCurrent(); - - if (!currentIsolate) { - return Qnil; - } - - return Isolate(currentIsolate); - } - template <> void Pointer::unwrap(VALUE value) { Data_Get_Struct(value, class v8::Isolate, pointer); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 9824a4f2..b77f8ece 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -8,9 +8,6 @@ namespace rr { static void Init(); static VALUE New(VALUE self); - static VALUE Enter(VALUE self); - static VALUE Exit(VALUE self); - static VALUE GetCurrent(VALUE self); // TODO: Add a Dispose method diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 26daf684..45253858 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -43,6 +43,8 @@ namespace rr { } Object::operator VALUE() { + Locker lock(getIsolate()); + if (handle.IsEmpty()) { return Qnil; } @@ -58,7 +60,7 @@ namespace rr { value = downcast(); backref = new Backref(value); - handle->SetHiddenValue(key, backref->toExternal()); + handle->SetHiddenValue(key, backref->toExternal(getIsolate())); } else { v8::External* wrapper = v8::External::Cast(*external); backref = (Backref*)wrapper->Value(); @@ -74,6 +76,8 @@ namespace rr { } VALUE Object::downcast() { + Locker lock(getIsolate()); + // TODO: Enable this when the methods are implemented // if (handle->IsFunction()) { // return Function((v8::Handle) v8::Function::Cast(*handle)); diff --git a/ext/v8/rr_string.cc b/ext/v8/rr_string.cc index 1b13bb4a..fe87b5ca 100644 --- a/ext/v8/rr_string.cc +++ b/ext/v8/rr_string.cc @@ -14,6 +14,7 @@ namespace rr { VALUE String::NewFromUtf8(VALUE StringClass, VALUE rb_isolate, VALUE string) { Isolate isolate(rb_isolate); + Locker lock(isolate); v8::Local v8_string = v8::String::NewFromUtf8(isolate, RSTRING_PTR(string), v8::String::kNormalString, (int)RSTRING_LEN(string)); @@ -22,6 +23,7 @@ namespace rr { VALUE String::Utf8Value(VALUE self) { String string(self); + Locker lock(string.getIsolate()); #ifdef HAVE_RUBY_ENCODING_H return rb_enc_str_new(*v8::String::Utf8Value(*string), string->Utf8Length(), rb_enc_find("utf-8")); @@ -32,11 +34,14 @@ namespace rr { VALUE String::Concat(VALUE self, VALUE left, VALUE right) { String left_string(left); + Locker lock(left_string.getIsolate()); return String(left_string.getIsolate(), v8::String::Concat(left_string, String(right))); } String::operator v8::Handle() const { + Locker lock(getIsolate()); + switch (TYPE(value)) { case T_STRING: return v8::String::NewFromUtf8(getIsolate(), RSTRING_PTR(value), v8::String::kNormalString, (int)RSTRING_LEN(value)); diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 22124b46..01bdb937 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -49,53 +49,87 @@ namespace rr { } VALUE Value::IsUndefined(VALUE self) { - return Bool(Value(self)->IsUndefined()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsUndefined()); } VALUE Value::IsNull(VALUE self) { - return Bool(Value(self)->IsNull()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsNull()); } VALUE Value::IsTrue(VALUE self) { - return Bool(Value(self)->IsTrue()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsTrue()); } VALUE Value::IsFalse(VALUE self) { - return Bool(Value(self)->IsFalse()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsFalse()); } VALUE Value::IsString(VALUE self) { - return Bool(Value(self)->IsString()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsString()); } VALUE Value::IsObject(VALUE self) { - return Bool(Value(self)->IsObject()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsObject()); } VALUE Value::IsExternal(VALUE self) { - return Bool(Value(self)->IsExternal()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsExternal()); } VALUE Value::IsInt32(VALUE self) { - return Bool(Value(self)->IsInt32()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsInt32()); } VALUE Value::IsUint32(VALUE self) { - return Bool(Value(self)->IsUint32()); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsUint32()); } VALUE Value::ToString(VALUE self) { Value value(self); + Locker lock(value.getIsolate()); return String(value.getIsolate(), value->ToString()); } VALUE Value::Equals(VALUE self, VALUE other) { - return Bool(Value(self)->Equals(Value(other))); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->Equals(Value(other))); } VALUE Value::StrictEquals(VALUE self, VALUE other) { - return Bool(Value(self)->StrictEquals(Value(other))); + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->StrictEquals(Value(other))); } VALUE Value::ToRubyObject(VALUE self) { @@ -164,8 +198,6 @@ namespace rr { } v8::Handle Value::rubyObjectToHandle(v8::Isolate* isolate, VALUE value) { - Locker lock(isolate); - if (rb_equal(value, Empty)) { return v8::Handle(); } From acf9be187321c99162ef2a0253a01974106d9ee0 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 4 Apr 2015 18:23:23 +0000 Subject: [PATCH 011/105] Implement Function and Script --- ext/v8/context.cc | 9 ++++ ext/v8/context.h | 3 +- ext/v8/function.cc | 95 +++++++++++++++++++++++++++++++++++++++++ ext/v8/function.h | 26 +++++++++++ ext/v8/init.cc | 4 +- ext/v8/ref.h | 20 +++++++++ ext/v8/rr.h | 9 ++++ ext/v8/script.cc | 48 +++++++++++++++++++++ ext/v8/script.h | 19 +++++++++ ext/v8/value.cc | 23 +++++++++- ext/v8/value.h | 4 +- spec/c/function_spec.rb | 53 +++++++++++++++++++++++ spec/c/script_spec.rb | 32 ++++++++++++++ spec/c_spec_helper.rb | 6 +-- 14 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 ext/v8/function.cc create mode 100644 ext/v8/function.h create mode 100644 ext/v8/script.cc create mode 100644 ext/v8/script.h create mode 100644 spec/c/function_spec.rb create mode 100644 spec/c/script_spec.rb diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 4789c120..e1594b40 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -10,6 +10,8 @@ namespace rr { defineMethod("Enter", &Enter). defineMethod("Exit", &Exit). + defineMethod("Global", &Global). + store(&Class); // TODO @@ -58,6 +60,13 @@ namespace rr { return Qnil; } + VALUE Context::Global(VALUE self) { + Context context(self); + Locker lock(context.getIsolate()); + + return Object(context->GetIsolate(), context->Global()); + } + // TODO // template <> void Pointer::unwrap(VALUE value) { // Data_Get_Struct(value, class v8::ExtensionConfiguration, pointer); diff --git a/ext/v8/context.h b/ext/v8/context.h index 449465af..967000c7 100644 --- a/ext/v8/context.h +++ b/ext/v8/context.h @@ -13,8 +13,9 @@ namespace rr { static VALUE Enter(VALUE self); static VALUE Exit(VALUE self); + static VALUE Global(VALUE self); + // TODO - // static VALUE Global(VALUE self); // static VALUE DetachGlobal(VALUE self); // static VALUE ReattachGlobal(VALUE self, VALUE global); // static VALUE GetEntered(VALUE self); diff --git a/ext/v8/function.cc b/ext/v8/function.cc new file mode 100644 index 00000000..83cbe136 --- /dev/null +++ b/ext/v8/function.cc @@ -0,0 +1,95 @@ +#include "rr.h" + +namespace rr { + + void Function::Init() { + ClassBuilder("Function", Object::Class). + + defineMethod("NewInstance", &NewInstance). + defineMethod("Call", &Call). + defineMethod("SetName", &SetName). + defineMethod("GetName", &GetName). + defineMethod("GetInferredName", &GetInferredName). + defineMethod("GetScriptLineNumber", &GetScriptLineNumber). + defineMethod("GetScriptColumnNumber", &GetScriptColumnNumber). + defineMethod("GetScriptId", &GetScriptId). + defineMethod("GetScriptOrigin", &GetScriptOrigin). + + store(&Class); + } + + VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { + VALUE args; + rb_scan_args(argc, argv, "01", &args); + + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + + if (RTEST(args)) { + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, args)); + return Object(isolate, function->NewInstance(RARRAY_LENINT(args), &vector[0])); + } else { + return Object(isolate, function->NewInstance()); + } + } + + VALUE Function::Call(VALUE self, VALUE receiver, VALUE argv) { + Function function(self); + v8::Isolate* isolate = function.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); + + return Value::handleToRubyObject(isolate, function->Call(Value(receiver), RARRAY_LENINT(argv), &vector[0])); + } + + VALUE Function::SetName(VALUE self, VALUE name) { + Function function(self); + Locker lock(function.getIsolate()); + + function->SetName(String(name)); + + return Qnil; + } + + VALUE Function::GetName(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return Value::handleToRubyObject(function.getIsolate(), function->GetName()); + } + + VALUE Function::GetInferredName(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return Value::handleToRubyObject(function.getIsolate(), function->GetInferredName()); + } + + VALUE Function::GetScriptLineNumber(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptLineNumber()); + } + + VALUE Function::GetScriptColumnNumber(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->GetScriptColumnNumber()); + } + + VALUE Function::GetScriptId(VALUE self) { + Function function(self); + Locker lock(function.getIsolate()); + + return INT2FIX(function->ScriptId()); + } + + VALUE Function::GetScriptOrigin(VALUE self) { + return not_implemented("GetScriptOrigin"); + } + +} diff --git a/ext/v8/function.h b/ext/v8/function.h new file mode 100644 index 00000000..60c489be --- /dev/null +++ b/ext/v8/function.h @@ -0,0 +1,26 @@ +#ifndef RR_FUNCTION +#define RR_FUNCTION + +namespace rr { + + class Function : public Ref { + public: + static void Init(); + + static VALUE NewInstance(int argc, VALUE argv[], VALUE self); + static VALUE Call(VALUE self, VALUE receiver, VALUE argv); + static VALUE SetName(VALUE self, VALUE name); + static VALUE GetName(VALUE self); + static VALUE GetInferredName(VALUE self); + static VALUE GetScriptLineNumber(VALUE self); + static VALUE GetScriptColumnNumber(VALUE self); + static VALUE GetScriptId(VALUE self); + static VALUE GetScriptOrigin(VALUE self); + + inline Function(VALUE value) : Ref(value) {} + inline Function(v8::Isolate* isolate, v8::Handle function) : Ref(isolate, function) {} + }; + +} + +#endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 795329d1..af60e348 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -17,16 +17,16 @@ extern "C" { Object::Init(); Primitive::Init(); String::Init(); + Function::Init(); + Script::Init(); // Accessor::Init(); // Invocation::Init(); // Signature::Init(); // Array::Init(); - // Function::Init(); // Date::Init(); // Constants::Init(); // External::Init(); - // Script::Init(); // Template::Init(); // Stack::Init(); // Message::Init(); diff --git a/ext/v8/ref.h b/ext/v8/ref.h index bdaae29c..d708b176 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -138,6 +138,26 @@ namespace rr { v8::Isolate* isolate; v8::Handle handle; + + public: + template + class array { + public: + inline array(VALUE ary) : argv(ary), vector(RARRAY_LENINT(argv)) { } + + inline operator v8::Handle*() { + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = C(rb_ary_entry(argv, i)); + } + + return &vector[0]; + } + + private: + VALUE argv; + std::vector< v8::Handle > vector; + }; + }; template diff --git a/ext/v8/rr.h b/ext/v8/rr.h index c85dfbd2..edc478ea 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -10,6 +10,12 @@ #include "ruby/encoding.h" #endif +inline VALUE not_implemented(const char* message) { + rb_raise(rb_eStandardError, "not yet implemented %s", message); + + return Qnil; +} + #include "class_builder.h" #include "equiv.h" @@ -33,4 +39,7 @@ // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" +#include "function.h" +#include "script.h" + #endif diff --git a/ext/v8/script.cc b/ext/v8/script.cc new file mode 100644 index 00000000..75f26200 --- /dev/null +++ b/ext/v8/script.cc @@ -0,0 +1,48 @@ +#include "rr.h" + +namespace rr { + + void Script::Init() { + ClassBuilder("Script"). + defineSingletonMethod("Compile", &Compile). + + defineMethod("Run", &Run). + // TODO + // defineMethod("RunWithTimeout", &RunWithTimeout). + + store(&Class); + + // TODO + // ClassBuilder("ScriptOrigin"). + // defineSingletonMethod("new", &ScriptOrigin::initialize). + // store(&ScriptOrigin::Class); + + // TODO + // ClassBuilder("ScriptData"). + // defineSingletonMethod("PreCompile", &ScriptData::PreCompile). + // defineSingletonMethod("New", &ScriptData::New). + // defineMethod("Length", &ScriptData::Length). + // defineMethod("Data", &ScriptData::Data). + // defineMethod("HasError", &ScriptData::HasError). + // store(&ScriptData::Class); + } + + VALUE Script::Compile(int argc, VALUE argv[], VALUE self) { + VALUE source, rb_context, origin; + rb_scan_args(argc, argv, "21", &source, &rb_context, &origin); + + Context context(rb_context); + Locker lock(context.getIsolate()); + + // TODO: ScriptOrigin + return Script(context.getIsolate(), v8::Script::Compile(String(source))); + } + + VALUE Script::Run(VALUE self, VALUE rb_context) { + Context context(rb_context); + Locker lock(context->GetIsolate()); + + return Value::handleToRubyObject(context->GetIsolate(), Script(self)->Run()); + } + +} diff --git a/ext/v8/script.h b/ext/v8/script.h new file mode 100644 index 00000000..9ed9d3c8 --- /dev/null +++ b/ext/v8/script.h @@ -0,0 +1,19 @@ +#ifndef RR_SCRIPT +#define RR_SCRIPT + +namespace rr { + + class Script : public Ref { + public: + static void Init(); + static VALUE Compile(int argc, VALUE argv[], VALUE self); + static VALUE Run(VALUE self, VALUE context); + // static VALUE RunWithTimeout(VALUE self, VALUE timeout); + + inline Script(VALUE value) : Ref(value) {} + inline Script(v8::Isolate* isolate, v8::Handle script) : Ref(isolate, script) {} + }; + +} + +#endif diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 01bdb937..b4e4f3f7 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -15,7 +15,7 @@ namespace rr { defineMethod("IsTrue", &IsTrue). defineMethod("IsFalse", &IsFalse). defineMethod("IsString", &IsString). - // defineMethod("IsFunction", &IsFunction). + defineMethod("IsFunction", &IsFunction). // defineMethod("IsArray", &IsArray). defineMethod("IsObject", &IsObject). // defineMethod("IsBoolean", &IsBoolean). @@ -83,6 +83,13 @@ namespace rr { return Bool(value->IsString()); } + VALUE Value::IsFunction(VALUE self) { + Value value(self); + Locker lock(value.getIsolate()); + + return Bool(value->IsFunction()); + } + VALUE Value::IsObject(VALUE self) { Value value(self); Locker lock(value.getIsolate()); @@ -190,6 +197,10 @@ namespace rr { // return Date((v8::Handle)v8::Date::Cast(*handle)); // } + if (handle->IsFunction()) { + return Function(isolate, v8::Handle::Cast(handle)); + } + if (handle->IsObject()) { return Object(isolate, handle->ToObject()); } @@ -239,4 +250,14 @@ namespace rr { return v8::Undefined(isolate); } + std::vector< v8::Handle > Value::convertRubyArray(v8::Isolate* isolate, VALUE value) { + std::vector< v8::Handle > vector(RARRAY_LENINT(value)); + + for (uint32_t i = 0; i < vector.size(); i++) { + vector[i] = Value::rubyObjectToHandle(isolate, rb_ary_entry(value, i)); + } + + return vector; + } + } diff --git a/ext/v8/value.h b/ext/v8/value.h index 9d676e8e..c7a9be65 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -12,7 +12,7 @@ namespace rr { static VALUE IsTrue(VALUE self); static VALUE IsFalse(VALUE self); static VALUE IsString(VALUE self); - // static VALUE IsFunction(VALUE self); + static VALUE IsFunction(VALUE self); // static VALUE IsArray(VALUE self); static VALUE IsObject(VALUE self); static VALUE IsBoolean(VALUE self); @@ -47,6 +47,8 @@ namespace rr { static VALUE handleToRubyObject(v8::Isolate* isolate, v8::Handle value); static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); + static std::vector< v8::Handle > convertRubyArray(v8::Isolate* isolate, VALUE value); + static VALUE Empty; }; diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb new file mode 100644 index 00000000..76ed15cb --- /dev/null +++ b/spec/c/function_spec.rb @@ -0,0 +1,53 @@ +require 'c_spec_helper' + +describe V8::C::Function do + requires_v8_context + + it 'can be called' do + fn = run '(function() { return "foo" })' + expect(fn.Call(@ctx.Global, []).Utf8Value).to eq 'foo' + end + + it 'can be called with arguments and context' do + fn = run '(function(one, two, three) {this.one = one; this.two = two; this.three = three})' + + one = V8::C::Object.New(@isolate) + two = V8::C::Object.New(@isolate) + + fn.Call(@ctx.Global, [one, two, 3]) + + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'one'))).to eq one + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'two'))).to eq two + expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'three'))).to eq 3 + end + + it 'can be called as a constructor' do + fn = run '(function() {this.foo = "foo"})' + expect(fn.NewInstance.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'foo' + end + + # it 'can be called as a constructor with arguments' do + # fn = run '(function(foo) {this.foo = foo})' + # object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) + + # expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' + # end + + # TODO + # it 'doesn\'t kill the world if invoking it throws a javascript exception' do + # V8::C::TryCatch do + # fn = run '(function() { throw new Error("boom!")})' + # fn.Call(@ctx.Global(), []) + # fn.NewInstance([]) + # end + # end + + def run(source) + source = V8::C::String.NewFromUtf8(@isolate, source.to_s) + filename = V8::C::String.NewFromUtf8(@isolate, "") + script = V8::C::Script.Compile(source, filename) + result = script.Run(@ctx) + + result.kind_of?(V8::C::String) ? result.Utf8Value : result + end +end diff --git a/spec/c/script_spec.rb b/spec/c/script_spec.rb new file mode 100644 index 00000000..d7cc61a5 --- /dev/null +++ b/spec/c/script_spec.rb @@ -0,0 +1,32 @@ +require 'c_spec_helper' + +describe V8::C::Script do + requires_v8_context + + # TODO + # it 'can run a script and return a polymorphic result' do + # source = V8::C::String::New("(new Array())") + # script = V8::C::Script::New(source) + # + # result = script.Run() + # expect(result).to be_an V8::C::Array + # end + + # TODO + # it 'can accept precompiled script data' do + # source = "7 * 6" + # name = V8::C::String::New("") + # origin = V8::C::ScriptOrigin.new(name) + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_false + # script = V8::C::Script::New(V8::C::String::New(source), origin, data) + # script.Run().should eql 42 + # end + + # TODO + # it 'can detect errors in the script data' do + # source = "^ = ;" + # data = V8::C::ScriptData::PreCompile(source, source.length) + # data.HasError().should be_true + # end +end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index 62397cbc..c5e52d53 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -14,12 +14,12 @@ def bootstrap_v8_context @isolate = V8::C::Isolate.New V8::C::HandleScope(@isolate) do - @cxt = V8::C::Context::New(@isolate) + @ctx = V8::C::Context::New(@isolate) begin - @cxt.Enter + @ctx.Enter yield ensure - @cxt.Exit + @ctx.Exit end end end From 93ae00ce0847bb85d624c80c97fc106b7509f262 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 5 Apr 2015 10:40:55 +0000 Subject: [PATCH 012/105] Add Array --- ext/v8/array.cc | 40 ++++++++++++++++++++++++++++++++++++++++ ext/v8/array.h | 20 ++++++++++++++++++++ ext/v8/init.cc | 2 +- ext/v8/ref.h | 4 ++-- ext/v8/rr.h | 1 + ext/v8/value.cc | 4 ++-- spec/c/array_spec.rb | 23 +++++++++++++++++++++++ 7 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 ext/v8/array.cc create mode 100644 ext/v8/array.h create mode 100644 spec/c/array_spec.rb diff --git a/ext/v8/array.cc b/ext/v8/array.cc new file mode 100644 index 00000000..1e3c7fdd --- /dev/null +++ b/ext/v8/array.cc @@ -0,0 +1,40 @@ +#include "rr.h" + +namespace rr { + + void Array::Init() { + ClassBuilder("Array", Object::Class). + + defineSingletonMethod("New", &New). + + defineMethod("Length", &Length). + defineMethod("CloneElementAt", &CloneElementAt). + + store(&Class); + } + + VALUE Array::New(int argc, VALUE argv[], VALUE self) { + VALUE rb_isolate, length; + rb_scan_args(argc, argv, "11", &rb_isolate, &length); + + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Array(isolate, v8::Array::New(isolate, RTEST(length) ? NUM2INT(length) : 0)); + } + + VALUE Array::Length(VALUE self) { + Array array(self); + Locker lock(array.getIsolate()); + + return UInt32(array->Length()); + } + + VALUE Array::CloneElementAt(VALUE self, VALUE index) { + Array array(self); + Locker lock(array.getIsolate()); + + return Object(array.getIsolate(), array->CloneElementAt(UInt32(index))); + } + +} diff --git a/ext/v8/array.h b/ext/v8/array.h new file mode 100644 index 00000000..da150a91 --- /dev/null +++ b/ext/v8/array.h @@ -0,0 +1,20 @@ +#ifndef RR_ARRAY +#define RR_ARRAY + +namespace rr { + + class Array : public Ref { + public: + static void Init(); + + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE Length(VALUE self); + static VALUE CloneElementAt(VALUE self, VALUE index); + + inline Array(v8::Isolate* isolate, v8::Handle array) : Ref(isolate, array) {} + inline Array(VALUE value) : Ref(value) {} + }; + +} + +#endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index af60e348..4ac6e99f 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -19,11 +19,11 @@ extern "C" { String::Init(); Function::Init(); Script::Init(); + Array::Init(); // Accessor::Init(); // Invocation::Init(); // Signature::Init(); - // Array::Init(); // Date::Init(); // Constants::Init(); // External::Init(); diff --git a/ext/v8/ref.h b/ext/v8/ref.h index d708b176..6beb7a50 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -65,11 +65,11 @@ namespace rr { /* * Coerce a Ref into a v8::Local. */ - virtual operator v8::Handle() const { + inline operator v8::Handle() const { return handle; } - v8::Isolate* getIsolate() const { + inline v8::Isolate* getIsolate() const { return isolate; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index edc478ea..2e7c6fa3 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -35,6 +35,7 @@ inline VALUE not_implemented(const char* message) { #include "backref.h" #include "object.h" +#include "array.h" #include "primitive.h" // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" diff --git a/ext/v8/value.cc b/ext/v8/value.cc index b4e4f3f7..02b4119e 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -37,8 +37,8 @@ namespace rr { // defineMethod("IntegerValue", &IntegerValue). // defineMethod("Uint32Value", &Uint32Value). // defineMethod("IntegerValue", &IntegerValue). - // defineMethod("Equals", &Equals). - // defineMethod("StrictEquals", &StrictEquals). + defineMethod("Equals", &Equals). + defineMethod("StrictEquals", &StrictEquals). defineMethod("ToRubyObject", &ToRubyObject). defineSingletonMethod("FromRubyObject", &FromRubyObject). diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb new file mode 100644 index 00000000..66a15c18 --- /dev/null +++ b/spec/c/array_spec.rb @@ -0,0 +1,23 @@ +require 'c_spec_helper' + +describe V8::C::Array do + requires_v8_context + + it 'can store and retrieve a value' do + o = V8::C::Object::New(@isolate) + a = V8::C::Array::New(@isolate) + + expect(a.Length).to eq 0 + + a.Set(0, o) + expect(a.Length).to eq 1 + + expect(a.Get(0).Equals(o)).to eq true + end + + it 'can be initialized with a length' do + a = V8::C::Array::New(@isolate, 5) + + expect(a.Length).to eq 5 + end +end From d1009c1429f305812c0f80c3dc0df0fc7d7a7ebe Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 29 Jun 2015 19:06:12 +0300 Subject: [PATCH 013/105] document the primitive equivalence classes --- ext/v8/bool.h | 42 +++++++++++++++++++++++++++++++++++++++++- ext/v8/equiv.h | 32 ++++++++++++++++++++++++++++++++ ext/v8/uint32.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/ext/v8/bool.h b/ext/v8/bool.h index 61195586..c431cecf 100644 --- a/ext/v8/bool.h +++ b/ext/v8/bool.h @@ -1,19 +1,59 @@ +// -*- mode: c++ -*- #ifndef RR_BOOL #define RR_BOOL namespace rr { + /** + * Seemlessly convert between Ruby booleans and C/C++. + * + * The `Bool` equivalence lets you plop in a Ruby boolean anywhere + * you might need a C++ boolean, and, by the same token, drop in a + * C/C++ boolean anywhere you might need a Ruby boolean. E.g. + * + * // Ruby -> C/C++ + * if (Bool(Qtrue)) { + * //always executed + * } + * + * // C/C++ -> Ruby + * if (RTEST(Bool(true))) { + * //always executed + * } + */ class Bool : public Equiv { public: + /** + * Construct a Bool from a Ruby VALUE + */ Bool(VALUE val) : Equiv(val) {} + + /** + * Constructo a Bool from a C/C++ boo. It is immediately + * converted into the corresponding; + */ Bool(bool b) : Equiv(b ? Qtrue : Qfalse) {} + + /** + * Construct a Bool from JavaScript. + */ Bool(v8::Handle b) : Equiv(b->Value() ? Qtrue : Qfalse) {} + /** + * Coerce this into a native C/C++ bool. Since it is stored as a + * Ruby VALUE, this is just a simple RTEST. + * + * bool b = Bool(true); // true + * b = Bool(false); // false + * b = Bool(Qtrue); // true + * b = Bool(Qfalse); // false + * b = Bool(Qnil); // false + * b = Bool(rb_cObject); // true + */ inline operator bool() { return RTEST(value); } }; - } #endif diff --git a/ext/v8/equiv.h b/ext/v8/equiv.h index c35455a7..d7a9f309 100644 --- a/ext/v8/equiv.h +++ b/ext/v8/equiv.h @@ -1,11 +1,43 @@ +// -*- mode: c++ -*- #ifndef RR_EQUIV #define RR_EQUIV namespace rr { + /** + * Defines an equivalence between a primitive Ruby and C/C++ types so + * that they can be used inter-changeably. In other words: given a + * Ruby `VALUE` it can convert it to a C/C++ type, and given a C/C++ + * type, it can convert it to a Ruby type. For example, the Boolean + * equivalence class defines the relationship between Ruby booleans + * and C/++ booleans, and can be used to place a Ruby VALUE anywhere + * you might need a C/C++ boolean such as an "if" statement: + * + * if (rr::Bool(Qnil)) { // + * throw "not going to happen!"; + * } + * + * This is the superclass of all equivalences classes, + * and is not meant to be instantiated. + * + * Internally, `Equiv`s are always stored as a Ruby `VALUE`, and so + * part of the job of the subclass is to have an appropriate + * constructor that converts the C/C++ type to a `VALUE` + */ class Equiv { public: + /** + * Constructs an `Equiv` from a Ruby VALUE. It's up to the + * subclass to determine what it can be converted to. + */ Equiv(VALUE val) : value(val) {} + + /** + * Converts this `Equiv` into a Ruby `VALUE`. This, of course, is + * just the value of the internal storage. In the Boolean example: + * + * VALUE val = Bool(Qnil); //=> Qnil; + */ inline operator VALUE() { return value; } protected: diff --git a/ext/v8/uint32.h b/ext/v8/uint32.h index c25986bd..727776b3 100644 --- a/ext/v8/uint32.h +++ b/ext/v8/uint32.h @@ -1,13 +1,41 @@ +// -*- mode: c++ -*- #ifndef RR_UINT32 #define RR_UINT32 namespace rr { + /** + * Converts between Ruby `Number` and the C/C++ `uint32_t`. + * + * This allows you to easily pass in `uint32_t` values whenever a + * Ruby VALUE is expected (such as a method call) E.g. + * + * uint_32_t myInt = 5; + * rb_funcall(UInt32(myInt), rb_intern("to_s")); //=> + * + * It also converts a Ruby `VALUE` into its corresponding + * `uint32_t`: + * + * uint_32_t myInt = UInt32(rb_eval_string("5")); //=> 5 + * + * Like all `Equiv`s, it stores itself internally as a Ruby `VALUE` + */ class UInt32 : public Equiv { public: + /** + * Construct a UInt32 from a Ruby `VALUE` + */ UInt32(VALUE val) : Equiv(val) {} + + /** + * Construct a UInt32 from a `uint32_t` by converting it into its + * corresponding `VALUE`. + */ UInt32(uint32_t ui) : Equiv(UINT2NUM(ui)) {} + /** + * Coerce the Ruby `VALUE` into a `uint32_t`. + */ inline operator uint32_t() { return RTEST(value) ? NUM2UINT(value) : 0; } From 902a6f25c340e0efe1538b2b1fa91628562c48b6 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 29 Jun 2015 22:42:36 -0500 Subject: [PATCH 014/105] add documentation for pointer class --- ext/v8/pointer.h | 201 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 173 insertions(+), 28 deletions(-) diff --git a/ext/v8/pointer.h b/ext/v8/pointer.h index c705bdc9..9f9d1d7a 100644 --- a/ext/v8/pointer.h +++ b/ext/v8/pointer.h @@ -1,38 +1,122 @@ +// -*- mode: c++ -*- #ifndef RR_POINTER #define RR_POINTER namespace rr { /** - * A pointer to V8 object managed by Ruby - * - * You deal with V8 objects as either pointers or handles. - * While handles are managed by the V8 garbage collector, pointers - * must be explicitly created and destroyed by your code. - * - * The pointer class provides a handly way to wrap V8 pointers - * into Ruby objects so that they will be deleted when the - * Ruby object is garbage collected. Automatic type coercion is - * used to make wrapping and unwrapping painless. - * - * To create Ruby VALUE: - * - * Pointer ptr(new v8::ScriptOrigin()); - * VALUE value = ptr; //automatically wraps in Data_Wrap_Struct - * - * Conversely, the pointer can be unwrapped from a struct - * created in this way and the underlying methods can be - * invoked: - * - * VALUE value = ...; - * Pointer ptr(value); - * ptr->CallMethod(); - */ - template - class Pointer { + * Conversion between a native C/C++ pointer and a Ruby Object. + * + * Use this class When you have a native C/C++ pointer that you want + * to make appear as a Ruby object. It will take care of all the + * book keeping like registering GC callbacks. It is also used to + * access from C/C++ a pointer that you had previously passed to Ruby. + * + * Suppose we have a C++ class `Greeter`. + * + * class Greeter { + * public: + * void sayHello() { + * println("Hello"); + * } + * } + * + * and that we want to pass an instance of that class to the + * following Ruby method: + * + * class Greet + * def self.greet(greeter) + * //we want greeter to wrap our C++ object. + * greeter.say_hello() + * end + * end + * + * We could do that from C by using a `Pointer` specified for + * `Greeter`: + * + * Greeter* greeter = new Greeter(); + * + * // get reference to Ruby `Greet` class from C + * VALUE GreeterClass = rb_define_class("Greet"); + * Pointer native_greeter(greeter); + * + * //now we can pass it to Ruby's `Greeter#new` + * rb_funcall(GreeterClass, rb_intern("greet"), 1, native_greeter); + * + * Once it is passed to Ruby, it is NO LONGER YOURS. It's lifecycle + * now belongs to Ruby, and you must not delete the pointer by + * hand. + * + * If we were to run the example above it would actually fail + * because by default, the Ruby Class of the native pointer is + * actually an opaque `Object`, and there is no method `say_hello` + * on `Object`. In order to make it work, we have to specify which + * class we want Ruby to treat our pointer as. We do that by setting + * the static Pointer::Class variable. + * + * Pointer::Class = rb_define_class("NativeGreeter"); + * + * Now, when we pass `Pointer(new Greeter())` to Ruby code, + * it will appear to have the class `NativeGreeter`. We can now + * define our `say_hello` method on that class in order to call the + * underlying C++ method. We can construct a `Pointer` from + * an instance of `NativeGreeter` and use it to automatically unwrap + * the C++ `Greeter` object and call its methods: + * + * + * static VALUE say_hello(VALUE self) { + * //unwrap the native `Greeter` and call its `sayHello()` + * Pointer(self)->sayHello(); + * return Qnil; + * } + * VALUE NativeGreeter = Pointer::Class; + * rb_define_method(NativeGreeter, "say_hello", (VALUE (*)(...))&say_hello, 0); + * + * Notice how we were able to just call the `sayHello()` method + * directly on the instance of Pointer? That's because it + * will automatically unbox and dereference the pointer so that you + * can automatically forward calls to the underlying object it wraps. + * + * Most of the time The Ruby Racer codebase uses subclassing to + * specify a Pointer. For example, `rr:Isolate ` looks like this: + * + * class Isolate : public Poiter { + * //.... + * } + * + * which means that it can be used in all the cases above without + * any template specifier: + * + * //wrap + * Isolate isolate(new v8::Isolate()); + * + * //unwrap + * VALUE rubyIsolate = getRubyObjectContainingIsolate(); + * Isolate isolate(rubyIsolate); + * + * //Ruby class for Isolate + * Isolate::Class //=> V8::C::Isolate + * + */ + template class Pointer { public: + /** + * Construct a `Pointer` from a C/C++ pointer. This is normally + * done in order to convert it into a Ruby `VALUE`. E.g. + * + * Greeter* greeter = new Greeter(); + * rb_funcall(Pointer(greeter), rb_intern("to_s")); + */ inline Pointer(T* t) : pointer(t) {}; - inline Pointer(VALUE v) { + + /** + * Construct a Pointer from a C/C++ pointer that was previously + * passed to Ruby. Use this access and call methods on the + * underlying pointer: + * + * Pointer(rubyValue)->sayHello(); + */ + inline Pointer(VALUE v) : { if (RTEST(v)) { this->unwrap(v); } else { @@ -40,26 +124,84 @@ namespace rr { } }; + /** + * Enable transparent pointer dereferencing via the `*` operator: + * + * *Pointer(new Greeter()).sayHello(); + */ inline operator T*() { return pointer; } + /** + * Enable transparent pointer dereferencing via the `->` operator: + * + * Pointer(new Greeter())->sayHello(); + */ inline T* operator ->() { return pointer; } + /** + * Coerce this Pointer into a VALUE. Once this happens, either by + * assigning it to variable of type VALUE, casting it to (VALUE), + * or passing it to a function that takes a VALUE as a parameter, + * this pointer now belongs to Ruby, and you should not `delete` + * it. + */ inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, &release, pointer); + VALUE RubyClass = Class ? Class : rb_cObject; + return Data_Wrap_Struct(RubyClass, 0, &release, pointer); } + /** + * Subclasses implement this to uwrap their particular + * datastructure. For example, the implementation for `Isolate` + * looks like: + * + * void Pointer::unwrap(VALUE value) { + * Data_Get_Struct(value, class v8::Isolate, pointer); + * } + * + * TODO: I would really like to get rid of this and it + * seems like it ought to be templatable, but I couldn't figure + * out a way to get the C++ compiler to play ball. Basically, it + * seems like there ought to be a generic function like: + * + * void inline unwrap(VALUE value) { + * Dat_Get_Struct(value, class T, pointer); + * } + * + * but it wouldn't work for me. + */ void unwrap(VALUE value); + + /** + * A static method to delete this pointer. It is implemented as a + * static method so that a function pointer to it can be passed to + * Ruby so that it can be called from Ruby when the object is + * garbage collected. + * TODO: rename to `destroy()` + */ static void release(T* pointer) { delete pointer; } + /** + * Storage for the Class variable. This is where you write to + * determine what Ruby class this Pointer will have: + * + * Pointer::Class = rb_define_class("NativeGreeter"); + */ static VALUE Class; + /** + * See if two `Pointer`s are equal by comparing their memory + * addresses. + * + * TODO: overload the `==` operator + */ static inline VALUE PointerEquals(VALUE self, VALUE other) { return Bool(Pointer(self).pointer == Pointer(other).pointer); } @@ -68,6 +210,9 @@ namespace rr { T* pointer; }; + /** + * Some C++ template nonsense. + */ template VALUE Pointer::Class; } From e452e123fdb276e9b8eac3168b56d0a9b1e45926 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 29 Jun 2015 22:43:30 -0500 Subject: [PATCH 015/105] add mode comments, fix indentation --- ext/v8/class_builder.h | 1 + ext/v8/ref.h | 69 +++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/ext/v8/class_builder.h b/ext/v8/class_builder.h index 163b88f1..c0c7b1e2 100644 --- a/ext/v8/class_builder.h +++ b/ext/v8/class_builder.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- #ifndef RR_CLASS_BUILDER #define RR_CLASS_BUILDER diff --git a/ext/v8/ref.h b/ext/v8/ref.h index 6beb7a50..8d4fe797 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -1,33 +1,34 @@ +// -*- mode: c++ -*- #ifndef RR_REF #define RR_REF namespace rr { /** - * A Reference to a V8 managed object - * - * Uses type coercion to quickly convert from a v8 handle - * to a ruby object and back again. Suppose we have a v8 handle - * that we want to return to Ruby. We can put it into a Ref: - * - * v8::Handle object = v8::Object::New(); - * VALUE val = Ref(object); - * - * this will create a `v8::Persistent` handle for the object - * so that it will not be garbage collected by v8. It then - * stuffs this new persistent handle into a Data_Wrap_Struct - * which can then be passed to Ruby code. When this struct - * is garbage collected by Ruby, it enqueues the corresponding - * v8 handle to be released during v8 gc. - * - * By the same token, you can use Refs to unwrap a Data_Wrap_Struct - * which has been generated in this fashion and call through to - * the underlying v8 methods. Suppose we are passed a VALUE `val` - * wrapping a v8::Object: - * - * Ref object(val); - * object->Get(v8::String::New("foo")); - * - */ + * A Reference to a V8 managed object + * + * Uses type coercion to quickly convert from a v8 handle + * to a ruby object and back again. Suppose we have a v8 handle + * that we want to return to Ruby. We can put it into a Ref: + * + * v8::Handle object = v8::Object::New(); + * VALUE val = Ref(object); + * + * this will create a `v8::Persistent` handle for the object + * so that it will not be garbage collected by v8. It then + * stuffs this new persistent handle into a Data_Wrap_Struct + * which can then be passed to Ruby code. When this struct + * is garbage collected by Ruby, it enqueues the corresponding + * v8 handle to be released during v8 gc. + * + * By the same token, you can use Refs to unwrap a Data_Wrap_Struct + * which has been generated in this fashion and call through to + * the underlying v8 methods. Suppose we are passed a VALUE `val` + * wrapping a v8::Object: + * + * Ref object(val); + * object->Get(v8::String::New("foo")); + * + */ template class Ref { public: @@ -52,8 +53,8 @@ namespace rr { virtual ~Ref() {} /* - * Coerce a Ref into a Ruby VALUE - */ + * Coerce a Ref into a Ruby VALUE + */ virtual operator VALUE() const { if (handle.IsEmpty()) { return Qnil; @@ -63,8 +64,8 @@ namespace rr { } /* - * Coerce a Ref into a v8::Local. - */ + * Coerce a Ref into a v8::Local. + */ inline operator v8::Handle() const { return handle; } @@ -80,11 +81,11 @@ namespace rr { } /* - * Pointer de-reference operators, this lets you use a ref to - * call through to underlying v8 methods. e.g - * - * Ref(value)->ToString(); - */ + * Pointer de-reference operators, this lets you use a ref to + * call through to underlying v8 methods. e.g + * + * Ref(value)->ToString(); + */ inline v8::Handle operator->() const { return *this; } inline v8::Handle operator*() const { return *this; } From b11e514b567a1f2a2757e6e6792cc4565592c14d Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 4 Jul 2015 12:20:50 -0500 Subject: [PATCH 016/105] upgrade to 4.5.x, add isolate.Dispose() --- ext/v8/.dir-locals.el | 5 +++++ ext/v8/extconf.rb | 8 ++++++-- ext/v8/isolate.cc | 9 ++++++++- ext/v8/isolate.h | 26 ++++++++++++-------------- ext/v8/pointer.h | 2 +- spec/c/isolate_spec.rb | 13 ++++++++----- therubyracer.gemspec | 2 +- 7 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 ext/v8/.dir-locals.el diff --git a/ext/v8/.dir-locals.el b/ext/v8/.dir-locals.el new file mode 100644 index 00000000..6b316388 --- /dev/null +++ b/ext/v8/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((c++-mode + (c-basic-offset . 2))) diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index bcbe4cf6..5ec06bf7 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -2,10 +2,14 @@ have_library('pthread') have_library('objc') if RUBY_PLATFORM =~ /darwin/ + $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall" $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g" -$CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic" +$CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic" unless RUBY_PLATFORM =~ /darwin/ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/ +$CPPFLAGS += " -std=c++11" + +$LDFLAGS += " -stdlib=libstdc++" CONFIG['LDSHARED'] = '$(CXX) -shared' unless RUBY_PLATFORM =~ /darwin/ if CONFIG['warnflags'] @@ -16,7 +20,7 @@ $CFLAGS += " -O0 -ggdb3" end -LIBV8_COMPATIBILITY = '~> 3.31.0' +LIBV8_COMPATIBILITY = '~> 4.5.95' begin require 'rubygems' diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index d989ee03..56cbce01 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -1,4 +1,6 @@ +// -*- mode: c++ -*- #include "rr.h" +#include "isolate.h" namespace rr { @@ -6,6 +8,7 @@ namespace rr { ClassBuilder("Isolate"). defineSingletonMethod("New", &New). + defineMethod("Dispose", &Isolate::Dispose). defineMethod("Equals", &rr::Isolate::PointerEquals). store(&Class); @@ -15,9 +18,13 @@ namespace rr { return Isolate(v8::Isolate::New()); } + VALUE Isolate::Dispose(VALUE self) { + Isolate(self)->Dispose(); + return Qnil; + } + template <> void Pointer::unwrap(VALUE value) { Data_Get_Struct(value, class v8::Isolate, pointer); } - } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index b77f8ece..1e2674f8 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -1,34 +1,32 @@ +// -*- mode: c++ -*- #ifndef RR_ISOLATE #define RR_ISOLATE namespace rr { - + /** + * V8::C::Isolate + * + * Represents a fully encapsulated V8 virtual machine. Allocated + * from Ruby by calling `V8::C::Isolate::New()` + * + * Note: You must call `Dispose()` on the isolate for its resources + * to be released. + */ class Isolate : public Pointer { public: static void Init(); static VALUE New(VALUE self); - // TODO: Add a Dispose method - inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, &release, pointer); + return Data_Wrap_Struct(Class, 0, 0, pointer); } - static void release(v8::Isolate* isolate) { - // The isolates must be released with Dispose. - // Using the delete operator is not allowed. - - // TODO: Do we want to dispose of the isolate when the object itself - // is garbage-collected? - // Can the isolate be used without it having a reference in ruby world? - // isolate->Dispose(); - } + static VALUE Dispose(VALUE self); }; - } #endif diff --git a/ext/v8/pointer.h b/ext/v8/pointer.h index 9f9d1d7a..30331827 100644 --- a/ext/v8/pointer.h +++ b/ext/v8/pointer.h @@ -116,7 +116,7 @@ namespace rr { * * Pointer(rubyValue)->sayHello(); */ - inline Pointer(VALUE v) : { + inline Pointer(VALUE v) { if (RTEST(v)) { this->unwrap(v); } else { diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 677a217d..02c0e724 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -1,15 +1,18 @@ require 'c_spec_helper' describe V8::C::Isolate do + let(:isolate) { V8::C::Isolate::New() } + it 'can create a new isolate' do - expect(V8::C::Isolate.New).to be + expect(isolate).to be end it 'can be tested for equality' do - isolate_one = V8::C::Isolate.New - isolate_two = V8::C::Isolate.New + expect(isolate.Equals(isolate)).to eq true + expect(isolate.Equals(V8::C::Isolate::New())).to eq false + end - expect(isolate_one.Equals(isolate_one)).to eq true - expect(isolate_one.Equals(isolate_two)).to eq false + it "can be disposed of" do + isolate.Dispose() end end diff --git a/therubyracer.gemspec b/therubyracer.gemspec index 5a6ace7d..bf67f9b5 100644 --- a/therubyracer.gemspec +++ b/therubyracer.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |gem| gem.license = 'MIT' gem.add_dependency 'ref' - gem.add_dependency 'libv8', '~> 3.16.14.0' + gem.add_dependency 'libv8', '~> 4.5.95.0' end From 1fac021d91549a5e8df719f859e9e553aba6b51e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 11:26:03 -0500 Subject: [PATCH 017/105] setup and teardown IsolateData with each Isolate --- ext/v8/isolate.cc | 4 +++- ext/v8/isolate.h | 29 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 56cbce01..3592cd6f 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -19,7 +19,9 @@ namespace rr { } VALUE Isolate::Dispose(VALUE self) { - Isolate(self)->Dispose(); + Isolate isolate(self); + delete isolate.data(); + isolate->Dispose(); return Qnil; } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 1e2674f8..3832a000 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -9,11 +9,19 @@ namespace rr { * Represents a fully encapsulated V8 virtual machine. Allocated * from Ruby by calling `V8::C::Isolate::New()` * + * Every v8::Isolate wrapped in Ruby will have an instance of + * `IsolateData` embedded in it that can be used for bookkeeping + * between the V8 and Ruby worlds. For example, when v8 objects are + * no longer needed by ruby, they'll be enqueued for later release + * inside the V8 garbarge collection thread. This queue lives in the + * `IsolateData` + * * Note: You must call `Dispose()` on the isolate for its resources - * to be released. + * to be released, otherwise, it will be leaked. */ class Isolate : public Pointer { public: + class IsolateData; static void Init(); static VALUE New(VALUE self); @@ -21,11 +29,30 @@ namespace rr { inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} + /** + * Converts the v8::Isolate into a Ruby Object, while setting up + * its book keeping data. E.g. + * VALUE rubyObject = Isolate(v8::Isolate::New()); + */ inline operator VALUE() { + pointer->SetData(0, new IsolateData()); return Data_Wrap_Struct(Class, 0, 0, pointer); } + /** + * Access the book-keeping data. e.g. + * + * Isolate(self).data(); + */ + inline IsolateData* data() { + return (IsolateData*)pointer->GetData(0); + } + static VALUE Dispose(VALUE self); + + class IsolateData { + + }; }; } From b8ba4c422a98a4dce2abede416faeebbc391243b Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 11:49:26 -0500 Subject: [PATCH 018/105] Context#Dispose() is not a thing in v8 4.5 --- ext/v8/context.cc | 7 - ext/v8/context.h | 3 - ext/v8/isolate.h | 14 +- ext/v8/vendor/concurrentqueue.h | 3542 +++++++++++++++++++++++++++++++ 4 files changed, 3553 insertions(+), 13 deletions(-) create mode 100644 ext/v8/vendor/concurrentqueue.h diff --git a/ext/v8/context.cc b/ext/v8/context.cc index e1594b40..bb7babeb 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -6,10 +6,8 @@ namespace rr { ClassBuilder("Context"). defineSingletonMethod("New", &New). - defineMethod("Dispose", &Dispose). defineMethod("Enter", &Enter). defineMethod("Exit", &Exit). - defineMethod("Global", &Global). store(&Class); @@ -37,11 +35,6 @@ namespace rr { )); } - VALUE Context::Dispose(VALUE self) { - Context(self).dispose(); - return Qnil; - } - VALUE Context::Enter(VALUE self) { Context context(self); Locker lock(context.getIsolate()); diff --git a/ext/v8/context.h b/ext/v8/context.h index 967000c7..6a20ce8f 100644 --- a/ext/v8/context.h +++ b/ext/v8/context.h @@ -8,11 +8,8 @@ namespace rr { static void Init(); static VALUE New(int argc, VALUE argv[], VALUE self); - static VALUE Dispose(VALUE self); - static VALUE Enter(VALUE self); static VALUE Exit(VALUE self); - static VALUE Global(VALUE self); // TODO diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 3832a000..30804f74 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -2,6 +2,10 @@ #ifndef RR_ISOLATE #define RR_ISOLATE +#include "vendor/concurrentqueue.h" + +using namespace moodycamel; + namespace rr { /** * V8::C::Isolate @@ -21,7 +25,7 @@ namespace rr { */ class Isolate : public Pointer { public: - class IsolateData; + struct IsolateData; static void Init(); static VALUE New(VALUE self); @@ -48,10 +52,14 @@ namespace rr { return (IsolateData*)pointer->GetData(0); } - static VALUE Dispose(VALUE self); + inline void scheduleDelete(v8::Global* cell) { + data()->queue.enqueue(cell); + } - class IsolateData { + static VALUE Dispose(VALUE self); + struct IsolateData { + ConcurrentQueue*> queue; }; }; } diff --git a/ext/v8/vendor/concurrentqueue.h b/ext/v8/vendor/concurrentqueue.h new file mode 100644 index 00000000..d3f35f27 --- /dev/null +++ b/ext/v8/vendor/concurrentqueue.h @@ -0,0 +1,3542 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + + +// Simplified BSD license: +// Copyright (c) 2013-2015, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#pragma once + +#if defined(__GNUC__) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +// Platform-specific definitions of a numeric thread ID type and an invalid value +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + static inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY if (true) +#define MOODYCAMEL_CATCH(...) else if (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +//// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +//// g++ <=4.7 doesn't support thread_local either +//#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +//// Assume `thread_local` is fully supported in all other C++11 compilers/runtimes +//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +//#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + inline bool likely(bool x) { return __builtin_expect((x), true); } + inline bool unlikely(bool x) { return __builtin_expect((x), false); } +#else + inline bool likely(bool x) { return x; } + inline bool unlikely(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::hash(id)); + } + + template + static inline bool circular_less_than(T a, T b) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__APPLE__) || defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + }; + + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + listener->next = tlsInst.tail; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + explicit ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + explicit ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = ((((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers)) * BLOCK_SIZE; + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + Traits::free(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue_bulk(std::forward(itemFirst), count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, std::forward(itemFirst), count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + return inner_enqueue_bulk(std::forward(itemFirst), count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, std::forward(itemFirst), count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if (details::likely(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + friend struct ExplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if (details::unlikely(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_add(-2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_add(-1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#if MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) + { +#if MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(index_t i) + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(index_t i, size_t count) + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + private: + char elements[sizeof(T) * BLOCK_SIZE]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#if MCDBGQ_TRACKMEM + void* owner; +#endif + }; + + +#if MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent, bool isExplicit) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit), + parent(parent) + { + } + + virtual ~ProducerBase() { }; + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent) : + ProducerBase(parent, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto next = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + Traits::free(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + if (allocMode == CannotAlloc || !new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + assert(overcommit <= myDequeueCount); + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / BLOCK_SIZE); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); + } + else { + element = std::move(el); + el.~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + if (allocMode == CannotAlloc || full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast(Traits::malloc(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent) : + ProducerBase(parent, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr && block->dynamicallyAllocated) { + // Free the old block + this->parent->destroy(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0) && this->tailBlock->dynamicallyAllocated) { + this->parent->destroy(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + Traits::free(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + tail = this->tailIndex.load(std::memory_order_acquire); + if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); + } + else { + element = std::move(el); + el.~T(); + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#if MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + assert(overcommit <= myDequeueCount); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + auto newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + if (allocMode == CannotAlloc || !new_block_index()) { + return false; + } + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / BLOCK_SIZE); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast(Traits::malloc( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#if MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#if MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + if (canAlloc == CanAlloc) { + return create(); + } + + return nullptr; + } + + +#if MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { + bool recycled; + return recycle_or_create_producer(isExplicit, recycled); + } + + ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) + { +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + + recycled = false; + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; + + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; + + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + auto newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast(Traits::malloc(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = newCapacity; + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + bool recycled; + auto producer = static_cast(recycle_or_create_producer(false, recycled)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + return nullptr; + } + if (recycled) { + implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from thread exit listeners + details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); + + // Remove from hash +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1; + probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + auto p = static_cast(Traits::malloc(sizeof(U) * count)); + if (p == nullptr) { + return nullptr; + } + + for (size_t i = 0; i != count; ++i) { + new (p + i) U(); + } + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) { + (p + --i)->~U(); + } + Traits::free(p); + } + } + + template + static inline U* create() + { + auto p = Traits::malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + auto p = Traits::malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + Traits::free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#if !MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = -1; +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = -1; +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif From 38229de06a7ab7c90ce7cd40942a3ce9746c9def Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 19:03:38 -0500 Subject: [PATCH 019/105] add reference queing and dequeinc --- ext/v8/context.cc | 2 +- ext/v8/isolate.cc | 9 ++++++- ext/v8/isolate.h | 53 ++++++++++++++++++++++++++++++++++++++---- ext/v8/ref.h | 44 +++++++++-------------------------- spec/c/context_spec.rb | 10 ++++++++ 5 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 spec/c/context_spec.rb diff --git a/ext/v8/context.cc b/ext/v8/context.cc index bb7babeb..25a425a4 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -24,7 +24,7 @@ namespace rr { Isolate isolate(rb_isolate); Locker lock(isolate); - + v8::HandleScope handle_scope(isolate); return Context(v8::Context::New( isolate // TODO diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 3592cd6f..98ca657e 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -14,8 +14,15 @@ namespace rr { store(&Class); } + VALUE Isolate::New(VALUE self) { - return Isolate(v8::Isolate::New()); + Isolate::IsolateData* data = new IsolateData(); + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = &data->array_buffer_allocator; + Isolate isolate(v8::Isolate::New(create_params)); + isolate->SetData(0, new IsolateData()); + isolate->AddGCPrologueCallback(&clearDeadReferences); + return isolate; } VALUE Isolate::Dispose(VALUE self) { diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 30804f74..47e358ac 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -33,13 +33,21 @@ namespace rr { inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} + static void clearDeadReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { + Isolate isolate(i); + v8::Persistent* cell; + while (isolate.data()->queue.try_dequeue(cell)) { + cell->Reset(); + delete cell; + } + } + /** * Converts the v8::Isolate into a Ruby Object, while setting up * its book keeping data. E.g. * VALUE rubyObject = Isolate(v8::Isolate::New()); */ inline operator VALUE() { - pointer->SetData(0, new IsolateData()); return Data_Wrap_Struct(Class, 0, 0, pointer); } @@ -52,14 +60,51 @@ namespace rr { return (IsolateData*)pointer->GetData(0); } - inline void scheduleDelete(v8::Global* cell) { - data()->queue.enqueue(cell); + /** + * Schedule a v8::Persistent reference to be be deleted with the next + * invocation of the V8 Garbarge Collector + */ + template + inline void scheduleDelete(v8::Persistent* cell) { + data()->queue.enqueue((v8::Persistent*)cell); + fprintf(stderr, "scheduleDelete()\n"); } static VALUE Dispose(VALUE self); + /** + * Recent versions of V8 will segfault unless you pass in an + * ArrayBufferAllocator into the create params of an isolate. This + * is the simplest implementation possible. + */ + class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { + public: + virtual void* Allocate(size_t length) { + void* data = AllocateUninitialized(length); + return data == NULL ? data : memset(data, 0, length); + } + virtual void* AllocateUninitialized(size_t length) { return malloc(length); } + virtual void Free(void* data, size_t) { free(data); } + }; + + /** + * Data specific to the Ruby embedding. It has the same life span + * as the isolate. + */ struct IsolateData { - ConcurrentQueue*> queue; + /** + * A custom ArrayBufferAllocator for this isolate. Why? because + * if you don't, it will segfault when you try and create an + * Context. That's why. + */ + ArrayBufferAllocator array_buffer_allocator; + + /** + * Queue to hold unused references to v8 objects. Once Ruby is + * finished with an object it will be enqueued here so that it + * can be released by the v8 garbarge collector later. + */ + ConcurrentQueue*> queue; }; }; } diff --git a/ext/v8/ref.h b/ext/v8/ref.h index 8d4fe797..ee008828 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -32,13 +32,14 @@ namespace rr { template class Ref { public: + struct Holder; Ref(VALUE value) { this->value = value; Holder* holder = unwrapHolder(); if (holder) { this->isolate = holder->isolate; - this->handle = v8::Local::New(holder->isolate, *holder->handle); + this->handle = v8::Local::New(holder->isolate, *holder->cell); } else { this->isolate = NULL; this->handle = v8::Local(); @@ -60,7 +61,7 @@ namespace rr { return Qnil; } - return Data_Wrap_Struct(Class, 0, &Holder::destroy, new Holder(isolate, handle)); + return Data_Wrap_Struct(Class, 0, &destroy, new Holder(isolate, handle)); } /* @@ -74,10 +75,8 @@ namespace rr { return isolate; } - void dispose() { - Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); - holder->dispose(); + static void destroy(Holder* holder) { + delete holder; } /* @@ -89,37 +88,16 @@ namespace rr { inline v8::Handle operator->() const { return *this; } inline v8::Handle operator*() const { return *this; } - class Holder { - friend class Ref; - public: - Holder(v8::Isolate* isolate, v8::Handle handle) { - this->disposed_p = false; - this->isolate = isolate; - this->handle = new v8::Persistent(isolate, handle); - } + struct Holder { + Holder(v8::Isolate* isolate, v8::Handle handle) : + isolate(isolate), cell(new v8::Persistent(isolate, handle)) {} virtual ~Holder() { - this->dispose(); - } - - void dispose() { - if (!this->disposed_p) { - handle->Reset(); - delete handle; - this->disposed_p = true; - } + Isolate(isolate).scheduleDelete(cell); } - protected: v8::Isolate* isolate; - v8::Persistent* handle; - bool disposed_p; - - static void destroy(Holder* holder) { - holder->dispose(); - - // TODO: Use a nonblocking queue with `AddGCPrologueCallback` - } + v8::Persistent* cell; }; static VALUE Class; @@ -128,7 +106,7 @@ namespace rr { Holder* unwrapHolder() const { if (RTEST(this->value)) { Holder* holder = NULL; - Data_Get_Struct(this->value, class Holder, holder); + Data_Get_Struct(this->value, struct Holder, holder); return holder; } else { return NULL; diff --git a/spec/c/context_spec.rb b/spec/c/context_spec.rb new file mode 100644 index 00000000..e0f6ed3f --- /dev/null +++ b/spec/c/context_spec.rb @@ -0,0 +1,10 @@ +require 'c_spec_helper' + +describe V8::C::Context do + let(:isolate) { V8::C::Isolate::New() } + let(:context) { V8::C::Context::New(isolate) } + + it "can be instantiated" do + expect(context).to be + end +end From 6cda380dec11eb59575d50d2c629c01f04dfb719 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 19:09:07 -0500 Subject: [PATCH 020/105] remove errant fprintf() --- ext/v8/isolate.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 47e358ac..252408be 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -67,7 +67,6 @@ namespace rr { template inline void scheduleDelete(v8::Persistent* cell) { data()->queue.enqueue((v8::Persistent*)cell); - fprintf(stderr, "scheduleDelete()\n"); } static VALUE Dispose(VALUE self); From 963c93f87d3fd3ff59700af9b5a7124760cd894c Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 19:33:05 -0500 Subject: [PATCH 021/105] add documentation for `clearReferences` method --- ext/v8/isolate.cc | 2 +- ext/v8/isolate.h | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 98ca657e..5f5afa22 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -21,7 +21,7 @@ namespace rr { create_params.array_buffer_allocator = &data->array_buffer_allocator; Isolate isolate(v8::Isolate::New(create_params)); isolate->SetData(0, new IsolateData()); - isolate->AddGCPrologueCallback(&clearDeadReferences); + isolate->AddGCPrologueCallback(&clearReferences); return isolate; } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 252408be..7a2c0452 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -33,15 +33,6 @@ namespace rr { inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} inline Isolate(VALUE value) : Pointer(value) {} - static void clearDeadReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { - Isolate isolate(i); - v8::Persistent* cell; - while (isolate.data()->queue.try_dequeue(cell)) { - cell->Reset(); - delete cell; - } - } - /** * Converts the v8::Isolate into a Ruby Object, while setting up * its book keeping data. E.g. @@ -69,6 +60,21 @@ namespace rr { data()->queue.enqueue((v8::Persistent*)cell); } + /** + * An instance of v8::GCPrologueCallback, this will run in the v8 + * GC thread, and clear out all the references that have been + * released from Ruby. + */ + static void clearReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { + Isolate isolate(i); + v8::Persistent* cell; + while (isolate.data()->queue.try_dequeue(cell)) { + cell->Reset(); + delete cell; + } + } + + static VALUE Dispose(VALUE self); /** From 4b7edae271033f51f3760a2cba4959048227c5e7 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 5 Jul 2015 19:53:17 -0500 Subject: [PATCH 022/105] don't create handle scope in context creation --- ext/v8/context.cc | 1 - spec/c/context_spec.rb | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/v8/context.cc b/ext/v8/context.cc index 25a425a4..c2e4bdc8 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -24,7 +24,6 @@ namespace rr { Isolate isolate(rb_isolate); Locker lock(isolate); - v8::HandleScope handle_scope(isolate); return Context(v8::Context::New( isolate // TODO diff --git a/spec/c/context_spec.rb b/spec/c/context_spec.rb index e0f6ed3f..02291403 100644 --- a/spec/c/context_spec.rb +++ b/spec/c/context_spec.rb @@ -3,6 +3,11 @@ describe V8::C::Context do let(:isolate) { V8::C::Isolate::New() } let(:context) { V8::C::Context::New(isolate) } + around do |example| + V8::C::HandleScope(isolate) do + example.run + end + end it "can be instantiated" do expect(context).to be From a91f59d90e6287e7a6ce9e3baa59a64ba33bcddb Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 00:36:25 -0500 Subject: [PATCH 023/105] retain and release ruby objects --- ext/v8/external.cc | 37 +++++++++++++++++++++++++++++++++++++ ext/v8/external.h | 38 ++++++++++++++++++++++++++++++++++++++ ext/v8/init.cc | 1 + ext/v8/isolate.cc | 8 +++++++- ext/v8/isolate.h | 39 +++++++++++++++++++++++++++++++++++++-- ext/v8/ref.h | 4 ++++ ext/v8/rr.h | 1 + spec/c/external_spec.rb | 23 +++++++++++++++++++++++ 8 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 ext/v8/external.cc create mode 100644 ext/v8/external.h create mode 100644 spec/c/external_spec.rb diff --git a/ext/v8/external.cc b/ext/v8/external.cc new file mode 100644 index 00000000..fa6adfdc --- /dev/null +++ b/ext/v8/external.cc @@ -0,0 +1,37 @@ +#include "rr.h" +#include "external.h" + +namespace rr { + void External::Init() { + ClassBuilder("External"). + defineSingletonMethod("New", &New). + + defineMethod("Value", &Value). + + store(&Class); + } + + VALUE External::New(VALUE self, VALUE r_isolate, VALUE object) { + Isolate isolate(r_isolate); + isolate.retainObject(object); + + Locker lock(isolate); + + Container* container = new Container(object); + v8::Local external(v8::External::New(isolate, (void*)container)); + + v8::Global* global(new v8::Global(isolate, external)); + container->global = global; + + global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); + + return External(isolate, external); + } + + VALUE External::Value(VALUE self) { + External external(self); + Locker lock(external); + Container* container((Container*)external->Value()); + return container->object; + } +} diff --git a/ext/v8/external.h b/ext/v8/external.h new file mode 100644 index 00000000..b9b9e329 --- /dev/null +++ b/ext/v8/external.h @@ -0,0 +1,38 @@ +// -*- mode: c++ -*- +#ifndef EXTERNAL_H +#define EXTERNAL_H + +namespace rr { + class External : Ref { + public: + + static void Init(); + static VALUE New(VALUE self, VALUE isolate, VALUE object); + static VALUE Value(VALUE self); + + inline External(VALUE value) : Ref(value) {} + inline External(v8::Isolate* isolate, v8::Handle handle) : + Ref(isolate, handle) {} + + struct Container { + Container(VALUE v) : object(v) {} + + v8::Global* global; + VALUE object; + }; + + static void release(const v8::WeakCallbackInfo& info) { + Container* container(info.GetParameter()); + if (info.IsFirstPass()) { + container->global->Reset(); + info.SetSecondPassCallback(&release); + } else { + Isolate isolate(info.GetIsolate()); + isolate.scheduleReleaseObject(container->object); + delete container; + } + } + }; +} + +#endif /* EXTERNAL_H */ diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 4ac6e99f..1d0a2f57 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -20,6 +20,7 @@ extern "C" { Function::Init(); Script::Init(); Array::Init(); + External::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 5f5afa22..9bf99393 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -5,6 +5,7 @@ namespace rr { void Isolate::Init() { + rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). @@ -17,11 +18,16 @@ namespace rr { VALUE Isolate::New(VALUE self) { Isolate::IsolateData* data = new IsolateData(); + VALUE rb_cRetainedObjects = rb_eval_string("V8::RetainedObjects"); + data->retained_objects = rb_funcall(rb_cRetainedObjects, rb_intern("new"), 0); + v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = &data->array_buffer_allocator; + Isolate isolate(v8::Isolate::New(create_params)); - isolate->SetData(0, new IsolateData()); + isolate->SetData(0, data); isolate->AddGCPrologueCallback(&clearReferences); + return isolate; } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 7a2c0452..d458e634 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -38,8 +38,8 @@ namespace rr { * its book keeping data. E.g. * VALUE rubyObject = Isolate(v8::Isolate::New()); */ - inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, pointer); + virtual operator VALUE() { + return Data_Wrap_Struct(Class, &releaseAndMarkRetainedObjects, 0, pointer); } /** @@ -60,6 +60,28 @@ namespace rr { data()->queue.enqueue((v8::Persistent*)cell); } + inline void retainObject(VALUE object) { + rb_funcall(data()->retained_objects, rb_intern("add"), 1, object); + } + + inline void releaseObject(VALUE object) { + rb_funcall(data()->retained_objects, rb_intern("remove"), 1, object); + } + + inline void scheduleReleaseObject(VALUE object) { + data()->release_queue.enqueue(object); + } + + static void releaseAndMarkRetainedObjects(v8::Isolate* isolate_) { + Isolate isolate(isolate_); + IsolateData* data = isolate.data(); + VALUE object; + while (data->release_queue.try_dequeue(object)) { + isolate.releaseObject(object); + } + rb_gc_mark(data->retained_objects); + } + /** * An instance of v8::GCPrologueCallback, this will run in the v8 * GC thread, and clear out all the references that have been @@ -97,6 +119,14 @@ namespace rr { * as the isolate. */ struct IsolateData { + + /** + * An instance of `V8::RetainedObjects` that contains all + * references held from from V8. As long as they are in this + * list, they won't be gc'd by Ruby. + */ + VALUE retained_objects; + /** * A custom ArrayBufferAllocator for this isolate. Why? because * if you don't, it will segfault when you try and create an @@ -110,6 +140,11 @@ namespace rr { * can be released by the v8 garbarge collector later. */ ConcurrentQueue*> queue; + + /** + * Queue to hold + */ + ConcurrentQueue release_queue; }; }; } diff --git a/ext/v8/ref.h b/ext/v8/ref.h index ee008828..d58dd8c3 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -75,6 +75,10 @@ namespace rr { return isolate; } + inline operator v8::Isolate*() const { + return isolate; + } + static void destroy(Holder* holder) { delete holder; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 2e7c6fa3..fb4f8dff 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -37,6 +37,7 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "array.h" #include "primitive.h" +#include "external.h" // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" diff --git a/spec/c/external_spec.rb b/spec/c/external_spec.rb new file mode 100644 index 00000000..e3372450 --- /dev/null +++ b/spec/c/external_spec.rb @@ -0,0 +1,23 @@ +require 'c_spec_helper' + +describe V8::C::External do + let(:isolate) { V8::C::Isolate::New() } + let(:value) { @external::Value() } + around { |example| V8::C::HandleScope(isolate) { example.run } } + + before do + Object.new.tap do |object| + @object_id = object.object_id + @external = V8::C::External::New(isolate, object) + end + end + + it "exists" do + expect(@external).to be + end + + it "can retrieve the ruby object out from V8 land" do + expect(value).to be + expect(value.object_id).to eql @object_id + end +end From 01033ce76e913c3fb1c87c0c139722bf80abf503 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 00:38:10 -0500 Subject: [PATCH 024/105] rename queue -> v8_release_queue --- ext/v8/isolate.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index d458e634..3ad38736 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -57,7 +57,7 @@ namespace rr { */ template inline void scheduleDelete(v8::Persistent* cell) { - data()->queue.enqueue((v8::Persistent*)cell); + data()->v8_release_queue.enqueue((v8::Persistent*)cell); } inline void retainObject(VALUE object) { @@ -90,7 +90,7 @@ namespace rr { static void clearReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { Isolate isolate(i); v8::Persistent* cell; - while (isolate.data()->queue.try_dequeue(cell)) { + while (isolate.data()->v8_release_queue.try_dequeue(cell)) { cell->Reset(); delete cell; } @@ -139,7 +139,7 @@ namespace rr { * finished with an object it will be enqueued here so that it * can be released by the v8 garbarge collector later. */ - ConcurrentQueue*> queue; + ConcurrentQueue*> v8_release_queue; /** * Queue to hold From 0fd81d52d8c73fe39e9f3a4a306f69f974446564 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 00:40:22 -0500 Subject: [PATCH 025/105] rename release_queue -> rb_release_queue --- ext/v8/isolate.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 3ad38736..4ed35e53 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -69,14 +69,14 @@ namespace rr { } inline void scheduleReleaseObject(VALUE object) { - data()->release_queue.enqueue(object); + data()->rb_release_queue.enqueue(object); } static void releaseAndMarkRetainedObjects(v8::Isolate* isolate_) { Isolate isolate(isolate_); IsolateData* data = isolate.data(); VALUE object; - while (data->release_queue.try_dequeue(object)) { + while (data->rb_release_queue.try_dequeue(object)) { isolate.releaseObject(object); } rb_gc_mark(data->retained_objects); @@ -144,7 +144,7 @@ namespace rr { /** * Queue to hold */ - ConcurrentQueue release_queue; + ConcurrentQueue rb_release_queue; }; }; } From ca2bf2a91ff29c108b3a95411c3cf015dff8b4a9 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 01:05:37 -0500 Subject: [PATCH 026/105] add documentation for the GC system --- ext/v8/isolate.h | 38 ++++++++++++++++++++++---- lib/v8/retained_objects.rb | 29 ++++++++++++++++++++ spec/v8/retained_objects_spec.rb | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 lib/v8/retained_objects.rb create mode 100644 spec/v8/retained_objects_spec.rb diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 4ed35e53..c6b29e1a 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -53,24 +53,52 @@ namespace rr { /** * Schedule a v8::Persistent reference to be be deleted with the next - * invocation of the V8 Garbarge Collector + * invocation of the V8 Garbarge Collector. It is safe to call + * this method from within the Ruby garbage collection thread or a place + * where you do not want to acquire any V8 locks. */ template - inline void scheduleDelete(v8::Persistent* cell) { + inline void scheduleReleaseObject(v8::Persistent* cell) { data()->v8_release_queue.enqueue((v8::Persistent*)cell); } + /** + * Schedule a Ruby object to be released with the next invocation + * of the Ruby garbage collector. This method is safe to call from places + * where you do not hold any Ruby locks (such as the V8 GC thread) + */ + inline void scheduleReleaseObject(VALUE object) { + data()->rb_release_queue.enqueue(object); + } + + /** + * Increase the reference count to this Ruby object by one. As + * long as there is more than 1 reference to this object, it will + * not be garbage collected, even if there are no references to + * from within Ruby code. + * + * Note: should be called from a place where Ruby locks are held. + */ inline void retainObject(VALUE object) { rb_funcall(data()->retained_objects, rb_intern("add"), 1, object); } + /** + * Decrease the reference count to this Ruby object by one. If the + * count falls below zero, this object will no longer be marked my + * this Isolate and will be eligible for garbage collection. + * + * Note: should be called from a place where Ruby locks are held. + */ inline void releaseObject(VALUE object) { rb_funcall(data()->retained_objects, rb_intern("remove"), 1, object); } - inline void scheduleReleaseObject(VALUE object) { - data()->rb_release_queue.enqueue(object); - } + + /** + * The `gc_mark()` callback for this Isolate's + * Data_Wrap_Struct. It releases all pending Ruby objects. + */ static void releaseAndMarkRetainedObjects(v8::Isolate* isolate_) { Isolate isolate(isolate_); diff --git a/lib/v8/retained_objects.rb b/lib/v8/retained_objects.rb new file mode 100644 index 00000000..0dbc898b --- /dev/null +++ b/lib/v8/retained_objects.rb @@ -0,0 +1,29 @@ +module V8 + class RetainedObjects + def initialize + @counts = {} + end + + def add(object) + if @counts[object] + @counts[object] += 1 + else + @counts[object] = 1 + end + end + + def remove(object) + if count = @counts[object] + if count <= 1 + @counts.delete object + else + @counts[object] -= 1 + end + end + end + + def retaining?(object) + !!@counts[object] + end + end +end diff --git a/spec/v8/retained_objects_spec.rb b/spec/v8/retained_objects_spec.rb new file mode 100644 index 00000000..06a3f338 --- /dev/null +++ b/spec/v8/retained_objects_spec.rb @@ -0,0 +1,47 @@ +require 'v8/retained_objects' + +describe V8::RetainedObjects do + let(:object) { Object.new } + let(:objects) { V8::RetainedObjects.new } + + it "knows that something isn't retained" do + expect(objects).not_to be_retaining object + end + + describe "adding a reference to an object" do + before do + objects.add(object) + end + + it "is now retained" do + expect(objects).to be_retaining object + end + + describe "removing the reference" do + before do + objects.remove(object) + end + it "is no longer retained" do + expect(objects).to_not be_retaining object + end + end + describe "adding another reference and then removing" do + before do + objects.add(object) + objects.remove(object) + end + it "is still retained" do + expect(objects).to be_retaining object + end + + describe "removing one more time" do + before do + objects.remove(object) + end + it "is no longer retained" do + expect(objects).to_not be_retaining object + end + end + end + end +end From dfb0a3c08f52bb301addfc310d69ac46ec5c0c33 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 01:19:59 -0500 Subject: [PATCH 027/105] more documentation on external --- ext/v8/external.cc | 10 ++++++++-- ext/v8/external.h | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ext/v8/external.cc b/ext/v8/external.cc index fa6adfdc..45b3d84d 100644 --- a/ext/v8/external.cc +++ b/ext/v8/external.cc @@ -13,17 +13,23 @@ namespace rr { VALUE External::New(VALUE self, VALUE r_isolate, VALUE object) { Isolate isolate(r_isolate); + + // as long as this external is alive within JavaScript, it should not be + // garbage collected by Ruby. isolate.retainObject(object); Locker lock(isolate); + // create the external. Container* container = new Container(object); v8::Local external(v8::External::New(isolate, (void*)container)); + // next, we create a weak reference to this external so that we can be + // notified when V8 is done with it. At that point, we can let Ruby know + // that this external is done with it. v8::Global* global(new v8::Global(isolate, external)); - container->global = global; - global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); + container->global = global; return External(isolate, external); } diff --git a/ext/v8/external.h b/ext/v8/external.h index b9b9e329..5a26e148 100644 --- a/ext/v8/external.h +++ b/ext/v8/external.h @@ -21,6 +21,14 @@ namespace rr { VALUE object; }; + /** + * Implements a v8::WeakCallbackInfo::Callback with all + * of its idiosyncracies. It happens in two passes. In the first + * pass, you are required to only reset the weak reference. In the + * second pass, you can actually do your cleanup. In this case, we + * schedule the referenced Ruby object to be released in the next + * Ruby gc pass. + */ static void release(const v8::WeakCallbackInfo& info) { Container* container(info.GetParameter()); if (info.IsFirstPass()) { From 4ba45bab9520bbe5a1524939d54dec909301ec20 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 6 Jul 2015 01:28:08 -0500 Subject: [PATCH 028/105] add docs for ruby_release_queue --- ext/v8/isolate.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index c6b29e1a..99ae660d 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -170,7 +170,10 @@ namespace rr { ConcurrentQueue*> v8_release_queue; /** - * Queue to hold + * Ruby objects that had been retained by this isolate, but that + * are eligible for release. Generally, an object ends up in a + * queue when the v8 object that had referenced it no longer + * needs it. */ ConcurrentQueue rb_release_queue; }; From ed982f0c5241a65bdda7ba8bc18195dab529856b Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 7 Jul 2015 20:32:55 -0500 Subject: [PATCH 029/105] make external fully integrated --- ext/v8/external.h | 4 +++- ext/v8/ref.h | 2 +- ext/v8/value.cc | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ext/v8/external.h b/ext/v8/external.h index 5a26e148..b9defb26 100644 --- a/ext/v8/external.h +++ b/ext/v8/external.h @@ -3,7 +3,7 @@ #define EXTERNAL_H namespace rr { - class External : Ref { + class External : public Ref { public: static void Init(); @@ -13,6 +13,8 @@ namespace rr { inline External(VALUE value) : Ref(value) {} inline External(v8::Isolate* isolate, v8::Handle handle) : Ref(isolate, handle) {} + inline External(v8::Isolate* isolate, v8::Handle value) : + External(isolate, v8::Handle::Cast(value)) {} struct Container { Container(VALUE v) : object(v) {} diff --git a/ext/v8/ref.h b/ext/v8/ref.h index d58dd8c3..deaf0086 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -97,7 +97,7 @@ namespace rr { isolate(isolate), cell(new v8::Persistent(isolate, handle)) {} virtual ~Holder() { - Isolate(isolate).scheduleDelete(cell); + Isolate(isolate).scheduleReleaseObject(cell); } v8::Isolate* isolate; diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 02b4119e..aa2df9d1 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -166,10 +166,9 @@ namespace rr { return Qfalse; } - // TODO - // if (handle->IsExternal()) { - // return External((v8::Handle)v8::External::Cast(*handle)); - // } + if (handle->IsExternal()) { + return External(isolate, handle); + } if (handle->IsUint32()) { return UInt32(handle->Uint32Value()); From a172288c38fb9192e58a506174ab643ff2dfe0d7 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 8 Jul 2015 23:17:39 -0500 Subject: [PATCH 030/105] add ScriptOrigin and round out Function --- ext/v8/function.cc | 25 ++++++++-- ext/v8/function.h | 5 +- ext/v8/init.cc | 1 + ext/v8/rr.h | 3 +- ext/v8/script-origin.cc | 51 +++++++++++++++++++ ext/v8/script-origin.h | 95 ++++++++++++++++++++++++++++++++++++ ext/v8/script.cc | 8 +-- ext/v8/script.h | 5 +- spec/c/function_spec.rb | 18 +++++-- spec/c/script_origin_spec.rb | 38 +++++++++++++++ 10 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 ext/v8/script-origin.cc create mode 100644 ext/v8/script-origin.h create mode 100644 spec/c/script_origin_spec.rb diff --git a/ext/v8/function.cc b/ext/v8/function.cc index 83cbe136..f9c570d1 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -12,7 +12,9 @@ namespace rr { defineMethod("GetInferredName", &GetInferredName). defineMethod("GetScriptLineNumber", &GetScriptLineNumber). defineMethod("GetScriptColumnNumber", &GetScriptColumnNumber). - defineMethod("GetScriptId", &GetScriptId). + defineMethod("IsBuiltin", &IsBuiltin). + defineMethod("ScriptId", &ScriptId). + defineMethod("GetBoundFunction", &GetBoundFunction). defineMethod("GetScriptOrigin", &GetScriptOrigin). store(&Class); @@ -81,15 +83,32 @@ namespace rr { return INT2FIX(function->GetScriptColumnNumber()); } - VALUE Function::GetScriptId(VALUE self) { + VALUE Function::IsBuiltin(VALUE self) { + Function function(self); + Locker lock(function); + + return Bool(function->IsBuiltin()); + } + + VALUE Function::ScriptId(VALUE self) { Function function(self); Locker lock(function.getIsolate()); return INT2FIX(function->ScriptId()); } + VALUE Function::GetBoundFunction(VALUE self) { + Function function(self); + Locker lock(function); + + return Value(function.getIsolate(), function->GetBoundFunction()); + } + VALUE Function::GetScriptOrigin(VALUE self) { - return not_implemented("GetScriptOrigin"); + Function function(self); + Locker lock(function); + + return ScriptOrigin(function, function->GetScriptOrigin()); } } diff --git a/ext/v8/function.h b/ext/v8/function.h index 60c489be..0aa0a59c 100644 --- a/ext/v8/function.h +++ b/ext/v8/function.h @@ -12,9 +12,12 @@ namespace rr { static VALUE SetName(VALUE self, VALUE name); static VALUE GetName(VALUE self); static VALUE GetInferredName(VALUE self); + static VALUE GetDisplayName(VALUE self); static VALUE GetScriptLineNumber(VALUE self); static VALUE GetScriptColumnNumber(VALUE self); - static VALUE GetScriptId(VALUE self); + static VALUE IsBuiltin(VALUE self); + static VALUE ScriptId(VALUE self); + static VALUE GetBoundFunction(VALUE self); static VALUE GetScriptOrigin(VALUE self); inline Function(VALUE value) : Ref(value) {} diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 1d0a2f57..1dfaba3a 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -19,6 +19,7 @@ extern "C" { String::Init(); Function::Init(); Script::Init(); + ScriptOrigin::Init(); Array::Init(); External::Init(); diff --git a/ext/v8/rr.h b/ext/v8/rr.h index fb4f8dff..d312862f 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -41,7 +41,8 @@ inline VALUE not_implemented(const char* message) { // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" -#include "function.h" #include "script.h" +#include "script-origin.h" +#include "function.h" #endif diff --git a/ext/v8/script-origin.cc b/ext/v8/script-origin.cc new file mode 100644 index 00000000..a529979e --- /dev/null +++ b/ext/v8/script-origin.cc @@ -0,0 +1,51 @@ +#include "rr.h" + +namespace rr { + VALUE ScriptOrigin::Class; + void ScriptOrigin::Init() { + ClassBuilder("ScriptOrigin"). + defineSingletonMethod("new", &initialize). + + defineMethod("ResourceName", &ResourceName). + defineMethod("ResourceLineOffset", &ResourceLineOffset). + defineMethod("ResourceColumnOffset", &ResourceColumnOffset). + defineMethod("ScriptID", &ScriptID). + defineMethod("SourceMapUrl", &SourceMapUrl). + + store(&Class); + } + + VALUE ScriptOrigin::initialize(int argc, VALUE argv[], VALUE self) { + Container* container = new Container(); + rb_scan_args(argc, argv, "17", + &container->name, + &container->line_offset, + &container->column_offset, + &container->is_shared_cross_origin, + &container->script_id, + &container->is_embedder_debug_script, + &container->source_map_url, + &container->is_opaque); + return ScriptOrigin(container); + } + + VALUE ScriptOrigin::ResourceName(VALUE self) { + return ScriptOrigin(self).container->name; + } + + VALUE ScriptOrigin::ResourceLineOffset(VALUE self) { + return ScriptOrigin(self).container->line_offset; + } + + VALUE ScriptOrigin::ResourceColumnOffset(VALUE self) { + return ScriptOrigin(self).container->column_offset; + } + + VALUE ScriptOrigin::ScriptID(VALUE self) { + return ScriptOrigin(self).container->script_id; + } + + VALUE ScriptOrigin::SourceMapUrl(VALUE self) { + return ScriptOrigin(self).container->source_map_url; + } +} diff --git a/ext/v8/script-origin.h b/ext/v8/script-origin.h new file mode 100644 index 00000000..cbcafb55 --- /dev/null +++ b/ext/v8/script-origin.h @@ -0,0 +1,95 @@ +// -*- mode: c++ -*- +#ifndef SCRIPT_ORIGIN_H +#define SCRIPT_ORIGIN_H + +namespace rr { + class ScriptOrigin { + struct Container { + inline Container() {} + + Container(VALUE name_, + VALUE line_offset_, + VALUE column_offset_, + VALUE is_shared_cross_origin_, + VALUE script_id_, + VALUE is_embedder_debug_script_, + VALUE source_map_url_, + VALUE is_opaque_) : + name(name_), + line_offset(line_offset_), + column_offset(column_offset_), + is_shared_cross_origin(is_shared_cross_origin_), + script_id(script_id_), + is_embedder_debug_script(is_embedder_debug_script_), + source_map_url(source_map_url_), + is_opaque(is_opaque_) {} + + + VALUE name; + VALUE line_offset; + VALUE column_offset; + VALUE is_shared_cross_origin; //option + VALUE script_id; + VALUE is_embedder_debug_script; //option + VALUE source_map_url; + VALUE is_opaque; //option + + }; + struct Integer : public Equiv { + Integer(v8::Handle value) : + Equiv(INT2FIX(value->IntegerValue())) { + } + }; + public: + static void Init(); + + ScriptOrigin(VALUE value) { + Data_Get_Struct(value, struct Container, container); + } + ScriptOrigin(Container* container_) : + container(container_) { + } + ScriptOrigin(v8::Isolate* isolate, v8::ScriptOrigin origin) : + ScriptOrigin(new Container( + Value(isolate, origin.ResourceName()), + Integer(origin.ResourceLineOffset()), + Integer(origin.ResourceColumnOffset()), + Bool(origin.Options().IsSharedCrossOrigin()), + Integer(origin.ScriptID()), + Bool(origin.Options().IsEmbedderDebugScript()), + Value(isolate, origin.SourceMapUrl()), + Bool(origin.Options().IsOpaque()))) { + } + + static void mark(Container* container) { + rb_gc_mark(container->name); + rb_gc_mark(container->line_offset); + rb_gc_mark(container->column_offset); + rb_gc_mark(container->script_id); + rb_gc_mark(container->source_map_url); + rb_gc_mark(container->is_shared_cross_origin); + rb_gc_mark(container->is_embedder_debug_script); + rb_gc_mark(container->is_opaque); + } + + static VALUE initialize(int argc, VALUE argv[], VALUE self); + + static void deallocate(Container* container) { + delete container; + } + inline operator VALUE() { + return Data_Wrap_Struct(Class, &mark, &deallocate, container); + } + static VALUE ResourceName(VALUE self); + static VALUE ResourceLineOffset(VALUE self); + static VALUE ResourceColumnOffset(VALUE self); + static VALUE ScriptID(VALUE self); + static VALUE SourceMapUrl(VALUE self); + + static VALUE Class; + private: + Container* container; + }; +} + +#endif /* SCRIPT_ORIGIN_H */ diff --git a/ext/v8/script.cc b/ext/v8/script.cc index 75f26200..77477db9 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -1,7 +1,6 @@ #include "rr.h" namespace rr { - void Script::Init() { ClassBuilder("Script"). defineSingletonMethod("Compile", &Compile). @@ -12,11 +11,6 @@ namespace rr { store(&Class); - // TODO - // ClassBuilder("ScriptOrigin"). - // defineSingletonMethod("new", &ScriptOrigin::initialize). - // store(&ScriptOrigin::Class); - // TODO // ClassBuilder("ScriptData"). // defineSingletonMethod("PreCompile", &ScriptData::PreCompile). @@ -27,6 +21,7 @@ namespace rr { // store(&ScriptData::Class); } + VALUE Script::Compile(int argc, VALUE argv[], VALUE self) { VALUE source, rb_context, origin; rb_scan_args(argc, argv, "21", &source, &rb_context, &origin); @@ -44,5 +39,4 @@ namespace rr { return Value::handleToRubyObject(context->GetIsolate(), Script(self)->Run()); } - } diff --git a/ext/v8/script.h b/ext/v8/script.h index 9ed9d3c8..860214e7 100644 --- a/ext/v8/script.h +++ b/ext/v8/script.h @@ -1,6 +1,9 @@ +// -*- mode: c++ -*- #ifndef RR_SCRIPT #define RR_SCRIPT +#include "rr.h" + namespace rr { class Script : public Ref { @@ -13,7 +16,5 @@ namespace rr { inline Script(VALUE value) : Ref(value) {} inline Script(v8::Isolate* isolate, v8::Handle script) : Ref(isolate, script) {} }; - } - #endif diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index 76ed15cb..f0c3a880 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -3,6 +3,14 @@ describe V8::C::Function do requires_v8_context + it "has a script origin" do + fn = run '(function() { return "foo" })' + origin = fn.GetScriptOrigin() + expect(origin.ResourceName().ToString().Utf8Value()).to eql 'undefined' + expect(origin.ResourceLineOffset()).to eql 0 + expect(origin.ResourceColumnOffset()).to eql 0 + end + it 'can be called' do fn = run '(function() { return "foo" })' expect(fn.Call(@ctx.Global, []).Utf8Value).to eq 'foo' @@ -26,12 +34,12 @@ expect(fn.NewInstance.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'foo' end - # it 'can be called as a constructor with arguments' do - # fn = run '(function(foo) {this.foo = foo})' - # object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) + it 'can be called as a constructor with arguments' do + fn = run '(function(foo) {this.foo = foo})' + object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) - # expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' - # end + expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' + end # TODO # it 'doesn\'t kill the world if invoking it throws a javascript exception' do diff --git a/spec/c/script_origin_spec.rb b/spec/c/script_origin_spec.rb new file mode 100644 index 00000000..0a85771e --- /dev/null +++ b/spec/c/script_origin_spec.rb @@ -0,0 +1,38 @@ +require 'c_spec_helper' + +describe V8::C::ScriptOrigin do + requires_v8_context + + describe "with only a name" do + let(:origin) { V8::C::ScriptOrigin.new V8::C::String::NewFromUtf8(@isolate, "bob.js") } + + it "it hase a resource name" do + expect(origin.ResourceName().Utf8Value).to eql "bob.js" + end + + it "has nil for all the other values" do + expect(origin.ResourceLineOffset()).to be_nil + expect(origin.ResourceColumnOffset()).to be_nil + expect(origin.ScriptID()).to be_nil + expect(origin.SourceMapUrl()).to be_nil + end + end + + describe "with all the other options" do + let(:origin) do + V8::C::ScriptOrigin.new( + V8::C::String::NewFromUtf8(@isolate, "bob.js"), 5, 25, + true, 100, true, + V8::C::String::NewFromUtf8(@isolate, "http://foo/bob.js.map"), + false + ) + end + it "maps the correct values" do + expect(origin.ResourceName().Utf8Value()).to eql 'bob.js' + expect(origin.ResourceLineOffset()).to eql 5 + expect(origin.ResourceColumnOffset()).to eql 25 + expect(origin.ScriptID()).to eql 100 + expect(origin.SourceMapUrl().Utf8Value()).to eql "http://foo/bob.js.map" + end + end +end From 7fee981c94aff2711d68361831daba65c12a296d Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 10 Jul 2015 19:17:06 -0500 Subject: [PATCH 031/105] add support for Symbols --- ext/v8/init.cc | 2 ++ ext/v8/name.cc | 16 ++++++++++ ext/v8/name.h | 15 +++++++++ ext/v8/rr.h | 2 ++ ext/v8/rr_string.cc | 2 +- ext/v8/symbol.cc | 74 +++++++++++++++++++++++++++++++++++++++++++ ext/v8/symbol.h | 26 +++++++++++++++ spec/c/symbol_spec.rb | 53 +++++++++++++++++++++++++++++++ 8 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 ext/v8/name.cc create mode 100644 ext/v8/name.h create mode 100644 ext/v8/symbol.cc create mode 100644 ext/v8/symbol.h create mode 100644 spec/c/symbol_spec.rb diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 1dfaba3a..aede0c95 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -16,7 +16,9 @@ extern "C" { Value::Init(); Object::Init(); Primitive::Init(); + Name::Init(); String::Init(); + Symbol::Init(); Function::Init(); Script::Init(); ScriptOrigin::Init(); diff --git a/ext/v8/name.cc b/ext/v8/name.cc new file mode 100644 index 00000000..55c341b4 --- /dev/null +++ b/ext/v8/name.cc @@ -0,0 +1,16 @@ +#include "rr.h" + +namespace rr { + void Name::Init() { + ClassBuilder("Name", Primitive::Class). + defineMethod("GetIdentityHash", &GetIdentityHash). + + store(&Class); + } + VALUE Name::GetIdentityHash(VALUE self) { + Name name(self); + Locker lock(name.getIsolate()); + + return INT2FIX(name->GetIdentityHash()); + } +} diff --git a/ext/v8/name.h b/ext/v8/name.h new file mode 100644 index 00000000..334d8d3b --- /dev/null +++ b/ext/v8/name.h @@ -0,0 +1,15 @@ +//mode -*- c++ -*- +#ifndef NAME_H +#define NAME_H + +namespace rr { + class Name : public Ref { + public: + static void Init(); + static VALUE GetIdentityHash(VALUE self); + + Name(VALUE self) : Ref(self) {} + }; +} + +#endif /* NAME_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index d312862f..f22e505f 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -39,7 +39,9 @@ inline VALUE not_implemented(const char* message) { #include "primitive.h" #include "external.h" // This one is named v8_string to avoid name collisions with C's string.h +#include "name.h" #include "rr_string.h" +#include "symbol.h" #include "script.h" #include "script-origin.h" diff --git a/ext/v8/rr_string.cc b/ext/v8/rr_string.cc index fe87b5ca..48ebf948 100644 --- a/ext/v8/rr_string.cc +++ b/ext/v8/rr_string.cc @@ -3,7 +3,7 @@ namespace rr { void String::Init() { - ClassBuilder("String", Primitive::Class). + ClassBuilder("String", Name::Class). defineSingletonMethod("NewFromUtf8", &NewFromUtf8). defineSingletonMethod("Concat", &Concat). diff --git a/ext/v8/symbol.cc b/ext/v8/symbol.cc new file mode 100644 index 00000000..a18b1c2d --- /dev/null +++ b/ext/v8/symbol.cc @@ -0,0 +1,74 @@ +#include "rr.h" + +namespace rr { + void Symbol::Init() { + ClassBuilder("Symbol", Name::Class). + defineSingletonMethod("New", &New). + defineSingletonMethod("For", &For). + defineSingletonMethod("ForApi", &ForApi). + defineSingletonMethod("GetIterator", &GetIterator). + defineSingletonMethod("GetUnscopables", &GetUnscopables). + defineSingletonMethod("GetToStringTag", &GetToStringTag). + defineMethod("Name", &Name). + + store(&Class); + } + + VALUE Symbol::New(int argc, VALUE argv[], VALUE self) { + VALUE rb_isolate, rb_name; + rb_scan_args(argc, argv, "11", &rb_isolate, &rb_name); + + Isolate isolate(rb_isolate); + Locker lock(isolate); + v8::HandleScope handle_scope(isolate); + + if (RTEST(rb_name)) { + return Symbol(isolate, v8::Symbol::New(isolate, String(rb_name))); + } else { + return Symbol(isolate, v8::Symbol::New(isolate)); + } + } + + VALUE Symbol::For(VALUE self, VALUE rb_isolate, VALUE name) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::For(isolate, String(name))); + } + + VALUE Symbol::ForApi(VALUE self, VALUE rb_isolate, VALUE name) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::ForApi(isolate, String(name))); + } + + VALUE Symbol::GetIterator(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetIterator(isolate)); + } + + VALUE Symbol::GetUnscopables(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetUnscopables(isolate)); + } + + VALUE Symbol::GetToStringTag(VALUE self, VALUE rb_isolate) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return Symbol(isolate, v8::Symbol::GetToStringTag(isolate)); + } + + VALUE Symbol::Name(VALUE self) { + Symbol symbol(self); + Isolate isolate(symbol.getIsolate()); + Locker lock(isolate); + + return Value::handleToRubyObject(isolate, symbol->Name()); + } +} diff --git a/ext/v8/symbol.h b/ext/v8/symbol.h new file mode 100644 index 00000000..75016619 --- /dev/null +++ b/ext/v8/symbol.h @@ -0,0 +1,26 @@ +// -*- mode: c++ -*- +#ifndef SYMBOL_H +#define SYMBOL_H + +#include "rr.h" + +namespace rr { + class Symbol : public Ref { + public: + static void Init(); + + static VALUE Name(VALUE self); + static VALUE New(int argc, VALUE argv[], VALUE self); + static VALUE For(VALUE self, VALUE isolate, VALUE name); + static VALUE ForApi(VALUE self, VALUE isolate, VALUE name); + static VALUE GetIterator(VALUE self, VALUE isolate); + static VALUE GetUnscopables(VALUE self, VALUE isolate); + static VALUE GetToStringTag(VALUE self, VALUE isolate); + + Symbol(VALUE self) : Ref(self) {} + Symbol(v8::Isolate* isolate, v8::Local symbol) : + Ref(isolate, symbol) {} + }; +} + +#endif /* SYMBOL_H */ diff --git a/spec/c/symbol_spec.rb b/spec/c/symbol_spec.rb new file mode 100644 index 00000000..11bf0b11 --- /dev/null +++ b/spec/c/symbol_spec.rb @@ -0,0 +1,53 @@ +require 'c_spec_helper' + +describe V8::C::Symbol do + requires_v8_context + + describe "without a description" do + let(:symbol) { V8::C::Symbol::New(@isolate)} + + it "exists" do + expect(symbol).to be + end + + it "has an identity hash" do + expect(symbol.GetIdentityHash()).to be + end + end + + describe "with a description" do + let(:description) { V8::C::String::NewFromUtf8(@isolate, "bob") } + let(:symbol) { V8::C::Symbol::New(@isolate, description) } + + it "has a name" do + expect(symbol.Name().Utf8Value()).to eql "bob" + end + end + + describe "from symbol registries" do + let(:key) { V8::C::String::NewFromUtf8(@isolate, "mysym") } + let(:global) { V8::C::Symbol::For(@isolate, key) } + let(:api) { V8::C::Symbol::ForApi(@isolate, key) } + + it "always retrieves the same value for a given key" do + expect(V8::C::Symbol::For(@isolate, key).StrictEquals(global)).to be true + expect(V8::C::Symbol::ForApi(@isolate, key).StrictEquals(api)).to be true + end + + it "returns different symbols for different registries" do + expect(global.StrictEquals(api)).to be false + end + end + + describe "well-known symbols" do + it "GetIterator" do + expect(V8::C::Symbol::GetIterator(@isolate)).to be_kind_of V8::C::Symbol + end + it "GetUnscopables" do + expect(V8::C::Symbol::GetUnscopables(@isolate)).to be_kind_of V8::C::Symbol + end + it "GetToStringTag" do + expect(V8::C::Symbol::GetToStringTag(@isolate)).to be_kind_of V8::C::Symbol + end + end +end From 38af5412d4ebf41ba2daf837711b083d41b71224 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 01:49:06 -0500 Subject: [PATCH 032/105] coordinate shared datastructure teardown --- ext/v8/isolate.cc | 17 +++---- ext/v8/isolate.h | 111 +++++++++++++++++++++++++++++++++-------- ext/v8/ref.h | 12 +++-- spec/c/isolate_spec.rb | 5 -- spec/c_spec_helper.rb | 2 + 5 files changed, 107 insertions(+), 40 deletions(-) diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 9bf99393..e9e001bb 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -4,42 +4,39 @@ namespace rr { + VALUE Isolate::Class; + void Isolate::Init() { rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). defineMethod("Dispose", &Isolate::Dispose). - defineMethod("Equals", &rr::Isolate::PointerEquals). store(&Class); } - VALUE Isolate::New(VALUE self) { Isolate::IsolateData* data = new IsolateData(); + VALUE rb_cRetainedObjects = rb_eval_string("V8::RetainedObjects"); data->retained_objects = rb_funcall(rb_cRetainedObjects, rb_intern("new"), 0); v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = &data->array_buffer_allocator; + v8::Isolate* isolate = v8::Isolate::New(create_params); + - Isolate isolate(v8::Isolate::New(create_params)); isolate->SetData(0, data); isolate->AddGCPrologueCallback(&clearReferences); - return isolate; + data->isolate = isolate; + return Isolate(isolate); } VALUE Isolate::Dispose(VALUE self) { Isolate isolate(self); - delete isolate.data(); isolate->Dispose(); return Qnil; } - - template <> - void Pointer::unwrap(VALUE value) { - Data_Get_Struct(value, class v8::Isolate, pointer); - } } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 99ae660d..7fc14802 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -23,15 +23,60 @@ namespace rr { * Note: You must call `Dispose()` on the isolate for its resources * to be released, otherwise, it will be leaked. */ - class Isolate : public Pointer { + class Isolate { public: struct IsolateData; + static VALUE Class; static void Init(); static VALUE New(VALUE self); - inline Isolate(v8::Isolate* isolate) : Pointer(isolate) {} - inline Isolate(VALUE value) : Pointer(value) {} + inline Isolate(IsolateData* data_) : data(data_) {} + inline Isolate(v8::Isolate* isolate) : + data((IsolateData*)isolate->GetData(0)) {} + inline Isolate(VALUE value) { + Data_Get_Struct(value, struct IsolateData, data); + } + + /** + * Called, when Ruby no longer has any more references to this + * instance of `V8::C::Isolate`. However, this does not + * necessarily mean that we can delete the isolate data because there + * could be outstanding objects... things like a V8::C::String + * that are part of this isolate that have yet to be garbage + * collected. The last object in the isolate, including the + * isolate itself is responsible for deleting the isolate data. + * + */ + static void destroy(IsolateData* data) { + Isolate isolate(data); + isolate.decrementTotalReferences(); + } + + /** + * Every time we take out a reference to a V8 object, call this + * method. + */ + inline void incrementTotalReferences() { + char counter(0); + data->total_ruby_references_queue.enqueue(counter); + } + + /** + * Every time a Ruby reference to a V8 object is garbage + * collected, call this method. If this is the last object + * associated with this isolate, then the isolate data can, and + * will, be safely deleted. + */ + inline bool decrementTotalReferences() { + char counter(0); + if (data->total_ruby_references_queue.try_dequeue(counter)) { + return true; + } else { + delete data; + return false; + } + } /** * Converts the v8::Isolate into a Ruby Object, while setting up @@ -39,17 +84,25 @@ namespace rr { * VALUE rubyObject = Isolate(v8::Isolate::New()); */ virtual operator VALUE() { - return Data_Wrap_Struct(Class, &releaseAndMarkRetainedObjects, 0, pointer); + return Data_Wrap_Struct(Class, &releaseAndMarkRetainedObjects, &destroy, data); } /** - * Access the book-keeping data. e.g. + * Convert this into a v8::Isolate* for those areas of the API + * that call for it: E.g. * - * Isolate(self).data(); + * v8::Context::New(Isolate(self)); */ - inline IsolateData* data() { - return (IsolateData*)pointer->GetData(0); - } + inline operator v8::Isolate*() { return data->isolate; } + + /** + * Dereference the underlying v8::Isolate so that we can call methods + * on it. E.g. + * + * Isolate(self)->IsDead(); + */ + + inline v8::Isolate* operator ->() { return data->isolate; } /** * Schedule a v8::Persistent reference to be be deleted with the next @@ -59,7 +112,9 @@ namespace rr { */ template inline void scheduleReleaseObject(v8::Persistent* cell) { - data()->v8_release_queue.enqueue((v8::Persistent*)cell); + if (this->decrementTotalReferences()) { + data->v8_release_queue.enqueue((v8::Persistent*)cell); + } } /** @@ -68,7 +123,7 @@ namespace rr { * where you do not hold any Ruby locks (such as the V8 GC thread) */ inline void scheduleReleaseObject(VALUE object) { - data()->rb_release_queue.enqueue(object); + data->rb_release_queue.enqueue(object); } /** @@ -80,7 +135,7 @@ namespace rr { * Note: should be called from a place where Ruby locks are held. */ inline void retainObject(VALUE object) { - rb_funcall(data()->retained_objects, rb_intern("add"), 1, object); + rb_funcall(data->retained_objects, rb_intern("add"), 1, object); } /** @@ -91,18 +146,15 @@ namespace rr { * Note: should be called from a place where Ruby locks are held. */ inline void releaseObject(VALUE object) { - rb_funcall(data()->retained_objects, rb_intern("remove"), 1, object); + rb_funcall(data->retained_objects, rb_intern("remove"), 1, object); } - /** * The `gc_mark()` callback for this Isolate's * Data_Wrap_Struct. It releases all pending Ruby objects. */ - - static void releaseAndMarkRetainedObjects(v8::Isolate* isolate_) { - Isolate isolate(isolate_); - IsolateData* data = isolate.data(); + static void releaseAndMarkRetainedObjects(IsolateData* data) { + Isolate isolate(data); VALUE object; while (data->rb_release_queue.try_dequeue(object)) { isolate.releaseObject(object); @@ -118,7 +170,7 @@ namespace rr { static void clearReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) { Isolate isolate(i); v8::Persistent* cell; - while (isolate.data()->v8_release_queue.try_dequeue(cell)) { + while (isolate.data->v8_release_queue.try_dequeue(cell)) { cell->Reset(); delete cell; } @@ -148,6 +200,11 @@ namespace rr { */ struct IsolateData { + /** + * The actual Isolate + */ + v8::Isolate* isolate; + /** * An instance of `V8::RetainedObjects` that contains all * references held from from V8. As long as they are in this @@ -157,7 +214,7 @@ namespace rr { /** * A custom ArrayBufferAllocator for this isolate. Why? because - * if you don't, it will segfault when you try and create an + * if you don't, it will segfault when you try and create a * Context. That's why. */ ArrayBufferAllocator array_buffer_allocator; @@ -176,7 +233,21 @@ namespace rr { * needs it. */ ConcurrentQueue rb_release_queue; + + /** + * Contains a number of tokens representing all of the live Ruby + * references that are currently active in this Isolate. Every + * time a Ruby reference to a V8 object is added, an item is + * added to this queue. Every time a ruby reference is garbage + * collected, an item is pulled out of the queue. Whenever this + * queue reaches 0, then it is time to delete the isolate data + * since there are no more object associated with it. + */ + ConcurrentQueue total_ruby_references_queue; + }; + + IsolateData* data; }; } diff --git a/ext/v8/ref.h b/ext/v8/ref.h index deaf0086..0e053c43 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -38,8 +38,8 @@ namespace rr { Holder* holder = unwrapHolder(); if (holder) { - this->isolate = holder->isolate; - this->handle = v8::Local::New(holder->isolate, *holder->cell); + this->isolate = holder->data->isolate; + this->handle = v8::Local::New(holder->data->isolate, *holder->cell); } else { this->isolate = NULL; this->handle = v8::Local(); @@ -94,13 +94,15 @@ namespace rr { struct Holder { Holder(v8::Isolate* isolate, v8::Handle handle) : - isolate(isolate), cell(new v8::Persistent(isolate, handle)) {} + data((Isolate::IsolateData*)isolate->GetData(0)), cell(new v8::Persistent(isolate, handle)) { + Isolate(data).incrementTotalReferences(); + } virtual ~Holder() { - Isolate(isolate).scheduleReleaseObject(cell); + Isolate(data).scheduleReleaseObject(cell); } - v8::Isolate* isolate; + Isolate::IsolateData* data; v8::Persistent* cell; }; diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 02c0e724..593c1b73 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -7,11 +7,6 @@ expect(isolate).to be end - it 'can be tested for equality' do - expect(isolate.Equals(isolate)).to eq true - expect(isolate.Equals(V8::C::Isolate::New())).to eq false - end - it "can be disposed of" do isolate.Dispose() end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index c5e52d53..0bc83e7b 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -22,6 +22,8 @@ def bootstrap_v8_context @ctx.Exit end end + ensure + @isolate.Dispose() end end From ef23f6ac65e51eb94df2dc574b7d8cbc86e85c06 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 21:08:38 -0500 Subject: [PATCH 033/105] only run C spec compiled with Clang In order to make sure we have valid failures on this branch, and not just a bunch of false negatives, we're only running the C specs, and building with clang. Eventually, we'll merge in support for gcc and more of the test suite. For now, we want to only include the specs we things should be passing --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 68d731ae..b396e9b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,12 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 +env: CXX=clang++ notifications: recipients: - cowboyd@thefrontside.net before_install: - gem update --system 2.1.11 -script: bundle exec rake compile spec +script: + - bundle exec rake compile + - bundle exec rspec spec/c From bc6668a8a3a04850bf20a9c688709cc8f642dd58 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 21:13:09 -0500 Subject: [PATCH 034/105] allow failures on older versions of ruby. We can decide whether to backport later --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index b396e9b6..9e0ad5d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,11 @@ rvm: - 1.8.7 - rbx-2.2.3 env: CXX=clang++ +matrix: + allow_failures: + - rvm: 1.9.3 + - rvm: 1.9.2 + - rvm: 1.8.7 notifications: recipients: - cowboyd@thefrontside.net From e82006e900f59a471dfc289b92bd159196fa1692 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 21:23:58 -0500 Subject: [PATCH 035/105] use clang for CC too? --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e0ad5d1..694fb51c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: CXX=clang++ +env: + - CC=clang + - CXX=clang++ matrix: allow_failures: - rvm: 1.9.3 From bd88a6c831a9c2e31fb98d35d99fe6603248b1f7 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 22:28:29 -0500 Subject: [PATCH 036/105] install modern g++ and clang befondhand --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.travis.yml b/.travis.yml index 694fb51c..6cdc3949 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,3 +22,17 @@ before_install: script: - bundle exec rake compile - bundle exec rspec spec/c +sudo: false +compiler: + - gcc + - clang +install: +- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + - g++-4.8 + - clang From e58b7aabecbe35c1a082e7047f2d92da11d0fbaa Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 22:33:06 -0500 Subject: [PATCH 037/105] let travis set the compiler, don't override the install --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6cdc3949..12c2b27d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,6 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: - - CC=clang - - CXX=clang++ matrix: allow_failures: - rvm: 1.9.3 @@ -27,7 +24,8 @@ compiler: - gcc - clang install: -- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi + - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi + - bundle install addons: apt: sources: From c4b6a5c7d89acccb1189aa72d07a91977ffd9d2a Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 22:47:58 -0500 Subject: [PATCH 038/105] attempt with just gcc-4.8 --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12c2b27d..fa48ea3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 +env: CXX=g++-4.8 matrix: allow_failures: - rvm: 1.9.3 @@ -20,12 +21,6 @@ script: - bundle exec rake compile - bundle exec rspec spec/c sudo: false -compiler: - - gcc - - clang -install: - - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi - - bundle install addons: apt: sources: From ea4cd3baab7becba5ab2822b1ef2098cdc51e78d Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 22:52:33 -0500 Subject: [PATCH 039/105] still have some command lines to debug, so lets try clang --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fa48ea3c..6ea4c566 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: CXX=g++-4.8 +env: CXX=clang++ matrix: allow_failures: - rvm: 1.9.3 From 2a5bf751ac7214ad4cbb580ac4d8a514ea8994ff Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 22:57:14 -0500 Subject: [PATCH 040/105] what versions do we have anyhow? --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6ea4c566..e8868433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,8 @@ notifications: before_install: - gem update --system 2.1.11 script: + - g++ --version + - clang++ --version - bundle exec rake compile - bundle exec rspec spec/c sudo: false From 73dce99124d29b753f67ca7dbec7b0a60a6a9256 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 23:06:03 -0500 Subject: [PATCH 041/105] print some docs --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8868433..5e692a5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: CXX=clang++ +env: CXX=g++-4.8 matrix: allow_failures: - rvm: 1.9.3 @@ -19,7 +19,9 @@ before_install: - gem update --system 2.1.11 script: - g++ --version + - g++-4.8 --version - clang++ --version + - ls /usr/bin/clang* - bundle exec rake compile - bundle exec rspec spec/c sudo: false From f13b23bff0beb03ac87ec6d8d000792a5ffaa9cd Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 23:12:45 -0500 Subject: [PATCH 042/105] print out contents of CXX and CC --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e692a5b..b10e230d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: CXX=g++-4.8 +env: + - CXX=g++-4.8 + - CC=gcc-4.8 matrix: allow_failures: - rvm: 1.9.3 @@ -18,10 +20,8 @@ notifications: before_install: - gem update --system 2.1.11 script: - - g++ --version - - g++-4.8 --version - - clang++ --version - - ls /usr/bin/clang* + - $(CXX) --version + - $(CC) --version - bundle exec rake compile - bundle exec rspec spec/c sudo: false From 31043be8a2829e3e722b4265c2adef587ea580d2 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 23:14:36 -0500 Subject: [PATCH 043/105] properly use shell variables --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b10e230d..3acc8d3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ notifications: before_install: - gem update --system 2.1.11 script: - - $(CXX) --version - - $(CC) --version + - $CXX --version + - $CC --version - bundle exec rake compile - bundle exec rspec spec/c sudo: false From 78f507aa9502fedcc81e6e1a2f5a6bd952c19111 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 11 Jul 2015 23:43:27 -0500 Subject: [PATCH 044/105] environment variables are not being set, hardcode for now --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3acc8d3f..e02df349 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,6 @@ rvm: - 1.9.2 - 1.8.7 - rbx-2.2.3 -env: - - CXX=g++-4.8 - - CC=gcc-4.8 matrix: allow_failures: - rvm: 1.9.3 @@ -20,9 +17,7 @@ notifications: before_install: - gem update --system 2.1.11 script: - - $CXX --version - - $CC --version - - bundle exec rake compile + - CXX=g++-4.8 bundle exec rake compile - bundle exec rspec spec/c sudo: false addons: From e1fbdb4162361d5ff6769864dbfd70bcb2c5c7df Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 12 Jul 2015 14:05:26 -0500 Subject: [PATCH 045/105] allow using a custom compiler --- .travis.yml | 3 +++ ext/v8/extconf.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index e02df349..692cbb84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ matrix: - rvm: 1.9.3 - rvm: 1.9.2 - rvm: 1.8.7 +env: + - CXX=g++-4.8 + - CXX=clang++ notifications: recipients: - cowboyd@thefrontside.net diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index 5ec06bf7..98967215 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -1,4 +1,5 @@ require 'mkmf' +RbConfig::MAKEFILE_CONFIG['CXX'] = ENV['CXX'] if ENV['CXX'] have_library('pthread') have_library('objc') if RUBY_PLATFORM =~ /darwin/ From d18f479967c4d2d8ac22c7aa49f1df8dd6db1116 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 12 Jul 2015 14:12:50 -0500 Subject: [PATCH 046/105] Don't hardcode g++ into build --- .travis.yml | 2 +- ext/v8/extconf.rb | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 692cbb84..ff3ec2b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ notifications: before_install: - gem update --system 2.1.11 script: - - CXX=g++-4.8 bundle exec rake compile + - bundle exec rake compile - bundle exec rspec spec/c sudo: false addons: diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index 98967215..17bbbd7f 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -1,5 +1,5 @@ require 'mkmf' -RbConfig::MAKEFILE_CONFIG['CXX'] = ENV['CXX'] if ENV['CXX'] +cxx = RbConfig::MAKEFILE_CONFIG['CXX'] = ENV['CXX'] if ENV['CXX'] have_library('pthread') have_library('objc') if RUBY_PLATFORM =~ /darwin/ @@ -10,7 +10,10 @@ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/ $CPPFLAGS += " -std=c++11" -$LDFLAGS += " -stdlib=libstdc++" + +if cxx =~ /clang/ + $LDFLAGS += " -stdlib=libstdc++" +end CONFIG['LDSHARED'] = '$(CXX) -shared' unless RUBY_PLATFORM =~ /darwin/ if CONFIG['warnflags'] From 51c13d1bff0c95b749146c0a59afe891802aae49 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 12 Jul 2015 14:26:37 -0500 Subject: [PATCH 047/105] disable bundler cache that contains yanked gems --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ff3ec2b9..943d0d32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -cache: bundler +#cache: bundler rvm: - 2.1.0 - 2.0.0 From 6f55073ce8ebe0c6cf26d46d299c6bccdf7495ad Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 12 Jul 2015 15:01:22 -0500 Subject: [PATCH 048/105] remove extra space --- ext/v8/extconf.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/v8/extconf.rb b/ext/v8/extconf.rb index 17bbbd7f..67ff23c5 100644 --- a/ext/v8/extconf.rb +++ b/ext/v8/extconf.rb @@ -10,7 +10,6 @@ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/ $CPPFLAGS += " -std=c++11" - if cxx =~ /clang/ $LDFLAGS += " -stdlib=libstdc++" end From 3f1bde04cc01a1eb5af106dae4f69c8c272a4b4e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 13 Jul 2015 13:57:10 -0500 Subject: [PATCH 049/105] rbx: not to big to fail. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 943d0d32..d27e5e15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ matrix: - rvm: 1.9.3 - rvm: 1.9.2 - rvm: 1.8.7 + - rvm: rbx-2.2.3 env: - CXX=g++-4.8 - CXX=clang++ From 82db08dc3b0af969d1b94277816df71f1ea7358e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 13 Jul 2015 18:39:26 -0500 Subject: [PATCH 050/105] add ignores for gnu global artifacts --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b594d04d..7de7901d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ spec/reports test/tmp test/version_tmp tmp +/GPATH +/GRTAGS +/GTAGS From 0681a9865017f584fbf9c65227104abe58d37fb6 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 15 Jul 2015 21:14:21 -0500 Subject: [PATCH 051/105] add ability to define a callback for a JS Function --- ext/v8/external.cc | 21 +--- ext/v8/external.h | 27 ++++++ ext/v8/function-callback.h | 192 +++++++++++++++++++++++++++++++++++++ ext/v8/function.cc | 17 +++- ext/v8/function.h | 5 +- ext/v8/init.cc | 2 + ext/v8/locks.h | 1 + ext/v8/object.cc | 8 ++ ext/v8/object.h | 2 +- ext/v8/rr.h | 2 + spec/c/function_spec.rb | 38 ++++++++ 11 files changed, 292 insertions(+), 23 deletions(-) create mode 100644 ext/v8/function-callback.h diff --git a/ext/v8/external.cc b/ext/v8/external.cc index 45b3d84d..7eb004ae 100644 --- a/ext/v8/external.cc +++ b/ext/v8/external.cc @@ -13,31 +13,14 @@ namespace rr { VALUE External::New(VALUE self, VALUE r_isolate, VALUE object) { Isolate isolate(r_isolate); - - // as long as this external is alive within JavaScript, it should not be - // garbage collected by Ruby. - isolate.retainObject(object); - Locker lock(isolate); - // create the external. - Container* container = new Container(object); - v8::Local external(v8::External::New(isolate, (void*)container)); - - // next, we create a weak reference to this external so that we can be - // notified when V8 is done with it. At that point, we can let Ruby know - // that this external is done with it. - v8::Global* global(new v8::Global(isolate, external)); - global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); - container->global = global; - - return External(isolate, external); + return External(isolate, wrap(isolate, object)); } VALUE External::Value(VALUE self) { External external(self); Locker lock(external); - Container* container((Container*)external->Value()); - return container->object; + return unwrap(external); } } diff --git a/ext/v8/external.h b/ext/v8/external.h index b9defb26..e6dfdf67 100644 --- a/ext/v8/external.h +++ b/ext/v8/external.h @@ -16,6 +16,33 @@ namespace rr { inline External(v8::Isolate* isolate, v8::Handle value) : External(isolate, v8::Handle::Cast(value)) {} + static inline v8::Local wrap(Isolate isolate, VALUE object) { + // as long as this external is alive within JavaScript, it should not be + // garbage collected by Ruby. + isolate.retainObject(object); + + // create the external. + Container* container = new Container(object); + v8::Local external(v8::External::New(isolate, (void*)container)); + + // next, we create a weak reference to this external so that we can be + // notified when V8 is done with it. At that point, we can let Ruby know + // that this external is done with it. + v8::Global* global(new v8::Global(isolate, external)); + global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); + container->global = global; + + return External(isolate, external); + } + static inline VALUE unwrap(v8::Local external) { + Container* container = (Container*)external->Value(); + return container->object; + }; + + static inline VALUE unwrap(v8::Local external) { + return unwrap(v8::Local::Cast(external)); + } + struct Container { Container(VALUE v) : object(v) {} diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h new file mode 100644 index 00000000..4c63892e --- /dev/null +++ b/ext/v8/function-callback.h @@ -0,0 +1,192 @@ +// -*- mode: c++ -*- +#ifndef RR_FUNCTION_CALLBACK_H +#define RR_FUNCTION_CALLBACK_H + +namespace rr { + + typedef Wrapper> ReturnValueWrapper; + + class ReturnValue : public ReturnValueWrapper { + public: + ReturnValue(v8::ReturnValue value) : ReturnValueWrapper(value) {} + ReturnValue(VALUE self) : ReturnValueWrapper(self) {} + + static VALUE Set(VALUE self, VALUE handle) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + v8::Local value((Value(handle))); + ret->Set(value); + return Qnil; + } + + static VALUE Set_bool(VALUE self, VALUE value) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set((bool)Bool(value)); + return Qnil; + } + + static VALUE Set_double(VALUE self, VALUE value) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2DBL(value)); + return Qnil; + } + + static VALUE Set_int32_t(VALUE self, VALUE i) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2INT(i)); + return Qnil; + } + + static VALUE Set_uint32_t(VALUE self, VALUE i) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2UINT(i)); + return Qnil; + } + + static VALUE SetNull(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetNull(); + return Qnil; + } + + static VALUE SetUndefined(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetUndefined(); + return Qnil; + } + + static VALUE SetEmptyString(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetEmptyString(); + return Qnil; + } + + static VALUE GetIsolate(VALUE self) { + ReturnValue ret(self); + return Isolate(ret->GetIsolate()); + } + + static inline void Init() { + ClassBuilder("ReturnValue"). + defineMethod("Set", &Set). + defineMethod("Set_bool", &Set_bool). + defineMethod("Set_double", &Set_double). + defineMethod("Set_int32_t", &Set_int32_t). + defineMethod("Set_uint32_t", &Set_uint32_t). + defineMethod("SetNull", &SetNull). + defineMethod("SetUndefined", &SetUndefined). + defineMethod("SetEmptyString", &SetEmptyString). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + + Container* container; + }; + + typedef Wrapper> FunctionCallbackInfoWrapper; + + class FunctionCallbackInfo : public FunctionCallbackInfoWrapper { + public: + + inline FunctionCallbackInfo(v8::FunctionCallbackInfo info, v8::Local data_) : + FunctionCallbackInfoWrapper(info), data(data_) {} + + inline FunctionCallbackInfo(VALUE self) : FunctionCallbackInfoWrapper(self) {} + + inline v8::Local operator [](int i) { + return this->container->content[i]; + } + + static v8::Local wrapData(v8::Isolate* isolate, VALUE r_callback, VALUE r_data) { + v8::Local holder = v8::Object::New(isolate); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + holder->SetHiddenValue(callback_key, External::wrap(isolate, r_callback)); + holder->SetHiddenValue(data_key, Value(r_data)); + return holder; + } + + static void invoke(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + v8::Local data(holder->GetHiddenValue(data_key)); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 1, (VALUE)FunctionCallbackInfo(info, data)); + } + + static VALUE Length(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return INT2FIX(info->Length()); + } + + static VALUE at(VALUE self, VALUE i) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Value::handleToRubyObject(info->GetIsolate(), info[NUM2INT(i)]); + } + + static VALUE Callee(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Function(info->GetIsolate(), info->Callee()); + } + + static VALUE This(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE IsConstructCall(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Bool(info->IsConstructCall()); + } + + static VALUE Data(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Value::handleToRubyObject(info->GetIsolate(), info.data); + } + + static VALUE GetIsolate(VALUE self) { + FunctionCallbackInfo info(self); + return Isolate(info->GetIsolate()); + } + + static VALUE GetReturnValue(VALUE self) { + FunctionCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return ReturnValue(info->GetReturnValue()); + } + + static inline void Init() { + ClassBuilder("FunctionCallbackInfo"). + defineMethod("Length", &Length). + defineMethod("[]", &at). + defineMethod("Callee", &Callee). + defineMethod("This", &This). + defineMethod("IsConstructCall", &IsConstructCall). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + + v8::Local data; + }; +} + +#endif /* RR_FUNCTION_CALLBACK_H */ diff --git a/ext/v8/function.cc b/ext/v8/function.cc index f9c570d1..86eaab89 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -4,7 +4,7 @@ namespace rr { void Function::Init() { ClassBuilder("Function", Object::Class). - + defineSingletonMethod("New", &New). defineMethod("NewInstance", &NewInstance). defineMethod("Call", &Call). defineMethod("SetName", &SetName). @@ -20,6 +20,21 @@ namespace rr { store(&Class); } + VALUE Function::New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, r_callback, r_data, r_length; + rb_scan_args(argc, argv, "22", &r_isolate, &r_callback, &r_data, &r_length); + Isolate isolate(r_isolate); + Locker lock(isolate); + + v8::Local data(FunctionCallbackInfo::wrapData(isolate, r_callback, r_data)); + + int length = RTEST(r_length) ? NUM2INT(r_length) : 0; + + v8::FunctionCallback callback = &FunctionCallbackInfo::invoke; + + return Function(isolate, v8::Function::New(isolate, callback, data, length)); + } + VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { VALUE args; rb_scan_args(argc, argv, "01", &args); diff --git a/ext/v8/function.h b/ext/v8/function.h index 0aa0a59c..e4f6c9bd 100644 --- a/ext/v8/function.h +++ b/ext/v8/function.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- #ifndef RR_FUNCTION #define RR_FUNCTION @@ -7,8 +8,9 @@ namespace rr { public: static void Init(); + static VALUE New(int argc, VALUE argv[], VALUE self); static VALUE NewInstance(int argc, VALUE argv[], VALUE self); - static VALUE Call(VALUE self, VALUE receiver, VALUE argv); + static VALUE Call(VALUE self, VALUE receiver, VALUE arguments); static VALUE SetName(VALUE self, VALUE name); static VALUE GetName(VALUE self); static VALUE GetInferredName(VALUE self); @@ -23,7 +25,6 @@ namespace rr { inline Function(VALUE value) : Ref(value) {} inline Function(v8::Isolate* isolate, v8::Handle function) : Ref(isolate, function) {} }; - } #endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index aede0c95..b94bfedc 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -20,6 +20,8 @@ extern "C" { String::Init(); Symbol::Init(); Function::Init(); + FunctionCallbackInfo::Init(); + ReturnValue::Init(); Script::Init(); ScriptOrigin::Init(); Array::Init(); diff --git a/ext/v8/locks.h b/ext/v8/locks.h index 6563a260..78a19403 100644 --- a/ext/v8/locks.h +++ b/ext/v8/locks.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- #ifndef RR_LOCKER #define RR_LOCKER diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 45253858..62cfbd78 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -8,6 +8,7 @@ namespace rr { defineMethod("Set", &Set). defineMethod("Get", &Get). + defineMethod("GetIdentityHash", &GetIdentityHash). store(&Class); } @@ -42,6 +43,13 @@ namespace rr { } } + VALUE Object::GetIdentityHash(VALUE self) { + Object object(self); + Locker lock(object.getIsolate()); + + return INT2FIX(object->GetIdentityHash()); + } + Object::operator VALUE() { Locker lock(getIsolate()); diff --git a/ext/v8/object.h b/ext/v8/object.h index 80c70797..7774ccc3 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -34,7 +34,7 @@ namespace rr { // static VALUE HasNamedLookupInterceptor(VALUE self); // static VALUE HasIndexedLookupInterceptor(VALUE self); // static VALUE TurnOnAccessCheck(VALUE self); - // static VALUE GetIdentityHash(VALUE self); + static VALUE GetIdentityHash(VALUE self); // static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); // static VALUE GetHiddenValue(VALUE self, VALUE key); // static VALUE DeleteHiddenValue(VALUE self, VALUE key); diff --git a/ext/v8/rr.h b/ext/v8/rr.h index f22e505f..abc3bf5a 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -21,6 +21,7 @@ inline VALUE not_implemented(const char* message) { #include "equiv.h" #include "bool.h" #include "pointer.h" +#include "wrapper.h" #include "isolate.h" #include "ref.h" @@ -46,5 +47,6 @@ inline VALUE not_implemented(const char* message) { #include "script.h" #include "script-origin.h" #include "function.h" +#include "function-callback.h" #endif diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f0c3a880..5aed08ba 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -41,6 +41,44 @@ expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' end + describe "with a callback" do + + class FunctionCallback + attr_reader :length, :callee, :this, :is_construct_call, :data + def initialize(isolate) + @isolate = isolate + end + def call(info) + @length = info.Length() + @callee = info.Callee() + @this = info.This() + @is_construct_call = info.IsConstructCall() + @data = info.Data() + + ohai = V8::C::String::NewFromUtf8(@isolate, "ohai ") + arg = info[0].ToString() + result = V8::C::String::Concat(ohai, arg) + info.GetReturnValue().Set(result) + end + end + + let(:callback) { FunctionCallback.new @isolate} + let(:fn) { V8::C::Function::New(@isolate, callback)} + + before do + expect(fn.Call(@ctx.Global(), ["world"]).Utf8Value()).to eql "ohai world" + end + + + it "has all of the function callback info available" do + expect(callback.length).to eql 1 + expect(callback.callee.GetIdentityHash()).to eql fn.GetIdentityHash() + expect(callback.this.GetIdentityHash()).to eql @ctx.Global().GetIdentityHash() + expect(callback.is_construct_call).to be false + end + end + + # TODO # it 'doesn\'t kill the world if invoking it throws a javascript exception' do # V8::C::TryCatch do From b9bbbeccaa0cc893ac8eb7329e26b6f5461633b9 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 15 Jul 2015 21:35:56 -0500 Subject: [PATCH 052/105] add documentation for wrapper class --- ext/v8/wrapper.h | 113 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 ext/v8/wrapper.h diff --git a/ext/v8/wrapper.h b/ext/v8/wrapper.h new file mode 100644 index 00000000..b6e81075 --- /dev/null +++ b/ext/v8/wrapper.h @@ -0,0 +1,113 @@ +// -*- mode: c++ -*- +#ifndef RR_WRAPPER_H +#define RR_WRAPPER_H + +namespace rr { + + /** + * A wrapper provides the ability to create a temporary reference to + * a stack-allocated value so that it can be passed to ruby. This is + * normally the case for a callback parameter, where you will be + * calling into Ruby from C++. Ruby should not call methods on this + * object outside the scope of the function call. E.g. + * + * class MyObjectWrapper : public Wrapper { + * // [...] + * }; + * + * MyObject foo; + * rb_funcall(object, rb_intern("count"), 1, MyObjectWrapper(foo)); + * // ruby should not use anymore, it might crash + * + * Note: This seems dangerous in practice, but since it is only ever + * used by the low-level api, wrapper objects do not escape into the + * wild, and so aren't used beyond their normal lifetime on the C++ + * stack. + * + * We may add a destructor that invalidates the pointer, + * and would throw an exception if you tried to use this object at + * some after it leaves scope. + */ + template + class Wrapper { + public: + /** + * The Ruby Class of this Wrapper + */ + static VALUE Class; + + /** + * Package up this wrapper so that it can be accessed from + * Ruby. Use this just before passing to a ruby call. E.g. + * + * MyObject object; + * rb_funcall(thing, rb_intern("take"), 1, MyObjectWrapper(object)) + */ + Wrapper(T content) : container(new Container(content)) {} + + /** + * Access the underlying object when it has been passed from Ruby + * to a C++ function. E.g. + * + * SomeFunction(VALUE self) { + * MyObjectWrapper object(self); + * } + */ + Wrapper(VALUE self) : container(Container::unwrap(self)) {} + + /** + * Access the wrapped value via pointer dereference: + * + * MyObjectWrapper object(self); + * object->SomeMethod(); + */ + inline T* operator ->() { + return &container->content; + } + + /** + * Convert this Wrapper into a Ruby VALUE so that it can be + * transparently passed to methods expecting a VALUE. This means + * you can do things like: + * + * MyObjectWrapper wrapper(self); + * rb_inspect(wrapper); + * + * Even though MyObjectWrapper is not a VALUE + */ + inline operator VALUE() { + return Container::wrap(container, Class); + } + + /** + * This is the struct that will be held inside the Ruby T_DATA + * object. It mainly serves to keep a reference to the stack value. + */ + struct Container { + + Container(T content_) : content(content_) {} + + static inline VALUE wrap(Container* container, VALUE rb_class) { + return Data_Wrap_Struct(rb_class, 0, &destroy, container); + } + + static inline Container* unwrap(VALUE object) { + Container* container; + Data_Get_Struct(object, struct Container, container); + return container; + } + + static void destroy(Container* container) { + delete container; + } + T content; + }; + + Container* container; + }; + + template + VALUE Wrapper::Class; +} + +#endif /* RR_WRAPPER_H */ From c6c9bc36c0880b6ad65e11f6e1d18bbc80323d55 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 15 Jul 2015 21:38:10 -0500 Subject: [PATCH 053/105] remove obsolete declaration --- ext/v8/function-callback.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index 4c63892e..fec91ed0 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -86,8 +86,6 @@ namespace rr { defineMethod("GetIsolate", &GetIsolate). store(&Class); } - - Container* container; }; typedef Wrapper> FunctionCallbackInfoWrapper; From 3792d742474a1adee4b3c1a75771b0b698a5697b Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 15 Jul 2015 21:43:51 -0500 Subject: [PATCH 054/105] extract ReturnValue into its own file --- ext/v8/function-callback.h | 84 ----------------------------------- ext/v8/function.cc | 2 + ext/v8/return-value.h | 90 ++++++++++++++++++++++++++++++++++++++ ext/v8/rr.h | 1 + 4 files changed, 93 insertions(+), 84 deletions(-) create mode 100644 ext/v8/return-value.h diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index fec91ed0..c3e0ea6e 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -4,90 +4,6 @@ namespace rr { - typedef Wrapper> ReturnValueWrapper; - - class ReturnValue : public ReturnValueWrapper { - public: - ReturnValue(v8::ReturnValue value) : ReturnValueWrapper(value) {} - ReturnValue(VALUE self) : ReturnValueWrapper(self) {} - - static VALUE Set(VALUE self, VALUE handle) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - v8::Local value((Value(handle))); - ret->Set(value); - return Qnil; - } - - static VALUE Set_bool(VALUE self, VALUE value) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set((bool)Bool(value)); - return Qnil; - } - - static VALUE Set_double(VALUE self, VALUE value) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2DBL(value)); - return Qnil; - } - - static VALUE Set_int32_t(VALUE self, VALUE i) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2INT(i)); - return Qnil; - } - - static VALUE Set_uint32_t(VALUE self, VALUE i) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2UINT(i)); - return Qnil; - } - - static VALUE SetNull(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetNull(); - return Qnil; - } - - static VALUE SetUndefined(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetUndefined(); - return Qnil; - } - - static VALUE SetEmptyString(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetEmptyString(); - return Qnil; - } - - static VALUE GetIsolate(VALUE self) { - ReturnValue ret(self); - return Isolate(ret->GetIsolate()); - } - - static inline void Init() { - ClassBuilder("ReturnValue"). - defineMethod("Set", &Set). - defineMethod("Set_bool", &Set_bool). - defineMethod("Set_double", &Set_double). - defineMethod("Set_int32_t", &Set_int32_t). - defineMethod("Set_uint32_t", &Set_uint32_t). - defineMethod("SetNull", &SetNull). - defineMethod("SetUndefined", &SetUndefined). - defineMethod("SetEmptyString", &SetEmptyString). - defineMethod("GetIsolate", &GetIsolate). - store(&Class); - } - }; - typedef Wrapper> FunctionCallbackInfoWrapper; class FunctionCallbackInfo : public FunctionCallbackInfoWrapper { diff --git a/ext/v8/function.cc b/ext/v8/function.cc index 86eaab89..1fac845d 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -26,6 +26,8 @@ namespace rr { Isolate isolate(r_isolate); Locker lock(isolate); + // package up the function's callback data to have bot the code and the data + // parameter. v8::Local data(FunctionCallbackInfo::wrapData(isolate, r_callback, r_data)); int length = RTEST(r_length) ? NUM2INT(r_length) : 0; diff --git a/ext/v8/return-value.h b/ext/v8/return-value.h new file mode 100644 index 00000000..62989474 --- /dev/null +++ b/ext/v8/return-value.h @@ -0,0 +1,90 @@ +// -*- mode: c++ -*- +#ifndef RR_RETURN_VALUE_H +#define RR_RETURN_VALUE_H + +namespace rr { + typedef Wrapper> ReturnValueWrapper; + + class ReturnValue : public ReturnValueWrapper { + public: + ReturnValue(v8::ReturnValue value) : ReturnValueWrapper(value) {} + ReturnValue(VALUE self) : ReturnValueWrapper(self) {} + + static VALUE Set(VALUE self, VALUE handle) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + v8::Local value((Value(handle))); + ret->Set(value); + return Qnil; + } + + static VALUE Set_bool(VALUE self, VALUE value) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set((bool)Bool(value)); + return Qnil; + } + + static VALUE Set_double(VALUE self, VALUE value) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2DBL(value)); + return Qnil; + } + + static VALUE Set_int32_t(VALUE self, VALUE i) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2INT(i)); + return Qnil; + } + + static VALUE Set_uint32_t(VALUE self, VALUE i) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2UINT(i)); + return Qnil; + } + + static VALUE SetNull(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetNull(); + return Qnil; + } + + static VALUE SetUndefined(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetUndefined(); + return Qnil; + } + + static VALUE SetEmptyString(VALUE self) { + ReturnValue ret(self); + Locker lock(ret->GetIsolate()); + ret->SetEmptyString(); + return Qnil; + } + + static VALUE GetIsolate(VALUE self) { + ReturnValue ret(self); + return Isolate(ret->GetIsolate()); + } + + static inline void Init() { + ClassBuilder("ReturnValue"). + defineMethod("Set", &Set). + defineMethod("Set_bool", &Set_bool). + defineMethod("Set_double", &Set_double). + defineMethod("Set_int32_t", &Set_int32_t). + defineMethod("Set_uint32_t", &Set_uint32_t). + defineMethod("SetNull", &SetNull). + defineMethod("SetUndefined", &SetUndefined). + defineMethod("SetEmptyString", &SetEmptyString). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + }; +} +#endif /* RR_RETURN_VALUE_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index abc3bf5a..315ada19 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -47,6 +47,7 @@ inline VALUE not_implemented(const char* message) { #include "script.h" #include "script-origin.h" #include "function.h" +#include "return-value.h" #include "function-callback.h" #endif From 638f6ea0e44dd45cc317ec97b02f49a327db239a Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 15 Jul 2015 21:57:46 -0500 Subject: [PATCH 055/105] add docs to the function callback --- ext/v8/function-callback.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index c3e0ea6e..ce306396 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -18,6 +18,17 @@ namespace rr { return this->container->content[i]; } + /** + * Package up the callback data for this function so that it can + * invoke a Ruby callable. + * + * Each `v8::Function` can have one `v8::Value` associated with it + * that is passed to its `v8::FunctionCallback`. To support this + * same API from ruby, we take the `Value` passed into the + * Function constructor *and* the callback and store them *both* + * in a single `v8::Object` which we use for the C++ level + * callback data. + */ static v8::Local wrapData(v8::Isolate* isolate, VALUE r_callback, VALUE r_data) { v8::Local holder = v8::Object::New(isolate); v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); @@ -27,6 +38,14 @@ namespace rr { return holder; } + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::FunctionCallback` API. + */ static void invoke(const v8::FunctionCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); v8::Local holder = v8::Local::Cast(info.Data()); From cbfb1b12691dbe4c79473b7de8446dc61e5442b2 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 16 Jul 2015 21:55:31 -0500 Subject: [PATCH 056/105] support Maybe APIs --- ext/v8/bool.h | 8 +++++ ext/v8/equiv.h | 21 +++++++++++ ext/v8/init.cc | 1 + ext/v8/maybe.cc | 6 ++++ ext/v8/maybe.h | 71 +++++++++++++++++++++++++++++++++++++ ext/v8/object.cc | 20 ++++++----- ext/v8/object.h | 4 +-- ext/v8/rr.h | 2 +- ext/v8/value.h | 18 ++++++++++ lib/v8/c/maybe.rb | 78 +++++++++++++++++++++++++++++++++++++++++ spec/c/array_spec.rb | 4 +-- spec/c/function_spec.rb | 10 +++--- spec/c/object_spec.rb | 13 ++++--- spec/c/value_spec.rb | 4 +-- spec/c_spec_helper.rb | 1 + 15 files changed, 237 insertions(+), 24 deletions(-) create mode 100644 ext/v8/maybe.cc create mode 100644 ext/v8/maybe.h create mode 100644 lib/v8/c/maybe.rb diff --git a/ext/v8/bool.h b/ext/v8/bool.h index c431cecf..8bdb47bd 100644 --- a/ext/v8/bool.h +++ b/ext/v8/bool.h @@ -23,6 +23,14 @@ namespace rr { */ class Bool : public Equiv { public: + /** + * Use to convert methods that return Maybe to a Ruby + * VALUE, Such as `Maybe v8::Object::Get()`: + * + * return Bool::Maybe(object->Get(context, key)); + */ + typedef Equiv::Maybe Maybe; + /** * Construct a Bool from a Ruby VALUE */ diff --git a/ext/v8/equiv.h b/ext/v8/equiv.h index d7a9f309..f0fa8828 100644 --- a/ext/v8/equiv.h +++ b/ext/v8/equiv.h @@ -40,6 +40,27 @@ namespace rr { */ inline operator VALUE() { return value; } + + /** + * A Maybe class for primitive conversions. Specify the primitive + * type and the covversion type, and it will be wrapped in a + * Maybe. E.g. + * + * v8::Maybe maybe = methodReturningMaybeBool(); + * return Equiv::Maybe(maybe); + * + * will yield a V8::C::Maybe wrapping the underlying maybe value. + */ + template + class Maybe : public rr::Maybe { + public: + Maybe(v8::Maybe maybe) { + if (maybe.IsJust()) { + just(S(maybe.FromJust())); + } + } + }; + protected: VALUE value; }; diff --git a/ext/v8/init.cc b/ext/v8/init.cc index aede0c95..4c7dc7fb 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -13,6 +13,7 @@ extern "C" { Handles::Init(); Context::Init(); Backref::Init(); + Maybe::Init(); Value::Init(); Object::Init(); Primitive::Init(); diff --git a/ext/v8/maybe.cc b/ext/v8/maybe.cc new file mode 100644 index 00000000..f342f3c8 --- /dev/null +++ b/ext/v8/maybe.cc @@ -0,0 +1,6 @@ +#include "rr.h" + +namespace rr { + VALUE Maybe::JustClass; + VALUE Maybe::Nothing; +} diff --git a/ext/v8/maybe.h b/ext/v8/maybe.h new file mode 100644 index 00000000..31eb2f08 --- /dev/null +++ b/ext/v8/maybe.h @@ -0,0 +1,71 @@ +// -*- mode: c++ -*- +#ifndef RR_MAYBE_H +#define RR_MAYBE_H + +namespace rr { + + /** + * A base class for conversion objects that will return an instance + * of `V8::C::Maybe` depending on whether a value is available. + * + * Sprinkled throughout the V8 API, there are methods that may or + * may not return a value based on whether there is an Exception + * that is pending in the context of that operation. + * + * This class provides the basis for helpers to convert the C++ + * Maybe and MaybeLocal objects into Ruby objects that force + * you to check if an operation suceeded. + * + * By default, it will be `V8::C::Nothing`, but if, in the + * constructor, you call `just(VALUE value)`, then it will take on + * that value. + */ + class Maybe { + public: + + /** + * V8::C::Just + */ + static VALUE JustClass; + + /** + * Singleton instance of `V8::C::Nothing` + */ + static VALUE Nothing; + + /** + * By default, everything starts out as nothing. + */ + Maybe() : object(Nothing) {} + + /** + * Convert this seemlessly to a VALUE, which in this case is just + * the v8::C::Maybe ruby instance. + */ + inline operator VALUE() { + return object; + } + + static inline void Init() { + rb_eval_string("require 'v8/c/maybe'"); + JustClass = rb_eval_string("::V8::C::Maybe::Just"); + Nothing = rb_eval_string("::V8::C::Maybe.nothing"); + } + + protected: + + /** + * Subclasses call this method in the constructor in order to + * indicate that this is a `V8::C::Just`, and to pass the + * underlying value. + */ + inline void just(VALUE v) { + object = rb_funcall(JustClass, rb_intern("new"), 1, v); + } + + // the underlying value. + VALUE object; + }; +} + +#endif /* RR_MAYBE_H */ diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 45253858..74bbd842 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -20,25 +20,29 @@ namespace rr { } // TODO: Allow setting of property attributes - VALUE Object::Set(VALUE self, VALUE key, VALUE value) { + VALUE Object::Set(VALUE self, VALUE r_context, VALUE key, VALUE value) { Object object(self); - Locker lock(object.getIsolate()); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool(object->Set(UInt32(key), Value::rubyObjectToHandle(object.getIsolate(), value))); + return Bool::Maybe(object->Set(context, UInt32(key), Value::rubyObjectToHandle(isolate, value))); } else { - return Bool(object->Set(*Value(key), Value::rubyObjectToHandle(object.getIsolate(), value))); + return Bool::Maybe(object->Set(context, *Value(key), Value::rubyObjectToHandle(isolate, value))); } } - VALUE Object::Get(VALUE self, VALUE key) { + VALUE Object::Get(VALUE self, VALUE r_context, VALUE key) { Object object(self); - Locker lock(object.getIsolate()); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value::handleToRubyObject(object.getIsolate(), object->Get(UInt32(key))); + return Value::Maybe(isolate, object->Get(context, UInt32(key))); } else { - return Value::handleToRubyObject(object.getIsolate(), object->Get(*Value(key))); + return Value::Maybe(isolate, object->Get(context, *Value(key))); } } diff --git a/ext/v8/object.h b/ext/v8/object.h index 80c70797..358c9f47 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -7,9 +7,9 @@ namespace rr { public: static void Init(); static VALUE New(VALUE self, VALUE isolate); - static VALUE Set(VALUE self, VALUE key, VALUE value); + static VALUE Set(VALUE self, VALUE context, VALUE key, VALUE value); // static VALUE ForceSet(VALUE self, VALUE key, VALUE value); - static VALUE Get(VALUE self, VALUE key); + static VALUE Get(VALUE self, VALUE context, VALUE key); // static VALUE GetPropertyAttributes(VALUE self, VALUE key); // static VALUE Has(VALUE self, VALUE key); // static VALUE Delete(VALUE self, VALUE key); diff --git a/ext/v8/rr.h b/ext/v8/rr.h index f22e505f..4410a59c 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -17,7 +17,7 @@ inline VALUE not_implemented(const char* message) { } #include "class_builder.h" - +#include "maybe.h" #include "equiv.h" #include "bool.h" #include "pointer.h" diff --git a/ext/v8/value.h b/ext/v8/value.h index c7a9be65..d585ecc2 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- #ifndef RR_VALUE #define RR_VALUE @@ -5,6 +6,23 @@ namespace rr { class Value : public Ref { public: + /** + * A conversion class for down-thunking a MaybeLocal + * and returning it to Ruby as a `V8::C::Maybe`. If there is a + * value present, then run it through the value conversion to get + * the most specific subclass of Value: + * + * return Value::Maybe(object->Get(cxt, key)); + */ + class Maybe : public rr::Maybe { + public: + Maybe(v8::Isolate* isolate, v8::MaybeLocal maybe) { + if (!maybe.IsEmpty()) { + just(Value::handleToRubyObject(isolate, maybe.ToLocalChecked())); + } + } + }; + static void Init(); static VALUE IsUndefined(VALUE self); diff --git a/lib/v8/c/maybe.rb b/lib/v8/c/maybe.rb new file mode 100644 index 00000000..8ebe5b2c --- /dev/null +++ b/lib/v8/c/maybe.rb @@ -0,0 +1,78 @@ +module V8 + module C + ## + # Some V8 API methods return a `Maybe` value that force you to check + # if the operation failed or not before accessing the underlying + # value. This implements the `v8::Maybe` and `v8::MaybeLocal` apis, + # and is returned by those low-level C methods: + # + # maybe = object.Get(context, key) + # if maybe.IsJust() + # puts maybe.class #=> V8::C::Maybe::Just + # puts "the value is #{maybe.FromJust()}" + # else + # puts maybe.class #=> V8::C::Maybe::Nothing + # puts "operation failed!" + # end + # + # Note: Instances of `V8::C::Maybe` are only ever created by the C + # extension, and should not ever be instantiated from Ruby code. + class Maybe + + ## + # If true, then this is an instance of `V8::C::Just`, and it does + # wrap a value + def IsJust() + false + end + + ## + # If true, then this is an instance of `V8::C::Nothing`, and it + # does not contain a value. Any attempt to access the value will + # raise an error. + def IsNothing() + false + end + + ## + # A Maybe that *does* contain a value. + class Just < Maybe + def initialize(value) + @value = value + end + + def IsJust() + true + end + + def FromJust() + @value + end + end + + ## + # A Maybe that *does* not contain any value + class Nothing < Maybe + + def IsNothing() + true + end + + def FromJust() + fail PendingExceptionError, "no value was available because an exception is pending in this context" + end + end + + ## + # Singleton instance of Nothing. + # + # We only need one instance of Nothing because there is no + # additional state apart from its nothingness. + def self.nothing + @nothing ||= Nothing.new + end + end + + class PendingExceptionError < StandardError;end + end +end diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb index 66a15c18..88a77ab5 100644 --- a/spec/c/array_spec.rb +++ b/spec/c/array_spec.rb @@ -9,10 +9,10 @@ expect(a.Length).to eq 0 - a.Set(0, o) + a.Set(@ctx, 0, o) expect(a.Length).to eq 1 - expect(a.Get(0).Equals(o)).to eq true + expect(a.Get(@ctx, 0).FromJust().Equals(o)).to eq true end it 'can be initialized with a length' do diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f0c3a880..bc774534 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -24,21 +24,21 @@ fn.Call(@ctx.Global, [one, two, 3]) - expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'one'))).to eq one - expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'two'))).to eq two - expect(@ctx.Global.Get(V8::C::String.NewFromUtf8(@isolate, 'three'))).to eq 3 + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one')).FromJust()).to eq one + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two')).FromJust()).to eq two + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust()).to eq 3 end it 'can be called as a constructor' do fn = run '(function() {this.foo = "foo"})' - expect(fn.NewInstance.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'foo' + expect(fn.NewInstance.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'foo')).FromJust().Utf8Value).to eq 'foo' end it 'can be called as a constructor with arguments' do fn = run '(function(foo) {this.foo = foo})' object = fn.NewInstance([V8::C::String.NewFromUtf8(@isolate, 'bar')]) - expect(object.Get(V8::C::String.NewFromUtf8(@isolate, 'foo')).Utf8Value).to eq 'bar' + expect(object.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'foo')).FromJust().Utf8Value).to eq 'bar' end # TODO diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 7d62bc55..a12caa01 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -12,8 +12,13 @@ key = V8::C::String.NewFromUtf8(@isolate, 'foo') value = V8::C::String.NewFromUtf8(@isolate, 'bar') - o.Set(key, value) - expect(o.Get(key).Utf8Value).to eq 'bar' + maybe = o.Set(@ctx, key, value) + expect(maybe.IsJust()).to be true + expect(maybe.FromJust()).to be true + + maybe = o.Get(@ctx, key) + expect(maybe.IsJust()).to be true + expect(maybe.FromJust().Utf8Value).to eq 'bar' end # TODO: Enable this when the methods are implemented in the extension @@ -52,7 +57,7 @@ one = V8::C::Object.New(@isolate) two = V8::C::Object.New(@isolate) - one.Set(key, two) - expect(one.Get(key)).to be two + one.Set(@ctx, key, two) + expect(one.Get(@ctx, key).FromJust()).to be two end end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb index 16b5a9cd..3464a1c7 100644 --- a/spec/c/value_spec.rb +++ b/spec/c/value_spec.rb @@ -19,7 +19,7 @@ def convert(value) object = V8::C::Object.New(@isolate) key = V8::C::String.NewFromUtf8(@isolate, 'key') - expect(object.Get(key)).to eq nil + expect(object.Get(@ctx, key).FromJust()).to eq nil end it 'converts FixNums' do @@ -33,7 +33,7 @@ def convert(value) it 'converts objects' do object = V8::C::Object.New(@isolate) - object.Set(V8::C::String.NewFromUtf8(@isolate, 'test'), 1) + object.Set(@ctx, V8::C::String.NewFromUtf8(@isolate, 'test'), 1) expect(convert(object)).to eq object end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index c5e52d53..c0116add 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -1,4 +1,5 @@ require 'v8/weak' +require 'v8/c/maybe' require 'v8/init' module V8ContextHelpers From 03f2f280aa813c6718b39806a1508f73abbb28c4 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 17 Jul 2015 10:20:06 -0500 Subject: [PATCH 057/105] [WIP] - store changes --- ext/v8/init.cc | 2 ++ ext/v8/object-template.h | 31 +++++++++++++++++++++++++++++++ ext/v8/rr.h | 3 +++ ext/v8/template.h | 31 +++++++++++++++++++++++++++++++ lib/v8/c/property_attribute.rb | 16 ++++++++++++++++ spec/c/object_template_spec.rb | 13 +++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 ext/v8/object-template.h create mode 100644 ext/v8/template.h create mode 100644 lib/v8/c/property_attribute.rb create mode 100644 spec/c/object_template_spec.rb diff --git a/ext/v8/init.cc b/ext/v8/init.cc index aede0c95..bab3f5ee 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -24,6 +24,8 @@ extern "C" { ScriptOrigin::Init(); Array::Init(); External::Init(); + Template::Init(); + ObjectTemplate::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/object-template.h b/ext/v8/object-template.h new file mode 100644 index 00000000..51170f96 --- /dev/null +++ b/ext/v8/object-template.h @@ -0,0 +1,31 @@ +// -*- mode: c++ -*- +#ifndef RR_OBJECT_TEMPLATE_H +#define RR_OBJECT_TEMPLATE_H + +namespace rr { + class ObjectTemplate : Ref { + public: + ObjectTemplate(v8::Isolate* isolate, v8::Handle tmpl) : + Ref(isolate, tmpl) {} + inline static void Init() { + ClassBuilder("ObjectTemplate", Template::Class). + defineSingletonMethod("New", &New). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, r_constructor; + rb_scan_args(argc, argv, "11", &r_isolate, &r_constructor); + Isolate isolate(r_isolate); + Locker lock(isolate); + return ObjectTemplate(isolate, v8::ObjectTemplate::New(isolate)); + } + + static VALUE NewInstance(VALUE self , VALUE context) { + + } + }; +} + + +#endif /* RR_OBJECT_TEMPLATE_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index f22e505f..70bcb73e 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -47,4 +47,7 @@ inline VALUE not_implemented(const char* message) { #include "script-origin.h" #include "function.h" +#include "template.h" +#include "object-template.h" + #endif diff --git a/ext/v8/template.h b/ext/v8/template.h new file mode 100644 index 00000000..1f2b66eb --- /dev/null +++ b/ext/v8/template.h @@ -0,0 +1,31 @@ +// -*- mode: c++ -*- +#ifndef RR_TEMPLATE_H +#define RR_TEMPLATE_H + +namespace rr { + class Template : public Ref { + public: + Template(VALUE t) : Ref(t) {} + + inline static void Init() { + ClassBuilder("Template"). + defineMethod("Set", &Set). + store(&Class); + } + + static VALUE Set(int argc, VALUE argv[], VALUE self) { + VALUE name, value, r_attributes; + rb_scan_args(argc, argv, "21", &name, &value, &r_attributes); + v8::PropertyAttribute attributes(v8::PropertyAttribute::None); + if (RTEST(attributes)) { + attributes = (v8::PropertyAttribute)NUM2INT(r_attributes); + } + v8::Local val = Value(value); + Template(self)->Set(Name(name), val, attributes); + return Qnil; + } + }; +} + + +#endif /* RR_TEMPLATE_H */ diff --git a/lib/v8/c/property_attribute.rb b/lib/v8/c/property_attribute.rb new file mode 100644 index 00000000..f6eb4d86 --- /dev/null +++ b/lib/v8/c/property_attribute.rb @@ -0,0 +1,16 @@ +module V8 + module C + module PropertyAttribute + None = 0 + ReadOnly = 1 << 0 + DontEnum = 1 << 1 + DontDelete = 1 << 2 + end + module AccessControl + DEFAULT = 0 + ALL_CAN_READ = 1 << 0 + ALL_CAN_WRITE = 1 << 1 + PROHIBITS_OVERWRITING = 1 << 2 + end + end +end diff --git a/spec/c/object_template_spec.rb b/spec/c/object_template_spec.rb new file mode 100644 index 00000000..c7d03fee --- /dev/null +++ b/spec/c/object_template_spec.rb @@ -0,0 +1,13 @@ +require 'c_spec_helper' + +describe V8::C::ObjectTemplate do + requires_v8_context + let(:template) { V8::C::ObjectTemplate::New(@isolate) } + + it "can be used to create object instances" do + expect(template.NewInstance(@ctx)).to be + end + it "can be used to create a new object" do + + end +end From 30dc90d62db995861a88dc77a4ecbf892d7936c6 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 9 Jul 2015 22:15:53 -0500 Subject: [PATCH 058/105] implement JavaScript numeric types --- ext/v8/array.cc | 4 +-- ext/v8/init.cc | 1 + ext/v8/number.cc | 69 +++++++++++++++++++++++++++++++++++++++++ ext/v8/number.h | 46 +++++++++++++++++++++++++++ ext/v8/object.cc | 4 +-- ext/v8/rr.h | 1 + ext/v8/script-origin.cc | 2 +- ext/v8/uint32.h | 14 ++++----- ext/v8/value.cc | 11 ++++--- spec/c/number_spec.rb | 38 +++++++++++++++++++++++ spec/c/value_spec.rb | 2 +- 11 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 ext/v8/number.cc create mode 100644 ext/v8/number.h create mode 100644 spec/c/number_spec.rb diff --git a/ext/v8/array.cc b/ext/v8/array.cc index 1e3c7fdd..ba3d4f6b 100644 --- a/ext/v8/array.cc +++ b/ext/v8/array.cc @@ -27,14 +27,14 @@ namespace rr { Array array(self); Locker lock(array.getIsolate()); - return UInt32(array->Length()); + return Uint32_t(array->Length()); } VALUE Array::CloneElementAt(VALUE self, VALUE index) { Array array(self); Locker lock(array.getIsolate()); - return Object(array.getIsolate(), array->CloneElementAt(UInt32(index))); + return Object(array.getIsolate(), array->CloneElementAt(Uint32_t(index))); } } diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 4c7dc7fb..6a22ad76 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -18,6 +18,7 @@ extern "C" { Object::Init(); Primitive::Init(); Name::Init(); + Number::Init(); String::Init(); Symbol::Init(); Function::Init(); diff --git a/ext/v8/number.cc b/ext/v8/number.cc new file mode 100644 index 00000000..5ddfa251 --- /dev/null +++ b/ext/v8/number.cc @@ -0,0 +1,69 @@ +#include "rr.h" + +namespace rr { + void Number::Init() { + ClassBuilder("Number", Primitive::Class). + + defineSingletonMethod("New", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { + Isolate isolate(r_isolate); + Locker lock(isolate); + + return Number(isolate, NUM2DBL(value)); + }). + + defineMethod("Value", [] (VALUE self) -> VALUE { + Number number(self); + Locker lock(number); + + return DBL2NUM(number->Value()); + }). + + store(&Class); + + ClassBuilder("Integer", Number::Class). + + defineSingletonMethod("New", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { + Isolate isolate(r_isolate); + Locker lock(isolate); + + auto integer = v8::Integer::New(isolate, NUM2INT(value)); + return Value::handleToRubyObject(isolate, integer); + }). + + defineSingletonMethod("NewFromUnsigned", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { + Isolate isolate(r_isolate); + Locker lock(isolate); + + auto uint = v8::Integer::NewFromUnsigned(isolate, NUM2UINT(value)); + return Value::handleToRubyObject(isolate, uint); + }). + + defineMethod("Value", [] (VALUE self) -> VALUE { + Integer integer(self); + Locker lock(integer); + + return INT2NUM(integer->Value()); + }). + + store(&Integer::Class); + + ClassBuilder("Int32", Integer::Class). + + defineMethod("Value", [] (VALUE self) -> VALUE { + Int32 int32(self); + Locker lock(int32); + + return INT2NUM(int32->Value()); + }). + store(&Int32::Class); + + ClassBuilder("Uint32", Integer::Class). + defineMethod("Value", [] (VALUE self) -> VALUE { + Uint32 uint32(self); + Locker lock(uint32); + + return UINT2NUM(uint32->Value()); + }). + store(&Uint32::Class); + } +} diff --git a/ext/v8/number.h b/ext/v8/number.h new file mode 100644 index 00000000..50d3a014 --- /dev/null +++ b/ext/v8/number.h @@ -0,0 +1,46 @@ +// -*- mode: c++ -*- +#ifndef NUMBER_H +#define NUMBER_H + +namespace rr { + class Number : public Ref { + public: + Number(v8::Isolate* isolate, double value) : + Ref(isolate, v8::Number::New(isolate, value)) {} + Number(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + Number(VALUE self) : + Ref(self) {} + + static void Init(); + }; + + class Integer : public Ref { + public: + Integer(v8::Isolate* isolate, int32_t value) : + Ref(isolate, v8::Integer::New(isolate, value)) {} + Integer(v8::Isolate* isolate, uint32_t value) : + Ref(isolate, v8::Integer::NewFromUnsigned(isolate, value)) {} + Integer(VALUE self) : + Ref(self) {} + }; + + class Int32 : public Ref { + public: + Int32(VALUE self) : + Ref(self) {} + Int32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + }; + + class Uint32 : public Ref { + public: + Uint32(VALUE self) : + Ref(self) {} + Uint32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + }; +} + + +#endif /* NUMBER_H */ diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 74bbd842..1e8b9aae 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -27,7 +27,7 @@ namespace rr { Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool::Maybe(object->Set(context, UInt32(key), Value::rubyObjectToHandle(isolate, value))); + return Bool::Maybe(object->Set(context, UInt32_t(key), Value::rubyObjectToHandle(isolate, value))); } else { return Bool::Maybe(object->Set(context, *Value(key), Value::rubyObjectToHandle(isolate, value))); } @@ -40,7 +40,7 @@ namespace rr { Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value::Maybe(isolate, object->Get(context, UInt32(key))); + return Value::Maybe(isolate, object->Get(context, UInt32_t(key))); } else { return Value::Maybe(isolate, object->Get(context, *Value(key))); } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 4410a59c..516a6cbf 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -37,6 +37,7 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "array.h" #include "primitive.h" +#include "number.h" #include "external.h" // This one is named v8_string to avoid name collisions with C's string.h #include "name.h" diff --git a/ext/v8/script-origin.cc b/ext/v8/script-origin.cc index a529979e..c050c738 100644 --- a/ext/v8/script-origin.cc +++ b/ext/v8/script-origin.cc @@ -1,7 +1,7 @@ #include "rr.h" namespace rr { - VALUE ScriptOrigin::Class; + VALUE ScriptOrigin::Class; void ScriptOrigin::Init() { ClassBuilder("ScriptOrigin"). defineSingletonMethod("new", &initialize). diff --git a/ext/v8/uint32.h b/ext/v8/uint32.h index 727776b3..1af771ff 100644 --- a/ext/v8/uint32.h +++ b/ext/v8/uint32.h @@ -11,27 +11,27 @@ namespace rr { * Ruby VALUE is expected (such as a method call) E.g. * * uint_32_t myInt = 5; - * rb_funcall(UInt32(myInt), rb_intern("to_s")); //=> + * rb_funcall(Uint32_t(myInt), rb_intern("to_s")); //=> * * It also converts a Ruby `VALUE` into its corresponding * `uint32_t`: * - * uint_32_t myInt = UInt32(rb_eval_string("5")); //=> 5 + * uint_32_t myInt = Uint32_t(rb_eval_string("5")); //=> 5 * * Like all `Equiv`s, it stores itself internally as a Ruby `VALUE` */ - class UInt32 : public Equiv { + class Uint32_t : public Equiv { public: /** - * Construct a UInt32 from a Ruby `VALUE` + * Construct a Uint32_t from a Ruby `VALUE` */ - UInt32(VALUE val) : Equiv(val) {} + Uint32_t(VALUE val) : Equiv(val) {} /** - * Construct a UInt32 from a `uint32_t` by converting it into its + * Construct a Uint32_t from a `uint32_t` by converting it into its * corresponding `VALUE`. */ - UInt32(uint32_t ui) : Equiv(UINT2NUM(ui)) {} + Uint32_t(uint32_t ui) : Equiv(UINT2NUM(ui)) {} /** * Coerce the Ruby `VALUE` into a `uint32_t`. diff --git a/ext/v8/value.cc b/ext/v8/value.cc index aa2df9d1..1701d7a0 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -171,11 +171,15 @@ namespace rr { } if (handle->IsUint32()) { - return UInt32(handle->Uint32Value()); + return Uint32(isolate, handle); } if (handle->IsInt32()) { - return INT2FIX(handle->Int32Value()); + return Int32(isolate, handle); + } + + if (handle->IsNumber()) { + return Number(isolate, handle); } if (handle->IsBoolean()) { @@ -183,9 +187,6 @@ namespace rr { } // TODO - // if (handle->IsNumber()) { - // return rb_float_new(handle->NumberValue()); - // } if (handle->IsString()) { return String(isolate, handle->ToString()); diff --git a/spec/c/number_spec.rb b/spec/c/number_spec.rb new file mode 100644 index 00000000..31a274f2 --- /dev/null +++ b/spec/c/number_spec.rb @@ -0,0 +1,38 @@ +require 'c_spec_helper' + +describe "V8::C::Number pyramid" do + requires_v8_context + + describe V8::C::Number do + let(:number) { V8::C::Number::New @isolate, 25.325 } + it "sees that value" do + expect(number.Value()).to eql 25.325 + end + it "is a kind of primitive" do + expect(number).to be_kind_of V8::C::Primitive + end + end + describe V8::C::Integer do + let(:int) { V8::C::Integer::New @isolate, 5 } + + it "is has a value of 5" do + expect(int.Value()).to eql 5 + end + + it "is a Uint32" do + expect(int.IsUint32()).to be_truthy + expect(int).to be_kind_of V8::C::Uint32 + end + end + describe V8::C::Int32 do + let(:int32) { V8::C::Integer::New @isolate, -5 } + + it "has a value of -5" do + expect(int32.Value()).to eql -5 + end + + it "is an Int32" do + expect(int32).to be_kind_of V8::C::Int32 + end + end +end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb index 3464a1c7..90ef9ce5 100644 --- a/spec/c/value_spec.rb +++ b/spec/c/value_spec.rb @@ -23,7 +23,7 @@ def convert(value) end it 'converts FixNums' do - expect(convert(42)).to eq 42 + expect(convert(42).Value()).to eq 42 end it 'converts booleans' do From 34804a99090727b5a69bb8d90c3a099ca68f341d Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 9 Jul 2015 22:20:55 -0500 Subject: [PATCH 059/105] add deprecation TODO --- ext/v8/uint32.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/v8/uint32.h b/ext/v8/uint32.h index 1af771ff..8a4b9389 100644 --- a/ext/v8/uint32.h +++ b/ext/v8/uint32.h @@ -2,6 +2,10 @@ #ifndef RR_UINT32 #define RR_UINT32 + +//TODO: remove this at some point. I don't see what this provides +//above and beyond just using UINT2NUM and NUM2UINT. +// --cowboyd Jul 9, 2015 namespace rr { /** From 18c07758a91986fd0e6d2bb5064c83b10e60d51f Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 13 Jul 2015 19:16:08 -0500 Subject: [PATCH 060/105] remove c++11 features --- ext/v8/init.cc | 3 +++ ext/v8/int32.h | 29 ++++++++++++++++++++ ext/v8/integer.h | 49 ++++++++++++++++++++++++++++++++++ ext/v8/number.cc | 69 ------------------------------------------------ ext/v8/number.h | 46 +++++++++++++------------------- ext/v8/rr.h | 6 ++++- ext/v8/uint32.h | 29 +++++++++++++++++--- 7 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 ext/v8/int32.h create mode 100644 ext/v8/integer.h delete mode 100644 ext/v8/number.cc diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 6a22ad76..5a4e30eb 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -19,6 +19,9 @@ extern "C" { Primitive::Init(); Name::Init(); Number::Init(); + Integer::Init(); + Int32::Init(); + Uint32::Init(); String::Init(); Symbol::Init(); Function::Init(); diff --git a/ext/v8/int32.h b/ext/v8/int32.h new file mode 100644 index 00000000..01fa4eda --- /dev/null +++ b/ext/v8/int32.h @@ -0,0 +1,29 @@ +// -*- mode: c++ -*- +#ifndef RR_INT32_H +#define RR_INT32_H + +namespace rr { + class Int32 : public Ref { + public: + Int32(VALUE self) : + Ref(self) {} + Int32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Int32 int32(self); + Locker lock(int32); + + return INT2NUM(int32->Value()); + } + + static void Init() { + ClassBuilder("Int32", Integer::Class). + defineMethod("Value", &Value). + store(&Int32::Class); + } + }; + +} + +#endif /* RR_INT32_H */ diff --git a/ext/v8/integer.h b/ext/v8/integer.h new file mode 100644 index 00000000..ce38328b --- /dev/null +++ b/ext/v8/integer.h @@ -0,0 +1,49 @@ +// -*- mode: c++ -*- +#ifndef RR_INTEGER_H +#define RR_INTEGER_H + +namespace rr { + class Integer : public Ref { + public: + Integer(v8::Isolate* isolate, int32_t value) : + Ref(isolate, v8::Integer::New(isolate, value)) {} + Integer(v8::Isolate* isolate, uint32_t value) : + Ref(isolate, v8::Integer::NewFromUnsigned(isolate, value)) {} + Integer(VALUE self) : + Ref(self) {} + + static VALUE New(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + v8::Local i = v8::Integer::New(isolate, NUM2INT(value)); + return Value::handleToRubyObject(isolate, i); + } + + static VALUE NewFromUnsigned(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + uint32_t uint = NUM2UINT(value); + v8::Local i = v8::Integer::NewFromUnsigned(isolate, uint); + return Value::handleToRubyObject(isolate, i); + } + + static VALUE Value(VALUE self) { + Integer integer(self); + Locker lock(integer); + + return INT2NUM(integer->Value()); + } + + static void Init() { + ClassBuilder("Integer", Number::Class). + defineSingletonMethod("New", &New). + defineSingletonMethod("NewFromUnsigned", &NewFromUnsigned). + defineMethod("Value", &Value). + store(&Class); + } + }; +} + +#endif /* RR_INTEGER_H */ diff --git a/ext/v8/number.cc b/ext/v8/number.cc deleted file mode 100644 index 5ddfa251..00000000 --- a/ext/v8/number.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include "rr.h" - -namespace rr { - void Number::Init() { - ClassBuilder("Number", Primitive::Class). - - defineSingletonMethod("New", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { - Isolate isolate(r_isolate); - Locker lock(isolate); - - return Number(isolate, NUM2DBL(value)); - }). - - defineMethod("Value", [] (VALUE self) -> VALUE { - Number number(self); - Locker lock(number); - - return DBL2NUM(number->Value()); - }). - - store(&Class); - - ClassBuilder("Integer", Number::Class). - - defineSingletonMethod("New", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { - Isolate isolate(r_isolate); - Locker lock(isolate); - - auto integer = v8::Integer::New(isolate, NUM2INT(value)); - return Value::handleToRubyObject(isolate, integer); - }). - - defineSingletonMethod("NewFromUnsigned", [] (VALUE self, VALUE r_isolate, VALUE value) -> VALUE { - Isolate isolate(r_isolate); - Locker lock(isolate); - - auto uint = v8::Integer::NewFromUnsigned(isolate, NUM2UINT(value)); - return Value::handleToRubyObject(isolate, uint); - }). - - defineMethod("Value", [] (VALUE self) -> VALUE { - Integer integer(self); - Locker lock(integer); - - return INT2NUM(integer->Value()); - }). - - store(&Integer::Class); - - ClassBuilder("Int32", Integer::Class). - - defineMethod("Value", [] (VALUE self) -> VALUE { - Int32 int32(self); - Locker lock(int32); - - return INT2NUM(int32->Value()); - }). - store(&Int32::Class); - - ClassBuilder("Uint32", Integer::Class). - defineMethod("Value", [] (VALUE self) -> VALUE { - Uint32 uint32(self); - Locker lock(uint32); - - return UINT2NUM(uint32->Value()); - }). - store(&Uint32::Class); - } -} diff --git a/ext/v8/number.h b/ext/v8/number.h index 50d3a014..e8f37169 100644 --- a/ext/v8/number.h +++ b/ext/v8/number.h @@ -1,6 +1,6 @@ // -*- mode: c++ -*- -#ifndef NUMBER_H -#define NUMBER_H +#ifndef RR_NUMBER_H +#define RR_NUMBER_H namespace rr { class Number : public Ref { @@ -12,35 +12,27 @@ namespace rr { Number(VALUE self) : Ref(self) {} - static void Init(); - }; + static VALUE New(VALUE self, VALUE r_isolate, VALUE value) { + Isolate isolate(r_isolate); + Locker lock(isolate); - class Integer : public Ref { - public: - Integer(v8::Isolate* isolate, int32_t value) : - Ref(isolate, v8::Integer::New(isolate, value)) {} - Integer(v8::Isolate* isolate, uint32_t value) : - Ref(isolate, v8::Integer::NewFromUnsigned(isolate, value)) {} - Integer(VALUE self) : - Ref(self) {} - }; + return Number(isolate, NUM2DBL(value)); + } - class Int32 : public Ref { - public: - Int32(VALUE self) : - Ref(self) {} - Int32(v8::Isolate* isolate, v8::Handle value) : - Ref(isolate, value.As()) {} - }; + static VALUE Value(VALUE self) { + Number number(self); + Locker lock(number); - class Uint32 : public Ref { - public: - Uint32(VALUE self) : - Ref(self) {} - Uint32(v8::Isolate* isolate, v8::Handle value) : - Ref(isolate, value.As()) {} + return DBL2NUM(number->Value()); + } + static void Init() { + ClassBuilder("Number", Primitive::Class). + defineSingletonMethod("New", &New). + defineMethod("Value", &Value). + store(&Class); + } }; } -#endif /* NUMBER_H */ +#endif /* RR_NUMBER_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 516a6cbf..a5fda067 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -30,7 +30,6 @@ inline VALUE not_implemented(const char* message) { #include "handles.h" #include "context.h" -#include "uint32.h" #include "value.h" #include "backref.h" @@ -38,6 +37,11 @@ inline VALUE not_implemented(const char* message) { #include "array.h" #include "primitive.h" #include "number.h" +#include "integer.h" +#include "int32.h" +#include "uint32.h" + + #include "external.h" // This one is named v8_string to avoid name collisions with C's string.h #include "name.h" diff --git a/ext/v8/uint32.h b/ext/v8/uint32.h index 8a4b9389..06584268 100644 --- a/ext/v8/uint32.h +++ b/ext/v8/uint32.h @@ -1,12 +1,35 @@ // -*- mode: c++ -*- -#ifndef RR_UINT32 -#define RR_UINT32 +#ifndef RR_UINT32_H +#define RR_UINT32_H + + +namespace rr { + + class Uint32 : public Ref { + public: + Uint32(VALUE self) : + Ref(self) {} + Uint32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Uint32 uint32(self); + Locker lock(uint32); + + return UINT2NUM(uint32->Value()); + } + + static inline void Init() { + ClassBuilder("Uint32", Integer::Class). + defineMethod("Value", &Value). + store(&Class); + } + }; //TODO: remove this at some point. I don't see what this provides //above and beyond just using UINT2NUM and NUM2UINT. // --cowboyd Jul 9, 2015 -namespace rr { /** * Converts between Ruby `Number` and the C/C++ `uint32_t`. From 712ad9318b465ea60234efc03d69e05314d7c929 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 13 Jul 2015 19:46:15 -0500 Subject: [PATCH 061/105] remove hacks around number usage --- ext/v8/integer.h | 6 ++---- ext/v8/number.h | 4 +--- ext/v8/script-origin.h | 12 ++++-------- ext/v8/value.cc | 2 +- spec/c/function_spec.rb | 4 ++-- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/ext/v8/integer.h b/ext/v8/integer.h index ce38328b..28bef8ce 100644 --- a/ext/v8/integer.h +++ b/ext/v8/integer.h @@ -5,10 +5,8 @@ namespace rr { class Integer : public Ref { public: - Integer(v8::Isolate* isolate, int32_t value) : - Ref(isolate, v8::Integer::New(isolate, value)) {} - Integer(v8::Isolate* isolate, uint32_t value) : - Ref(isolate, v8::Integer::NewFromUnsigned(isolate, value)) {} + Integer(v8::Isolate* isolate, v8::Handle integer) : + Ref(isolate, integer) {} Integer(VALUE self) : Ref(self) {} diff --git a/ext/v8/number.h b/ext/v8/number.h index e8f37169..7b907a48 100644 --- a/ext/v8/number.h +++ b/ext/v8/number.h @@ -5,8 +5,6 @@ namespace rr { class Number : public Ref { public: - Number(v8::Isolate* isolate, double value) : - Ref(isolate, v8::Number::New(isolate, value)) {} Number(v8::Isolate* isolate, v8::Handle value) : Ref(isolate, value.As()) {} Number(VALUE self) : @@ -16,7 +14,7 @@ namespace rr { Isolate isolate(r_isolate); Locker lock(isolate); - return Number(isolate, NUM2DBL(value)); + return Number(isolate, v8::Number::New(isolate, NUM2DBL(value))); } static VALUE Value(VALUE self) { diff --git a/ext/v8/script-origin.h b/ext/v8/script-origin.h index cbcafb55..e341cb08 100644 --- a/ext/v8/script-origin.h +++ b/ext/v8/script-origin.h @@ -35,11 +35,7 @@ namespace rr { VALUE is_opaque; //option }; - struct Integer : public Equiv { - Integer(v8::Handle value) : - Equiv(INT2FIX(value->IntegerValue())) { - } - }; + public: static void Init(); @@ -52,10 +48,10 @@ namespace rr { ScriptOrigin(v8::Isolate* isolate, v8::ScriptOrigin origin) : ScriptOrigin(new Container( Value(isolate, origin.ResourceName()), - Integer(origin.ResourceLineOffset()), - Integer(origin.ResourceColumnOffset()), + Integer(isolate, origin.ResourceLineOffset()), + Integer(isolate, origin.ResourceColumnOffset()), Bool(origin.Options().IsSharedCrossOrigin()), - Integer(origin.ScriptID()), + Integer(isolate, origin.ScriptID()), Bool(origin.Options().IsEmbedderDebugScript()), Value(isolate, origin.SourceMapUrl()), Bool(origin.Options().IsOpaque()))) { diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 1701d7a0..88df57ec 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -45,7 +45,7 @@ namespace rr { store(&Class); - rb_gc_register_address(&Empty); + rb_gc_register_address(&Empty); } VALUE Value::IsUndefined(VALUE self) { diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index bc774534..f8b46011 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -7,8 +7,8 @@ fn = run '(function() { return "foo" })' origin = fn.GetScriptOrigin() expect(origin.ResourceName().ToString().Utf8Value()).to eql 'undefined' - expect(origin.ResourceLineOffset()).to eql 0 - expect(origin.ResourceColumnOffset()).to eql 0 + expect(origin.ResourceLineOffset().Value()).to eql 0 + expect(origin.ResourceColumnOffset().Value()).to eql 0 end it 'can be called' do From 0e4d915faf2fdfdc323fc8ff03c2a2f0197186a2 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 17 Jul 2015 12:17:14 -0500 Subject: [PATCH 062/105] return specific subtypes of integer in constructor --- ext/v8/init.cc | 4 +-- ext/v8/int32.h | 29 ------------------ ext/v8/integer.h | 52 +++++++++++++++++++++++++++++---- ext/v8/rr.h | 3 +- ext/v8/{uint32.h => uint32_t.h} | 27 ----------------- 5 files changed, 49 insertions(+), 66 deletions(-) delete mode 100644 ext/v8/int32.h rename ext/v8/{uint32.h => uint32_t.h} (62%) diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 5a4e30eb..b0c4b986 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -17,11 +17,9 @@ extern "C" { Value::Init(); Object::Init(); Primitive::Init(); - Name::Init(); Number::Init(); Integer::Init(); - Int32::Init(); - Uint32::Init(); + Name::Init(); String::Init(); Symbol::Init(); Function::Init(); diff --git a/ext/v8/int32.h b/ext/v8/int32.h deleted file mode 100644 index 01fa4eda..00000000 --- a/ext/v8/int32.h +++ /dev/null @@ -1,29 +0,0 @@ -// -*- mode: c++ -*- -#ifndef RR_INT32_H -#define RR_INT32_H - -namespace rr { - class Int32 : public Ref { - public: - Int32(VALUE self) : - Ref(self) {} - Int32(v8::Isolate* isolate, v8::Handle value) : - Ref(isolate, value.As()) {} - - static VALUE Value(VALUE self) { - Int32 int32(self); - Locker lock(int32); - - return INT2NUM(int32->Value()); - } - - static void Init() { - ClassBuilder("Int32", Integer::Class). - defineMethod("Value", &Value). - store(&Int32::Class); - } - }; - -} - -#endif /* RR_INT32_H */ diff --git a/ext/v8/integer.h b/ext/v8/integer.h index 28bef8ce..d5266aff 100644 --- a/ext/v8/integer.h +++ b/ext/v8/integer.h @@ -3,6 +3,36 @@ #define RR_INTEGER_H namespace rr { + class Uint32 : public Ref { + public: + Uint32(VALUE self) : + Ref(self) {} + Uint32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Uint32 uint32(self); + Locker lock(uint32); + + return UINT2NUM(uint32->Value()); + } + }; + + class Int32 : public Ref { + public: + Int32(VALUE self) : + Ref(self) {} + Int32(v8::Isolate* isolate, v8::Handle value) : + Ref(isolate, value.As()) {} + + static VALUE Value(VALUE self) { + Int32 int32(self); + Locker lock(int32); + + return INT2NUM(int32->Value()); + } + }; + class Integer : public Ref { public: Integer(v8::Isolate* isolate, v8::Handle integer) : @@ -14,17 +44,21 @@ namespace rr { Isolate isolate(r_isolate); Locker lock(isolate); - v8::Local i = v8::Integer::New(isolate, NUM2INT(value)); - return Value::handleToRubyObject(isolate, i); + v8::Local integer(v8::Integer::New(isolate, NUM2INT(value))); + if (integer->IsUint32()) { + return Uint32(isolate, integer); + } else if (integer->IsInt32()) { + return Int32(isolate, integer); + } else { + return Integer(isolate, integer); + } } static VALUE NewFromUnsigned(VALUE self, VALUE r_isolate, VALUE value) { Isolate isolate(r_isolate); Locker lock(isolate); - uint32_t uint = NUM2UINT(value); - v8::Local i = v8::Integer::NewFromUnsigned(isolate, uint); - return Value::handleToRubyObject(isolate, i); + return Uint32(isolate, v8::Integer::NewFromUnsigned(isolate, NUM2UINT(value))); } static VALUE Value(VALUE self) { @@ -40,6 +74,14 @@ namespace rr { defineSingletonMethod("NewFromUnsigned", &NewFromUnsigned). defineMethod("Value", &Value). store(&Class); + + ClassBuilder("Int32", Integer::Class). + defineMethod("Value", &Value). + store(&Int32::Class); + + ClassBuilder("Uint32", Integer::Class). + defineMethod("Value", &Value). + store(&Uint32::Class); } }; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index a5fda067..4566c257 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -20,6 +20,7 @@ inline VALUE not_implemented(const char* message) { #include "maybe.h" #include "equiv.h" #include "bool.h" +#include "uint32_t.h" #include "pointer.h" #include "isolate.h" @@ -38,8 +39,6 @@ inline VALUE not_implemented(const char* message) { #include "primitive.h" #include "number.h" #include "integer.h" -#include "int32.h" -#include "uint32.h" #include "external.h" diff --git a/ext/v8/uint32.h b/ext/v8/uint32_t.h similarity index 62% rename from ext/v8/uint32.h rename to ext/v8/uint32_t.h index 06584268..5c3beb3b 100644 --- a/ext/v8/uint32.h +++ b/ext/v8/uint32_t.h @@ -4,33 +4,6 @@ namespace rr { - - class Uint32 : public Ref { - public: - Uint32(VALUE self) : - Ref(self) {} - Uint32(v8::Isolate* isolate, v8::Handle value) : - Ref(isolate, value.As()) {} - - static VALUE Value(VALUE self) { - Uint32 uint32(self); - Locker lock(uint32); - - return UINT2NUM(uint32->Value()); - } - - static inline void Init() { - ClassBuilder("Uint32", Integer::Class). - defineMethod("Value", &Value). - store(&Class); - } - }; - - -//TODO: remove this at some point. I don't see what this provides -//above and beyond just using UINT2NUM and NUM2UINT. -// --cowboyd Jul 9, 2015 - /** * Converts between Ruby `Number` and the C/C++ `uint32_t`. * From 7fc20dcbeb33e50ef0b320cf92065dc2a95ca87a Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 17 Jul 2015 17:26:20 -0500 Subject: [PATCH 063/105] resolve merge conflicts --- ext/v8/object.cc | 4 ++-- spec/c/function_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 1e8b9aae..067395bf 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -27,7 +27,7 @@ namespace rr { Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Bool::Maybe(object->Set(context, UInt32_t(key), Value::rubyObjectToHandle(isolate, value))); + return Bool::Maybe(object->Set(context, Uint32_t(key), Value::rubyObjectToHandle(isolate, value))); } else { return Bool::Maybe(object->Set(context, *Value(key), Value::rubyObjectToHandle(isolate, value))); } @@ -40,7 +40,7 @@ namespace rr { Locker lock(isolate); if (rb_obj_is_kind_of(key, rb_cNumeric)) { - return Value::Maybe(isolate, object->Get(context, UInt32_t(key))); + return Value::Maybe(isolate, object->Get(context, Uint32_t(key))); } else { return Value::Maybe(isolate, object->Get(context, *Value(key))); } diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f8b46011..bdfb95b6 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -26,7 +26,7 @@ expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one')).FromJust()).to eq one expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two')).FromJust()).to eq two - expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust()).to eq 3 + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust().Value()).to eq 3 end it 'can be called as a constructor' do From 5ca52d58532bac3fc3ea7ce9b89e322ae3517a0b Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 18 Jul 2015 15:29:28 -0500 Subject: [PATCH 064/105] remove backref code. The current backref implementation is designed to ensure that if a V8 object is converted to a ruby object, it gets the same `Data_Wrap_Struct` every time it is passed to Ruby. While this is not necessarily a bad approach, it is definitely what I'd classify as an optimization. The current issues with The Ruby Racer all center around stability and none center around performance, and so I'd like to remove it until such time as we have that as a hard requirement. I know that it will result in more object allocations in both the Ruby world and the V8 world, but the trade off is code that is more difficult to reason about, and might be prone to subtle race conditions that involve multiple garbage collectors. The Ruby side already has an identity map, so that we can resolve 'sameness' at the layer above. --- ext/v8/backref.cc | 47 ----------------------------------------- ext/v8/backref.h | 29 ------------------------- ext/v8/init.cc | 2 -- ext/v8/object.cc | 40 ++++------------------------------- ext/v8/object.h | 6 +----- ext/v8/rr.h | 2 -- spec/c/function_spec.rb | 4 ++-- spec/c/object_spec.rb | 9 -------- spec/c/value_spec.rb | 2 +- 9 files changed, 8 insertions(+), 133 deletions(-) delete mode 100644 ext/v8/backref.cc delete mode 100644 ext/v8/backref.h diff --git a/ext/v8/backref.cc b/ext/v8/backref.cc deleted file mode 100644 index 60a67dab..00000000 --- a/ext/v8/backref.cc +++ /dev/null @@ -1,47 +0,0 @@ -#include "rr.h" - -namespace rr { - - VALUE Backref::Storage; - ID Backref::_new; - ID Backref::object; - - void Backref::Init() { - Storage = rb_eval_string("V8::Weak::Ref"); - rb_gc_register_address(&Storage); - _new = rb_intern("new"); - object = rb_intern("object"); - } - - Backref::Backref(VALUE initial) { - set(initial); - rb_gc_register_address(&storage); - } - - Backref::~Backref() { - rb_gc_unregister_address(&storage); - } - - VALUE Backref::set(VALUE data) { - this->storage = rb_funcall(Storage, _new, 1, data); - return data; - } - - VALUE Backref::get() { - return rb_funcall(storage, object, 0); - } - - v8::Handle Backref::toExternal(v8::Isolate* isolate) { - v8::Local wrapper = v8::External::New(isolate, this); - v8::Persistent(isolate, wrapper).SetWeak(this, &release); - - return wrapper; - } - - void Backref::release(const v8::WeakCallbackData& data) { - // The Persistent handle is disposed of automatically. - - delete data.GetParameter(); - } - -} diff --git a/ext/v8/backref.h b/ext/v8/backref.h deleted file mode 100644 index c835774f..00000000 --- a/ext/v8/backref.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef RR_BACKREF -#define RR_BACKREF - -namespace rr { - - class Backref { - public: - static void Init(); - - Backref(VALUE value); - - virtual ~Backref(); - - VALUE get(); - VALUE set(VALUE value); - - v8::Handle toExternal(v8::Isolate*); - - static void release(const v8::WeakCallbackData& data); - private: - VALUE storage; - static VALUE Storage; - static ID _new; - static ID object; - }; - -} - -#endif diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 4f1fe0c9..9e415591 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -12,7 +12,6 @@ extern "C" { Isolate::Init(); Handles::Init(); Context::Init(); - Backref::Init(); Maybe::Init(); Value::Init(); Object::Init(); @@ -35,7 +34,6 @@ extern "C" { // Signature::Init(); // Date::Init(); // Constants::Init(); - // External::Init(); // Template::Init(); // Stack::Init(); // Message::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 1a271ad4..ec32a0d8 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -55,46 +55,14 @@ namespace rr { } Object::operator VALUE() { - Locker lock(getIsolate()); + Isolate isolate(getIsolate()); + Locker lock(isolate); - if (handle.IsEmpty()) { - return Qnil; + if (handle->IsFunction()) { + return Function(isolate, handle.As()); } - Backref* backref; - - v8::Local key(v8::String::NewFromUtf8(getIsolate(), "rr::Backref")); - v8::Local external = handle->GetHiddenValue(key); - - VALUE value; - - if (external.IsEmpty()) { - value = downcast(); - backref = new Backref(value); - - handle->SetHiddenValue(key, backref->toExternal(getIsolate())); - } else { - v8::External* wrapper = v8::External::Cast(*external); - backref = (Backref*)wrapper->Value(); - value = backref->get(); - - if (!RTEST(value)) { - value = downcast(); - backref->set(value); - } - } - - return value; - } - - VALUE Object::downcast() { - Locker lock(getIsolate()); - // TODO: Enable this when the methods are implemented - // if (handle->IsFunction()) { - // return Function((v8::Handle) v8::Function::Cast(*handle)); - // } - // // if (handle->IsArray()) { // return Array((v8::Handle)v8::Array::Cast(*handle)); // } diff --git a/ext/v8/object.h b/ext/v8/object.h index 08d8dde1..e43610a6 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -56,11 +56,7 @@ namespace rr { inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} - - virtual operator VALUE(); - - protected: - VALUE downcast(); + operator VALUE(); }; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 3b7aa0c6..00d6a1c2 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -33,8 +33,6 @@ inline VALUE not_implemented(const char* message) { #include "context.h" #include "value.h" -#include "backref.h" - #include "object.h" #include "array.h" #include "primitive.h" diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f175b101..f4b05d33 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -24,8 +24,8 @@ fn.Call(@ctx.Global, [one, two, 3]) - expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one')).FromJust()).to eq one - expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two')).FromJust()).to eq two + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one')).FromJust().StrictEquals(one)).to be true + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two')).FromJust().StrictEquals(two)).to be true expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust().Value()).to eq 3 end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index a12caa01..5e883489 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -51,13 +51,4 @@ # o.Set(property, V8::C::String::New('Bro! ')) # o.Get(property).Utf8Value().should eql 'Bro! I am Legend' # end - - it 'always returns the same ruby object for the same V8 object' do - key = V8::C::String.NewFromUtf8(@isolate, 'two') - one = V8::C::Object.New(@isolate) - two = V8::C::Object.New(@isolate) - - one.Set(@ctx, key, two) - expect(one.Get(@ctx, key).FromJust()).to be two - end end diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb index 90ef9ce5..122e16ed 100644 --- a/spec/c/value_spec.rb +++ b/spec/c/value_spec.rb @@ -35,6 +35,6 @@ def convert(value) object = V8::C::Object.New(@isolate) object.Set(@ctx, V8::C::String.NewFromUtf8(@isolate, 'test'), 1) - expect(convert(object)).to eq object + expect(convert(object).StrictEquals(object)).to be true end end From 4e880b136d7c4484a2d175b0188a09b6ce99d5c5 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 18 Jul 2015 16:04:01 -0500 Subject: [PATCH 065/105] add support for NewInstance() --- ext/v8/maybe.h | 10 ++++++++++ ext/v8/object-template.h | 16 ++++++++++++---- ext/v8/object.h | 1 + spec/c/object_template_spec.rb | 5 +---- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ext/v8/maybe.h b/ext/v8/maybe.h index 31eb2f08..3837ee48 100644 --- a/ext/v8/maybe.h +++ b/ext/v8/maybe.h @@ -66,6 +66,16 @@ namespace rr { // the underlying value. VALUE object; }; + + template + class MaybeLocal : public Maybe { + public: + MaybeLocal(v8::Isolate* isolate, v8::MaybeLocal maybe) { + if (!maybe.IsEmpty()) { + just(T(isolate, maybe.ToLocalChecked())); + } + } + }; } #endif /* RR_MAYBE_H */ diff --git a/ext/v8/object-template.h b/ext/v8/object-template.h index 51170f96..c6daeda9 100644 --- a/ext/v8/object-template.h +++ b/ext/v8/object-template.h @@ -3,13 +3,15 @@ #define RR_OBJECT_TEMPLATE_H namespace rr { - class ObjectTemplate : Ref { + class ObjectTemplate : Ref { public: + ObjectTemplate(VALUE self) : Ref(self) {} ObjectTemplate(v8::Isolate* isolate, v8::Handle tmpl) : - Ref(isolate, tmpl) {} + Ref(isolate, tmpl) {} inline static void Init() { ClassBuilder("ObjectTemplate", Template::Class). defineSingletonMethod("New", &New). + defineMethod("NewInstance", &NewInstance). store(&Class); } @@ -21,9 +23,15 @@ namespace rr { return ObjectTemplate(isolate, v8::ObjectTemplate::New(isolate)); } - static VALUE NewInstance(VALUE self , VALUE context) { - + static VALUE NewInstance(VALUE self , VALUE r_context) { + ObjectTemplate t(self); + Context context(r_context); + Isolate isolate(context.getIsolate()); + Locker lock(isolate); + v8::MaybeLocal object(t->NewInstance()); + return Object::Maybe(isolate, ObjectTemplate(self)->NewInstance(context)); } + }; } diff --git a/ext/v8/object.h b/ext/v8/object.h index 08d8dde1..b519d328 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -56,6 +56,7 @@ namespace rr { inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} + typedef MaybeLocal Maybe; virtual operator VALUE(); diff --git a/spec/c/object_template_spec.rb b/spec/c/object_template_spec.rb index c7d03fee..a62e7801 100644 --- a/spec/c/object_template_spec.rb +++ b/spec/c/object_template_spec.rb @@ -5,9 +5,6 @@ let(:template) { V8::C::ObjectTemplate::New(@isolate) } it "can be used to create object instances" do - expect(template.NewInstance(@ctx)).to be - end - it "can be used to create a new object" do - + expect(template.NewInstance(@ctx).FromJust()).to be_instance_of V8::C::Object end end From 5d09e42109c2d9285e49e78d58d598cb285e5919 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 18 Jul 2015 16:39:36 -0500 Subject: [PATCH 066/105] implement ObjectTemplte#Set() --- ext/v8/init.cc | 2 ++ ext/v8/template.h | 2 +- spec/c_spec_helper.rb | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 7456262c..5a907aeb 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -8,6 +8,8 @@ using namespace rr; extern "C" { void Init_init() { + rb_eval_string("require 'v8/c'"); + V8::Init(); Isolate::Init(); Handles::Init(); diff --git a/ext/v8/template.h b/ext/v8/template.h index 1f2b66eb..67ab29aa 100644 --- a/ext/v8/template.h +++ b/ext/v8/template.h @@ -17,7 +17,7 @@ namespace rr { VALUE name, value, r_attributes; rb_scan_args(argc, argv, "21", &name, &value, &r_attributes); v8::PropertyAttribute attributes(v8::PropertyAttribute::None); - if (RTEST(attributes)) { + if (RTEST(r_attributes)) { attributes = (v8::PropertyAttribute)NUM2INT(r_attributes); } v8::Local val = Value(value); diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index c57f12c1..06995dda 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -1,5 +1,3 @@ -require 'v8/weak' -require 'v8/c/maybe' require 'v8/init' module V8ContextHelpers From e704d7356857628164222b3196a53dca6a11f096 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sat, 18 Jul 2015 20:00:32 -0500 Subject: [PATCH 067/105] add FunctionTemplate#{New,GetFunction} --- ext/v8/function-template.h | 33 ++++++++++++++++++++++ ext/v8/function.h | 1 + ext/v8/init.cc | 1 + ext/v8/rr.h | 1 + ext/v8/template.h | 8 ++++++ lib/v8/c.rb | 3 ++ spec/c/function_template_spec.rb | 19 +++++++++++++ spec/c/template_spec.rb | 48 ++++++++++++++++++++++++++++++++ 8 files changed, 114 insertions(+) create mode 100644 ext/v8/function-template.h create mode 100644 lib/v8/c.rb create mode 100644 spec/c/function_template_spec.rb create mode 100644 spec/c/template_spec.rb diff --git a/ext/v8/function-template.h b/ext/v8/function-template.h new file mode 100644 index 00000000..6038b8f1 --- /dev/null +++ b/ext/v8/function-template.h @@ -0,0 +1,33 @@ +// -*- mode: c++ -*- + +namespace rr { + class FunctionTemplate : public Ref { + public: + FunctionTemplate(VALUE self) : Ref(self) {} + FunctionTemplate(v8::Isolate* isolate, v8::Local t) : + Ref(isolate, t) {} + static inline void Init() { + ClassBuilder("FunctionTemplate", Template::Class). + defineSingletonMethod("New", &New). + defineMethod("GetFunction", &GetFunction). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate, callback, data, signature, length; + rb_scan_args(argc, argv, "14", &r_isolate, &callback, &data, &signature, &length); + Isolate isolate(r_isolate); + Locker lock(isolate); + + return FunctionTemplate(isolate, v8::FunctionTemplate::New(isolate)); + } + + static VALUE GetFunction(VALUE self, VALUE context) { + FunctionTemplate t(self); + Isolate isolate(t.getIsolate()); + Locker lock(isolate); + + return Function::Maybe(isolate, t->GetFunction(Context(context))); + } + }; +} diff --git a/ext/v8/function.h b/ext/v8/function.h index e4f6c9bd..15a1fde1 100644 --- a/ext/v8/function.h +++ b/ext/v8/function.h @@ -24,6 +24,7 @@ namespace rr { inline Function(VALUE value) : Ref(value) {} inline Function(v8::Isolate* isolate, v8::Handle function) : Ref(isolate, function) {} + typedef MaybeLocal Maybe; }; } diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 5a907aeb..24d93e27 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -33,6 +33,7 @@ extern "C" { External::Init(); Template::Init(); ObjectTemplate::Init(); + FunctionTemplate::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/rr.h b/ext/v8/rr.h index ae18f327..8d8dd7e8 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -55,6 +55,7 @@ inline VALUE not_implemented(const char* message) { #include "function-callback.h" #include "template.h" +#include "function-template.h" #include "object-template.h" #endif diff --git a/ext/v8/template.h b/ext/v8/template.h index 67ab29aa..600b4f25 100644 --- a/ext/v8/template.h +++ b/ext/v8/template.h @@ -10,6 +10,7 @@ namespace rr { inline static void Init() { ClassBuilder("Template"). defineMethod("Set", &Set). + defineMethod("SetAccessorProperty", &SetAccessorProperty). store(&Class); } @@ -24,6 +25,13 @@ namespace rr { Template(self)->Set(Name(name), val, attributes); return Qnil; } + + static VALUE SetAccessorProperty(int argc, VALUE argv[], VALUE self) { + VALUE r_name, r_getter, setter, attributes, access; + rb_scan_args(argc, argv, "14", &r_name, &r_getter, &setter, &attributes, &access); + Template(self)->SetAccessorProperty(Name(r_name)); + return Qnil; + } }; } diff --git a/lib/v8/c.rb b/lib/v8/c.rb new file mode 100644 index 00000000..d43e2b86 --- /dev/null +++ b/lib/v8/c.rb @@ -0,0 +1,3 @@ +require 'v8/weak' +require 'v8/c/maybe' +require 'v8/c/property_attribute' diff --git a/spec/c/function_template_spec.rb b/spec/c/function_template_spec.rb new file mode 100644 index 00000000..5da0f861 --- /dev/null +++ b/spec/c/function_template_spec.rb @@ -0,0 +1,19 @@ +require 'c_spec_helper' + +describe V8::C::FunctionTemplate do + requires_v8_context + + describe "New()" do + describe "with no arguments" do + let(:function) { template.GetFunction(@ctx).FromJust() } + let(:template) { V8::C::FunctionTemplate::New(@isolate) } + it "creates an empty template" do + expect(template).to be + end + it "has an empty function" do + expect(function).to be + expect(function.Call(@ctx.Global(), []).StrictEquals(@ctx.Global())).to be true + end + end + end +end diff --git a/spec/c/template_spec.rb b/spec/c/template_spec.rb new file mode 100644 index 00000000..d0d7061e --- /dev/null +++ b/spec/c/template_spec.rb @@ -0,0 +1,48 @@ +require 'c_spec_helper' + +describe V8::C::Template do + requires_v8_context + + let(:key) { V8::C::String::NewFromUtf8(@isolate, "key") } + let(:value) { V8::C::Integer::New(@isolate, 23) } + let(:template) { V8::C::ObjectTemplate::New(@isolate) } + + describe "Set()" do + + describe "without property attributes" do + before do + template.Set(key, value) + end + it "places those values on all new instances" do + object = template.NewInstance(@ctx).FromJust() + expect(object.Get(@ctx, key).FromJust().Value).to be 23 + end + end + + describe "with property attributes" do + before do + template.Set(key, value, V8::C::PropertyAttribute::ReadOnly) + @object = template.NewInstance(@ctx).FromJust() + @object.Set(@ctx, key, V8::C::Integer::New(@isolate, 25)) + end + it "applies those property attributes" do + expect(@object.Get(@ctx, key).FromJust().Value).to be 23 + end + end + end + + describe "SetAccessorProperty()" do + describe "with the bare minimum" do + before do + template.SetAccessorProperty(key) + @object = template.NewInstance(@ctx).FromJust() + @object.Set(@ctx, key, value) + end + it "cannot be shared across contexts" do + ctx2 = V8::C::Context::New(@isolate) + expect(@object.Get(@ctx, key).FromJust().Value).to be 23 + expect(@object.Get(ctx2, key).FromJust()).to be_nil + end + end + end +end From f5b5644a7d5a11a007f774e0988dd3846be462a7 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 19 Jul 2015 12:13:49 -0500 Subject: [PATCH 068/105] supply a function callback to function template --- ext/v8/function-callback.h | 106 ++++++++++++++++++------------- ext/v8/function-template.h | 17 ++++- ext/v8/function.cc | 8 +-- ext/v8/init.cc | 1 + ext/v8/object-template.h | 1 - ext/v8/rr.h | 1 + ext/v8/signature.cc | 13 ++++ ext/v8/signature.h | 22 +++++++ spec/c/function_spec.rb | 5 +- spec/c/function_template_spec.rb | 18 +++++- spec/c/template_spec.rb | 15 ----- 11 files changed, 137 insertions(+), 70 deletions(-) create mode 100644 ext/v8/signature.cc create mode 100644 ext/v8/signature.h diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index ce306396..970e4243 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -9,8 +9,8 @@ namespace rr { class FunctionCallbackInfo : public FunctionCallbackInfoWrapper { public: - inline FunctionCallbackInfo(v8::FunctionCallbackInfo info, v8::Local data_) : - FunctionCallbackInfoWrapper(info), data(data_) {} + inline FunctionCallbackInfo(v8::FunctionCallbackInfo info) : + FunctionCallbackInfoWrapper(info) {} inline FunctionCallbackInfo(VALUE self) : FunctionCallbackInfoWrapper(self) {} @@ -18,46 +18,6 @@ namespace rr { return this->container->content[i]; } - /** - * Package up the callback data for this function so that it can - * invoke a Ruby callable. - * - * Each `v8::Function` can have one `v8::Value` associated with it - * that is passed to its `v8::FunctionCallback`. To support this - * same API from ruby, we take the `Value` passed into the - * Function constructor *and* the callback and store them *both* - * in a single `v8::Object` which we use for the C++ level - * callback data. - */ - static v8::Local wrapData(v8::Isolate* isolate, VALUE r_callback, VALUE r_data) { - v8::Local holder = v8::Object::New(isolate); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - holder->SetHiddenValue(callback_key, External::wrap(isolate, r_callback)); - holder->SetHiddenValue(data_key, Value(r_data)); - return holder; - } - - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::FunctionCallback` API. - */ - static void invoke(const v8::FunctionCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); - v8::Local data(holder->GetHiddenValue(data_key)); - - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - Unlocker unlock(info.GetIsolate()); - rb_funcall(code, rb_intern("call"), 1, (VALUE)FunctionCallbackInfo(info, data)); - } - static VALUE Length(VALUE self) { FunctionCallbackInfo info(self); Locker lock(info->GetIsolate()); @@ -90,8 +50,13 @@ namespace rr { static VALUE Data(VALUE self) { FunctionCallbackInfo info(self); + Isolate isolate(info->GetIsolate()); Locker lock(info->GetIsolate()); - return Value::handleToRubyObject(info->GetIsolate(), info.data); + + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); + return Value::handleToRubyObject(isolate, data); } static VALUE GetIsolate(VALUE self) { @@ -117,8 +82,61 @@ namespace rr { defineMethod("GetReturnValue", &GetReturnValue). store(&Class); } + }; + + class FunctionCallback { + public: + FunctionCallback(v8::Isolate* i, VALUE c, VALUE d) : isolate(i), code(c), data(d) {} + + /** + * Package up the callback data for this function so that it can + * invoke a Ruby callable. + * + * Each `v8::Function` can have one `v8::Value` associated with it + * that is passed to its `v8::FunctionCallback`. To support this + * same API from ruby, we take the `Value` passed into the + * Function constructor *and* the callback and store them *both* + * in a single `v8::Object` which we use for the C++ level + * callback data. + */ + inline operator v8::Local() { + v8::Local holder = v8::Object::New(isolate); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + holder->SetHiddenValue(callback_key, External::wrap(isolate, code)); + holder->SetHiddenValue(data_key, Value(data)); + return holder; + } + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::FunctionCallback` API. + */ + static void invoke(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::callback"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 1, (VALUE)FunctionCallbackInfo(info)); + } + + inline operator v8::FunctionCallback() { + if (RTEST(code)) { + return &invoke; + } else { + return 0; + } + } - v8::Local data; + v8::Isolate* isolate; + VALUE code; + VALUE data; }; } diff --git a/ext/v8/function-template.h b/ext/v8/function-template.h index 6038b8f1..9069fae4 100644 --- a/ext/v8/function-template.h +++ b/ext/v8/function-template.h @@ -1,6 +1,11 @@ // -*- mode: c++ -*- +#ifndef RR_FUNCTION_TEMPLATE_H +#define RR_FUNCTION_TEMPLATE_H + namespace rr { + + class FunctionTemplate : public Ref { public: FunctionTemplate(VALUE self) : Ref(self) {} @@ -14,12 +19,16 @@ namespace rr { } static VALUE New(int argc, VALUE argv[], VALUE self) { - VALUE r_isolate, callback, data, signature, length; - rb_scan_args(argc, argv, "14", &r_isolate, &callback, &data, &signature, &length); + VALUE r_isolate, r_callback, r_data, r_signature, r_length; + rb_scan_args(argc, argv, "14", &r_isolate, &r_callback, &r_data, &r_signature, &r_length); Isolate isolate(r_isolate); Locker lock(isolate); - return FunctionTemplate(isolate, v8::FunctionTemplate::New(isolate)); + FunctionCallback callback(isolate, r_callback, r_data); + Signature signature(r_signature); + int length(RTEST(r_length) ? NUM2INT(r_length) : 0); + + return FunctionTemplate(isolate, v8::FunctionTemplate::New(isolate, callback, callback, v8::Local(), length)); } static VALUE GetFunction(VALUE self, VALUE context) { @@ -31,3 +40,5 @@ namespace rr { } }; } + +#endif /* RR_FUNCTION_TEMPLATE_H */ diff --git a/ext/v8/function.cc b/ext/v8/function.cc index 1fac845d..0c2e28fe 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -26,15 +26,13 @@ namespace rr { Isolate isolate(r_isolate); Locker lock(isolate); - // package up the function's callback data to have bot the code and the data + // package up the function's callback data to have both the code and the data // parameter. - v8::Local data(FunctionCallbackInfo::wrapData(isolate, r_callback, r_data)); + FunctionCallback callback(isolate, r_callback, r_data); int length = RTEST(r_length) ? NUM2INT(r_length) : 0; - v8::FunctionCallback callback = &FunctionCallbackInfo::invoke; - - return Function(isolate, v8::Function::New(isolate, callback, data, length)); + return Function(isolate, v8::Function::New(isolate, callback, callback, length)); } VALUE Function::NewInstance(int argc, VALUE argv[], VALUE self) { diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 24d93e27..bd8ae71b 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -34,6 +34,7 @@ extern "C" { Template::Init(); ObjectTemplate::Init(); FunctionTemplate::Init(); + Signature::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/object-template.h b/ext/v8/object-template.h index c6daeda9..b25682b6 100644 --- a/ext/v8/object-template.h +++ b/ext/v8/object-template.h @@ -31,7 +31,6 @@ namespace rr { v8::MaybeLocal object(t->NewInstance()); return Object::Maybe(isolate, ObjectTemplate(self)->NewInstance(context)); } - }; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 8d8dd7e8..c72de776 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -55,6 +55,7 @@ inline VALUE not_implemented(const char* message) { #include "function-callback.h" #include "template.h" +#include "signature.h" #include "function-template.h" #include "object-template.h" diff --git a/ext/v8/signature.cc b/ext/v8/signature.cc new file mode 100644 index 00000000..d1eee359 --- /dev/null +++ b/ext/v8/signature.cc @@ -0,0 +1,13 @@ +#include "rr.h" + +namespace rr { + VALUE Signature::New(int argc, VALUE argv[], VALUE self) { + VALUE r_isolate; + VALUE r_receiver; + rb_scan_args(argc, argv, "11", &r_isolate, &r_receiver); + + Isolate isolate(r_isolate); + Locker lock(isolate); + return Signature(isolate, v8::Signature::New(isolate, FunctionTemplate(r_receiver))); + } +} diff --git a/ext/v8/signature.h b/ext/v8/signature.h new file mode 100644 index 00000000..802f48bc --- /dev/null +++ b/ext/v8/signature.h @@ -0,0 +1,22 @@ +// -*- mode: c++ -*- +#ifndef RR_SIGNATURE_H +#define RR_SIGNATURE_H + +namespace rr { + class Signature : public Ref { + public: + Signature(VALUE self) : Ref(self) {} + Signature(v8::Isolate* i, v8::Local s) : + Ref(i, s) {} + + static void Init() { + ClassBuilder("Signature"). + defineSingletonMethod("New", &New). + store(&Class); + } + + static VALUE New(int argc, VALUE argv[], VALUE self); + }; +} + +#endif /* RR_SIGNATURE_H */ diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f175b101..54d87aa3 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -62,8 +62,9 @@ def call(info) end end + let(:data) { V8::C::Object::New(@isolate) } let(:callback) { FunctionCallback.new @isolate} - let(:fn) { V8::C::Function::New(@isolate, callback)} + let(:fn) { V8::C::Function::New(@isolate, callback, data)} before do expect(fn.Call(@ctx.Global(), ["world"]).Utf8Value()).to eql "ohai world" @@ -75,6 +76,8 @@ def call(info) expect(callback.callee.GetIdentityHash()).to eql fn.GetIdentityHash() expect(callback.this.GetIdentityHash()).to eql @ctx.Global().GetIdentityHash() expect(callback.is_construct_call).to be false + expect(callback.data).not_to be_nil + expect(callback.data.StrictEquals(data)).to be true end end diff --git a/spec/c/function_template_spec.rb b/spec/c/function_template_spec.rb index 5da0f861..2f39ef4e 100644 --- a/spec/c/function_template_spec.rb +++ b/spec/c/function_template_spec.rb @@ -2,10 +2,10 @@ describe V8::C::FunctionTemplate do requires_v8_context + let(:function) { template.GetFunction(@ctx).FromJust() } describe "New()" do describe "with no arguments" do - let(:function) { template.GetFunction(@ctx).FromJust() } let(:template) { V8::C::FunctionTemplate::New(@isolate) } it "creates an empty template" do expect(template).to be @@ -15,5 +15,21 @@ expect(function.Call(@ctx.Global(), []).StrictEquals(@ctx.Global())).to be true end end + describe "with a function callback" do + let(:data) { V8::C::Integer::New(@isolate, 42) } + let(:signature) { V8::C::Signature::New(@isolate) } + let(:template) { V8::C::FunctionTemplate::New(@isolate, @callback, data, @signature, 0) } + before do + @callback = ->(info) do + @data = info.Data() + end + function.Call(@ctx.Global(), []) + end + + it "does something" do + expect(@data).not_to be_nil + expect(@data.Value()).to be 42 + end + end end end diff --git a/spec/c/template_spec.rb b/spec/c/template_spec.rb index d0d7061e..6004c409 100644 --- a/spec/c/template_spec.rb +++ b/spec/c/template_spec.rb @@ -30,19 +30,4 @@ end end end - - describe "SetAccessorProperty()" do - describe "with the bare minimum" do - before do - template.SetAccessorProperty(key) - @object = template.NewInstance(@ctx).FromJust() - @object.Set(@ctx, key, value) - end - it "cannot be shared across contexts" do - ctx2 = V8::C::Context::New(@isolate) - expect(@object.Get(@ctx, key).FromJust().Value).to be 23 - expect(@object.Get(ctx2, key).FromJust()).to be_nil - end - end - end end From 949f4db447f1f8e7efa6b30d294fe938c06f1da1 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 19 Jul 2015 17:09:40 -0500 Subject: [PATCH 069/105] remove Empty value. With the Maybe apis in use everywhere, it won't be necessary anymore. --- ext/v8/value.cc | 12 ------------ ext/v8/value.h | 2 -- 2 files changed, 14 deletions(-) diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 88df57ec..8250f156 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -2,14 +2,8 @@ namespace rr { - VALUE Value::Empty; - void Value::Init() { - Empty = rb_eval_string("Object.new"); - ClassBuilder("Value"). - defineConst("Empty", Empty). - defineMethod("IsUndefined", &IsUndefined). defineMethod("IsNull", &IsNull). defineMethod("IsTrue", &IsTrue). @@ -44,8 +38,6 @@ namespace rr { defineSingletonMethod("FromRubyObject", &FromRubyObject). store(&Class); - - rb_gc_register_address(&Empty); } VALUE Value::IsUndefined(VALUE self) { @@ -209,10 +201,6 @@ namespace rr { } v8::Handle Value::rubyObjectToHandle(v8::Isolate* isolate, VALUE value) { - if (rb_equal(value, Empty)) { - return v8::Handle(); - } - switch (TYPE(value)) { case T_FIXNUM: return v8::Integer::New(isolate, NUM2INT(value)); diff --git a/ext/v8/value.h b/ext/v8/value.h index d585ecc2..b465d6f7 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -66,8 +66,6 @@ namespace rr { static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); static std::vector< v8::Handle > convertRubyArray(v8::Isolate* isolate, VALUE value); - - static VALUE Empty; }; } From 11a3530cc5ebb53740201a8ca9ac8ba5ab589c23 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 19 Jul 2015 23:24:54 -0500 Subject: [PATCH 070/105] noramize value thunking --- ext/v8/boolean.h | 40 +++++++++++++++++++++ ext/v8/function-callback.h | 4 +-- ext/v8/function.cc | 6 ++-- ext/v8/init.cc | 3 ++ ext/v8/name.h | 2 ++ ext/v8/null.h | 18 ++++++++++ ext/v8/rr.h | 3 ++ ext/v8/script.cc | 2 +- ext/v8/symbol.cc | 2 +- ext/v8/undefined.h | 18 ++++++++++ ext/v8/value.cc | 74 +++++++++++++++++++++----------------- ext/v8/value.h | 8 ++--- spec/c/value_spec.rb | 64 ++++++++++++++++++--------------- 13 files changed, 172 insertions(+), 72 deletions(-) create mode 100644 ext/v8/boolean.h create mode 100644 ext/v8/null.h create mode 100644 ext/v8/undefined.h diff --git a/ext/v8/boolean.h b/ext/v8/boolean.h new file mode 100644 index 00000000..49dff722 --- /dev/null +++ b/ext/v8/boolean.h @@ -0,0 +1,40 @@ +// -*- mode: c++ -*- + +#ifndef RR_BOOLEAN_H +#define RR_BOOLEAN_H + +namespace rr { + class Boolean : public Ref { + public: + Boolean(v8::Isolate* isolate, v8::Local handle) : + Ref(isolate, handle) {} + Boolean(VALUE value) : Ref(value) {} + + static void Init() { + ClassBuilder("Boolean", Value::Class). + defineSingletonMethod("New", &New). + defineMethod("Value", &Value). + store(&Class); + } + + static VALUE New(VALUE self, VALUE r_isolate, VALUE boolean) { + Isolate isolate(r_isolate); + Locker lock(isolate); + + return Boolean(isolate, v8::Boolean::New(isolate, RTEST(boolean))); + } + + static VALUE Value(VALUE self) { + Boolean boolean(self); + Locker lock(boolean); + + if (boolean->Value()) { + return Qtrue; + } else { + return Qfalse; + } + } + }; +} + +#endif /* RR_BOOLEAN_H */ diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index ce306396..97dfa326 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -67,7 +67,7 @@ namespace rr { static VALUE at(VALUE self, VALUE i) { FunctionCallbackInfo info(self); Locker lock(info->GetIsolate()); - return Value::handleToRubyObject(info->GetIsolate(), info[NUM2INT(i)]); + return Value(info->GetIsolate(), info[NUM2INT(i)]); } static VALUE Callee(VALUE self) { @@ -91,7 +91,7 @@ namespace rr { static VALUE Data(VALUE self) { FunctionCallbackInfo info(self); Locker lock(info->GetIsolate()); - return Value::handleToRubyObject(info->GetIsolate(), info.data); + return Value(info->GetIsolate(), info.data); } static VALUE GetIsolate(VALUE self) { diff --git a/ext/v8/function.cc b/ext/v8/function.cc index 1fac845d..09a391e7 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -60,7 +60,7 @@ namespace rr { std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); - return Value::handleToRubyObject(isolate, function->Call(Value(receiver), RARRAY_LENINT(argv), &vector[0])); + return Value(isolate, function->Call(Value(receiver), RARRAY_LENINT(argv), &vector[0])); } VALUE Function::SetName(VALUE self, VALUE name) { @@ -76,14 +76,14 @@ namespace rr { Function function(self); Locker lock(function.getIsolate()); - return Value::handleToRubyObject(function.getIsolate(), function->GetName()); + return Value(function.getIsolate(), function->GetName()); } VALUE Function::GetInferredName(VALUE self) { Function function(self); Locker lock(function.getIsolate()); - return Value::handleToRubyObject(function.getIsolate(), function->GetInferredName()); + return Value(function.getIsolate(), function->GetInferredName()); } VALUE Function::GetScriptLineNumber(VALUE self) { diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 9e415591..f783f5d8 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -16,6 +16,9 @@ extern "C" { Value::Init(); Object::Init(); Primitive::Init(); + Null::Init(); + Undefined::Init(); + Boolean::Init(); Number::Init(); Integer::Init(); Name::Init(); diff --git a/ext/v8/name.h b/ext/v8/name.h index 334d8d3b..a1062510 100644 --- a/ext/v8/name.h +++ b/ext/v8/name.h @@ -9,6 +9,8 @@ namespace rr { static VALUE GetIdentityHash(VALUE self); Name(VALUE self) : Ref(self) {} + Name(v8::Isolate* isolate, v8::Local name) : + Ref(isolate, name) {} }; } diff --git a/ext/v8/null.h b/ext/v8/null.h new file mode 100644 index 00000000..8912e1e0 --- /dev/null +++ b/ext/v8/null.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_NULL_H +#define RR_NULL_H + +namespace rr { + class Null : public Ref { + public: + Null(v8::Isolate* isolate, v8::Local undefined) : + Ref(isolate, undefined) {} + + static inline void Init() { + ClassBuilder("Null", Primitive::Class). + store(&Class); + } + }; +} + +#endif /* RR_NULL_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 00d6a1c2..faa1f80a 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -33,9 +33,12 @@ inline VALUE not_implemented(const char* message) { #include "context.h" #include "value.h" +#include "boolean.h" #include "object.h" #include "array.h" #include "primitive.h" +#include "undefined.h" +#include "null.h" #include "number.h" #include "integer.h" diff --git a/ext/v8/script.cc b/ext/v8/script.cc index 77477db9..4077adb8 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -37,6 +37,6 @@ namespace rr { Context context(rb_context); Locker lock(context->GetIsolate()); - return Value::handleToRubyObject(context->GetIsolate(), Script(self)->Run()); + return Value(context->GetIsolate(), Script(self)->Run()); } } diff --git a/ext/v8/symbol.cc b/ext/v8/symbol.cc index a18b1c2d..539fe216 100644 --- a/ext/v8/symbol.cc +++ b/ext/v8/symbol.cc @@ -69,6 +69,6 @@ namespace rr { Isolate isolate(symbol.getIsolate()); Locker lock(isolate); - return Value::handleToRubyObject(isolate, symbol->Name()); + return Value(isolate, symbol->Name()); } } diff --git a/ext/v8/undefined.h b/ext/v8/undefined.h new file mode 100644 index 00000000..addea976 --- /dev/null +++ b/ext/v8/undefined.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_UNDEFINED_H +#define RR_UNDEFINED_H + +namespace rr { + class Undefined : public Ref { + public: + Undefined(v8::Isolate* isolate, v8::Local undefined) : + Ref(isolate, undefined) {} + + static inline void Init() { + ClassBuilder("Undefined", Primitive::Class). + store(&Class); + } + }; +} + +#endif /* RR_UNDEFINED_H */ diff --git a/ext/v8/value.cc b/ext/v8/value.cc index 8250f156..d58a2d5c 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -8,12 +8,12 @@ namespace rr { defineMethod("IsNull", &IsNull). defineMethod("IsTrue", &IsTrue). defineMethod("IsFalse", &IsFalse). + defineMethod("IsBoolean", &IsBoolean). defineMethod("IsString", &IsString). defineMethod("IsFunction", &IsFunction). // defineMethod("IsArray", &IsArray). defineMethod("IsObject", &IsObject). - // defineMethod("IsBoolean", &IsBoolean). - // defineMethod("IsNumber", &IsNumber). + defineMethod("IsNumber", &IsNumber). defineMethod("IsExternal", &IsExternal). defineMethod("IsInt32", &IsInt32). defineMethod("IsUint32", &IsUint32). @@ -26,7 +26,7 @@ namespace rr { defineMethod("ToString", &ToString). // defineMethod("ToDetailString", &ToDetailString). // defineMethod("ToObject", &ToObject). - // defineMethod("BooleanValue", &BooleanValue). + defineMethod("BooleanValue", &BooleanValue). // defineMethod("NumberValue", &NumberValue). // defineMethod("IntegerValue", &IntegerValue). // defineMethod("Uint32Value", &Uint32Value). @@ -34,9 +34,6 @@ namespace rr { defineMethod("Equals", &Equals). defineMethod("StrictEquals", &StrictEquals). - defineMethod("ToRubyObject", &ToRubyObject). - defineSingletonMethod("FromRubyObject", &FromRubyObject). - store(&Class); } @@ -68,6 +65,13 @@ namespace rr { return Bool(value->IsFalse()); } + VALUE Value::IsBoolean(VALUE self) { + Value value(self); + Locker lock(value); + + return Bool(value->IsBoolean()); + } + VALUE Value::IsString(VALUE self) { Value value(self); Locker lock(value.getIsolate()); @@ -96,6 +100,12 @@ namespace rr { return Bool(value->IsExternal()); } + VALUE Value::IsNumber(VALUE self) { + Value value(self); + Locker lock(value); + return Bool(value->IsNumber()); + } + VALUE Value::IsInt32(VALUE self) { Value value(self); Locker lock(value.getIsolate()); @@ -117,45 +127,42 @@ namespace rr { return String(value.getIsolate(), value->ToString()); } - VALUE Value::Equals(VALUE self, VALUE other) { + VALUE Value::BooleanValue(VALUE self, VALUE r_context) { Value value(self); - Locker lock(value.getIsolate()); + Locker lock(value); - return Bool(value->Equals(Value(other))); + return Bool::Maybe(value->BooleanValue(Context(r_context))); } - VALUE Value::StrictEquals(VALUE self, VALUE other) { + VALUE Value::Equals(VALUE self, VALUE other) { Value value(self); Locker lock(value.getIsolate()); - return Bool(value->StrictEquals(Value(other))); + return Bool(value->Equals(Value(other))); } - VALUE Value::ToRubyObject(VALUE self) { + VALUE Value::StrictEquals(VALUE self, VALUE other) { Value value(self); Locker lock(value.getIsolate()); - return handleToRubyObject(value.getIsolate(), value); - } - - VALUE Value::FromRubyObject(VALUE selfClass, VALUE rb_isolate, VALUE value) { - Isolate isolate(rb_isolate); - Locker lock(isolate); - - return Value(isolate, rubyObjectToHandle(isolate, value)); + return Bool(value->StrictEquals(Value(other))); } - VALUE Value::handleToRubyObject(v8::Isolate* isolate, v8::Handle handle) { - if (handle.IsEmpty() || handle->IsUndefined() || handle->IsNull()) { + Value::operator VALUE() { + if (handle.IsEmpty()) { return Qnil; } - if (handle->IsTrue()) { - return Qtrue; + if (handle->IsUndefined()) { + return Undefined(isolate, handle); + } + + if (handle->IsNull()) { + return Null(isolate, handle); } - if (handle->IsFalse()) { - return Qfalse; + if (handle->IsBoolean()) { + return Boolean(isolate, handle.As()); } if (handle->IsExternal()) { @@ -174,14 +181,16 @@ namespace rr { return Number(isolate, handle); } - if (handle->IsBoolean()) { - return handle->BooleanValue() ? Qtrue : Qfalse; + if (handle->IsString()) { + return String(isolate, handle.As()); } - // TODO + if (handle->IsSymbol()) { + return Symbol(isolate, handle.As()); + } - if (handle->IsString()) { - return String(isolate, handle->ToString()); + if (handle->IsName()) { + return Name(isolate, handle.As()); } // TODO @@ -196,8 +205,7 @@ namespace rr { if (handle->IsObject()) { return Object(isolate, handle->ToObject()); } - - return Value(isolate, handle); + return Ref::operator VALUE(); } v8::Handle Value::rubyObjectToHandle(v8::Isolate* isolate, VALUE value) { diff --git a/ext/v8/value.h b/ext/v8/value.h index b465d6f7..16933362 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -18,7 +18,7 @@ namespace rr { public: Maybe(v8::Isolate* isolate, v8::MaybeLocal maybe) { if (!maybe.IsEmpty()) { - just(Value::handleToRubyObject(isolate, maybe.ToLocalChecked())); + just(Value(isolate, maybe.ToLocalChecked())); } } }; @@ -34,7 +34,7 @@ namespace rr { // static VALUE IsArray(VALUE self); static VALUE IsObject(VALUE self); static VALUE IsBoolean(VALUE self); - // static VALUE IsNumber(VALUE self); + static VALUE IsNumber(VALUE self); static VALUE IsExternal(VALUE self); static VALUE IsInt32(VALUE self); static VALUE IsUint32(VALUE self); @@ -47,7 +47,7 @@ namespace rr { static VALUE ToString(VALUE self); // static VALUE ToDetailString(VALUE self); // static VALUE ToObject(VALUE self); - // static VALUE BooleanValue(VALUE self); + static VALUE BooleanValue(VALUE self, VALUE context); // static VALUE NumberValue(VALUE self); // static VALUE IntegerValue(VALUE self); // static VALUE Uint32Value(VALUE self); @@ -62,7 +62,7 @@ namespace rr { inline Value(VALUE value) : Ref(value) {} inline Value(v8::Isolate* isolate, v8::Handle value) : Ref(isolate, value) {} - static VALUE handleToRubyObject(v8::Isolate* isolate, v8::Handle value); + operator VALUE(); static v8::Handle rubyObjectToHandle(v8::Isolate* isolate, VALUE value); static std::vector< v8::Handle > convertRubyArray(v8::Isolate* isolate, VALUE value); diff --git a/spec/c/value_spec.rb b/spec/c/value_spec.rb index 122e16ed..ee825301 100644 --- a/spec/c/value_spec.rb +++ b/spec/c/value_spec.rb @@ -3,38 +3,46 @@ describe V8::C::Value do requires_v8_context - def convert(value) - V8::C::Value.FromRubyObject(@isolate, value).ToRubyObject + describe "Boolean" do + let(:t) { V8::C::Boolean::New @isolate, true } + let(:f) { V8::C::Boolean::New @isolate, false } + + it "knows true is true, and false is false" do + expect(t.IsTrue()).to be true + expect(t.IsFalse()).to be false + expect(f.IsTrue()).to be false + expect(f.IsFalse()).to be true + expect(t.IsBoolean()).to be true + expect(f.IsBoolean()).to be true + end + + it "has a BooleanValue" do + expect(t.BooleanValue(@ctx).FromJust()).to be true + expect(f.BooleanValue(@ctx).FromJust()).to be false + end end - it 'converts strings' do - expect(convert('value').Utf8Value).to eq 'value' + describe "String" do + let(:string) { V8::C::String::NewFromUtf8(@isolate, "value")} + it "is a string" do + expect(string.IsString()).to be true + end end - it 'converts nil' do - expect(convert(nil)).to eq nil + describe "Number" do + let(:int) { V8::C::Integer::New(@isolate, -10) } + let(:num) { V8::C::Number::New(@isolate, 5.5) } + let(:uint) { V8::C::Integer::NewFromUnsigned(@isolate, 10) } + + it "recognizes integer types" do + expect(int.IsNumber).to be true + expect(int.IsInt32).to be true + expect(int.IsUint32).to be false + expect(num.IsNumber()).to be true + expect(num.IsInt32).to be false + expect(uint.IsNumber).to be true + expect(uint.IsUint32).to be true + end end - it 'converts undefined to nil' do - object = V8::C::Object.New(@isolate) - key = V8::C::String.NewFromUtf8(@isolate, 'key') - - expect(object.Get(@ctx, key).FromJust()).to eq nil - end - - it 'converts FixNums' do - expect(convert(42).Value()).to eq 42 - end - - it 'converts booleans' do - expect(convert(true)).to eq true - expect(convert(false)).to eq false - end - - it 'converts objects' do - object = V8::C::Object.New(@isolate) - object.Set(@ctx, V8::C::String.NewFromUtf8(@isolate, 'test'), 1) - - expect(convert(object).StrictEquals(object)).to be true - end end From 07352d62c1e1b8068a1de5197a2d4379ebe6a690 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Thu, 16 Jul 2015 17:37:48 +0000 Subject: [PATCH 071/105] Implement Object.SetAccessor, Has and Delete --- ext/v8/enums.h | 27 ++++++++ ext/v8/init.cc | 4 +- ext/v8/object.cc | 49 ++++++++++++++- ext/v8/object.h | 53 +++------------- ext/v8/property-callback-void.h | 84 +++++++++++++++++++++++++ ext/v8/property-callback.h | 107 ++++++++++++++++++++++++++++++++ ext/v8/rr.h | 18 ++++-- spec/c/enum_spec.rb | 21 +++++++ spec/c/object_spec.rb | 79 +++++++++++++++++++++++ 9 files changed, 387 insertions(+), 55 deletions(-) create mode 100644 ext/v8/enums.h create mode 100644 ext/v8/property-callback-void.h create mode 100644 ext/v8/property-callback.h create mode 100644 spec/c/enum_spec.rb diff --git a/ext/v8/enums.h b/ext/v8/enums.h new file mode 100644 index 00000000..58d60ae6 --- /dev/null +++ b/ext/v8/enums.h @@ -0,0 +1,27 @@ +#include "rr.h" + +namespace rr { + + class Enums { + public: + static void Init() { + ClassBuilder("AccessControl"). + defineConst("DEFAULT", INT2FIX(v8::DEFAULT)). + defineConst("ALL_CAN_READ", INT2FIX(v8::ALL_CAN_READ)). + defineConst("ALL_CAN_WRITE", INT2FIX(v8::ALL_CAN_WRITE)). + defineConst("PROHIBITS_OVERWRITING", INT2FIX(v8::PROHIBITS_OVERWRITING)); + + ClassBuilder("PropertyAttribute"). + defineConst("None", INT2FIX(v8::None)). + defineConst("ReadOnly", INT2FIX(v8::ReadOnly)). + defineConst("DontEnum", INT2FIX(v8::DontEnum)). + defineConst("DontDelete", INT2FIX(v8::DontDelete)); + } + + template + static T fromRubyValue(VALUE rb_int) { + return (T)NUM2INT(rb_int); + } + }; + +}; diff --git a/ext/v8/init.cc b/ext/v8/init.cc index f783f5d8..3543825b 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -9,6 +9,7 @@ using namespace rr; extern "C" { void Init_init() { V8::Init(); + Enums::Init(); Isolate::Init(); Handles::Init(); Context::Init(); @@ -26,13 +27,14 @@ extern "C" { Symbol::Init(); Function::Init(); FunctionCallbackInfo::Init(); + PropertyCallbackInfo::Init(); + PropertyCallbackInfoVoid::Init(); ReturnValue::Init(); Script::Init(); ScriptOrigin::Init(); Array::Init(); External::Init(); - // Accessor::Init(); // Invocation::Init(); // Signature::Init(); // Date::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index ec32a0d8..7a0d2900 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -1,7 +1,6 @@ #include "rr.h" namespace rr { - void Object::Init() { ClassBuilder("Object", Value::Class). defineSingletonMethod("New", &New). @@ -9,6 +8,9 @@ namespace rr { defineMethod("Set", &Set). defineMethod("Get", &Get). defineMethod("GetIdentityHash", &GetIdentityHash). + defineMethod("Has", &Has). + defineMethod("Delete", &Delete). + defineMethod("SetAccessor", &SetAccessor). store(&Class); } @@ -49,11 +51,53 @@ namespace rr { VALUE Object::GetIdentityHash(VALUE self) { Object object(self); - Locker lock(object.getIsolate()); + Locker lock(object); return INT2FIX(object->GetIdentityHash()); } + VALUE Object::Has(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->Has(Context(r_context), Uint32_t(key))); + } else { + return Bool::Maybe(object->Has(Context(r_context), *Value(key))); + } + } + + VALUE Object::Delete(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->Delete(Context(r_context), Uint32_t(key))); + } else { + return Bool::Maybe(object->Delete(Context(r_context), *Value(key))); + } + } + + VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { + VALUE r_context, name, getter, setter, data, settings, attribute; + rb_scan_args(argc, argv, "52", &r_context, &name, &getter, &setter, &data, &settings, &attribute); + + Object object(self); + Context context(r_context); + Isolate isolate(object.getIsolate()); + Locker lock(isolate); + + return Bool::Maybe(object->SetAccessor( + context, + Name(name), + &PropertyCallbackInfo::invokeGetter, + RTEST(setter) ? &PropertyCallbackInfoVoid::invokeSetter : 0, + v8::MaybeLocal(PropertyCallbackInfo::wrapData(isolate, getter, setter, data)), + RTEST(settings) ? Enums::fromRubyValue(settings) : v8::DEFAULT, + RTEST(attribute) ? Enums::fromRubyValue(attribute) : v8::None + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); @@ -89,4 +133,5 @@ namespace rr { return Ref::operator VALUE(); } + } diff --git a/ext/v8/object.h b/ext/v8/object.h index e43610a6..eef56fd6 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -6,53 +6,14 @@ namespace rr { class Object : public Ref { public: static void Init(); - static VALUE New(VALUE self, VALUE isolate); - static VALUE Set(VALUE self, VALUE context, VALUE key, VALUE value); - // static VALUE ForceSet(VALUE self, VALUE key, VALUE value); - static VALUE Get(VALUE self, VALUE context, VALUE key); - // static VALUE GetPropertyAttributes(VALUE self, VALUE key); - // static VALUE Has(VALUE self, VALUE key); - // static VALUE Delete(VALUE self, VALUE key); - // static VALUE ForceDelete(VALUE self, VALUE key); - // static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); - // static VALUE GetPropertyNames(VALUE self); - // static VALUE GetOwnPropertyNames(VALUE self); - // static VALUE GetPrototype(VALUE self); - // static VALUE SetPrototype(VALUE self, VALUE prototype); - // static VALUE FindInstanceInPrototypeChain(VALUE self, VALUE impl); - // static VALUE ObjectProtoToString(VALUE self); - // static VALUE GetConstructorName(VALUE self); - // static VALUE InternalFieldCount(VALUE self); - // static VALUE GetInternalField(VALUE self, VALUE idx); - // static VALUE SetInternalField(VALUE self, VALUE idx, VALUE value); - // static VALUE HasOwnProperty(VALUE self, VALUE key); - // static VALUE HasRealNamedProperty(VALUE self, VALUE key); - // static VALUE HasRealIndexedProperty(VALUE self, VALUE idx); - // static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE key); - // static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE key); - // static VALUE GetRealNamedProperty(VALUE self, VALUE key); - // static VALUE HasNamedLookupInterceptor(VALUE self); - // static VALUE HasIndexedLookupInterceptor(VALUE self); - // static VALUE TurnOnAccessCheck(VALUE self); + + static VALUE New(VALUE self, VALUE rb_isolate); + static VALUE Set(VALUE self, VALUE r_context, VALUE key, VALUE value); + static VALUE Get(VALUE self, VALUE r_context, VALUE key); static VALUE GetIdentityHash(VALUE self); - // static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); - // static VALUE GetHiddenValue(VALUE self, VALUE key); - // static VALUE DeleteHiddenValue(VALUE self, VALUE key); - // static VALUE IsDirty(VALUE self); - // static VALUE Clone(VALUE self); - // static VALUE CreationContext(VALUE self); - // static VALUE SetIndexedPropertiesToPixelData(VALUE self, VALUE data, VALUE length); - // static VALUE GetIndexedPropertiesPixelData(VALUE self); - // static VALUE HasIndexedPropertiesInPixelData(VALUE self); - // static VALUE GetIndexedPropertiesPixelDataLength(VALUE self); - // static VALUE SetIndexedPropertiesToExternalArrayData(VALUE self); - // static VALUE HasIndexedPropertiesInExternalArrayData(VALUE self); - // static VALUE GetIndexedPropertiesExternalArrayData(VALUE self); - // static VALUE GetIndexedPropertiesExternalArrayDataType(VALUE self); - // static VALUE GetIndexedPropertiesExternalArrayDataLength(VALUE self); - // static VALUE IsCallable(VALUE self); - // static VALUE CallAsFunction(VALUE self, VALUE recv, VALUE argv); - // static VALUE CallAsConstructor(VALUE self, VALUE argv); + static VALUE Has(VALUE self, VALUE r_context, VALUE key); + static VALUE Delete(VALUE self, VALUE r_context, VALUE key); + static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/ext/v8/property-callback-void.h b/ext/v8/property-callback-void.h new file mode 100644 index 00000000..b212a97f --- /dev/null +++ b/ext/v8/property-callback-void.h @@ -0,0 +1,84 @@ +// -*- mode: c++ -*- +#ifndef RR_PROPERTY_CALLBACK_VOID_H +#define RR_PROPERTY_CALLBACK_VOID_H + +namespace rr { + + typedef Wrapper> PropertyCallbackInfoVoidWrapper; + + class PropertyCallbackInfoVoid : public PropertyCallbackInfoVoidWrapper { + public: + + inline PropertyCallbackInfoVoid(v8::PropertyCallbackInfo info) : + PropertyCallbackInfoVoidWrapper(info) {} + + inline PropertyCallbackInfoVoid(VALUE self) : PropertyCallbackInfoVoidWrapper(self) {} + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameSetterCallback` API. + */ + static void invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall( + code, rb_intern("call"), 3, + rb_property, + (VALUE)Value::handleToRubyObject(isolate, value), + (VALUE)PropertyCallbackInfoVoid(info) + ); + } + + static VALUE This(VALUE self) { + PropertyCallbackInfoVoid info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE Data(VALUE self) { + PropertyCallbackInfoVoid info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(isolate); + + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); + + return Value::handleToRubyObject(info->GetIsolate(), data); + } + + static VALUE GetIsolate(VALUE self) { + PropertyCallbackInfoVoid info(self); + return Isolate(info->GetIsolate()); + } + + static inline void Init() { + ClassBuilder("PropertyCallbackVoidInfo"). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + + v8::Local data; + }; +} + +#endif /* RR_PROPERTY_CALLBACK_VOID_H */ diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback.h new file mode 100644 index 00000000..2da30a5c --- /dev/null +++ b/ext/v8/property-callback.h @@ -0,0 +1,107 @@ +// -*- mode: c++ -*- +#ifndef RR_PROPERTY_CALLBACK_H +#define RR_PROPERTY_CALLBACK_H + +namespace rr { + + typedef Wrapper> PropertyCallbackInfoWrapper; + + class PropertyCallbackInfo : public PropertyCallbackInfoWrapper { + public: + + inline PropertyCallbackInfo(v8::PropertyCallbackInfo info) : + PropertyCallbackInfoWrapper(info) {} + + inline PropertyCallbackInfo(VALUE self) : PropertyCallbackInfoWrapper(self) {} + + /** + * Package up the callback data for this object so that it can + * invoke Ruby callables. + * + * Each accessor can have one `v8::Value` associated with it + * that is passed to the `SetAccessor` method. To support this + * same API from ruby, we take the passed data constructor *and* + * the callbacks and store them *both* in a single `v8::Object` + * which we use for the C++ level callback data. + */ + static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { + v8::Local holder = v8::Object::New(isolate); + + v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); + holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); + holder->SetHiddenValue(data_key, Value(r_data)); + + return holder; + } + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameGetterCallback` API. + */ + static void invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)PropertyCallbackInfo(info)); + } + + static VALUE This(VALUE self) { + PropertyCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE Data(VALUE self) { + PropertyCallbackInfo info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(isolate); + + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); + + return Value::handleToRubyObject(info->GetIsolate(), data); + } + + static VALUE GetIsolate(VALUE self) { + PropertyCallbackInfo info(self); + return Isolate(info->GetIsolate()); + } + + static VALUE GetReturnValue(VALUE self) { + PropertyCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return ReturnValue(info->GetReturnValue()); + } + + static inline void Init() { + ClassBuilder("PropertyCallbackInfo"). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + }; +} + +#endif /* RR_PROPERTY_CALLBACK_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index faa1f80a..affcd04e 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -17,6 +17,8 @@ inline VALUE not_implemented(const char* message) { } #include "class_builder.h" +#include "enums.h" + #include "maybe.h" #include "equiv.h" #include "bool.h" @@ -34,25 +36,29 @@ inline VALUE not_implemented(const char* message) { #include "value.h" #include "boolean.h" -#include "object.h" -#include "array.h" + #include "primitive.h" #include "undefined.h" #include "null.h" #include "number.h" #include "integer.h" - #include "external.h" -// This one is named v8_string to avoid name collisions with C's string.h #include "name.h" +// This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" #include "symbol.h" -#include "script.h" -#include "script-origin.h" #include "function.h" + +#include "object.h" #include "return-value.h" +#include "property-callback-void.h" +#include "property-callback.h" +#include "array.h" + +#include "script.h" +#include "script-origin.h" #include "function-callback.h" #endif diff --git a/spec/c/enum_spec.rb b/spec/c/enum_spec.rb new file mode 100644 index 00000000..06b7fdf1 --- /dev/null +++ b/spec/c/enum_spec.rb @@ -0,0 +1,21 @@ +require 'c_spec_helper' + +describe V8::C do + describe 'AccessControl' do + it 'has all enum values' do + expect(V8::C::AccessControl.const_defined? 'DEFAULT').to be true + expect(V8::C::AccessControl.const_defined? 'ALL_CAN_READ').to be true + expect(V8::C::AccessControl.const_defined? 'ALL_CAN_WRITE').to be true + expect(V8::C::AccessControl.const_defined? 'PROHIBITS_OVERWRITING').to be true + end + end + + describe 'PropertyAttribute' do + it 'has all enum values' do + expect(V8::C::PropertyAttribute.const_defined? 'None').to be true + expect(V8::C::PropertyAttribute.const_defined? 'ReadOnly').to be true + expect(V8::C::PropertyAttribute.const_defined? 'DontEnum').to be true + expect(V8::C::PropertyAttribute.const_defined? 'DontDelete').to be true + end + end +end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 5e883489..8d3d658e 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -21,6 +21,85 @@ expect(maybe.FromJust().Utf8Value).to eq 'bar' end + it 'can determine if a key has been set' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + o.Set(@ctx, key, key) + + maybe = o.Has(@ctx, key) + expect(maybe.IsJust()).to be true + expect(maybe.FromJust()).to eq true + end + + it 'can delete keys' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + o.Set(@ctx, key, key) + + maybe = o.Delete(@ctx, key) + expect(maybe.IsJust()).to be true + expect(maybe.FromJust()).to eq true + + maybe = o.Has(@ctx, key) + expect(maybe.IsJust()).to be true + expect(maybe.FromJust()).to eq false + end + + describe '#SetAccessor' do + it 'can set getters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + data = V8::C::String.NewFromUtf8(@isolate, 'data') + get_value = V8::C::String.NewFromUtf8(@isolate, 'bar') + + get_name = nil + get_data = nil + + getter = proc do |name, info| + get_name = name + get_data = info.Data + + info.GetReturnValue.Set(get_value) + end + + o.SetAccessor(@ctx, key, getter, nil, data) + + maybe = o.Get(@ctx, key) + expect(maybe.IsJust).to be true + expect(maybe.FromJust.StrictEquals(get_value)).to be true + + expect(get_name.Equals(key)).to be true + expect(get_data.StrictEquals(data)).to be true + end + + it 'can set setters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + set_value = nil + set_data = nil + + setter = proc do |name, value, info| + set_data = info.Data + + set_value = value + end + + o.SetAccessor(@ctx, key, proc { }, setter, data) + + maybe = o.Set(@ctx, key, data) + expect(maybe.IsJust).to be true + expect(maybe.FromJust).to be true + + expect(set_data.StrictEquals(data)).to be true + expect(set_value.StrictEquals(data)).to be true + end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From ac1d0e04e8324e4c53eefa81071693bc8c7e7821 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 19 Jul 2015 17:55:45 +0000 Subject: [PATCH 072/105] Fix the required number of arguments for the SetAccessor method --- ext/v8/object.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 7a0d2900..8d759bff 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -80,7 +80,7 @@ namespace rr { VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) { VALUE r_context, name, getter, setter, data, settings, attribute; - rb_scan_args(argc, argv, "52", &r_context, &name, &getter, &setter, &data, &settings, &attribute); + rb_scan_args(argc, argv, "34", &r_context, &name, &getter, &setter, &data, &settings, &attribute); Object object(self); Context context(r_context); From de7b0f57d432068b1e2b892ccdd5a1da93056cf1 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 19 Jul 2015 19:20:11 +0000 Subject: [PATCH 073/105] Use a constructor for Enum conversion from Ruby values --- ext/v8/enum.h | 37 +++++++++++++++++++++++++++++++++++++ ext/v8/enums.h | 27 --------------------------- ext/v8/init.cc | 2 +- ext/v8/object.cc | 4 ++-- ext/v8/rr.h | 2 +- 5 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 ext/v8/enum.h delete mode 100644 ext/v8/enums.h diff --git a/ext/v8/enum.h b/ext/v8/enum.h new file mode 100644 index 00000000..1ac72fc2 --- /dev/null +++ b/ext/v8/enum.h @@ -0,0 +1,37 @@ +#include "rr.h" + +#ifndef ENUM_H +#define ENUM_H + +namespace rr { + + template + class Enum { + VALUE value; + T defaultValue; + + public: + Enum(VALUE value_, T defaultValue_) : value(value_), defaultValue(defaultValue_) { } + + operator T() { + return RTEST(value) ? (T)NUM2INT(value) : defaultValue; + } + }; + + inline void DefineEnums() { + ClassBuilder("AccessControl"). + defineConst("DEFAULT", INT2FIX(v8::DEFAULT)). + defineConst("ALL_CAN_READ", INT2FIX(v8::ALL_CAN_READ)). + defineConst("ALL_CAN_WRITE", INT2FIX(v8::ALL_CAN_WRITE)). + defineConst("PROHIBITS_OVERWRITING", INT2FIX(v8::PROHIBITS_OVERWRITING)); + + ClassBuilder("PropertyAttribute"). + defineConst("None", INT2FIX(v8::None)). + defineConst("ReadOnly", INT2FIX(v8::ReadOnly)). + defineConst("DontEnum", INT2FIX(v8::DontEnum)). + defineConst("DontDelete", INT2FIX(v8::DontDelete)); + } + +}; + +#endif /* ENUM_H */ diff --git a/ext/v8/enums.h b/ext/v8/enums.h deleted file mode 100644 index 58d60ae6..00000000 --- a/ext/v8/enums.h +++ /dev/null @@ -1,27 +0,0 @@ -#include "rr.h" - -namespace rr { - - class Enums { - public: - static void Init() { - ClassBuilder("AccessControl"). - defineConst("DEFAULT", INT2FIX(v8::DEFAULT)). - defineConst("ALL_CAN_READ", INT2FIX(v8::ALL_CAN_READ)). - defineConst("ALL_CAN_WRITE", INT2FIX(v8::ALL_CAN_WRITE)). - defineConst("PROHIBITS_OVERWRITING", INT2FIX(v8::PROHIBITS_OVERWRITING)); - - ClassBuilder("PropertyAttribute"). - defineConst("None", INT2FIX(v8::None)). - defineConst("ReadOnly", INT2FIX(v8::ReadOnly)). - defineConst("DontEnum", INT2FIX(v8::DontEnum)). - defineConst("DontDelete", INT2FIX(v8::DontDelete)); - } - - template - static T fromRubyValue(VALUE rb_int) { - return (T)NUM2INT(rb_int); - } - }; - -}; diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 3543825b..d4199efa 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -9,7 +9,7 @@ using namespace rr; extern "C" { void Init_init() { V8::Init(); - Enums::Init(); + DefineEnums(); Isolate::Init(); Handles::Init(); Context::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 8d759bff..046399b9 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -93,8 +93,8 @@ namespace rr { &PropertyCallbackInfo::invokeGetter, RTEST(setter) ? &PropertyCallbackInfoVoid::invokeSetter : 0, v8::MaybeLocal(PropertyCallbackInfo::wrapData(isolate, getter, setter, data)), - RTEST(settings) ? Enums::fromRubyValue(settings) : v8::DEFAULT, - RTEST(attribute) ? Enums::fromRubyValue(attribute) : v8::None + Enum(settings, v8::DEFAULT), + Enum(attribute, v8::None) )); } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index affcd04e..db9363e1 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -17,7 +17,7 @@ inline VALUE not_implemented(const char* message) { } #include "class_builder.h" -#include "enums.h" +#include "enum.h" #include "maybe.h" #include "equiv.h" From 45ddb40ec8609cfec128799742c7bdb0c78ef749 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Mon, 20 Jul 2015 18:57:55 +0000 Subject: [PATCH 074/105] Use template for PropertyCallbackInfo --- ext/v8/init.cc | 2 +- ext/v8/object.cc | 6 +- ext/v8/property-callback-void.h | 84 ----------------------- ext/v8/property-callback.h | 116 ++++++++++++++++++++++++-------- ext/v8/rr.h | 1 - 5 files changed, 91 insertions(+), 118 deletions(-) delete mode 100644 ext/v8/property-callback-void.h diff --git a/ext/v8/init.cc b/ext/v8/init.cc index d4199efa..0e84f1cc 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -27,7 +27,7 @@ extern "C" { Symbol::Init(); Function::Init(); FunctionCallbackInfo::Init(); - PropertyCallbackInfo::Init(); + PropertyCallbackInfoValue::Init(); PropertyCallbackInfoVoid::Init(); ReturnValue::Init(); Script::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 046399b9..0d58ea06 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -90,9 +90,9 @@ namespace rr { return Bool::Maybe(object->SetAccessor( context, Name(name), - &PropertyCallbackInfo::invokeGetter, - RTEST(setter) ? &PropertyCallbackInfoVoid::invokeSetter : 0, - v8::MaybeLocal(PropertyCallbackInfo::wrapData(isolate, getter, setter, data)), + &PropertyCallbackInfoValue::invoke, + RTEST(setter) ? &PropertyCallbackInfoVoid::invoke : 0, + v8::MaybeLocal(PropertyCallbackInfo::wrapData(isolate, getter, setter, data)), Enum(settings, v8::DEFAULT), Enum(attribute, v8::None) )); diff --git a/ext/v8/property-callback-void.h b/ext/v8/property-callback-void.h deleted file mode 100644 index b212a97f..00000000 --- a/ext/v8/property-callback-void.h +++ /dev/null @@ -1,84 +0,0 @@ -// -*- mode: c++ -*- -#ifndef RR_PROPERTY_CALLBACK_VOID_H -#define RR_PROPERTY_CALLBACK_VOID_H - -namespace rr { - - typedef Wrapper> PropertyCallbackInfoVoidWrapper; - - class PropertyCallbackInfoVoid : public PropertyCallbackInfoVoidWrapper { - public: - - inline PropertyCallbackInfoVoid(v8::PropertyCallbackInfo info) : - PropertyCallbackInfoVoidWrapper(info) {} - - inline PropertyCallbackInfoVoid(VALUE self) : PropertyCallbackInfoVoidWrapper(self) {} - - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::AccessorNameSetterCallback` API. - */ - static void invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); - - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - - VALUE rb_property; - if (property->IsSymbol()) { - rb_property = Symbol(isolate, v8::Local::Cast(property)); - } else { - rb_property = String(isolate, property->ToString()); - } - - Unlocker unlock(info.GetIsolate()); - rb_funcall( - code, rb_intern("call"), 3, - rb_property, - (VALUE)Value::handleToRubyObject(isolate, value), - (VALUE)PropertyCallbackInfoVoid(info) - ); - } - - static VALUE This(VALUE self) { - PropertyCallbackInfoVoid info(self); - Locker lock(info->GetIsolate()); - return Object(info->GetIsolate(), info->This()); - } - - static VALUE Data(VALUE self) { - PropertyCallbackInfoVoid info(self); - Isolate isolate(info->GetIsolate()); - Locker lock(isolate); - - v8::Local holder = v8::Local::Cast(info->Data()); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - v8::Local data(holder->GetHiddenValue(data_key)); - - return Value::handleToRubyObject(info->GetIsolate(), data); - } - - static VALUE GetIsolate(VALUE self) { - PropertyCallbackInfoVoid info(self); - return Isolate(info->GetIsolate()); - } - - static inline void Init() { - ClassBuilder("PropertyCallbackVoidInfo"). - defineMethod("This", &This). - defineMethod("Data", &Data). - defineMethod("GetIsolate", &GetIsolate). - store(&Class); - } - - v8::Local data; - }; -} - -#endif /* RR_PROPERTY_CALLBACK_VOID_H */ diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback.h index 2da30a5c..3178ffdb 100644 --- a/ext/v8/property-callback.h +++ b/ext/v8/property-callback.h @@ -4,15 +4,14 @@ namespace rr { - typedef Wrapper> PropertyCallbackInfoWrapper; - - class PropertyCallbackInfo : public PropertyCallbackInfoWrapper { + template + class PropertyCallbackInfo : public Wrapper> { public: - inline PropertyCallbackInfo(v8::PropertyCallbackInfo info) : - PropertyCallbackInfoWrapper(info) {} + inline PropertyCallbackInfo(v8::PropertyCallbackInfo info) : + Wrapper>(info) {} - inline PropertyCallbackInfo(VALUE self) : PropertyCallbackInfoWrapper(self) {} + inline PropertyCallbackInfo(VALUE self) : Wrapper>(self) {} /** * Package up the callback data for this object so that it can @@ -38,6 +37,38 @@ namespace rr { return holder; } + static VALUE This(VALUE self) { + PropertyCallbackInfo info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } + + static VALUE Data(VALUE self) { + PropertyCallbackInfo info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(isolate); + + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); + + return Value::handleToRubyObject(info->GetIsolate(), data); + } + + static VALUE GetIsolate(VALUE self) { + PropertyCallbackInfo info(self); + return Isolate(info->GetIsolate()); + } + }; + + class PropertyCallbackInfoValue : public PropertyCallbackInfo { + public: + + inline PropertyCallbackInfoValue(v8::PropertyCallbackInfo info) : + PropertyCallbackInfo(info) {} + + inline PropertyCallbackInfoValue(VALUE self) : PropertyCallbackInfo(self) {} + /** * Call the Ruby code associated with this callback. * @@ -46,7 +77,7 @@ namespace rr { * * Note: This function implements the `v8::AccessorNameGetterCallback` API. */ - static void invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info) { + static void invoke(v8::Local property, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); v8::Local holder = v8::Local::Cast(info.Data()); v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); @@ -64,44 +95,71 @@ namespace rr { rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)PropertyCallbackInfo(info)); } - static VALUE This(VALUE self) { - PropertyCallbackInfo info(self); + static VALUE GetReturnValue(VALUE self) { + PropertyCallbackInfoValue info(self); Locker lock(info->GetIsolate()); - return Object(info->GetIsolate(), info->This()); + return ReturnValue(info->GetReturnValue()); } - static VALUE Data(VALUE self) { - PropertyCallbackInfo info(self); - Isolate isolate(info->GetIsolate()); - Locker lock(isolate); + static inline void Init() { + ClassBuilder("PropertyCallbackInfoValue"). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } + }; - v8::Local holder = v8::Local::Cast(info->Data()); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - v8::Local data(holder->GetHiddenValue(data_key)); + class PropertyCallbackInfoVoid : public PropertyCallbackInfo { + public: - return Value::handleToRubyObject(info->GetIsolate(), data); - } + inline PropertyCallbackInfoVoid(v8::PropertyCallbackInfo info) : + PropertyCallbackInfo(info) {} - static VALUE GetIsolate(VALUE self) { - PropertyCallbackInfo info(self); - return Isolate(info->GetIsolate()); - } + inline PropertyCallbackInfoVoid(VALUE self) : PropertyCallbackInfo(self) {} - static VALUE GetReturnValue(VALUE self) { - PropertyCallbackInfo info(self); - Locker lock(info->GetIsolate()); - return ReturnValue(info->GetReturnValue()); + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameSetterCallback` API. + */ + static void invoke(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall( + code, rb_intern("call"), 3, + rb_property, + (VALUE)Value::handleToRubyObject(isolate, value), + (VALUE)PropertyCallbackInfoVoid(info) + ); } static inline void Init() { - ClassBuilder("PropertyCallbackInfo"). + ClassBuilder("PropertyCallbackInfoVoid"). defineMethod("This", &This). defineMethod("Data", &Data). defineMethod("GetIsolate", &GetIsolate). - defineMethod("GetReturnValue", &GetReturnValue). store(&Class); } }; + } #endif /* RR_PROPERTY_CALLBACK_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index db9363e1..7b6827af 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -53,7 +53,6 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "return-value.h" -#include "property-callback-void.h" #include "property-callback.h" #include "array.h" From 19438dde6a4042132b9ecec130a9861548c0cf74 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Tue, 21 Jul 2015 17:26:24 +0000 Subject: [PATCH 075/105] Namespace the PropertyCallbackInfo classes --- ext/v8/class_builder.cc | 10 ++ ext/v8/class_builder.h | 2 + ext/v8/init.cc | 5 +- ext/v8/object.cc | 6 +- ext/v8/property-callback.h | 279 ++++++++++++++++++++----------------- 5 files changed, 167 insertions(+), 135 deletions(-) diff --git a/ext/v8/class_builder.cc b/ext/v8/class_builder.cc index 047f19ad..1413e1c2 100644 --- a/ext/v8/class_builder.cc +++ b/ext/v8/class_builder.cc @@ -11,6 +11,12 @@ namespace rr { return klass; } + VALUE ClassBuilder::defineClassUnder(const char* name, VALUE module, VALUE superclass) { + VALUE klass = rb_define_class_under(module, name, superclass); + rb_funcall(klass, rb_intern("private_class_method"), 1, rb_str_new2("new")); + return klass; + } + VALUE ClassBuilder::defineModule(const char *name) { VALUE V8 = rb_define_module("V8"); VALUE V8_C = rb_define_module_under(V8, "C"); @@ -21,6 +27,10 @@ namespace rr { this->value = ClassBuilder::defineClass(name, superclass); } + ClassBuilder::ClassBuilder(const char* name, VALUE module, VALUE superclass) { + this->value = ClassBuilder::defineClassUnder(name, module, superclass); + } + ClassBuilder::ClassBuilder(const char* name, const char* supername) { VALUE superclass = ClassBuilder::defineClass(supername); this->value = ClassBuilder::defineClass(name, superclass); diff --git a/ext/v8/class_builder.h b/ext/v8/class_builder.h index c0c7b1e2..b2358f9c 100644 --- a/ext/v8/class_builder.h +++ b/ext/v8/class_builder.h @@ -7,10 +7,12 @@ namespace rr { class ClassBuilder { public: static VALUE defineClass(const char *name, VALUE superclass = rb_cObject); + static VALUE defineClassUnder(const char *name, VALUE module, VALUE superclass = rb_cObject); static VALUE defineModule(const char *name); ClassBuilder() {}; ClassBuilder(const char* name, VALUE superclass = rb_cObject); + ClassBuilder(const char* name, VALUE module, VALUE superclass); ClassBuilder(const char* name, const char* supername); ClassBuilder& defineConst(const char* name, VALUE value); diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 0e84f1cc..b1c18f63 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -6,6 +6,8 @@ extern "C" { using namespace rr; +VALUE PropertyCallbackInfo::Class; + extern "C" { void Init_init() { V8::Init(); @@ -27,8 +29,7 @@ extern "C" { Symbol::Init(); Function::Init(); FunctionCallbackInfo::Init(); - PropertyCallbackInfoValue::Init(); - PropertyCallbackInfoVoid::Init(); + PropertyCallbackInfo::Init(); ReturnValue::Init(); Script::Init(); ScriptOrigin::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 0d58ea06..8a0d6b8b 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -90,9 +90,9 @@ namespace rr { return Bool::Maybe(object->SetAccessor( context, Name(name), - &PropertyCallbackInfoValue::invoke, - RTEST(setter) ? &PropertyCallbackInfoVoid::invoke : 0, - v8::MaybeLocal(PropertyCallbackInfo::wrapData(isolate, getter, setter, data)), + &PropertyCallbackInfo::Value::invoke, + RTEST(setter) ? &PropertyCallbackInfo::Void::invoke : 0, + v8::MaybeLocal(PropertyCallbackInfo::Base::wrapData(isolate, getter, setter, data)), Enum(settings, v8::DEFAULT), Enum(attribute, v8::None) )); diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback.h index 3178ffdb..2412187e 100644 --- a/ext/v8/property-callback.h +++ b/ext/v8/property-callback.h @@ -4,160 +4,179 @@ namespace rr { - template - class PropertyCallbackInfo : public Wrapper> { + class PropertyCallbackInfo { public: - inline PropertyCallbackInfo(v8::PropertyCallbackInfo info) : - Wrapper>(info) {} - - inline PropertyCallbackInfo(VALUE self) : Wrapper>(self) {} - - /** - * Package up the callback data for this object so that it can - * invoke Ruby callables. - * - * Each accessor can have one `v8::Value` associated with it - * that is passed to the `SetAccessor` method. To support this - * same API from ruby, we take the passed data constructor *and* - * the callbacks and store them *both* in a single `v8::Object` - * which we use for the C++ level callback data. - */ - static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { - v8::Local holder = v8::Object::New(isolate); - - v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); - v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - - holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); - holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); - holder->SetHiddenValue(data_key, Value(r_data)); - - return holder; - } + template + class Base : public Wrapper> { + public: + + inline Base(v8::PropertyCallbackInfo info) : + Wrapper>(info) {} + + inline Base(VALUE self) : Wrapper>(self) {} + + /** + * Package up the callback data for this object so that it can + * invoke Ruby callables. + * + * Each accessor can have one `v8::Value` associated with it + * that is passed to the `SetAccessor` method. To support this + * same API from ruby, we take the passed data constructor *and* + * the callbacks and store them *both* in a single `v8::Object` + * which we use for the C++ level callback data. + */ + static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { + v8::Local holder = v8::Object::New(isolate); + + v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); + holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); + holder->SetHiddenValue(data_key, rr::Value(r_data)); + + return holder; + } - static VALUE This(VALUE self) { - PropertyCallbackInfo info(self); - Locker lock(info->GetIsolate()); - return Object(info->GetIsolate(), info->This()); - } + static VALUE This(VALUE self) { + Base info(self); + Locker lock(info->GetIsolate()); + return Object(info->GetIsolate(), info->This()); + } - static VALUE Data(VALUE self) { - PropertyCallbackInfo info(self); - Isolate isolate(info->GetIsolate()); - Locker lock(isolate); + static VALUE Data(VALUE self) { + Base info(self); + Isolate isolate(info->GetIsolate()); + Locker lock(isolate); - v8::Local holder = v8::Local::Cast(info->Data()); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - v8::Local data(holder->GetHiddenValue(data_key)); + v8::Local holder = v8::Local::Cast(info->Data()); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + v8::Local data(holder->GetHiddenValue(data_key)); - return Value::handleToRubyObject(info->GetIsolate(), data); - } + return rr::Value::handleToRubyObject(info->GetIsolate(), data); + } - static VALUE GetIsolate(VALUE self) { - PropertyCallbackInfo info(self); - return Isolate(info->GetIsolate()); - } - }; + static VALUE GetIsolate(VALUE self) { + Base info(self); + return Isolate(info->GetIsolate()); + } - class PropertyCallbackInfoValue : public PropertyCallbackInfo { - public: + }; - inline PropertyCallbackInfoValue(v8::PropertyCallbackInfo info) : - PropertyCallbackInfo(info) {} - - inline PropertyCallbackInfoValue(VALUE self) : PropertyCallbackInfo(self) {} - - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::AccessorNameGetterCallback` API. - */ - static void invoke(v8::Local property, const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); - - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - - VALUE rb_property; - if (property->IsSymbol()) { - rb_property = Symbol(isolate, v8::Local::Cast(property)); - } else { - rb_property = String(isolate, property->ToString()); - } + class Value : public Base { + public: - Unlocker unlock(info.GetIsolate()); - rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)PropertyCallbackInfo(info)); - } + inline Value(v8::PropertyCallbackInfo info) : + Base(info) {} - static VALUE GetReturnValue(VALUE self) { - PropertyCallbackInfoValue info(self); - Locker lock(info->GetIsolate()); - return ReturnValue(info->GetReturnValue()); - } + inline Value(VALUE self) : Base(self) {} - static inline void Init() { - ClassBuilder("PropertyCallbackInfoValue"). - defineMethod("This", &This). - defineMethod("Data", &Data). - defineMethod("GetIsolate", &GetIsolate). - defineMethod("GetReturnValue", &GetReturnValue). - store(&Class); - } - }; + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameGetterCallback` API. + */ + static void invoke(v8::Local property, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); - class PropertyCallbackInfoVoid : public PropertyCallbackInfo { - public: + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - inline PropertyCallbackInfoVoid(v8::PropertyCallbackInfo info) : - PropertyCallbackInfo(info) {} + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } - inline PropertyCallbackInfoVoid(VALUE self) : PropertyCallbackInfo(self) {} + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)Value(info)); + } - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::AccessorNameSetterCallback` API. - */ - static void invoke(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); + static VALUE GetReturnValue(VALUE self) { + Value info(self); + Locker lock(info->GetIsolate()); + return ReturnValue(info->GetReturnValue()); + } - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + static inline void Init() { + ClassBuilder("Value", PropertyCallbackInfo::Class, PropertyCallbackInfo::Class). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); + } - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + }; + + class Void : public Base { + public: + + inline Void(v8::PropertyCallbackInfo info) : + Base(info) {} + + inline Void(VALUE self) : Base(self) {} + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameSetterCallback` API. + */ + static void invoke(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + v8::Local holder = v8::Local::Cast(info.Data()); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + + VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall( + code, rb_intern("call"), 3, + rb_property, + (VALUE)rr::Value::handleToRubyObject(isolate, value), + (VALUE)Void(info) + ); + } - VALUE rb_property; - if (property->IsSymbol()) { - rb_property = Symbol(isolate, v8::Local::Cast(property)); - } else { - rb_property = String(isolate, property->ToString()); + static inline void Init() { + ClassBuilder("Void", PropertyCallbackInfo::Class, PropertyCallbackInfo::Class). + defineMethod("This", &This). + defineMethod("Data", &Data). + defineMethod("GetIsolate", &GetIsolate). + // defineMethod("GetReturnValue", &GetReturnValue). + store(&Class); } - Unlocker unlock(info.GetIsolate()); - rb_funcall( - code, rb_intern("call"), 3, - rb_property, - (VALUE)Value::handleToRubyObject(isolate, value), - (VALUE)PropertyCallbackInfoVoid(info) - ); - } + }; - static inline void Init() { - ClassBuilder("PropertyCallbackInfoVoid"). - defineMethod("This", &This). - defineMethod("Data", &Data). - defineMethod("GetIsolate", &GetIsolate). + static VALUE Class; + + static void Init() { + ClassBuilder("PropertyCallbackInfo"). store(&Class); + + Value::Init(); + Void::Init(); } + }; } From 7491cc687d1c5060934526266d49f0c2b354b324 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Tue, 21 Jul 2015 18:00:14 +0000 Subject: [PATCH 076/105] Add return value to PropertyCallbackInfo --- ext/v8/function-callback.h | 2 +- ext/v8/init.cc | 1 + ...ty-callback.h => property-callback-info.h} | 10 +- ext/v8/return-value.h | 175 +++++++++++------- ext/v8/rr.h | 2 +- 5 files changed, 118 insertions(+), 72 deletions(-) rename ext/v8/{property-callback.h => property-callback-info.h} (95%) diff --git a/ext/v8/function-callback.h b/ext/v8/function-callback.h index 97dfa326..b1ae189b 100644 --- a/ext/v8/function-callback.h +++ b/ext/v8/function-callback.h @@ -102,7 +102,7 @@ namespace rr { static VALUE GetReturnValue(VALUE self) { FunctionCallbackInfo info(self); Locker lock(info->GetIsolate()); - return ReturnValue(info->GetReturnValue()); + return ReturnValue::Value(info->GetReturnValue()); } static inline void Init() { diff --git a/ext/v8/init.cc b/ext/v8/init.cc index b1c18f63..ded672f9 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -7,6 +7,7 @@ extern "C" { using namespace rr; VALUE PropertyCallbackInfo::Class; +VALUE ReturnValue::Class; extern "C" { void Init_init() { diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback-info.h similarity index 95% rename from ext/v8/property-callback.h rename to ext/v8/property-callback-info.h index 2412187e..a7eff11e 100644 --- a/ext/v8/property-callback.h +++ b/ext/v8/property-callback-info.h @@ -102,7 +102,7 @@ namespace rr { static VALUE GetReturnValue(VALUE self) { Value info(self); Locker lock(info->GetIsolate()); - return ReturnValue(info->GetReturnValue()); + return ReturnValue::Value(info->GetReturnValue()); } static inline void Init() { @@ -156,12 +156,18 @@ namespace rr { ); } + static VALUE GetReturnValue(VALUE self) { + Value info(self); + Locker lock(info->GetIsolate()); + return ReturnValue::Void(info->GetReturnValue()); + } + static inline void Init() { ClassBuilder("Void", PropertyCallbackInfo::Class, PropertyCallbackInfo::Class). defineMethod("This", &This). defineMethod("Data", &Data). defineMethod("GetIsolate", &GetIsolate). - // defineMethod("GetReturnValue", &GetReturnValue). + defineMethod("GetReturnValue", &GetReturnValue). store(&Class); } diff --git a/ext/v8/return-value.h b/ext/v8/return-value.h index 62989474..6929b2d4 100644 --- a/ext/v8/return-value.h +++ b/ext/v8/return-value.h @@ -3,88 +3,127 @@ #define RR_RETURN_VALUE_H namespace rr { - typedef Wrapper> ReturnValueWrapper; - class ReturnValue : public ReturnValueWrapper { + class ReturnValue { public: - ReturnValue(v8::ReturnValue value) : ReturnValueWrapper(value) {} - ReturnValue(VALUE self) : ReturnValueWrapper(self) {} - - static VALUE Set(VALUE self, VALUE handle) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - v8::Local value((Value(handle))); - ret->Set(value); - return Qnil; - } - static VALUE Set_bool(VALUE self, VALUE value) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set((bool)Bool(value)); - return Qnil; - } + template + class Base : public Wrapper> { + public: - static VALUE Set_double(VALUE self, VALUE value) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2DBL(value)); - return Qnil; - } + Base(v8::ReturnValue value) : Wrapper>(value) {} + Base(VALUE self) : Wrapper>(self) {} - static VALUE Set_int32_t(VALUE self, VALUE i) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2INT(i)); - return Qnil; - } + static VALUE GetIsolate(VALUE self) { + Base ret(self); + return Isolate(ret->GetIsolate()); + } - static VALUE Set_uint32_t(VALUE self, VALUE i) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->Set(NUM2UINT(i)); - return Qnil; - } + }; - static VALUE SetNull(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetNull(); - return Qnil; - } + class Value : public Base { + public: - static VALUE SetUndefined(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetUndefined(); - return Qnil; - } + Value(v8::ReturnValue value) : Base(value) {} + Value(VALUE self) : Base(self) {} - static VALUE SetEmptyString(VALUE self) { - ReturnValue ret(self); - Locker lock(ret->GetIsolate()); - ret->SetEmptyString(); - return Qnil; - } + static VALUE Set(VALUE self, VALUE handle) { + Value ret(self); + Locker lock(ret->GetIsolate()); + v8::Local value((rr::Value(handle))); + ret->Set(value); + return Qnil; + } - static VALUE GetIsolate(VALUE self) { - ReturnValue ret(self); - return Isolate(ret->GetIsolate()); - } + static VALUE Set_bool(VALUE self, VALUE value) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->Set((bool)Bool(value)); + return Qnil; + } + + static VALUE Set_double(VALUE self, VALUE value) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2DBL(value)); + return Qnil; + } + + static VALUE Set_int32_t(VALUE self, VALUE i) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2INT(i)); + return Qnil; + } + + static VALUE Set_uint32_t(VALUE self, VALUE i) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->Set(NUM2UINT(i)); + return Qnil; + } - static inline void Init() { + static VALUE SetNull(VALUE self) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->SetNull(); + return Qnil; + } + + static VALUE SetUndefined(VALUE self) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->SetUndefined(); + return Qnil; + } + + static VALUE SetEmptyString(VALUE self) { + Value ret(self); + Locker lock(ret->GetIsolate()); + ret->SetEmptyString(); + return Qnil; + } + + static inline void Init() { + ClassBuilder("Value", ReturnValue::Class, ReturnValue::Class). + defineMethod("Set", &Set). + defineMethod("Set_bool", &Set_bool). + defineMethod("Set_double", &Set_double). + defineMethod("Set_int32_t", &Set_int32_t). + defineMethod("Set_uint32_t", &Set_uint32_t). + defineMethod("SetNull", &SetNull). + defineMethod("SetUndefined", &SetUndefined). + defineMethod("SetEmptyString", &SetEmptyString). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + + }; + + class Void : public Base { + public: + + Void(v8::ReturnValue value) : Base(value) {} + Void(VALUE self) : Base(self) {} + + static inline void Init() { + ClassBuilder("Void", ReturnValue::Class, ReturnValue::Class). + defineMethod("GetIsolate", &GetIsolate). + store(&Class); + } + + }; + + static VALUE Class; + + static void Init() { ClassBuilder("ReturnValue"). - defineMethod("Set", &Set). - defineMethod("Set_bool", &Set_bool). - defineMethod("Set_double", &Set_double). - defineMethod("Set_int32_t", &Set_int32_t). - defineMethod("Set_uint32_t", &Set_uint32_t). - defineMethod("SetNull", &SetNull). - defineMethod("SetUndefined", &SetUndefined). - defineMethod("SetEmptyString", &SetEmptyString). - defineMethod("GetIsolate", &GetIsolate). store(&Class); + + Value::Init(); + Void::Init(); } + }; } #endif /* RR_RETURN_VALUE_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 7b6827af..d7389c6a 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -53,7 +53,7 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "return-value.h" -#include "property-callback.h" +#include "property-callback-info.h" #include "array.h" #include "script.h" From 60f6069771facaeff41922f78601e74d76f3f747 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Tue, 21 Jul 2015 18:15:25 +0000 Subject: [PATCH 077/105] Move object wrapping for Object callbacks to a separate class --- ext/v8/object.cc | 6 +-- ext/v8/property-callback-info.h | 94 ++------------------------------- ext/v8/property-callback.cc | 77 +++++++++++++++++++++++++++ ext/v8/property-callback.h | 61 +++++++++++++++++++++ ext/v8/rr.h | 1 + 5 files changed, 146 insertions(+), 93 deletions(-) create mode 100644 ext/v8/property-callback.cc create mode 100644 ext/v8/property-callback.h diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 8a0d6b8b..9bcf0ddf 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -90,9 +90,9 @@ namespace rr { return Bool::Maybe(object->SetAccessor( context, Name(name), - &PropertyCallbackInfo::Value::invoke, - RTEST(setter) ? &PropertyCallbackInfo::Void::invoke : 0, - v8::MaybeLocal(PropertyCallbackInfo::Base::wrapData(isolate, getter, setter, data)), + &PropertyCallback::invokeGetter, + RTEST(setter) ? &PropertyCallback::invokeSetter : 0, + v8::MaybeLocal(PropertyCallback::wrapData(isolate, getter, setter, data)), Enum(settings, v8::DEFAULT), Enum(attribute, v8::None) )); diff --git a/ext/v8/property-callback-info.h b/ext/v8/property-callback-info.h index a7eff11e..726a7d25 100644 --- a/ext/v8/property-callback-info.h +++ b/ext/v8/property-callback-info.h @@ -1,6 +1,6 @@ // -*- mode: c++ -*- -#ifndef RR_PROPERTY_CALLBACK_H -#define RR_PROPERTY_CALLBACK_H +#ifndef RR_PROPERTY_CALLBACK_INFO_H +#define RR_PROPERTY_CALLBACK_INFO_H namespace rr { @@ -16,30 +16,6 @@ namespace rr { inline Base(VALUE self) : Wrapper>(self) {} - /** - * Package up the callback data for this object so that it can - * invoke Ruby callables. - * - * Each accessor can have one `v8::Value` associated with it - * that is passed to the `SetAccessor` method. To support this - * same API from ruby, we take the passed data constructor *and* - * the callbacks and store them *both* in a single `v8::Object` - * which we use for the C++ level callback data. - */ - static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { - v8::Local holder = v8::Object::New(isolate); - - v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); - v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - - holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); - holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); - holder->SetHiddenValue(data_key, rr::Value(r_data)); - - return holder; - } - static VALUE This(VALUE self) { Base info(self); Locker lock(info->GetIsolate()); @@ -51,11 +27,7 @@ namespace rr { Isolate isolate(info->GetIsolate()); Locker lock(isolate); - v8::Local holder = v8::Local::Cast(info->Data()); - v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); - v8::Local data(holder->GetHiddenValue(data_key)); - - return rr::Value::handleToRubyObject(info->GetIsolate(), data); + return PropertyCallback::unwrapData(isolate, info->Data()) ; } static VALUE GetIsolate(VALUE self) { @@ -73,32 +45,6 @@ namespace rr { inline Value(VALUE self) : Base(self) {} - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::AccessorNameGetterCallback` API. - */ - static void invoke(v8::Local property, const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); - - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - - VALUE rb_property; - if (property->IsSymbol()) { - rb_property = Symbol(isolate, v8::Local::Cast(property)); - } else { - rb_property = String(isolate, property->ToString()); - } - - Unlocker unlock(info.GetIsolate()); - rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)Value(info)); - } - static VALUE GetReturnValue(VALUE self) { Value info(self); Locker lock(info->GetIsolate()); @@ -124,38 +70,6 @@ namespace rr { inline Void(VALUE self) : Base(self) {} - /** - * Call the Ruby code associated with this callback. - * - * Unpack the Ruby code, and the callback data from the C++ - * callback data, and then invoke that code. - * - * Note: This function implements the `v8::AccessorNameSetterCallback` API. - */ - static void invoke(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - - v8::Local holder = v8::Local::Cast(info.Data()); - v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); - - VALUE code(External::unwrap(holder->GetHiddenValue(callback_key))); - - VALUE rb_property; - if (property->IsSymbol()) { - rb_property = Symbol(isolate, v8::Local::Cast(property)); - } else { - rb_property = String(isolate, property->ToString()); - } - - Unlocker unlock(info.GetIsolate()); - rb_funcall( - code, rb_intern("call"), 3, - rb_property, - (VALUE)rr::Value::handleToRubyObject(isolate, value), - (VALUE)Void(info) - ); - } - static VALUE GetReturnValue(VALUE self) { Value info(self); Locker lock(info->GetIsolate()); @@ -187,4 +101,4 @@ namespace rr { } -#endif /* RR_PROPERTY_CALLBACK_H */ +#endif /* RR_PROPERTY_CALLBACK_INFO_H */ diff --git a/ext/v8/property-callback.cc b/ext/v8/property-callback.cc new file mode 100644 index 00000000..59f1ccfe --- /dev/null +++ b/ext/v8/property-callback.cc @@ -0,0 +1,77 @@ +#include "rr.h" + +namespace rr { + + v8::Local PropertyCallback::wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data) { + v8::Local holder = v8::Object::New(isolate); + + v8::Local getter_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + v8::Local setter_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + holder->SetHiddenValue(getter_key, External::wrap(isolate, r_getter)); + holder->SetHiddenValue(setter_key, External::wrap(isolate, r_setter)); + holder->SetHiddenValue(data_key, rr::Value(r_data)); + + return holder; + } + + VALUE PropertyCallback::unwrapData(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local data_key = v8::String::NewFromUtf8(isolate, "rr::data"); + + return Value(isolate, holder->GetHiddenValue(data_key)); + } + + VALUE PropertyCallback::unwrapGetter(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::getter"); + + return External::unwrap(holder->GetHiddenValue(callback_key)); + } + + VALUE PropertyCallback::unwrapSetter(v8::Isolate* isolate, v8::Local value) { + v8::Local holder = v8::Local::Cast(value); + v8::Local callback_key = v8::String::NewFromUtf8(isolate, "rr::setter"); + + return External::unwrap(holder->GetHiddenValue(callback_key)); + } + + void PropertyCallback::invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + VALUE code(unwrapGetter(isolate, info.Data())); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall(code, rb_intern("call"), 2, rb_property, (VALUE)PropertyCallbackInfo::Value(info)); + } + + void PropertyCallback::invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + + VALUE code(unwrapSetter(isolate, info.Data())); + + VALUE rb_property; + if (property->IsSymbol()) { + rb_property = Symbol(isolate, v8::Local::Cast(property)); + } else { + rb_property = String(isolate, property->ToString()); + } + + Unlocker unlock(info.GetIsolate()); + rb_funcall( + code, rb_intern("call"), 3, + rb_property, + (VALUE)Value(isolate, value), + (VALUE)PropertyCallbackInfo::Void(info) + ); + } + +} diff --git a/ext/v8/property-callback.h b/ext/v8/property-callback.h new file mode 100644 index 00000000..92d42497 --- /dev/null +++ b/ext/v8/property-callback.h @@ -0,0 +1,61 @@ +// -*- mode: c++ -*- +#ifndef RR_PROPERTY_CALLBACK_H +#define RR_PROPERTY_CALLBACK_H + +namespace rr { + + class PropertyCallback { + public: + + /** + * Package up the callback data for this object so that it can + * invoke Ruby callables. + * + * Each accessor can have one `v8::Value` associated with it + * that is passed to the `SetAccessor` method. To support this + * same API from ruby, we take the passed data constructor *and* + * the callbacks and store them *both* in a single `v8::Object` + * which we use for the C++ level callback data. + */ + static v8::Local wrapData(v8::Isolate* isolate, VALUE r_getter, VALUE r_setter, VALUE r_data); + + /** + * Get the data packaged in the object. + */ + static VALUE unwrapData(v8::Isolate* isolate, v8::Local holder); + + /** + * Get the getter packaged in the object. + */ + static VALUE unwrapGetter(v8::Isolate* isolate, v8::Local holder); + + /** + * Get the setter packaged in the object. + */ + static VALUE unwrapSetter(v8::Isolate* isolate, v8::Local holder); + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameGetterCallback` API. + */ + static void invokeGetter(v8::Local property, const v8::PropertyCallbackInfo& info); + + /** + * Call the Ruby code associated with this callback. + * + * Unpack the Ruby code, and the callback data from the C++ + * callback data, and then invoke that code. + * + * Note: This function implements the `v8::AccessorNameSetterCallback` API. + */ + static void invokeSetter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info); + + }; + +} + +#endif /* RR_PROPERTY_CALLBACK_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index d7389c6a..2ef7fa44 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -53,6 +53,7 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "return-value.h" +#include "property-callback.h" #include "property-callback-info.h" #include "array.h" From e1c8f9ef5ffabe70c6e5306c3733b29c8c68094e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 22 Jul 2015 00:06:15 -0500 Subject: [PATCH 078/105] compile scripts via maybe apis This adds the ability to compile and run an script, passing in the ScriptOrigin so that it can track file information. --- ext/v8/maybe.h | 10 ++++++++++ ext/v8/object.cc | 7 ++++--- ext/v8/script-origin.h | 12 ++++++++++++ ext/v8/script.cc | 28 ++++++++++++++++++---------- ext/v8/script.h | 1 + ext/v8/value.h | 17 +---------------- spec/c/script_spec.rb | 19 +++++++++++-------- 7 files changed, 57 insertions(+), 37 deletions(-) diff --git a/ext/v8/maybe.h b/ext/v8/maybe.h index 31eb2f08..3837ee48 100644 --- a/ext/v8/maybe.h +++ b/ext/v8/maybe.h @@ -66,6 +66,16 @@ namespace rr { // the underlying value. VALUE object; }; + + template + class MaybeLocal : public Maybe { + public: + MaybeLocal(v8::Isolate* isolate, v8::MaybeLocal maybe) { + if (!maybe.IsEmpty()) { + just(T(isolate, maybe.ToLocalChecked())); + } + } + }; } #endif /* RR_MAYBE_H */ diff --git a/ext/v8/object.cc b/ext/v8/object.cc index ec32a0d8..a1f9632a 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -62,10 +62,11 @@ namespace rr { return Function(isolate, handle.As()); } + if (handle->IsArray()) { + return Array(isolate, handle.As()); + } + // TODO: Enable this when the methods are implemented - // if (handle->IsArray()) { - // return Array((v8::Handle)v8::Array::Cast(*handle)); - // } // // if (handle->IsDate()) { // // return Date(handle); diff --git a/ext/v8/script-origin.h b/ext/v8/script-origin.h index e341cb08..5f2ac003 100644 --- a/ext/v8/script-origin.h +++ b/ext/v8/script-origin.h @@ -57,6 +57,18 @@ namespace rr { Bool(origin.Options().IsOpaque()))) { } + inline operator v8::ScriptOrigin() { + return v8::ScriptOrigin( + Value(container->name), + Integer(container->line_offset), + Integer(container->column_offset), + Boolean(container->is_shared_cross_origin), + Integer(container->script_id), + Boolean(container->is_embedder_debug_script), + Value(container->source_map_url), + Boolean(container->is_opaque)); + } + static void mark(Container* container) { rb_gc_mark(container->name); rb_gc_mark(container->line_offset); diff --git a/ext/v8/script.cc b/ext/v8/script.cc index 4077adb8..fbfd5c4d 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -23,20 +23,28 @@ namespace rr { VALUE Script::Compile(int argc, VALUE argv[], VALUE self) { - VALUE source, rb_context, origin; - rb_scan_args(argc, argv, "21", &source, &rb_context, &origin); - - Context context(rb_context); - Locker lock(context.getIsolate()); - - // TODO: ScriptOrigin - return Script(context.getIsolate(), v8::Script::Compile(String(source))); + VALUE r_source, r_context, r_origin; + rb_scan_args(argc, argv, "21", &r_context, &r_source, &r_origin); + + Context context(r_context); + Isolate isolate(context.getIsolate()); + Locker lock(isolate); + + + if (RTEST(r_origin)) { + v8::ScriptOrigin origin = ScriptOrigin(r_origin); + v8::MaybeLocal script = v8::Script::Compile(context, String(r_source), &origin); + return Script::Maybe(isolate, script); + } + else { + return Script::Maybe(isolate, v8::Script::Compile(context, String(r_source))); + } } VALUE Script::Run(VALUE self, VALUE rb_context) { Context context(rb_context); - Locker lock(context->GetIsolate()); + Locker lock(context); - return Value(context->GetIsolate(), Script(self)->Run()); + return Value::Maybe(context, Script(self)->Run(context)); } } diff --git a/ext/v8/script.h b/ext/v8/script.h index 860214e7..b8cf46d1 100644 --- a/ext/v8/script.h +++ b/ext/v8/script.h @@ -15,6 +15,7 @@ namespace rr { inline Script(VALUE value) : Ref(value) {} inline Script(v8::Isolate* isolate, v8::Handle script) : Ref(isolate, script) {} + typedef MaybeLocal Maybe; }; } #endif diff --git a/ext/v8/value.h b/ext/v8/value.h index 16933362..8f353e15 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -6,22 +6,7 @@ namespace rr { class Value : public Ref { public: - /** - * A conversion class for down-thunking a MaybeLocal - * and returning it to Ruby as a `V8::C::Maybe`. If there is a - * value present, then run it through the value conversion to get - * the most specific subclass of Value: - * - * return Value::Maybe(object->Get(cxt, key)); - */ - class Maybe : public rr::Maybe { - public: - Maybe(v8::Isolate* isolate, v8::MaybeLocal maybe) { - if (!maybe.IsEmpty()) { - just(Value(isolate, maybe.ToLocalChecked())); - } - } - }; + typedef MaybeLocal Maybe; static void Init(); diff --git a/spec/c/script_spec.rb b/spec/c/script_spec.rb index d7cc61a5..0609f3cf 100644 --- a/spec/c/script_spec.rb +++ b/spec/c/script_spec.rb @@ -3,14 +3,17 @@ describe V8::C::Script do requires_v8_context - # TODO - # it 'can run a script and return a polymorphic result' do - # source = V8::C::String::New("(new Array())") - # script = V8::C::Script::New(source) - # - # result = script.Run() - # expect(result).to be_an V8::C::Array - # end + it 'can run a script and return a polymorphic result' do + source = V8::C::String::NewFromUtf8(@isolate, "(new Array())") + name = V8::C::String::NewFromUtf8(@isolate, "a/file.js") + origin = V8::C::ScriptOrigin.new(name) + script = V8::C::Script::Compile(@ctx, source, origin) + expect(script.IsJust()).to be true + + result = script.FromJust().Run(@ctx) + expect(result.IsJust()).to be true + expect(result.FromJust()).to be_an V8::C::Array + end # TODO # it 'can accept precompiled script data' do From 46713f800590a81ee5ba8bba9cf7996824ca03f9 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 22 Jul 2015 00:13:42 -0500 Subject: [PATCH 079/105] adjust function_spec to use maybe apis --- spec/c/function_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index f4b05d33..25ad188f 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -90,10 +90,9 @@ def call(info) def run(source) source = V8::C::String.NewFromUtf8(@isolate, source.to_s) - filename = V8::C::String.NewFromUtf8(@isolate, "") - script = V8::C::Script.Compile(source, filename) - result = script.Run(@ctx) - - result.kind_of?(V8::C::String) ? result.Utf8Value : result + script = V8::C::Script.Compile(@ctx, source) + result = script.FromJust().Run(@ctx) + checked = result.FromJust() + checked.kind_of?(V8::C::String) ? checked.Utf8Value : checked end end From 38caf7ec27b98395acafc9a451ea05c52a671188 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Wed, 22 Jul 2015 20:06:51 +0000 Subject: [PATCH 080/105] Move the set methods to the base class of ReturnValue --- ext/v8/return-value.h | 57 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/ext/v8/return-value.h b/ext/v8/return-value.h index 6929b2d4..ff22c366 100644 --- a/ext/v8/return-value.h +++ b/ext/v8/return-value.h @@ -7,83 +7,82 @@ namespace rr { class ReturnValue { public: - template - class Base : public Wrapper> { + template + class Base : public Wrapper> { public: - Base(v8::ReturnValue value) : Wrapper>(value) {} - Base(VALUE self) : Wrapper>(self) {} - - static VALUE GetIsolate(VALUE self) { - Base ret(self); - return Isolate(ret->GetIsolate()); - } - - }; - - class Value : public Base { - public: - - Value(v8::ReturnValue value) : Base(value) {} - Value(VALUE self) : Base(self) {} + Base(v8::ReturnValue value) : Wrapper>(value) {} + Base(VALUE self) : Wrapper>(self) {} static VALUE Set(VALUE self, VALUE handle) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); - v8::Local value((rr::Value(handle))); + v8::Local value((RRType(handle))); ret->Set(value); return Qnil; } static VALUE Set_bool(VALUE self, VALUE value) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set((bool)Bool(value)); return Qnil; } static VALUE Set_double(VALUE self, VALUE value) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2DBL(value)); return Qnil; } static VALUE Set_int32_t(VALUE self, VALUE i) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2INT(i)); return Qnil; } static VALUE Set_uint32_t(VALUE self, VALUE i) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2UINT(i)); return Qnil; } static VALUE SetNull(VALUE self) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetNull(); return Qnil; } static VALUE SetUndefined(VALUE self) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetUndefined(); return Qnil; } static VALUE SetEmptyString(VALUE self) { - Value ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetEmptyString(); return Qnil; } + static VALUE GetIsolate(VALUE self) { + Base ret(self); + return Isolate(ret->GetIsolate()); + } + + }; + + class Value : public Base { + public: + Value(v8::ReturnValue value) : Base(value) {} + Value(VALUE self) : Base(self) {} + static inline void Init() { ClassBuilder("Value", ReturnValue::Class, ReturnValue::Class). defineMethod("Set", &Set). @@ -100,11 +99,11 @@ namespace rr { }; - class Void : public Base { + class Void : public Base { public: - Void(v8::ReturnValue value) : Base(value) {} - Void(VALUE self) : Base(self) {} + Void(v8::ReturnValue value) : Base(value) {} + Void(VALUE self) : Base(self) {} static inline void Init() { ClassBuilder("Void", ReturnValue::Class, ReturnValue::Class). From 8b66c3443e0bfe480f79063758e628825a02c58f Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 23 Jul 2015 14:04:48 -0400 Subject: [PATCH 081/105] remove redundant template parameters --- ext/v8/return-value.h | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ext/v8/return-value.h b/ext/v8/return-value.h index ff22c366..f2b9f843 100644 --- a/ext/v8/return-value.h +++ b/ext/v8/return-value.h @@ -15,7 +15,7 @@ namespace rr { Base(VALUE self) : Wrapper>(self) {} static VALUE Set(VALUE self, VALUE handle) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); v8::Local value((RRType(handle))); ret->Set(value); @@ -23,59 +23,58 @@ namespace rr { } static VALUE Set_bool(VALUE self, VALUE value) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set((bool)Bool(value)); return Qnil; } static VALUE Set_double(VALUE self, VALUE value) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2DBL(value)); return Qnil; } static VALUE Set_int32_t(VALUE self, VALUE i) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2INT(i)); return Qnil; } static VALUE Set_uint32_t(VALUE self, VALUE i) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->Set(NUM2UINT(i)); return Qnil; } static VALUE SetNull(VALUE self) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetNull(); return Qnil; } static VALUE SetUndefined(VALUE self) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetUndefined(); return Qnil; } static VALUE SetEmptyString(VALUE self) { - Base ret(self); + Base ret(self); Locker lock(ret->GetIsolate()); ret->SetEmptyString(); return Qnil; } static VALUE GetIsolate(VALUE self) { - Base ret(self); + Base ret(self); return Isolate(ret->GetIsolate()); } - }; class Value : public Base { @@ -96,7 +95,6 @@ namespace rr { defineMethod("GetIsolate", &GetIsolate). store(&Class); } - }; class Void : public Base { @@ -110,7 +108,6 @@ namespace rr { defineMethod("GetIsolate", &GetIsolate). store(&Class); } - }; static VALUE Class; @@ -122,7 +119,6 @@ namespace rr { Value::Init(); Void::Init(); } - }; } #endif /* RR_RETURN_VALUE_H */ From 78ac679a7a9e5f1900ccd2e4742312058b06b9f8 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 23 Jul 2015 17:00:54 -0400 Subject: [PATCH 082/105] add back simple evaluation specs --- .travis.yml | 1 + lib/v8.rb | 51 +- lib/v8/context.rb | 82 ++- lib/v8/conversion.rb | 60 +- lib/v8/conversion/fundamental.rb | 6 +- lib/v8/conversion/primitive.rb | 7 - lib/v8/isolate.rb | 9 + spec/v8/context_spec.rb | 1043 +++++++++++++++++++++++++++++- 8 files changed, 1150 insertions(+), 109 deletions(-) delete mode 100644 lib/v8/conversion/primitive.rb create mode 100644 lib/v8/isolate.rb diff --git a/.travis.yml b/.travis.yml index d27e5e15..e0a9086b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ before_install: script: - bundle exec rake compile - bundle exec rspec spec/c + - bundle exec rspec spec/v8/context_spec.rb sudo: false addons: apt: diff --git a/lib/v8.rb b/lib/v8.rb index 892550a4..320afe9d 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -1,30 +1,31 @@ require "v8/version" -require 'v8/weak' require 'v8/init' -require 'v8/error' -require 'v8/stack' +require 'v8/isolate' +require 'v8/context' +# require 'v8/error' +# require 'v8/stack' require 'v8/conversion/fundamental' -require 'v8/conversion/indentity' -require 'v8/conversion/reference' -require 'v8/conversion/primitive' -require 'v8/conversion/code' -require 'v8/conversion/class' -require 'v8/conversion/object' -require 'v8/conversion/time' -require 'v8/conversion/hash' -require 'v8/conversion/array' -require 'v8/conversion/proc' -require 'v8/conversion/method' -require 'v8/conversion/symbol' -require 'v8/conversion/string' -require 'v8/conversion/fixnum' +# require 'v8/conversion/indentity' +# require 'v8/conversion/reference' +# require 'v8/conversion/primitive' +# require 'v8/conversion/code' +# require 'v8/conversion/class' +# require 'v8/conversion/object' +# require 'v8/conversion/time' +# require 'v8/conversion/hash' +# require 'v8/conversion/array' +# require 'v8/conversion/proc' +# require 'v8/conversion/method' +# require 'v8/conversion/symbol' +# require 'v8/conversion/string' +# require 'v8/conversion/fixnum' require 'v8/conversion' -require 'v8/access/names' -require 'v8/access/indices' -require 'v8/access/invocation' -require 'v8/access' -require 'v8/context' -require 'v8/object' -require 'v8/array' -require 'v8/function' +# require 'v8/access/names' +# require 'v8/access/indices' +# require 'v8/access/invocation' +# require 'v8/access' +# require 'v8/context' +# require 'v8/object' +# require 'v8/array' +# require 'v8/function' diff --git a/lib/v8/context.rb b/lib/v8/context.rb index 05f362f0..0267b176 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -26,7 +26,7 @@ module V8 # cxt.eval('num') # => 5 # end class Context - include V8::Error::Try + # include V8::Error::Try # @!attribute [r] conversion # @return [V8::Conversion] conversion behavior for this context @@ -44,6 +44,10 @@ class Context # @return [Number] maximum execution time in milliseconds for scripts executed in this context attr_reader :timeout + # @!attribute [r] isolate + # @return [V8::Isolate] + attr_reader :isolate + # Creates a new context. # # If passed the `:with` option, that object will be used as @@ -62,20 +66,24 @@ class Context # * :with scope serves as the global scope of the new context # @yield [V8::Context] the newly created context def initialize(options = {}) - @conversion = Conversion.new - @access = Access.new - @timeout = options[:timeout] - if global = options[:with] - Context.new.enter do - global_template = global.class.to_template.InstanceTemplate() - @native = V8::C::Context::New(nil, global_template) - end - enter {link global, @native.Global()} - else - V8::C::Locker() do - @native = V8::C::Context::New() - end + @isolate = options[:isolate] || V8::Isolate.new + V8::C::HandleScope(@isolate.native) do + @native = V8::C::Context::New(@isolate.native) end + @conversion = Conversion.new + # @access = Access.new + # @timeout = options[:timeout] + # if global = options[:with] + # Context.new.enter do + # global_template = global.class.to_template.InstanceTemplate() + # @native = V8::C::Context::New(nil, global_template) + # end + # enter {link global, @native.Global()} + # else + # V8::C::Locker() do + # @native = V8::C::Context::New() + # end + # end yield self if block_given? end @@ -92,12 +100,11 @@ def eval(source, filename = '', line = 1) source = source.read end enter do - script = try { V8::C::Script::New(source.to_s, filename.to_s) } - if @timeout - to_ruby try {script.RunWithTimeout(@timeout)} - else - to_ruby try {script.Run()} - end + src = V8::C::String::NewFromUtf8(@isolate.native, source.to_s) + filename = V8::C::String::NewFromUtf8(@isolate.native, filename.to_s) + origin = V8::C::ScriptOrigin.new(filename); + script = V8::C::Script::Compile(@native, src, origin).FromJust() + to_ruby script.Run(@native).FromJust() end end @@ -107,7 +114,7 @@ def eval(source, filename = '', line = 1) # @return [Object] value the value at `key` def [](key) enter do - to_ruby(@native.Global().Get(to_v8(key))) + to_ruby(@native.Global().Get(@cnative, to_v8(key)).FromJust()) end end @@ -117,26 +124,11 @@ def [](key) # @param [Object] value the value to bind def []=(key, value) enter do - @native.Global().Set(to_v8(key), to_v8(value)) + @native.Global().Set(@native, to_v8(key), to_v8(value)) end return value end - # Destroy this context and release any internal references it may - # contain to embedded Ruby objects. - # - # A disposed context may never again be used for anything, and all - # objects created with it will become unusable. - def dispose - return unless @native - @native.Dispose() - @native = nil - V8::C::V8::ContextDisposedNotification() - def self.enter - fail "cannot enter a context which has already been disposed" - end - end - # Returns this context's global object. This will be a `V8::Object` # if no scope was provided or just an `Object` if a Ruby object # is serving as the global scope. @@ -165,7 +157,7 @@ def to_ruby(v8_object) # @return [V8::C::Object] to pass to V8 # @see V8::Conversion for customizing and extending this mechanism def to_v8(ruby_object) - @conversion.to_v8(ruby_object) + @conversion.to_v8(self, ruby_object) end # Marks a Ruby object and a v8 C++ Object as being the same. In other @@ -241,14 +233,12 @@ def self.current=(context) def lock_scope_and_enter current = Context.current Context.current = self - V8::C::Locker() do - V8::C::HandleScope() do - begin - @native.Enter() - yield if block_given? - ensure - @native.Exit() - end + V8::C::HandleScope(@isolate.native) do + begin + @native.Enter() + yield if block_given? + ensure + @native.Exit() end end ensure diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 3c2518bd..2cc202fe 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -1,36 +1,62 @@ - class V8::Conversion include Fundamental - include Identity + # include Identity def to_ruby(v8_object) super v8_object end - def to_v8(ruby_object) - super ruby_object + def to_v8(context, ruby_object) + super context, ruby_object end end -for type in [TrueClass, FalseClass, NilClass, Float] do - type.class_eval do - include V8::Conversion::Primitive +module V8::C + class String + alias_method :to_ruby, :Utf8Value end -end -for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do - type.class_eval do - include V8::Conversion.const_get(type.name) + class Number + alias_method :to_ruby, :Value end -end -class UnboundMethod - include V8::Conversion::Method + class Undefined + def to_ruby + nil + end + end + + class Boolean + def to_ruby + IsTrue() + end + end end -for type in [:Object, :String, :Date] do - V8::C::const_get(type).class_eval do - include V8::Conversion::const_get("Native#{type}") +class String + def to_v8(context) + V8::C::String::NewFromUtf8(context.isolate.native, self) end end +# for type in [TrueClass, FalseClass, NilClass, Float] do +# type.class_eval do +# include V8::Conversion::Primitive +# end +# end + +# for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do +# type.class_eval do +# include V8::Conversion.const_get(type.name) +# end +# end + +# class UnboundMethod +# include V8::Conversion::Method +# end + +# for type in [:Object, :String, :Date] do +# V8::C::const_get(type).class_eval do +# include V8::Conversion::const_get("Native#{type}") +# end +# end diff --git a/lib/v8/conversion/fundamental.rb b/lib/v8/conversion/fundamental.rb index a1ec5dae..66fd272e 100644 --- a/lib/v8/conversion/fundamental.rb +++ b/lib/v8/conversion/fundamental.rb @@ -4,8 +4,8 @@ def to_ruby(v8_object) v8_object.to_ruby end - def to_v8(ruby_object) - ruby_object.to_v8 + def to_v8(context, ruby_object) + ruby_object.to_v8 context end end -end \ No newline at end of file +end diff --git a/lib/v8/conversion/primitive.rb b/lib/v8/conversion/primitive.rb deleted file mode 100644 index 8602d9ae..00000000 --- a/lib/v8/conversion/primitive.rb +++ /dev/null @@ -1,7 +0,0 @@ -class V8::Conversion - module Primitive - def to_v8 - return self - end - end -end \ No newline at end of file diff --git a/lib/v8/isolate.rb b/lib/v8/isolate.rb new file mode 100644 index 00000000..b18a5db9 --- /dev/null +++ b/lib/v8/isolate.rb @@ -0,0 +1,9 @@ +module V8 + class Isolate + attr_reader :native + + def initialize() + @native = V8::C::Isolate::New() + end + end +end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 850dcf0a..79dbd90b 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -1,19 +1,1040 @@ +# -*- encoding: utf-8 -*- require 'spec_helper' -describe V8::Context do - it "can be disposed of" do - cxt = V8::Context.new - cxt.enter do - cxt['object'] = V8::Object.new +describe "V8::Context" do + + describe "Basic Evaluation", :compat => '0.1.0' do + + before do + @cxt = V8::Context.new + end + + it "can evaluate some javascript" do + @cxt.eval("5 + 3").should == 8 + end + + it "can pass back null to ruby" do + @cxt.eval("null").should be_nil + end + + it "can pass back undefined to ruby" do + @cxt.eval("this.undefined").should be_nil + end + + it "can pass the empty string back to ruby" do + @cxt.eval("''").should == "" + end + + it "can pass doubles back to ruby" do + @cxt.eval("2.5").should == 2.5 + end + + it "can pass fixed numbers back to ruby" do + @cxt.eval("1").should == 1 + end + + it "can pass boolean values back to ruby" do + @cxt.eval("true").should be(true) + @cxt.eval("false").should be(false) + end + + it "treats nil and the empty string as the same thing when it comes to eval" do + @cxt.eval(nil).should == @cxt.eval('') + end + + it "can pass back strings to ruby" do + @cxt['foo'] = "Hello World" + @cxt.eval("foo").should == "Hello World" + end + + it "can pass back very long strings to ruby" do + lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis faucibus, diam vel pellentesque aliquet, nisl sapien molestie eros, vitae vehicula libero massa vel neque. Phasellus tempor pharetra ipsum vel venenatis. Quisque vitae nisl vitae quam mattis pellentesque et in sapien. Sed at lectus quis eros pharetra feugiat non ac neque. Vivamus lacus eros, feugiat at volutpat at, viverra id nisl. Vivamus ac dolor eleifend libero venenatis pharetra ut iaculis arcu. Donec neque nibh, vehicula non porta a, consectetur eu erat. Sed eleifend, metus vel euismod placerat, lectus lectus sollicitudin nisl, ac elementum sem quam nec dolor. In hac habitasse platea dictumst. Proin vitae suscipit orci. Suspendisse a ipsum vel lorem tempus scelerisque et vitae neque. Proin sodales, tellus sit amet consequat cursus, odio massa ultricies enim, eu fermentum velit lectus in lacus. Quisque eu porttitor diam. Nunc felis purus, facilisis non tristique ac, pulvinar nec nulla. Duis dolor risus, egestas nec tristique ac, ullamcorper cras amet." + @cxt.eval("'#{lorem}'").should == lorem + end + + it "correctly handles translating strings that have non-standard characters" do + @cxt['utf8'] = "Σὲ γνωρίζω ἀπὸ τὴν κόψη" + @cxt['utf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη" + @cxt.eval('var nativeUtf8 = "Σὲ γνωρίζω ἀπὸ τὴν κόψη"') + @cxt['nativeUtf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη" end - cxt.dispose() - lambda {cxt.eval('1 + 1')}.should raise_error - lambda {cxt['object']}.should raise_error + # xit "translates JavaScript dates properly into ruby Time objects" do + # now = Time.now + # @cxt.eval('new Date()').tap do |time| + # time.should be_kind_of(Time) + # time.year.should == now.year + # time.day.should == now.day + # time.month.should == now.month + # time.min.should == now.min + # time.sec.should == now.sec + # end + # end + + # xit "can pass objects back to ruby" do + # @cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object| + # object.should_not be_nil + # object['foo'].should == 'bar' + # object['baz'].should == 'bang' + # object['5'].should == 5 + # object['embedded'].tap do |embedded| + # embedded.should_not be_nil + # embedded['badda'].should == 'bing' + # end + # end + # end + + # xit "can pass int properties to ruby", :compat => '0.2.1' do + # @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object| + # object[ 4 ].should == '4' + # object['4'].should == '4' + # object[ 5 ].should == 5 + # object['5'].should == 5 + # object['6'].should == true + # end + # end + + # xit "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do + # Object.new.tap do |o| + # @cxt['get'] = lambda {o} + # @cxt.eval('get()').should be(o) + # end + # end + + # xit "always returns the same ruby object for a single javascript object" do + # obj = @cxt.eval('obj = {}') + # obj.should be(@cxt['obj']) + # @cxt.eval('obj').should be(@cxt['obj']) + # @cxt['obj'].should be(@cxt['obj']) + # end + + # xit "converts arrays to javascript" do + # @cxt['a'] = [1,2,4] + # @cxt.eval('var sum = 0;for (var i = 0; i < a.length; i++) {sum += a[i]}; sum').should == 7 + # @cxt.eval('[1,2,3]').tap do |a| + # a.length.should == 3 + # a.to_a.should == [1,2,3] + # end + # end + + # xit "can iterate over arrays" do + # @cxt['a'] = @cxt.eval('[{num: 1},{num:2},{num:3},{num: 4}]') + # a = @cxt['a'] + # a.inject(0) do |sum, item| + # sum + item['num'] + # end.should == 10 + + # end + + # xit "converts ruby hashes to javascript objects" do + # @cxt['h'] = {:foo => 'bar', :baz => 'bang', :bar => {'hello' => 'world'}} + # @cxt['h']['foo'].should == 'bar' + # @cxt['h']['baz'].should == 'bang' + # @cxt['h']['bar']['hello'].should == 'world' + # end end - it "can be disposed of any number of times" do - cxt = V8::Context.new - 10.times {cxt.dispose()} +# describe "Calling Ruby Code From Within Javascript", :compat => '0.1.0' do + +# before(:each) do +# @class = Class.new +# @instance = @class.new +# @cxt = V8::Context.new +# @cxt['o'] = @instance +# end + +# xit "can embed a closure into a context and call it" do +# @cxt["say"] = lambda { |*args| args[-2] * args[-1] } +# @cxt.eval("say('Hello', 2)").should == "HelloHello" +# end + +# xit "recognizes the same closure embedded into the same context as the same function object" do +# @cxt['say'] = @cxt['declare'] = lambda { |*args| args } +# @cxt.eval('say == declare').should be(true) +# @cxt.eval('say === declare').should be(true) +# end + +# xit "translates ruby Array to Javascript Array" do +# class_eval do +# def ruby_array +# [] +# end +# end +# evaljs('o.ruby_array instanceof Array').should == true +# end + +# xit "translates ruby Time to Javascript Date" do +# now = Time.now +# class_eval do +# def ruby_time +# @now +# end +# end +# @instance.instance_variable_set(:@now, now) +# evaljs('o.ruby_time instanceof Date').should == true +# evaljs('o.ruby_time.getFullYear()').should == now.year +# evaljs('o.ruby_time.getMonth() + 1').should == now.month +# evaljs('o.ruby_time.getDate()').should == now.day +# evaljs('o.ruby_time.getMinutes()').should == now.min +# evaljs('o.ruby_time.getSeconds()').should == now.sec +# end + +# xit "translates ruby true to Javascript true" do +# class_eval do +# def bool +# true +# end +# end +# evaljs('o.bool === true').should == true +# end + +# xit "translates ruby false to Javascript false" do +# class_eval do +# def bool +# false +# end +# end +# evaljs('o.bool === false').should == true +# end + +# xit "can embed a ruby object into a context and call its methods" do +# class_eval do +# def say_hello(to) +# "Hello #{to}!" +# end +# end +# evaljs('o.say_hello("Gracie")').should == "Hello Gracie!" +# end + +# xit "recognizes object method as the same function" do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt['obj'] = @class.new +# @cxt.eval('obj.foo === obj.foo').should be(true) +# end + +# xit "recognizes functions on objects of the same class being equal", :compat => '0.2.0' do +# @class.class_eval do +# def foo(*args); args; end +# self +# end +# @cxt['one'] = @class.new +# @cxt['two'] = @class.new +# @cxt.eval('one.foo == two.foo').should be(true) +# end + +# xit "recognizes functions on objects of the same class being the same", :compat => '0.2.1' do +# @class.class_eval do +# def foo(*args); args; end +# self +# end +# @cxt['one'] = @class.new +# @cxt['two'] = @class.new +# @cxt.eval('one.foo === two.foo').should be(true) +# #TODO: nice to have, but a bit tricky. +# # @cxt.eval('one.foo === one.constructor.prototype.foo').should be(true) +# end + +# xit "fails without the correct context passed to an object function", :compat => '0.2.0' do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt['obj'] = @class.new +# @cxt.eval('var foo = obj.foo;') +# lambda { @cxt.eval('foo()') }.should raise_error +# end + +# xit "can call a bound ruby method" do +# five = class_eval do +# def initialize(lhs) +# @lhs = lhs +# end +# def times(rhs) +# @lhs * rhs +# end +# new(5) +# end + +# @cxt['timesfive'] = five.method(:times) +# @cxt.eval('timesfive(3)').should == 15 +# end + +# xit "reports object's type being object", :compat => '0.1.1' do +# @cxt.eval('typeof( o )').should == 'object' +# end + +# xit "reports object method type as function", :compat => '0.1.1' do +# @class.class_eval do +# def foo(*args); args; end +# end +# @cxt.eval('typeof o.foo ').should == 'function' +# end + +# xit "reports wrapped class as of type function", :compat => '0.1.1' do +# @cxt['RObject'] = Object +# @cxt.eval('typeof(RObject)').should == 'function' +# end + +# xit "truncates lambda arguments passed in to match the arity of the function", :compat => '0.4.2' do +# @cxt['testing'] = lambda { |arg| arg } +# lambda { +# @cxt.eval('testing(1,2,3)') +# }.should_not raise_error + +# @cxt['testing'] = lambda { } +# lambda { +# @cxt.eval('testing(1,2,3)') +# }.should_not raise_error +# end + +# xit "truncates method arguments passed in to match the arity of the function", :compat => '0.4.3' do +# @instance.instance_eval do +# def foo(arg); arg; end +# def bar(*args); args; end +# end + +# lambda { +# @cxt.eval('o.foo(1,2,3)').should == 1 +# }.should_not raise_error +# lambda { +# @cxt.eval('o.bar(1,2,3)').to_a.should == [ 1, 2, 3 ] +# }.should_not raise_error +# end + +# xit "fills in missing arguments with nils to match the arity of the function", :compat => '0.4.3' do +# @instance.instance_eval do +# def foo(a1, a2, a3); [a1, a2, a3]; end +# end + +# lambda { +# @cxt.eval('o.foo(1)').to_a.should == [ 1, nil, nil ] +# @cxt.eval('o.foo()').to_a.should == [ nil, nil, nil ] +# }.should_not raise_error +# end + +# describe "Default Ruby Object Access" do + +# xit "can call public locally defined ruby methods" do +# class_eval do +# def voo(str) +# "voo#{str}" +# end +# end +# evaljs("o.voo('doo')").should == "voodoo" +# end + +# xit "reports ruby methods that do not exist as undefined" do +# V8::Context.new(:with => Object.new) do |cxt| +# cxt.eval('this.foobar').should be nil +# end +# end + +# xit "can access methods defined in an object's superclass" do +# o = Class.new.class_eval do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# Class.new(self) +# end.new +# V8::Context.new(:with => o) do |cxt| +# cxt.eval('this.foo').should == 'FOO' +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows a ruby object to intercept property access with []" do +# Class.new.class_eval do +# def [](val) +# "FOO" +# end +# V8::Context.new(:with => new) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.bar').should == "FOO" +# end +# end +# end + +# xit "allows a ruby object to intercept property setting with []=" do +# Class.new.class_eval do +# def initialize +# @properties = {} +# end +# def [](name) +# @properties[name] +# end +# def []=(name, value) +# @properties[name] = value +# end + +# V8::Context.new(:with => new) do |cxt| +# cxt.eval('this.foo = "bar"').should == "bar" +# cxt.eval('this.foo').should == "bar" +# end +# end +# end + +# xit "allows a ruby object which intercepts property access to take a pass on intercepting the property", :compat => '0.4.0' do +# klass = Class.new do +# def initialize +# @attrs = {} +# end +# def [](name) +# name =~ /foo/ ? @attrs[name] : yield +# end +# def []=(name, value) +# name =~ /foo/ ? @attrs[name] = "#{value}-diddly" : yield +# end +# end + +# V8::Context.new do |cxt| +# cxt['foo'] = klass.new +# cxt.eval('typeof foo.bar').should == 'undefined' +# cxt.eval('foo.bar = "baz"') +# cxt.eval('foo.bar').should == 'baz' +# cxt.eval('foo.foobar').should == nil +# cxt.eval('foo.foobar = "baz"') +# cxt.eval('foo.foobar').should == "baz-diddly" +# end +# end + +# xit "allows a ruby object to take a pass on intercepting an indexed property", :compat => '0.4.0' do +# klass = Class.new do +# def initialize +# @arr = [] +# end +# def [](i) +# i >= 5 ? @arr[i] : yield +# end +# def []=(i, value) +# i >= 5 ? @arr[i] = "#{value}-diddly" : yield +# end +# end + +# V8::Context.new do |cxt| +# cxt['obj'] = klass.new +# cxt.eval('typeof obj[1]').should == 'undefined' +# cxt.eval('obj[1] = "foo"') +# cxt.eval('obj[1]').should == "foo" +# cxt.eval('obj[5] = "foo"').should == "foo" +# cxt.eval('obj[5]').should == "foo-diddly" +# end +# end + +# xit "does not make the [] and []= methods visible or enumerable by default", :compat => '0.4.1' do +# klass = Class.new do +# def [](name); name; end +# def []=(name, value); name && value; end +# end + +# V8::Context.new do |cxt| +# cxt['o'] = klass.new +# cxt.eval('o["[]"]').should == nil +# cxt.eval('o["[]="]').should == nil +# cxt.eval('a = new Array(); for (var i in o) a.push(i);') +# cxt['a'].length.should == 0 +# end +# end + +# xit "doesn't kill the whole process if a dynamic interceptor or setter throws an exception" do +# klass = Class.new do +# def [](name) +# raise "BOOM #{name}!" +# end +# def []=(name, value) +# raise "Bam #{name} = #{value} !" +# end +# end + +# V8::Context.new do |cxt| +# cxt['foo'] = klass.new +# lambda { +# cxt.eval('foo.bar') +# }.should raise_error +# lambda { +# cxt.eval('foo.bar = "baz"') +# }.should raise_error +# end +# end + +# xit "doesn't kill the whole process if reader or accessor throws an exception" do +# klass = Class.new do +# def foo +# raise "NO GET 4 U!" +# end +# def foo=(val) +# raise "NO SET 4 U!" +# end +# end + +# cxt = V8::Context.new(:with => klass.new) +# lambda { +# cxt.eval(this.foo) +# }.should raise_error +# lambda { +# cxt.eval("this.foo = 'bar'") +# }.should raise_error +# end + +# xit "allows access to methods defined on an objects included/extended modules (class)" do +# modul = Module.new do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# end +# klass = Class.new do +# include modul +# end + +# V8::Context.new(:with => klass.new) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows access to methods defined on an objects included/extended modules (instance)" do +# modul = Module.new do +# attr_accessor :foo +# def foo +# @foo ||= "FOO" +# end +# self +# end + +# obj = Object.new; obj.extend(modul) +# V8::Context.new(:with => obj) do |cxt| +# cxt.eval('this.foo').should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "allows access to public singleton methods" do +# obj = Object.new +# class << obj; attr_accessor :foo; end +# def obj.foo; @foo ||= "FOO"; end + +# V8::Context.new(:with => obj) do |cxt| +# cxt.eval("this.foo").should == "FOO" +# cxt.eval('this.foo = "bar!"') +# cxt.eval('this.foo').should == "bar!" +# end +# end + +# xit "does not allow access to methods defined on Object and above", :compat => '0.5.0' do +# klass = Class.new do +# def foo; "FOO"; end +# end + +# V8::Context.new(:with => klass.new) do |cxt| +# for method in Object.public_instance_methods +# cxt.eval("this['#{method}']").should be nil +# end +# end +# end + +# xit "hides methods derived from Object, Kernel, etc...", :compat => '0.5.0' do +# evaljs("o.to_s").should be nil # Object +# evaljs("o.puts").should be nil # Kernel +# end + +# xit "does not allow to call a ruby constructor, unless that constructor has been directly embedded", :compat => '0.5.0' do +# klass = Class.new +# @cxt['obj'] = klass.new +# lambda { +# @cxt.eval('new (obj.constructor)()') +# }.should raise_js_error +# end + +# describe "with an integer index" do + +# xit "allows accessing indexed properties via the []() method" do +# class_eval do +# def [](i) +# "foo" * i +# end +# end +# evaljs("o[3]").should == "foofoofoo" +# end + +# xit "allows setting indexed properties via the []=() method" do +# class_eval do +# def [](i) +# @storage ||= [] +# @storage[i] +# end +# def []=(i, val) +# @storage ||= [] +# @storage[i] = val +# end +# end +# evaljs("o[3] = 'three'").should == 'three' +# evaljs("o[3]").should == 'three' +# end + +# xit "doesn't kill the whole process if indexed interceptors throw exceptions" do +# class_eval do +# def [](idx) +# raise "No Indexed [#{idx}] For You!" +# end +# def []=(idx, value) +# raise "No Indexed [#{idx}] = #{value} For You!" +# end +# end +# lambda { +# evaljs("o[1] = 'boo'") +# }.should raise_error +# lambda { +# evaljs("o[1]") +# }.should raise_error +# end + +# #TODO: I'm not sure this is warranted +# #it "will enumerate indexed properties if a length property is provided" +# end + +# end + +# xit "will see a method that appears after the wrapper was first created" do +# @cxt['o'] = @instance +# class_eval do +# def whiz(str) +# "whiz#{str}!" +# end +# end +# @cxt.eval("o.whiz('bang')").should == "whizbang!" +# end + +# xit "treats ruby methods that have an arity of 0 as javascript properties by default" do +# class_eval do +# def property +# "flan!" +# end +# end +# evaljs('o.property').should == 'flan!' +# end + +# xit "will call ruby accesssor function when setting a property from javascript" do +# class_eval do +# def dollars +# @dollars +# end + +# def dollars=(amount) +# @dollars = amount +# end +# end +# evaljs('o.dollars = 50') +# @instance.dollars.should == 50 +# end + +# xit "will accept expando properties by default for properties on ruby object that are not implemented in ruby" do +# evaljs('o.five = 5; o.five').should == 5 +# end + +# xit "it silently fails to replace properties which are defined on ruby objects but which are read-only" do +# class_eval do +# def bar +# "baz" +# end +# end +# evaljs('o.bar = "bing"; o.bar').should == "baz" +# end + +# def evaljs(str) +# @cxt.eval(str) +# end + +# def class_eval(&body) +# @class.class_eval &body +# end + +# end + +# describe "Calling JavaScript Code From Within Ruby", :compat => '0.1.0' do + +# before(:each) do +# @cxt = V8::Context.new +# end + +# xit "allows you to capture a reference to a javascript function and call it" do +# f = @cxt.eval('(function add(lhs, rhs) {return lhs + rhs})') +# f.call(1,2).should == 3 +# end + +# xit "can path the 'this' object into a function as context with methodcall()" do +# obj = @cxt.eval('({num: 5})') +# times = @cxt.eval('(function times(num) {return this.num * num})') +# times.methodcall(obj, 5).should == 25 +# end + +# xit "unwraps objects that are backed by javascript objects to pass their native equivalents" do +# @cxt.eval('obj = {foo: "bar"}') +# f = @cxt.eval('(function() {return this == obj})') +# f.methodcall(@cxt['obj']).should be(true) +# end + +# xit "can invoke a javacript constructor and return the new object reflected into ruby" do +# wrapper = @cxt.eval('(function Wrapper(value) {this.value = value})') +# wrapper.new(5)['value'].should == 5 +# end + +# xit "can call a javascript method directly from a ruby object" do +# obj = @cxt.eval('Object').new +# obj.should respond_to(:toString) +# obj.toString().should == '[object Object]' +# end + +# xit "can access properties defined on a javascript object through ruby" do +# obj = @cxt.eval('({ str: "bar", num: 5 })') +# obj.str.should == "bar" +# obj.num.should == 5 +# end + +# xit "can set properties on the javascript object via ruby setter methods" do +# obj = @cxt.eval('({ str: "bar", num: 5 })') +# obj.str = "baz" +# obj.str.should == "baz" +# obj.double = proc {|i| i * 2} +# obj.double.call(2).should == 4 +# obj.array = 1,2,3 +# obj.array.to_a.should == [1,2,3] +# obj.array = [1,2,3] +# obj.array.to_a.should == [1,2,3] +# end + +# xit "is an error to try and pass parameters to a property" do +# obj = @cxt.eval('({num: 1})') +# lambda { +# obj.num(5) +# }.should raise_error(ArgumentError) +# end +# end + +# describe "Setting up the Host Environment", :compat => '0.1.0' do + +# before(:each) do +# @cxt = V8::Context.new +# end + +# xit "can eval javascript with a given ruby object as the scope." do +# scope = Class.new.class_eval do +# def plus(lhs, rhs) +# lhs + rhs +# end + +# def minus(lhs, rhs) +# lhs - rhs +# end + +# new +# end + +# V8::Context.new(:with => scope) do |cxt| +# cxt.eval("plus(1,2)", "test").should == 3 +# cxt.eval("minus(10, 20)", "test").should == -10 +# cxt.eval("this").should be(scope) +# end +# end + +# xit "can directly embed ruby values into javascript" do +# @cxt["bar"] = 9 +# @cxt['foo'] = "bar" +# @cxt['num'] = 3.14 +# @cxt['trU'] = true +# @cxt['falls'] = false +# @cxt.eval("bar + 10").should == 19 +# @cxt.eval('foo').should == "bar" +# @cxt.eval('num').should == 3.14 +# @cxt.eval('trU').should be(true) +# @cxt.eval('falls').should be(false) +# end + +# xit "has the global object available as a javascript value" do +# @cxt['foo'] = 'bar' +# @cxt.scope.should_not be(nil) +# @cxt.scope.should respond_to :'[]' +# @cxt.scope['foo'].should == 'bar' +# end + +# xit "will treat class objects as constructors by default" do +# @cxt[:MyClass] = Class.new do +# attr_reader :one, :two +# def initialize(one, two) +# @one, @two = one, two +# end +# end +# @cxt.eval('new MyClass(1,2).one').should == 1 +# @cxt.eval('new MyClass(1,2).two').should == 2 +# end + +# xit "exposes class properties as javascript properties on the corresponding constructor" do +# @cxt[:MyClass] = Class.new do +# def self.foo(*args) +# args.inspect +# end +# end +# @cxt.eval('MyClass.foo').should_not be nil +# @cxt.eval('MyClass.foo()').should == "[]" +# end + +# xit "unwraps reflected ruby constructor objects into their underlying ruby classes" do +# @cxt['RubyObject'] = Object +# @cxt['RubyObject'].should be(Object) +# end + +# xit "honors the instanceof operator for ruby instances when compared to their reflected constructors" do +# @cxt['RubyObject'] = Object +# @cxt['rb_object'] = Object.new +# @cxt.eval('rb_object instanceof RubyObject').should be(true) +# @cxt.eval('new RubyObject() instanceof RubyObject').should be(true) +# @cxt.eval('new RubyObject() instanceof Array').should be(false) +# @cxt.eval('new RubyObject() instanceof Object').should be(true) +# end + +# xit "reports constructor function as being an instance of Function", :compat => '0.1.1' do +# @cxt['RubyObject'] = Object +# @cxt.eval('RubyObject instanceof Function').should be(true) +# end + +# xit "respects ruby's inheritance chain with the instanceof operator", :compat => '0.1.1' do +# @cxt['Class1'] = klass1 = Class.new(Object) +# @cxt['Class2'] = klass2 = Class.new(klass1) +# @cxt['obj1'] = klass1.new +# @cxt['obj2'] = klass2.new + +# @cxt.eval('obj1 instanceof Class1').should == true +# @cxt.eval('obj1 instanceof Class2').should == false +# @cxt.eval('obj2 instanceof Class2').should == true +# @cxt.eval('obj2 instanceof Class1').should == true +# end + +# xit "unwraps instances created by a native constructor when passing them back to ruby" do +# @cxt['RubyClass'] = Class.new do +# def definitely_a_product_of_this_one_off_class? +# true +# end +# end +# @cxt.eval('new RubyClass()').should be_definitely_a_product_of_this_one_off_class +# end +# end + +# describe "Loading javascript source into the interpreter", :compat => '0.1.0' do + +# xit "can take an IO object in the eval method instead of a string" do +# source = StringIO.new(<<-EOJS) +# /* +# * we want to have a fairly verbose function so that we can be assured tha +# * we overflow the buffer size so that we see that the reader is chunking +# * it's payload in at least several fragments. +# * +# * That's why we're wasting space here +# */ +# function five() { +# return 5 +# } +# foo = 'bar' +# five(); +# EOJS +# V8::Context.new do |cxt| +# cxt.eval(source, "StringIO").should == 5 +# cxt['foo'].should == "bar" +# end +# end + +# xit "can load a file into the runtime" do +# filename = Pathname(__FILE__).dirname.join("fixtures/loadme.js").to_s +# V8::Context.new.load(filename).should == "I am Legend" +# end +# end + +# describe "A Javascript Object Reflected Into Ruby", :compat => '0.1.0' do + +# before(:each) do +# @cxt = V8::Context.new +# @o = @cxt.eval("o = new Object(); o") +# end + +# def evaljs(js) +# @cxt.eval(js) +# end + +# xit "can have its properties manipulated via ruby style [] hash access" do +# @o["foo"] = 'bar' +# evaljs('o.foo').should == "bar" +# evaljs('o.blue = "blam"') +# @o["blue"].should == "blam" +# end + +# xit "doesn't matter if you use a symbol or a string to set a value" do +# @o[:foo] = "bar" +# @o['foo'].should == "bar" +# @o['baz'] = "bang" +# @o[:baz].should == "bang" +# end + +# xit "returns nil when the value is null, null, or not defined" do +# @o[:foo].should be_nil +# end + +# xit "traverses the prototype chain when hash accessing properties from the ruby object" do +# V8::Context.new do |cxt| +# cxt.eval(< 'bar', "bang" => 'baz', 5 => 'flip'} +# end +# end +# end + +# describe "Exception Handling", :compat => '0.1.0' do + +# xit "raises javascript exceptions as ruby exceptions" do +# lambda { +# V8::Context.new.eval('foo') +# }.should raise_js_error +# end + +# xit "can handle syntax errors" do +# lambda { +# V8::Context.eval('does not compiles') +# }.should raise_error +# end + +# xit "will allow exceptions to pass through multiple languages boundaries (i.e. js -> rb -> js -> rb)" do +# V8::Context.new do |cxt| +# cxt['one'] = lambda do +# cxt.eval('two()', 'one.js') +# end +# cxt['two'] = lambda do +# cxt.eval('three()', 'two.js') +# end +# cxt['three'] = lambda do +# cxt.eval('throw "BOOM!"', "three.js") +# end +# lambda { +# cxt['one'].call(cxt.scope) +# }.should raise_error {|e| +# #TODO: assert something about the contents of the stack? +# #--cowboyd 05/25/2010 +# } +# end +# end + +# xit "translates ruby exceptions into javascript exceptions if they are thrown from code called xit javascript", :compat => '0.3.0' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise "BOOM!" } +# cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()').should == 'BOOM!' +# end +# end + +# xit "allows javascript to catch ScriptError", :compat => '0.4.4' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise ScriptError, "BOOM!" } +# cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()').should == 'BOOM!' +# end +# end + +# xit "will not let JavaScript catch other errors such as a SystemExit and fatal", :compat => '0.4.4' do +# V8::Context.new do |cxt| +# cxt['boom'] = lambda { raise Exception, "ByE!" } +# expect {cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()')}.should raise_error Exception +# end +# end + +# xit "propagates javascript thrown values into the ruby side", :compat => '0.4.5' do +# V8::Context.new do |cxt| +# begin +# cxt.eval('throw 42') +# rescue => e +# e.should respond_to(:value) +# e.value.should == 42 +# else +# fail "not raised !" +# end +# begin +# cxt.eval("throw { foo: 'bar' }") +# rescue => e +# e.should respond_to(:value) +# e.value.should respond_to(:'[]') +# e.value['foo'].should == 'bar' +# else +# fail "not raised !" +# end +# end +# end + +# xit "propagates a javascript thrown error from a function", :compat => '0.4.6' do +# context = V8::Context.new +# context.eval 'function bar() { throw new Error("bar"); }' +# begin +# context['bar'].call +# rescue Exception => e +# e.should be_a defined?(V8::Error) ? V8::Error : StandardError +# else +# fail "function bar() not raised !" +# end +# end + +# xit "propagates a javascript thrown error from a constructor", :compat => '0.4.6' do +# context = V8::Context.new +# context.eval 'Foo = function() { throw new Error("Foo"); };' +# begin +# context['Foo'].new +# rescue Exception => e +# e.should be_a defined?(V8::Error) ? V8::Error : StandardError +# else +# fail "not raised !" +# end +# end + +# end + +# describe "A Ruby class reflected into JavaScript", :compat => '0.6.0' do + +# xit "will extend instances of the class when properties are added to the corresponding JavaScript constructor's prototype" do +# klass = Class.new +# V8::Context.new do |cxt| +# cxt['RubyObject'] = klass +# cxt.eval('RubyObject.prototype.foo = function() {return "bar"}') +# cxt['o'] = klass.new +# cxt.eval('o.foo()').should == "bar" +# end +# end + +# xit"will extend instances of subclasses when properties are added to the corresponding JavaScript constructor's prototype" do +# superclass = Class.new +# subclass = Class.new(superclass) +# V8::Context.new do |cxt| +# cxt['SuperClass'] = superclass +# cxt['SubClass'] = subclass +# cxt['o'] = subclass.new +# cxt.eval('SuperClass.prototype.foo = function() {return "bar"}') +# cxt.eval('o.foo()').should == "bar" +# end +# end + +# end + + private + + def raise_js_error + defined?(V8::Error) ? raise_error(V8::Error) : raise_error end + end From d969b1cdb9204cd89184a7a3b9b6b3be5a7d6ad6 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 28 Jul 2015 17:57:01 +0300 Subject: [PATCH 083/105] add back the basics of memory testing. This adds back the memory tests which ensure The Ruby Racer's stability. It also includes the changes needed to make these initial specs pass. At this point to disallow calling Dispose() directly on the underlying Isolate. If you do, then you could have all of the Ruby objects happily sitting in memory, but trying to use them would result in a segfault for trying to use a dead isolate. In order to avoid that, we'd have to put in safeguards everywhere to make sure for any operation on any context or object, that it's isolate was still alive. In order to reduce the complexity of memory management, the safest thing is to let the garbage collection hook dispose of the isolate itself. That way there is no doubt that the isolate is no longer in use. --- .travis.yml | 1 + ext/v8/isolate.cc | 10 +++++----- ext/v8/isolate.h | 13 +++++++++++-- spec/mem/blunt_spec.rb | 23 +++++++++++++---------- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0a9086b..1bbff0b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ script: - bundle exec rake compile - bundle exec rspec spec/c - bundle exec rspec spec/v8/context_spec.rb + - bundle exec rspec spec/mem sudo: false addons: apt: diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index e9e001bb..a185128b 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -11,7 +11,7 @@ namespace rr { ClassBuilder("Isolate"). defineSingletonMethod("New", &New). - defineMethod("Dispose", &Isolate::Dispose). + defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). store(&Class); } @@ -26,7 +26,6 @@ namespace rr { create_params.array_buffer_allocator = &data->array_buffer_allocator; v8::Isolate* isolate = v8::Isolate::New(create_params); - isolate->SetData(0, data); isolate->AddGCPrologueCallback(&clearReferences); @@ -34,9 +33,10 @@ namespace rr { return Isolate(isolate); } - VALUE Isolate::Dispose(VALUE self) { + + VALUE Isolate::IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds) { Isolate isolate(self); - isolate->Dispose(); - return Qnil; + Locker lock(isolate); + return Bool(isolate->IdleNotificationDeadline(NUM2DBL(deadline_in_seconds))); } } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 7fc14802..59d27821 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -50,6 +50,7 @@ namespace rr { */ static void destroy(IsolateData* data) { Isolate isolate(data); + isolate->Dispose(); isolate.decrementTotalReferences(); } @@ -159,7 +160,15 @@ namespace rr { while (data->rb_release_queue.try_dequeue(object)) { isolate.releaseObject(object); } - rb_gc_mark(data->retained_objects); + //TODO: This should not be necessary since sometimes the + //instance of V8::RetainedObjects appears to magically be of + //type T_NONE instead of T_OBJECT. Later, it will be T_OBJECT, + //but if called while T_NONE, it will cause rb_gc_mark to dump + //core. + //See https://bugs.ruby-lang.org/issues/10803 + if (TYPE(data->retained_objects) != T_NONE) { + rb_gc_mark(data->retained_objects); + } } /** @@ -177,7 +186,7 @@ namespace rr { } - static VALUE Dispose(VALUE self); + static VALUE IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds); /** * Recent versions of V8 will segfault unless you pass in an diff --git a/spec/mem/blunt_spec.rb b/spec/mem/blunt_spec.rb index 7c5a0582..a3f346e7 100644 --- a/spec/mem/blunt_spec.rb +++ b/spec/mem/blunt_spec.rb @@ -7,7 +7,7 @@ end #allocate a single context to make sure that v8 loads its snapshot and #we pay the overhead. - V8::Context.new + V8::Isolate.new @start_memory = process_memory GC.stress = true end @@ -15,23 +15,27 @@ after do GC.stress = false end + + it "can create 3 isolates in a row" do + 3.times { V8::Isolate.new } + end + it "won't increase process memory by more than 50% no matter how many contexts we create" do - 500.times do - V8::Context.new - run_v8_gc + 250.times do + isolate = V8::Context.new.isolate.native + isolate.IdleNotificationDeadline(0.1) end - process_memory.should <= @start_memory * 1.5 + expect(process_memory).to be <= @start_memory * 1.5 end it "can eval simple value passing statements repeatedly without significantly increasing memory" do - V8::C::Locker() do - cxt = V8::Context.new + V8::Context.new do |cxt| 500.times do cxt.eval('7 * 6') - run_v8_gc + cxt.isolate.native.IdleNotificationDeadline(0.1) end end - process_memory.should <= @start_memory * 1.1 + expect(process_memory).to be <= @start_memory * 1.1 end def process_memory @@ -39,4 +43,3 @@ def process_memory end end - From 5e21f1deaeacf1d00afb304954f41173e12b671e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 28 Jul 2015 18:29:38 +0300 Subject: [PATCH 084/105] remove calls to Dispose in specs --- spec/c/isolate_spec.rb | 4 ---- spec/c_spec_helper.rb | 2 -- spec/spec_helper.rb | 6 ------ 3 files changed, 12 deletions(-) diff --git a/spec/c/isolate_spec.rb b/spec/c/isolate_spec.rb index 593c1b73..61656dca 100644 --- a/spec/c/isolate_spec.rb +++ b/spec/c/isolate_spec.rb @@ -6,8 +6,4 @@ it 'can create a new isolate' do expect(isolate).to be end - - it "can be disposed of" do - isolate.Dispose() - end end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index c57f12c1..c0116add 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -23,8 +23,6 @@ def bootstrap_v8_context @ctx.Exit end end - ensure - @isolate.Dispose() end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 41785c81..b31ecb41 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,11 +1,5 @@ require 'v8' -def run_v8_gc - V8::C::V8::LowMemoryNotification() - while !V8::C::V8::IdleNotification() do - end -end - def rputs(msg) puts "
#{ERB::Util.h(msg)}
" $stdout.flush From 277926fc7fda066893754febf49fcd51197261a9 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 29 Jul 2015 00:23:50 +0300 Subject: [PATCH 085/105] implement passing objects from JS -> Ruby --- lib/v8.rb | 3 +-- lib/v8/conversion.rb | 6 ++++++ lib/v8/object.rb | 6 +++--- spec/v8/context_spec.rb | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/v8.rb b/lib/v8.rb index 320afe9d..a5f271a0 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -25,7 +25,6 @@ # require 'v8/access/indices' # require 'v8/access/invocation' # require 'v8/access' -# require 'v8/context' -# require 'v8/object' +require 'v8/object' # require 'v8/array' # require 'v8/function' diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 2cc202fe..8f08c9a4 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -31,6 +31,12 @@ def to_ruby IsTrue() end end + + class Object + def to_ruby + ::V8::Object.new self + end + end end class String diff --git a/lib/v8/object.rb b/lib/v8/object.rb index 71f6281b..5382f16c 100644 --- a/lib/v8/object.rb +++ b/lib/v8/object.rb @@ -6,12 +6,12 @@ class V8::Object def initialize(native = nil) @context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context" @native = block_given? ? yield : native || V8::C::Object::New() - @context.link self, @native + #@context.link self, @native end def [](key) @context.enter do - @context.to_ruby @native.Get(@context.to_v8(key)) + @context.to_ruby @native.Get(@context.native, @context.to_v8(key)).FromJust() end end @@ -76,4 +76,4 @@ def method_missing(name, *args, &block) raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty? end end -end \ No newline at end of file +end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 79dbd90b..e996677e 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -71,18 +71,18 @@ # end # end - # xit "can pass objects back to ruby" do - # @cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object| - # object.should_not be_nil - # object['foo'].should == 'bar' - # object['baz'].should == 'bang' - # object['5'].should == 5 - # object['embedded'].tap do |embedded| - # embedded.should_not be_nil - # embedded['badda'].should == 'bing' - # end - # end - # end + it "can pass objects back to ruby" do + @cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object| + object.should_not be_nil + object['foo'].should == 'bar' + object['baz'].should == 'bang' + object['5'].should == 5 + object['embedded'].tap do |embedded| + embedded.should_not be_nil + embedded['badda'].should == 'bing' + end + end + end # xit "can pass int properties to ruby", :compat => '0.2.1' do # @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object| From 7c293f0b68b1a1787c9229547889b9ecd5932124 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 29 Jul 2015 17:01:11 +0300 Subject: [PATCH 086/105] add conversion for fixnum --- lib/v8/conversion.rb | 6 ++++++ spec/v8/context_spec.rb | 18 +++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 8f08c9a4..656fa0bd 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -45,6 +45,12 @@ def to_v8(context) end end +class Fixnum + def to_v8(context) + V8::C::Integer::New(context.isolate.native, self) + end +end + # for type in [TrueClass, FalseClass, NilClass, Float] do # type.class_eval do # include V8::Conversion::Primitive diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index e996677e..3b2b7e2e 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -84,15 +84,15 @@ end end - # xit "can pass int properties to ruby", :compat => '0.2.1' do - # @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object| - # object[ 4 ].should == '4' - # object['4'].should == '4' - # object[ 5 ].should == 5 - # object['5'].should == 5 - # object['6'].should == true - # end - # end + it "can pass int properties to ruby", :compat => '0.2.1' do + @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object| + object[ 4 ].should == '4' + object['4'].should == '4' + object[ 5 ].should == 5 + object['5'].should == 5 + object['6'].should == true + end + end # xit "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do # Object.new.tap do |o| From c53027c53aa7b16d15cd5178f47526ae8a878516 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 29 Jul 2015 20:45:31 +0300 Subject: [PATCH 087/105] remove obsolete enums They are defined in C++ now --- lib/v8/c.rb | 1 - lib/v8/c/property_attribute.rb | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 lib/v8/c/property_attribute.rb diff --git a/lib/v8/c.rb b/lib/v8/c.rb index d43e2b86..287dbf35 100644 --- a/lib/v8/c.rb +++ b/lib/v8/c.rb @@ -1,3 +1,2 @@ require 'v8/weak' require 'v8/c/maybe' -require 'v8/c/property_attribute' diff --git a/lib/v8/c/property_attribute.rb b/lib/v8/c/property_attribute.rb deleted file mode 100644 index f6eb4d86..00000000 --- a/lib/v8/c/property_attribute.rb +++ /dev/null @@ -1,16 +0,0 @@ -module V8 - module C - module PropertyAttribute - None = 0 - ReadOnly = 1 << 0 - DontEnum = 1 << 1 - DontDelete = 1 << 2 - end - module AccessControl - DEFAULT = 0 - ALL_CAN_READ = 1 << 0 - ALL_CAN_WRITE = 1 << 1 - PROHIBITS_OVERWRITING = 1 << 2 - end - end -end From 1bd5c3b26e5fe84b34f15ff940be7dae7dd7e73a Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 30 Jul 2015 21:15:10 +0300 Subject: [PATCH 088/105] implement date and related conversions --- ext/v8/date.h | 18 ++++++++++++++++++ ext/v8/init.cc | 1 + ext/v8/object.cc | 7 +++---- ext/v8/rr.h | 1 + ext/v8/value.cc | 5 ----- lib/v8.rb | 3 ++- lib/v8/conversion.rb | 19 +++++++++++++++++++ lib/v8/date.rb | 7 +++++++ lib/v8/function.rb | 6 +++--- lib/v8/object.rb | 7 +++++-- spec/v8/context_spec.rb | 21 ++++++++++----------- 11 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 ext/v8/date.h create mode 100644 lib/v8/date.rb diff --git a/ext/v8/date.h b/ext/v8/date.h new file mode 100644 index 00000000..7ffcb7f6 --- /dev/null +++ b/ext/v8/date.h @@ -0,0 +1,18 @@ +// -*- mode: c++ -*- +#ifndef RR_DATE_H +#define RR_DATE_H + +namespace rr { + class Date : public Ref { + public: + Date(v8::Isolate* isolate, v8::Local date) : + Ref(isolate, date) {} + + static void Init() { + ClassBuilder("Date", Object::Class). + store(&Class); + }; + }; +} + +#endif /* RR_DATE_H */ diff --git a/ext/v8/init.cc b/ext/v8/init.cc index ded672f9..c849806d 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -19,6 +19,7 @@ extern "C" { Maybe::Init(); Value::Init(); Object::Init(); + Date::Init(); Primitive::Init(); Null::Init(); Undefined::Init(); diff --git a/ext/v8/object.cc b/ext/v8/object.cc index c69f3ecb..e7a58071 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -105,16 +105,15 @@ namespace rr { if (handle->IsFunction()) { return Function(isolate, handle.As()); } - if (handle->IsArray()) { return Array(isolate, handle.As()); } + if (handle->IsDate()) { + return Date(isolate, handle.As()); + } // TODO: Enable this when the methods are implemented // - // if (handle->IsDate()) { - // // return Date(handle); - // } // // if (handle->IsBooleanObject()) { // // return BooleanObject(handle); diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 2ef7fa44..aacde8ac 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -52,6 +52,7 @@ inline VALUE not_implemented(const char* message) { #include "function.h" #include "object.h" +#include "date.h" #include "return-value.h" #include "property-callback.h" #include "property-callback-info.h" diff --git a/ext/v8/value.cc b/ext/v8/value.cc index d58a2d5c..ce5061c8 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -193,11 +193,6 @@ namespace rr { return Name(isolate, handle.As()); } - // TODO - // if (handle->IsDate()) { - // return Date((v8::Handle)v8::Date::Cast(*handle)); - // } - if (handle->IsFunction()) { return Function(isolate, v8::Handle::Cast(handle)); } diff --git a/lib/v8.rb b/lib/v8.rb index a5f271a0..c2f68213 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -26,5 +26,6 @@ # require 'v8/access/invocation' # require 'v8/access' require 'v8/object' +require 'v8/date' # require 'v8/array' -# require 'v8/function' +require 'v8/function' diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 656fa0bd..64a1a36b 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -37,6 +37,18 @@ def to_ruby ::V8::Object.new self end end + + class Date + def to_ruby + ::V8::Date.new self + end + end + + class Function + def to_ruby + ::V8::Function.new self + end + end end class String @@ -51,6 +63,13 @@ def to_v8(context) end end +class Symbol + def to_v8(context) + isolate = context.isolate.native + V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s)) + end +end + # for type in [TrueClass, FalseClass, NilClass, Float] do # type.class_eval do # include V8::Conversion::Primitive diff --git a/lib/v8/date.rb b/lib/v8/date.rb new file mode 100644 index 00000000..0fc4e766 --- /dev/null +++ b/lib/v8/date.rb @@ -0,0 +1,7 @@ +module V8 + class Date < V8::Object + def value_of + @context.to_ruby @native.ValueOf() + end + end +end diff --git a/lib/v8/function.rb b/lib/v8/function.rb index c1cbbb4b..b3ad6651 100644 --- a/lib/v8/function.rb +++ b/lib/v8/function.rb @@ -1,5 +1,5 @@ class V8::Function < V8::Object - include V8::Error::Try + #include V8::Error::Try def initialize(native = nil) super do @@ -10,7 +10,7 @@ def initialize(native = nil) def methodcall(this, *args) @context.enter do this ||= @context.native.Global() - @context.to_ruby try {native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})} + @context.to_ruby native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a}) end end @@ -25,4 +25,4 @@ def new(*args) @context.to_ruby try {native.NewInstance(args.map {|a| @context.to_v8 a})} end end -end \ No newline at end of file +end diff --git a/lib/v8/object.rb b/lib/v8/object.rb index 5382f16c..83123e36 100644 --- a/lib/v8/object.rb +++ b/lib/v8/object.rb @@ -1,7 +1,6 @@ class V8::Object include Enumerable attr_reader :native - alias_method :to_v8, :native def initialize(native = nil) @context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context" @@ -11,7 +10,7 @@ def initialize(native = nil) def [](key) @context.enter do - @context.to_ruby @native.Get(@context.native, @context.to_v8(key)).FromJust() + @context.to_ruby @native.Get(@context.native, @context.to_v8(key.to_s)).FromJust() end end @@ -52,6 +51,10 @@ def to_s end end + def to_v8(context) + native + end + def respond_to?(method) super or self[method] != nil end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 3b2b7e2e..732ef11b 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -59,17 +59,16 @@ @cxt['nativeUtf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη" end - # xit "translates JavaScript dates properly into ruby Time objects" do - # now = Time.now - # @cxt.eval('new Date()').tap do |time| - # time.should be_kind_of(Time) - # time.year.should == now.year - # time.day.should == now.day - # time.month.should == now.month - # time.min.should == now.min - # time.sec.should == now.sec - # end - # end + it "translates JavaScript dates objects" do + now = Time.now + @cxt.eval('new Date()').tap do |time| + expect(time.getDate().to_i).to eql now.day + expect(time.getFullYear().to_i).to eql now.year + expect(time.getMonth().to_i).to eql now.month - 1 + expect(time.getMinutes().to_i).to eql now.min + expect(time.getSeconds().to_i).to eql now.sec + end + end it "can pass objects back to ruby" do @cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object| From 213c619ae84d741adc1a05c87d01c837bd145fec Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 31 Jul 2015 14:48:26 +0300 Subject: [PATCH 089/105] add back in the memory map + documentation --- lib/v8.rb | 1 - lib/v8/conversion.rb | 93 +++++++++++++++++++++++--------- lib/v8/conversion/fundamental.rb | 70 +++++++++++++++++++++++- lib/v8/conversion/indentity.rb | 31 ----------- lib/v8/object.rb | 3 +- spec/v8/context_spec.rb | 12 ++--- 6 files changed, 143 insertions(+), 67 deletions(-) delete mode 100644 lib/v8/conversion/indentity.rb diff --git a/lib/v8.rb b/lib/v8.rb index c2f68213..69e50e5b 100644 --- a/lib/v8.rb +++ b/lib/v8.rb @@ -6,7 +6,6 @@ # require 'v8/error' # require 'v8/stack' require 'v8/conversion/fundamental' -# require 'v8/conversion/indentity' # require 'v8/conversion/reference' # require 'v8/conversion/primitive' # require 'v8/conversion/code' diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 64a1a36b..814f9705 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -1,23 +1,85 @@ +## +# An extensible conversion mechanism for converting objects to and +# from their V8 representations. +# +# The Ruby Racer has two levels of representation for JavaScript +# objects: the low-level C++ objects which are just thin wrappers +# native counterparts. These are the objects in the `V8::C::*` +# namespace. It also has high-level Ruby objects which can +# either be instances of `V8::Object`, `V8::Date`, or just plain Ruby +# objects that have representations in the JavaScript runtime. +# +# The conversion object held by the context captures this transition +# from one object type to the other. It is implemented as a "middleware" +# stack where at the base is the `Fundamental` conversion which does +# basic conversion and identity mapping. +# +# In order to "extend" or override the conversion mechanism, you can +# extend this object to add behaviors. For example, the following +# extension will add a `__fromRuby__` property to every ruby object +# that is embedded into this context. +# +# module TagRubyObjects +# def to_v8(context, ruby_object) +# super.tap do |v8_object| +# v8_object.Set(V8::C::String::NewFromUtf8("__fromRuby__"), V8::C::Boolean::New(true)) +# end +# end +# end +# context.conversion.extend TagRubyObjects +# +# @see V8::Conversion::Fundamental for the basic conversion operation. class V8::Conversion include Fundamental - # include Identity + + ## + # Convert a low-level instance of `V8::C::Value` or one of its + # subclasses into a Ruby object. + # + # The `Fundamental` conversion will call `v8_object.to_ruby`, but + # any number of middlewares can be inserted between then. + # + # @param [V8::C::Value] the object to convert + # @return [Object] the corresponding Ruby value def to_ruby(v8_object) super v8_object end + ## + # Convert a Ruby Object into a low-level `V8::C::Value` or one of + # its subclasses. Note here that things like `V8::Object` are + # considered Ruby objects and *not* V8 objects. So, for example, the + # fundamental conversion for `V8::Object` will return a + # `V8::C::Object` + # + # The `Fundamental` conversion will call + # `ruby_object.to_v8(context)` optionally storing the result in an + # identity map in the case where the result is a `V8::C::Object` + # + # @param [V8::Context] the Ruby context in the conversion happens + # @param [Object] Ruby object to convert + # @return [V8::C::Value] the v8 representation def to_v8(context, ruby_object) super context, ruby_object end end + +## +# The folowing represent the default conversions from instances of +# `V8::C::Value` into their Ruby counterparts. module V8::C class String - alias_method :to_ruby, :Utf8Value + def to_ruby + self.Utf8Value() + end end class Number - alias_method :to_ruby, :Value + def to_ruby + self.Value() + end end class Undefined @@ -51,6 +113,9 @@ def to_ruby end end +## +# The following are the default conversions from Ruby objects into +# low-level C++ objects. class String def to_v8(context) V8::C::String::NewFromUtf8(context.isolate.native, self) @@ -69,25 +134,3 @@ def to_v8(context) V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s)) end end - -# for type in [TrueClass, FalseClass, NilClass, Float] do -# type.class_eval do -# include V8::Conversion::Primitive -# end -# end - -# for type in [Class, Object, Array, Hash, String, Symbol, Time, Proc, Method, Fixnum] do -# type.class_eval do -# include V8::Conversion.const_get(type.name) -# end -# end - -# class UnboundMethod -# include V8::Conversion::Method -# end - -# for type in [:Object, :String, :Date] do -# V8::C::const_get(type).class_eval do -# include V8::Conversion::const_get("Native#{type}") -# end -# end diff --git a/lib/v8/conversion/fundamental.rb b/lib/v8/conversion/fundamental.rb index 66fd272e..9e780b88 100644 --- a/lib/v8/conversion/fundamental.rb +++ b/lib/v8/conversion/fundamental.rb @@ -1,11 +1,77 @@ +require 'v8/weak' + class V8::Conversion + ## + # This is the fundamental conversion for from Ruby objects into + # `V8::C::Value`s and vice-versa. For instances of `V8::C::Object`, + # the result is stored in an identity map, so that subsequent + # conversions return the same object both to and from ruby. + # + # It sits at the top of the conversion "middleware stack" module Fundamental + ## + # Convert a low-level `V8::C::Value` into a Ruby object, + # optionally storing the result in an identity map so that it can + # be re-used for subsequent conversions. + # + # @param [V8::C::Value] low-level C++ object. + # @return [Object] Ruby object counterpart def to_ruby(v8_object) - v8_object.to_ruby + # Only objects can be reliably identified. If this is an + # object, then we want to see if there is already a ruby object + # associated with it. + if v8_object.kind_of? V8::C::Object + if rb_object = v8_idmap[v8_object.GetIdentityHash()] + # it was in the id map, so return the existing instance. + rb_object + else + # it was not in the id map, so we run the default conversion + # and store it in the id map + v8_object.to_ruby.tap do |object| + equate object, v8_object + end + end + else + # not an object, just do the default conversion + v8_object.to_ruby + end end + ## + # Convert a Ruby object into a low-level C++ `V8::C::Value`. + # + # First it checks to see if there is an entry in the id map for + # this object. Otherwise, it will run the default conversion. def to_v8(context, ruby_object) - ruby_object.to_v8 context + rb_idmap[ruby_object.object_id] || ruby_object.to_v8(context) + end + + ## + # Mark a ruby object and a low-level V8 C++ object as being + # equivalent in this context. + # + # After being equated the two objects are like mirrors of each + # other, where one exists in the Ruby world, and the other exists + # in the V8 world. It's all very through the looking glass. + # + # Whenever `ruby_object` is reflected into the V8 runtime, then + # `v8_object` will be used in its stead, and whenever `v8_object` + # is reflected into the Ruby runtime, then `ruby_object` will be + # used in *its* stead. + # + # @param [Object] the ruby object + # @param [V8::C::Value] the v8 object + def equate(ruby_object, v8_object) + v8_idmap[v8_object.GetIdentityHash()] = ruby_object + rb_idmap[ruby_object.object_id] = v8_object + end + + def v8_idmap + @v8_idmap ||= V8::Weak::WeakValueMap.new + end + + def rb_idmap + @ruby_idmap ||= V8::Weak::WeakValueMap.new end end end diff --git a/lib/v8/conversion/indentity.rb b/lib/v8/conversion/indentity.rb deleted file mode 100644 index f566f950..00000000 --- a/lib/v8/conversion/indentity.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'ref' - -class V8::Conversion - module Identity - def to_ruby(v8_object) - if v8_object.class <= V8::C::Object - v8_idmap[v8_object.GetIdentityHash()] || super(v8_object) - else - super(v8_object) - end - end - - def to_v8(ruby_object) - return super(ruby_object) if ruby_object.is_a?(String) || ruby_object.is_a?(Primitive) - rb_idmap[ruby_object.object_id] || super(ruby_object) - end - - def equate(ruby_object, v8_object) - v8_idmap[v8_object.GetIdentityHash()] = ruby_object - rb_idmap[ruby_object.object_id] = v8_object - end - - def v8_idmap - @v8_idmap ||= V8::Weak::WeakValueMap.new - end - - def rb_idmap - @ruby_idmap ||= V8::Weak::WeakValueMap.new - end - end -end \ No newline at end of file diff --git a/lib/v8/object.rb b/lib/v8/object.rb index 83123e36..95e7c29b 100644 --- a/lib/v8/object.rb +++ b/lib/v8/object.rb @@ -4,8 +4,7 @@ class V8::Object def initialize(native = nil) @context = V8::Context.current or fail "tried to initialize a #{self.class} without being in an entered V8::Context" - @native = block_given? ? yield : native || V8::C::Object::New() - #@context.link self, @native + @native = native || V8::C::Object::New(@context.isolate.native) end def [](key) diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 732ef11b..cf15900a 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -100,12 +100,12 @@ # end # end - # xit "always returns the same ruby object for a single javascript object" do - # obj = @cxt.eval('obj = {}') - # obj.should be(@cxt['obj']) - # @cxt.eval('obj').should be(@cxt['obj']) - # @cxt['obj'].should be(@cxt['obj']) - # end + it "always returns the same ruby object for a single javascript object" do + obj = @cxt.eval('obj = {}') + obj.should be(@cxt['obj']) + @cxt.eval('obj').should be(@cxt['obj']) + @cxt['obj'].should be(@cxt['obj']) + end # xit "converts arrays to javascript" do # @cxt['a'] = [1,2,4] From 7fc1ebd61287229af6df6dc6c276e6519f8936c8 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 31 Jul 2015 15:10:18 +0300 Subject: [PATCH 090/105] enable calling JS code from Ruby turns out that this "just worked" (exception handling still remains un-implemented) --- lib/v8/conversion.rb | 6 ++++ lib/v8/function.rb | 2 +- spec/v8/context_spec.rb | 76 ++++++++++++++++++++--------------------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 814f9705..64f3fdfa 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -111,6 +111,12 @@ def to_ruby ::V8::Function.new self end end + + class Value + def to_v8(context) + self + end + end end ## diff --git a/lib/v8/function.rb b/lib/v8/function.rb index b3ad6651..4a366944 100644 --- a/lib/v8/function.rb +++ b/lib/v8/function.rb @@ -22,7 +22,7 @@ def call(*args) def new(*args) @context.enter do - @context.to_ruby try {native.NewInstance(args.map {|a| @context.to_v8 a})} + @context.to_ruby native.NewInstance(args.map {|a| @context.to_v8 a}) end end end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index cf15900a..60f78a7f 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -654,45 +654,45 @@ # end -# describe "Calling JavaScript Code From Within Ruby", :compat => '0.1.0' do + describe "Calling JavaScript Code From Within Ruby", :compat => '0.1.0' do -# before(:each) do -# @cxt = V8::Context.new -# end + before(:each) do + @cxt = V8::Context.new + end -# xit "allows you to capture a reference to a javascript function and call it" do -# f = @cxt.eval('(function add(lhs, rhs) {return lhs + rhs})') -# f.call(1,2).should == 3 -# end + it "allows you to capture a reference to a javascript function and call it" do + f = @cxt.eval('(function add(lhs, rhs) {return lhs + rhs})') + expect(f.call 1,2).to eql 3 + end -# xit "can path the 'this' object into a function as context with methodcall()" do -# obj = @cxt.eval('({num: 5})') -# times = @cxt.eval('(function times(num) {return this.num * num})') -# times.methodcall(obj, 5).should == 25 -# end + it "can path the 'this' object into a function as context with methodcall()" do + obj = @cxt.eval('({num: 5})') + times = @cxt.eval('(function times(num) {return this.num * num})') + expect(times.methodcall obj, 5).to eql 25 + end -# xit "unwraps objects that are backed by javascript objects to pass their native equivalents" do -# @cxt.eval('obj = {foo: "bar"}') -# f = @cxt.eval('(function() {return this == obj})') -# f.methodcall(@cxt['obj']).should be(true) -# end + it "unwraps objects that are backed by javascript objects to pass their native equivalents" do + @cxt.eval('obj = {foo: "bar"}') + f = @cxt.eval('(function() {return this == obj})') + expect(f.methodcall @cxt['obj']).to be true + end -# xit "can invoke a javacript constructor and return the new object reflected into ruby" do -# wrapper = @cxt.eval('(function Wrapper(value) {this.value = value})') -# wrapper.new(5)['value'].should == 5 -# end + it "can invoke a javacript constructor and return the new object reflected into ruby" do + wrapper = @cxt.eval('(function Wrapper(value) {this.value = value})') + wrapper.new(5)['value'].should == 5 + end -# xit "can call a javascript method directly from a ruby object" do -# obj = @cxt.eval('Object').new -# obj.should respond_to(:toString) -# obj.toString().should == '[object Object]' -# end + it "can call a javascript method directly from a ruby object" do + obj = @cxt.eval('Object').new + expect(obj).to respond_to(:toString) + expect(obj.toString()).to eql '[object Object]' + end -# xit "can access properties defined on a javascript object through ruby" do -# obj = @cxt.eval('({ str: "bar", num: 5 })') -# obj.str.should == "bar" -# obj.num.should == 5 -# end + it "can access properties defined on a javascript object through ruby" do + obj = @cxt.eval('({ str: "bar", num: 5 })') + expect(obj.str).to eql "bar" + expect(obj.num).to eql 5 + end # xit "can set properties on the javascript object via ruby setter methods" do # obj = @cxt.eval('({ str: "bar", num: 5 })') @@ -706,13 +706,11 @@ # obj.array.to_a.should == [1,2,3] # end -# xit "is an error to try and pass parameters to a property" do -# obj = @cxt.eval('({num: 1})') -# lambda { -# obj.num(5) -# }.should raise_error(ArgumentError) -# end -# end + it "is an error to try and pass parameters to a property" do + obj = @cxt.eval('({num: 1})') + expect { obj.num(5) }.to raise_error(ArgumentError) + end + end # describe "Setting up the Host Environment", :compat => '0.1.0' do From f24985c23c55b7d319f2c7815f87126522c213a1 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 18:30:53 +0000 Subject: [PATCH 091/105] Add rspec matchers for Maybe and (Strict)?Equals `expect(a).to eq_just b` <=> `a.IsJust && a.FromJust == b` `expect(a).to be_successful` <=> `a.IsJust && a.FromJust` `expect(a).to strict_eq b` <=> if a is Maybe: `a.IsJust && a.FromJust.StrictEquals(b)`; else `a.StrictEquals(b)` `expect(a).to v8_eq b` <=> same as above but for `Equals` instead of `StrictEquals` --- spec/c/array_spec.rb | 2 +- spec/c/function_spec.rb | 6 ++--- spec/c/object_spec.rb | 49 ++++++++++++------------------------- spec/c/symbol_spec.rb | 2 +- spec/c_spec_helper.rb | 29 +--------------------- spec/support/context.rb | 28 +++++++++++++++++++++ spec/support/v8_matchers.rb | 45 ++++++++++++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 67 deletions(-) create mode 100644 spec/support/context.rb create mode 100644 spec/support/v8_matchers.rb diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb index 88a77ab5..ce0551ef 100644 --- a/spec/c/array_spec.rb +++ b/spec/c/array_spec.rb @@ -12,7 +12,7 @@ a.Set(@ctx, 0, o) expect(a.Length).to eq 1 - expect(a.Get(@ctx, 0).FromJust().Equals(o)).to eq true + expect(a.Get(@ctx, 0)).to v8_eq o end it 'can be initialized with a length' do diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index 0768d5a7..905ada78 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -24,8 +24,8 @@ fn.Call(@ctx.Global, [one, two, 3]) - expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one')).FromJust().StrictEquals(one)).to be true - expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two')).FromJust().StrictEquals(two)).to be true + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'one'))).to strict_eq one + expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'two'))).to strict_eq two expect(@ctx.Global.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'three')).FromJust().Value()).to eq 3 end @@ -77,7 +77,7 @@ def call(info) expect(callback.this.GetIdentityHash()).to eql @ctx.Global().GetIdentityHash() expect(callback.is_construct_call).to be false expect(callback.data).not_to be_nil - expect(callback.data.StrictEquals(data)).to be true + expect(callback.data).to strict_eq data end end diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 8d3d658e..0ca7e72a 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -12,39 +12,25 @@ key = V8::C::String.NewFromUtf8(@isolate, 'foo') value = V8::C::String.NewFromUtf8(@isolate, 'bar') - maybe = o.Set(@ctx, key, value) - expect(maybe.IsJust()).to be true - expect(maybe.FromJust()).to be true - - maybe = o.Get(@ctx, key) - expect(maybe.IsJust()).to be true - expect(maybe.FromJust().Utf8Value).to eq 'bar' + expect(o.Set(@ctx, key, value)).to be_successful + expect(o.Get(@ctx, key)).to strict_eq value end it 'can determine if a key has been set' do o = V8::C::Object.New(@isolate) key = V8::C::String.NewFromUtf8(@isolate, 'foo') - o.Set(@ctx, key, key) - - maybe = o.Has(@ctx, key) - expect(maybe.IsJust()).to be true - expect(maybe.FromJust()).to eq true + expect(o.Set(@ctx, key, key)).to be_successful + expect(o.Has(@ctx, key)).to be_successful end it 'can delete keys' do o = V8::C::Object.New(@isolate) key = V8::C::String.NewFromUtf8(@isolate, 'foo') - o.Set(@ctx, key, key) - - maybe = o.Delete(@ctx, key) - expect(maybe.IsJust()).to be true - expect(maybe.FromJust()).to eq true - - maybe = o.Has(@ctx, key) - expect(maybe.IsJust()).to be true - expect(maybe.FromJust()).to eq false + expect(o.Set(@ctx, key, key)).to be_successful + expect(o.Delete(@ctx, key)).to be_successful + expect(o.Has(@ctx, key)).to eq_just false end describe '#SetAccessor' do @@ -65,14 +51,11 @@ info.GetReturnValue.Set(get_value) end - o.SetAccessor(@ctx, key, getter, nil, data) - - maybe = o.Get(@ctx, key) - expect(maybe.IsJust).to be true - expect(maybe.FromJust.StrictEquals(get_value)).to be true + expect(o.SetAccessor(@ctx, key, getter, nil, data)).to be_successful + expect(o.Get(@ctx, key)).to strict_eq get_value - expect(get_name.Equals(key)).to be true - expect(get_data.StrictEquals(data)).to be true + expect(get_name).to v8_eq key + expect(get_data).to strict_eq data end it 'can set setters' do @@ -89,14 +72,12 @@ set_value = value end - o.SetAccessor(@ctx, key, proc { }, setter, data) + expect(o.SetAccessor(@ctx, key, proc { }, setter, data)).to be_successful - maybe = o.Set(@ctx, key, data) - expect(maybe.IsJust).to be true - expect(maybe.FromJust).to be true + expect(o.Set(@ctx, key, data)).to be_successful - expect(set_data.StrictEquals(data)).to be true - expect(set_value.StrictEquals(data)).to be true + expect(set_data).to strict_eq data + expect(set_value).to strict_eq data end end diff --git a/spec/c/symbol_spec.rb b/spec/c/symbol_spec.rb index 11bf0b11..a229f701 100644 --- a/spec/c/symbol_spec.rb +++ b/spec/c/symbol_spec.rb @@ -35,7 +35,7 @@ end it "returns different symbols for different registries" do - expect(global.StrictEquals(api)).to be false + expect(global).to_not strict_eq api end end diff --git a/spec/c_spec_helper.rb b/spec/c_spec_helper.rb index fa7b919b..be0779b2 100644 --- a/spec/c_spec_helper.rb +++ b/spec/c_spec_helper.rb @@ -1,30 +1,3 @@ require 'v8/init' -module V8ContextHelpers - module GroupMethods - def requires_v8_context - around(:each) do |example| - bootstrap_v8_context(&example) - end - end - end - - def bootstrap_v8_context - @isolate = V8::C::Isolate.New - - V8::C::HandleScope(@isolate) do - @ctx = V8::C::Context::New(@isolate) - begin - @ctx.Enter - yield - ensure - @ctx.Exit - end - end - end -end - -RSpec.configure do |c| - c.include V8ContextHelpers - c.extend V8ContextHelpers::GroupMethods -end +Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |helper| require_relative helper } diff --git a/spec/support/context.rb b/spec/support/context.rb new file mode 100644 index 00000000..faba9fbd --- /dev/null +++ b/spec/support/context.rb @@ -0,0 +1,28 @@ +module V8ContextHelpers + module GroupMethods + def requires_v8_context + around(:each) do |example| + bootstrap_v8_context(&example) + end + end + end + + def bootstrap_v8_context + @isolate = V8::C::Isolate.New + + V8::C::HandleScope(@isolate) do + @ctx = V8::C::Context::New(@isolate) + begin + @ctx.Enter + yield + ensure + @ctx.Exit + end + end + end +end + +RSpec.configure do |c| + c.include V8ContextHelpers + c.extend V8ContextHelpers::GroupMethods +end diff --git a/spec/support/v8_matchers.rb b/spec/support/v8_matchers.rb new file mode 100644 index 00000000..3ed2fb22 --- /dev/null +++ b/spec/support/v8_matchers.rb @@ -0,0 +1,45 @@ +module V8Matchers + extend RSpec::Matchers::DSL + + matcher :eq_just do |expected| + match do |maybe| + return false unless maybe.IsJust + + maybe.FromJust == expected + end + end + + matcher :be_successful do + match do |maybe| + return false unless maybe.IsJust + + maybe.FromJust + end + end + + matcher :strict_eq do |expected| + match do |object| + if object.respond_to? 'IsJust' + return false unless object.IsJust + object = object.FromJust + end + + object.StrictEquals(expected) + end + end + + matcher :v8_eq do |expected| + match do |object| + if object.respond_to? 'IsJust' + return false unless object.IsJust + object = object.FromJust + end + + object.Equals(expected) + end + end +end + +RSpec.configure do |c| + c.include V8Matchers +end From 7f6c40f028e1c14b83c981efd84a6e8f60067edd Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 18:39:09 +0000 Subject: [PATCH 092/105] Add `Object::CreateDataProperty` --- ext/v8/object.cc | 12 ++++++++++++ ext/v8/object.h | 2 ++ spec/c/object_spec.rb | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index e7a58071..6963c6ad 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -11,6 +11,7 @@ namespace rr { defineMethod("Has", &Has). defineMethod("Delete", &Delete). defineMethod("SetAccessor", &SetAccessor). + defineMethod("CreateDataProperty", &CreateDataProperty). store(&Class); } @@ -98,6 +99,17 @@ namespace rr { )); } + VALUE Object::CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value) { + Object object(self); + Locker lock(object); + + if (rb_obj_is_kind_of(key, rb_cNumeric)) { + return Bool::Maybe(object->CreateDataProperty(Context(r_context), Uint32_t(key), Value(value))); + } else { + return Bool::Maybe(object->CreateDataProperty(Context(r_context), *Name(key), Value(value))); + } + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index ff7d7506..be666286 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -15,6 +15,8 @@ namespace rr { static VALUE Delete(VALUE self, VALUE r_context, VALUE key); static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); + static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); + inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 0ca7e72a..0b851c5c 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -81,6 +81,17 @@ end end + describe '#CreateDataProperty' do + it 'can set the property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.CreateDataProperty(@ctx, key, data)).to be_successful + expect(o.Get(@ctx, key)).to be_successful + end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From dd7b049241d404345f771ceb52906ed8c0326b69 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 18:49:35 +0000 Subject: [PATCH 093/105] Add Object::DefineOwnProperty --- ext/v8/object.cc | 16 ++++++++++++++++ ext/v8/object.h | 1 + spec/c/object_spec.rb | 11 +++++++++++ 3 files changed, 28 insertions(+) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 6963c6ad..6384ef02 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -12,6 +12,7 @@ namespace rr { defineMethod("Delete", &Delete). defineMethod("SetAccessor", &SetAccessor). defineMethod("CreateDataProperty", &CreateDataProperty). + defineMethod("DefineOwnProperty", &DefineOwnProperty). store(&Class); } @@ -110,6 +111,21 @@ namespace rr { } } + VALUE Object::DefineOwnProperty(int argc, VALUE* argv, VALUE self) { + VALUE r_context, key, value, attribute; + rb_scan_args(argc, argv, "31", &r_context, &key, &value, &attribute); + + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->DefineOwnProperty( + Context(r_context), + *Name(key), + Value(value), + Enum(attribute, v8::None) + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index be666286..bf9e8ebf 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -16,6 +16,7 @@ namespace rr { static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); + static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 0b851c5c..256e0a9c 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -92,6 +92,17 @@ end end + describe '#DefineOwnProperty' do + it 'can set the property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data)).to be_successful + expect(o.Get(@ctx, key)).to be_successful + end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From a018c34e4da31524b3eaaad15ea85b8b9b93e6e6 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 18:56:41 +0000 Subject: [PATCH 094/105] Add Object::GetPropertyAttributes --- ext/v8/enum.h | 9 +++++++++ ext/v8/object.cc | 11 +++++++++++ ext/v8/object.h | 1 + ext/v8/rr.h | 3 ++- spec/c/object_spec.rb | 11 +++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ext/v8/enum.h b/ext/v8/enum.h index 1ac72fc2..ed4b0053 100644 --- a/ext/v8/enum.h +++ b/ext/v8/enum.h @@ -16,6 +16,15 @@ namespace rr { operator T() { return RTEST(value) ? (T)NUM2INT(value) : defaultValue; } + + class Maybe : public rr::Maybe { + public: + Maybe(v8::Maybe maybe) { + if (maybe.IsJust()) { + just(INT2FIX((int)maybe.FromJust())); + } + } + }; }; inline void DefineEnums() { diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 6384ef02..ad03f022 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -13,6 +13,7 @@ namespace rr { defineMethod("SetAccessor", &SetAccessor). defineMethod("CreateDataProperty", &CreateDataProperty). defineMethod("DefineOwnProperty", &DefineOwnProperty). + defineMethod("GetPropertyAttributes", &GetPropertyAttributes). store(&Class); } @@ -126,6 +127,16 @@ namespace rr { )); } + VALUE Object::GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Enum::Maybe(object->GetPropertyAttributes( + Context(r_context), + *Name(key) + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index bf9e8ebf..2466181a 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -17,6 +17,7 @@ namespace rr { static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); + static VALUE GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 024c2c16..e68b34ec 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -17,9 +17,10 @@ inline VALUE not_implemented(const char* message) { } #include "class_builder.h" -#include "enum.h" #include "maybe.h" +#include "enum.h" + #include "equiv.h" #include "bool.h" #include "uint32_t.h" diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 256e0a9c..9bf08db4 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -103,6 +103,17 @@ end end + describe '#GetPropertyAttributes' do + it 'can get the set attributes' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data, V8::C::PropertyAttribute::DontEnum)).to be_successful + expect(o.GetPropertyAttributes(@ctx, key)).to eq_just V8::C::PropertyAttribute::DontEnum + end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From 21c35c61ff7a3eaf3c2164c8dc7e037d7df86d94 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 19:55:04 +0000 Subject: [PATCH 095/105] Add Object::GetOwnPropertyAccessor One of the tests is commented out until SetAccessorProperty is implemented --- ext/v8/object.cc | 11 ++++++ ext/v8/object.h | 1 + spec/c/function_spec.rb | 4 +-- spec/c/object_spec.rb | 77 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index ad03f022..6d7857e3 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -14,6 +14,7 @@ namespace rr { defineMethod("CreateDataProperty", &CreateDataProperty). defineMethod("DefineOwnProperty", &DefineOwnProperty). defineMethod("GetPropertyAttributes", &GetPropertyAttributes). + defineMethod("GetOwnPropertyDescriptor", &GetOwnPropertyDescriptor). store(&Class); } @@ -137,6 +138,16 @@ namespace rr { )); } + VALUE Object::GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Value::Maybe(object.getIsolate(), object->GetOwnPropertyDescriptor( + Context(r_context), + *rr::String(key) + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index 2466181a..5631a73a 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -18,6 +18,7 @@ namespace rr { static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); static VALUE GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key); + static VALUE GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/function_spec.rb b/spec/c/function_spec.rb index 905ada78..1bce3b0a 100644 --- a/spec/c/function_spec.rb +++ b/spec/c/function_spec.rb @@ -63,8 +63,8 @@ def call(info) end let(:data) { V8::C::Object::New(@isolate) } - let(:callback) { FunctionCallback.new @isolate} - let(:fn) { V8::C::Function::New(@isolate, callback, data)} + let(:callback) { FunctionCallback.new @isolate } + let(:fn) { V8::C::Function::New(@isolate, callback, data) } before do expect(fn.Call(@ctx.Global(), ["world"]).Utf8Value()).to eql "ohai world" diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 9bf08db4..2a22dd70 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -114,6 +114,83 @@ end end + describe '#GetOwnPropertyDescriptor' do + it 'can read the descriptor of a data property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key, data, V8::C::PropertyAttribute::DontEnum)).to be_successful + + maybe = o.GetOwnPropertyDescriptor(@ctx, key) + expect(maybe).to be_successful + + descriptor = maybe.FromJust + value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) + writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) + get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) + set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) + configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) + enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) + + expect(value).to strict_eq data + + expect(writable).to be_successful + expect(writable.FromJust.Value).to be true + + expect(get).to be_successful + expect(get.FromJust).to be_a V8::C::Undefined + + expect(set).to be_successful + expect(set.FromJust).to be_a V8::C::Undefined + + expect(configurable).to be_successful + expect(configurable.FromJust.Value).to be true + + expect(enumerable).to be_successful + expect(enumerable.FromJust.Value).to be false + end + + # it 'can read the descriptor of an accessor property' do + # o = V8::C::Object.New(@isolate) + # key = V8::C::String.NewFromUtf8(@isolate, 'foo') + # data = V8::C::String.NewFromUtf8(@isolate, 'data') + # + # getter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + # setter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + # + # expect(o.SetAccessorProperty(@ctx, key, getter, setter)).to be_successful + # + # maybe = o.GetOwnPropertyDescriptor(@ctx, key) + # expect(maybe).to be_successful + # + # descriptor = maybe.FromJust + # value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) + # writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) + # get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) + # set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) + # configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) + # enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) + # + # expect(value).to be_successful + # + # expect(writable).to be_successful + # expect(writable.FromJust.Value).to be true + # + # expect(get).to be_successful + # expect(get.FromJust).to be_a V8::C::Undefined + # + # expect(set).to be_successful + # expect(set.FromJust).to be_a V8::C::Undefined + # + # expect(configurable).to be_successful + # expect(configurable.FromJust.Value).to be true + # + # expect(enumerable).to be_successful + # expect(enumerable.FromJust.Value).to be true + # end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From 1cfbe81ceb62e51e87d70cf3421cd0e3c475d186 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sat, 1 Aug 2015 20:13:11 +0000 Subject: [PATCH 096/105] Implement Object::SetAccessorProperty --- ext/v8/object.cc | 19 +++++++++ ext/v8/object.h | 2 + spec/c/object_spec.rb | 99 ++++++++++++++++++++++++++----------------- 3 files changed, 82 insertions(+), 38 deletions(-) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 6d7857e3..b4a532bc 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -11,6 +11,7 @@ namespace rr { defineMethod("Has", &Has). defineMethod("Delete", &Delete). defineMethod("SetAccessor", &SetAccessor). + defineMethod("SetAccessorProperty", &SetAccessorProperty). defineMethod("CreateDataProperty", &CreateDataProperty). defineMethod("DefineOwnProperty", &DefineOwnProperty). defineMethod("GetPropertyAttributes", &GetPropertyAttributes). @@ -102,6 +103,24 @@ namespace rr { )); } + VALUE Object::SetAccessorProperty(int argc, VALUE* argv, VALUE self) { + VALUE r_context, name, getter, setter, attribute, settings; + rb_scan_args(argc, argv, "33", &r_context, &name, &getter, &setter, &attribute, &settings); + + Object object(self); + Locker lock(object); + + object->SetAccessorProperty( + Name(name), + *Function(getter), + RTEST(setter) ? *Function(setter) : v8::Local(), + Enum(attribute, v8::None), + Enum(settings, v8::DEFAULT) + ); + + return Qnil; + } + VALUE Object::CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value) { Object object(self); Locker lock(object); diff --git a/ext/v8/object.h b/ext/v8/object.h index 5631a73a..6dc7d767 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -13,7 +13,9 @@ namespace rr { static VALUE GetIdentityHash(VALUE self); static VALUE Has(VALUE self, VALUE r_context, VALUE key); static VALUE Delete(VALUE self, VALUE r_context, VALUE key); + static VALUE SetAccessor(int argc, VALUE* argv, VALUE self); + static VALUE SetAccessorProperty(int argc, VALUE* argv, VALUE self); static VALUE CreateDataProperty(VALUE self, VALUE r_context, VALUE key, VALUE value); static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 2a22dd70..ae39493d 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -81,6 +81,33 @@ end end + describe '#SetAccessorProperty' do + it 'can set getters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + get_value = V8::C::String.NewFromUtf8(@isolate, 'bar') + getter = V8::C::Function.New(@isolate, proc { |info| info.GetReturnValue.Set(get_value) }) + + o.SetAccessorProperty(@ctx, key, getter) + expect(o.Get(@ctx, key)).to strict_eq get_value + end + + it 'can set setters' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + + set_value = nil + + getter = V8::C::Function.New(@isolate, proc { }) + setter = V8::C::Function.New(@isolate, proc { |info| set_value = info[0] }) + + o.SetAccessorProperty(@ctx, key, getter, setter) + expect(o.Set(@ctx, key, key)).to be_successful + expect(set_value).to strict_eq key + end + end + describe '#CreateDataProperty' do it 'can set the property' do o = V8::C::Object.New(@isolate) @@ -151,44 +178,40 @@ expect(enumerable.FromJust.Value).to be false end - # it 'can read the descriptor of an accessor property' do - # o = V8::C::Object.New(@isolate) - # key = V8::C::String.NewFromUtf8(@isolate, 'foo') - # data = V8::C::String.NewFromUtf8(@isolate, 'data') - # - # getter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) - # setter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) - # - # expect(o.SetAccessorProperty(@ctx, key, getter, setter)).to be_successful - # - # maybe = o.GetOwnPropertyDescriptor(@ctx, key) - # expect(maybe).to be_successful - # - # descriptor = maybe.FromJust - # value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) - # writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) - # get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) - # set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) - # configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) - # enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) - # - # expect(value).to be_successful - # - # expect(writable).to be_successful - # expect(writable.FromJust.Value).to be true - # - # expect(get).to be_successful - # expect(get.FromJust).to be_a V8::C::Undefined - # - # expect(set).to be_successful - # expect(set.FromJust).to be_a V8::C::Undefined - # - # expect(configurable).to be_successful - # expect(configurable.FromJust.Value).to be true - # - # expect(enumerable).to be_successful - # expect(enumerable.FromJust.Value).to be true - # end + it 'can read the descriptor of an accessor property' do + o = V8::C::Object.New(@isolate) + key = V8::C::String.NewFromUtf8(@isolate, 'foo') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + getter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + setter = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + + o.SetAccessorProperty(@ctx, key, getter, setter) + + maybe = o.GetOwnPropertyDescriptor(@ctx, key) + expect(maybe).to be_successful + + descriptor = maybe.FromJust + value = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'value')) + writable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'writable')) + get = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'get')) + set = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'set')) + configurable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'configurable')) + enumerable = descriptor.Get(@ctx, V8::C::String.NewFromUtf8(@isolate, 'enumerable')) + + expect(value).to be_successful + expect(value.FromJust).to be_a V8::C::Undefined + expect(writable).to be_successful + + expect(get).to strict_eq getter + expect(set).to strict_eq setter + + expect(configurable).to be_successful + expect(configurable.FromJust.Value).to be true + + expect(enumerable).to be_successful + expect(enumerable.FromJust.Value).to be true + end end # TODO: Enable this when the methods are implemented in the extension From 397a47f8edf9e321b5368902b605da62b7a71d5f Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 2 Aug 2015 14:49:23 +0000 Subject: [PATCH 097/105] Add Object::Get(Own)PropertyNames --- ext/v8/array.h | 2 ++ ext/v8/object.cc | 16 ++++++++++++++++ ext/v8/object.h | 2 ++ spec/c/object_spec.rb | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/ext/v8/array.h b/ext/v8/array.h index da150a91..3884e172 100644 --- a/ext/v8/array.h +++ b/ext/v8/array.h @@ -13,6 +13,8 @@ namespace rr { inline Array(v8::Isolate* isolate, v8::Handle array) : Ref(isolate, array) {} inline Array(VALUE value) : Ref(value) {} + + typedef MaybeLocal Maybe; }; } diff --git a/ext/v8/object.cc b/ext/v8/object.cc index b4a532bc..65ec7e96 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -16,6 +16,8 @@ namespace rr { defineMethod("DefineOwnProperty", &DefineOwnProperty). defineMethod("GetPropertyAttributes", &GetPropertyAttributes). defineMethod("GetOwnPropertyDescriptor", &GetOwnPropertyDescriptor). + defineMethod("GetPropertyNames", &GetPropertyNames). + defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). store(&Class); } @@ -167,6 +169,20 @@ namespace rr { )); } + VALUE Object::GetPropertyNames(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); + + return Array::Maybe(object.getIsolate(), object->GetPropertyNames(Context(r_context))); + } + + VALUE Object::GetOwnPropertyNames(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); + + return Array::Maybe(object.getIsolate(), object->GetOwnPropertyNames(Context(r_context))); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index 6dc7d767..222d17e0 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -21,6 +21,8 @@ namespace rr { static VALUE DefineOwnProperty(int argc, VALUE* argv, VALUE self); static VALUE GetPropertyAttributes(VALUE self, VALUE r_context, VALUE key); static VALUE GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key); + static VALUE GetPropertyNames(VALUE self, VALUE r_context); + static VALUE GetOwnPropertyNames(VALUE self, VALUE r_context); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index ae39493d..5c97d9fe 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -214,6 +214,42 @@ end end + describe '#GetPropertyNames' do + it 'can get array of attribute names' do + o = V8::C::Object.New(@isolate) + key_one = V8::C::String.NewFromUtf8(@isolate, 'foo 1') + key_two = V8::C::String.NewFromUtf8(@isolate, 'foo 2') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key_one, data)).to be_successful + expect(o.DefineOwnProperty(@ctx, key_two, data)).to be_successful + + names = o.GetPropertyNames(@ctx) + expect(names).to be_successful + + expect(names.FromJust.Get(@ctx, 0)).to v8_eq key_one + expect(names.FromJust.Get(@ctx, 1)).to v8_eq key_two + end + end + + describe '#GetOwnPropertyNames' do + it 'can get array of attribute names' do + o = V8::C::Object.New(@isolate) + key_one = V8::C::String.NewFromUtf8(@isolate, 'foo 1') + key_two = V8::C::String.NewFromUtf8(@isolate, 'foo 2') + data = V8::C::String.NewFromUtf8(@isolate, 'data') + + expect(o.DefineOwnProperty(@ctx, key_one, data)).to be_successful + expect(o.DefineOwnProperty(@ctx, key_two, data)).to be_successful + + names = o.GetOwnPropertyNames(@ctx) + expect(names).to be_successful + + expect(names.FromJust.Get(@ctx, 0)).to v8_eq key_one + expect(names.FromJust.Get(@ctx, 1)).to v8_eq key_two + end + end + # TODO: Enable this when the methods are implemented in the extension # it 'can retrieve all property names' do # o = V8::C::Object.New From d8497996a2b56531c9bd92fd957e7e019e82a8f1 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 2 Aug 2015 15:05:52 +0000 Subject: [PATCH 098/105] Add Object::GetPrototype and Object::SetPrototype --- ext/v8/object.cc | 19 +++++++++++++ ext/v8/object.h | 2 ++ spec/c/object_spec.rb | 62 ++++++++++++++++++++++--------------------- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 65ec7e96..6665933d 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -18,6 +18,8 @@ namespace rr { defineMethod("GetOwnPropertyDescriptor", &GetOwnPropertyDescriptor). defineMethod("GetPropertyNames", &GetPropertyNames). defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). + defineMethod("GetPrototype", &GetPrototype). + defineMethod("SetPrototype", &SetPrototype). store(&Class); } @@ -183,6 +185,23 @@ namespace rr { return Array::Maybe(object.getIsolate(), object->GetOwnPropertyNames(Context(r_context))); } + VALUE Object::GetPrototype(VALUE self) { + Object object(self); + Locker lock(object); + + return Value(object.getIsolate(), object->GetPrototype()); + } + + VALUE Object::SetPrototype(VALUE self, VALUE r_context, VALUE prototype) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->SetPrototype( + Context(r_context), + *Value(prototype) + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index 222d17e0..c0eb994f 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -23,6 +23,8 @@ namespace rr { static VALUE GetOwnPropertyDescriptor(VALUE self, VALUE r_context, VALUE key); static VALUE GetPropertyNames(VALUE self, VALUE r_context); static VALUE GetOwnPropertyNames(VALUE self, VALUE r_context); + static VALUE GetPrototype(VALUE self); + static VALUE SetPrototype(VALUE self, VALUE r_context, VALUE prototype); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 5c97d9fe..8cc22751 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -250,34 +250,36 @@ end end - # TODO: Enable this when the methods are implemented in the extension - # it 'can retrieve all property names' do - # o = V8::C::Object.New - # o.Set(V8::C::String.New('foo'), V8::C::String.New('bar')) - # o.Set(V8::C::String.New('baz'), V8::C::String.New('bang')) - # - # names = o.GetPropertyNames() - # names.Length().should eql 2 - # names.Get(0).Utf8Value().should eql 'foo' - # names.Get(1).Utf8Value().should eql 'baz' - # end - # - # it 'can set an accessor from ruby' do - # o = V8::C::Object.New - # property = V8::C::String.New('statement') - # callback_data = V8::C::String.New('I am Legend') - # left = V8::C::String.New('Yo! ') - # getter = proc do |name, info| - # info.This().StrictEquals(o).should be_true - # info.Holder().StrictEquals(o).should be_true - # V8::C::String::Concat(left, info.Data()) - # end - # setter = proc do |name, value, info| - # left = value - # end - # o.SetAccessor(property, getter, setter, callback_data) - # o.Get(property).Utf8Value().should eql 'Yo! I am Legend' - # o.Set(property, V8::C::String::New('Bro! ')) - # o.Get(property).Utf8Value().should eql 'Bro! I am Legend' - # end + context 'with prototypes' do + let(:o) { V8::C::Object.New(@isolate) } + let(:prototype) { V8::C::Object.New(@isolate) } + let(:prototype_key) { V8::C::String.NewFromUtf8(@isolate, 'foo') } + let(:o_key) { V8::C::String.NewFromUtf8(@isolate, 'bar') } + + before do + expect(prototype.Set(@ctx, prototype_key, prototype_key)).to be_successful + expect(o.Set(@ctx, o_key, o_key)).to be_successful + + expect(o.SetPrototype(@ctx, prototype)).to be_successful + end + + it 'can set the object prototype' do + expect(o.Get(@ctx, prototype_key)).to strict_eq prototype_key + end + + it 'can enumerate only own properties' do + names = o.GetOwnPropertyNames(@ctx) + + expect(names).to be_successful + expect(names.FromJust.Get(@ctx, 0)).to v8_eq o_key + end + + it 'can enumerate all properties' do + names = o.GetPropertyNames(@ctx) + + expect(names).to be_successful + expect(names.FromJust.Get(@ctx, 0)).to v8_eq o_key + expect(names.FromJust.Get(@ctx, 1)).to v8_eq prototype_key + end + end end From 200d6170894a902969c52f862b679320c41e417d Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 2 Aug 2015 15:15:13 +0000 Subject: [PATCH 099/105] Add Object::ObjectProtoToString --- ext/v8/object.cc | 8 ++++++++ ext/v8/object.h | 2 ++ ext/v8/rr_string.h | 2 ++ spec/c/object_spec.rb | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/ext/v8/object.cc b/ext/v8/object.cc index 6665933d..be803974 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -20,6 +20,7 @@ namespace rr { defineMethod("GetOwnPropertyNames", &GetOwnPropertyNames). defineMethod("GetPrototype", &GetPrototype). defineMethod("SetPrototype", &SetPrototype). + defineMethod("ObjectProtoToString", &ObjectProtoToString). store(&Class); } @@ -202,6 +203,13 @@ namespace rr { )); } + VALUE Object::ObjectProtoToString(VALUE self, VALUE r_context) { + Object object(self); + Locker lock(object); + + return String::Maybe(object.getIsolate(), object->ObjectProtoToString(Context(r_context))); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index c0eb994f..6d536620 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -26,6 +26,8 @@ namespace rr { static VALUE GetPrototype(VALUE self); static VALUE SetPrototype(VALUE self, VALUE r_context, VALUE prototype); + static VALUE ObjectProtoToString(VALUE self, VALUE r_context); + inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/ext/v8/rr_string.h b/ext/v8/rr_string.h index 698dd903..cc844467 100644 --- a/ext/v8/rr_string.h +++ b/ext/v8/rr_string.h @@ -15,6 +15,8 @@ namespace rr { inline String(v8::Isolate* isolate, v8::Handle string) : Ref(isolate, string) {} virtual operator v8::Handle() const; + + typedef MaybeLocal Maybe; }; } diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index 8cc22751..ef9fa038 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -282,4 +282,24 @@ expect(names.FromJust.Get(@ctx, 1)).to v8_eq prototype_key end end + + describe '#ObjectProtoToString' do + it 'can return a string representation of simple objects' do + o = V8::C::Object.New(@isolate) + str = o.ObjectProtoToString(@ctx) + + expect(str).to be_successful + expect(str.FromJust.Utf8Value).to eq '[object Object]' + end + + it 'can return a string representation of simple objects' do + a = V8::C::Array.New(@isolate) + a.Set(@ctx, 0, 2) + a.Set(@ctx, 1, 3) + + str = a.ObjectProtoToString(@ctx) + expect(str).to be_successful + expect(str.FromJust.Utf8Value).to eq '[object Array]' + end + end end From 5ac3483215209d3cae3d2025560702c47c9fa6b3 Mon Sep 17 00:00:00 2001 From: Georgy Angelov Date: Sun, 2 Aug 2015 16:57:00 +0000 Subject: [PATCH 100/105] Implement the rest of the Object mehods: - GetConstructorName - InternalFieldCount - GetInternalField - SetInternalField - HasOwnProperty - HasRealNamedProperty - HasRealIndexedProperty - HasRealNamedCallbackProperty - GetRealNamedPropertyInPrototypeChain - GetRealNamedProperty - GetRealNamedPropertyAttributes - GetRealNamedPropertyAttributesInPrototypeChain - HasNamedLookupInterceptor - HasIndexedLookupInterceptor - SetHiddenValue - GetHiddenValue - DeleteHiddenValue - Clone - CreationContext - IsCallable - CallAsFunction - CallAsConstructor --- ext/v8/object-template.h | 18 +++- ext/v8/object.cc | 208 +++++++++++++++++++++++++++++++++++++++ ext/v8/object.h | 29 ++++++ spec/c/object_spec.rb | 138 +++++++++++++++++++++++++- 4 files changed, 388 insertions(+), 5 deletions(-) diff --git a/ext/v8/object-template.h b/ext/v8/object-template.h index b25682b6..98f1342f 100644 --- a/ext/v8/object-template.h +++ b/ext/v8/object-template.h @@ -8,10 +8,12 @@ namespace rr { ObjectTemplate(VALUE self) : Ref(self) {} ObjectTemplate(v8::Isolate* isolate, v8::Handle tmpl) : Ref(isolate, tmpl) {} + inline static void Init() { ClassBuilder("ObjectTemplate", Template::Class). defineSingletonMethod("New", &New). defineMethod("NewInstance", &NewInstance). + defineMethod("SetInternalFieldCount", &SetInternalFieldCount). store(&Class); } @@ -20,16 +22,26 @@ namespace rr { rb_scan_args(argc, argv, "11", &r_isolate, &r_constructor); Isolate isolate(r_isolate); Locker lock(isolate); + return ObjectTemplate(isolate, v8::ObjectTemplate::New(isolate)); } - static VALUE NewInstance(VALUE self , VALUE r_context) { + static VALUE NewInstance(VALUE self, VALUE r_context) { ObjectTemplate t(self); Context context(r_context); Isolate isolate(context.getIsolate()); Locker lock(isolate); - v8::MaybeLocal object(t->NewInstance()); - return Object::Maybe(isolate, ObjectTemplate(self)->NewInstance(context)); + + return Object::Maybe(isolate, t->NewInstance(context)); + } + + static VALUE SetInternalFieldCount(VALUE self, VALUE value) { + ObjectTemplate t(self); + Locker lock(t); + + t->SetInternalFieldCount(NUM2INT(value)); + + return Qnil; } }; } diff --git a/ext/v8/object.cc b/ext/v8/object.cc index be803974..53015aca 100644 --- a/ext/v8/object.cc +++ b/ext/v8/object.cc @@ -21,6 +21,28 @@ namespace rr { defineMethod("GetPrototype", &GetPrototype). defineMethod("SetPrototype", &SetPrototype). defineMethod("ObjectProtoToString", &ObjectProtoToString). + defineMethod("GetConstructorName", &GetConstructorName). + defineMethod("InternalFieldCount", &InternalFieldCount). + defineMethod("GetInternalField", &GetInternalField). + defineMethod("SetInternalField", &SetInternalField). + defineMethod("HasOwnProperty", &HasOwnProperty). + defineMethod("HasRealNamedProperty", &HasRealNamedProperty). + defineMethod("HasRealIndexedProperty", &HasRealIndexedProperty). + defineMethod("HasRealNamedCallbackProperty", &HasRealNamedCallbackProperty). + defineMethod("GetRealNamedPropertyInPrototypeChain", &GetRealNamedPropertyInPrototypeChain). + defineMethod("GetRealNamedProperty", &GetRealNamedProperty). + defineMethod("GetRealNamedPropertyAttributes", &GetRealNamedPropertyAttributes). + defineMethod("GetRealNamedPropertyAttributesInPrototypeChain", &GetRealNamedPropertyAttributesInPrototypeChain). + defineMethod("HasNamedLookupInterceptor", &HasNamedLookupInterceptor). + defineMethod("HasIndexedLookupInterceptor", &HasIndexedLookupInterceptor). + defineMethod("SetHiddenValue", &SetHiddenValue). + defineMethod("GetHiddenValue", &GetHiddenValue). + defineMethod("DeleteHiddenValue", &DeleteHiddenValue). + defineMethod("Clone", &Clone). + defineMethod("CreationContext", &CreationContext). + defineMethod("IsCallable", &IsCallable). + defineMethod("CallAsFunction", &CallAsFunction). + defineMethod("CallAsConstructor", &CallAsConstructor). store(&Class); } @@ -210,6 +232,192 @@ namespace rr { return String::Maybe(object.getIsolate(), object->ObjectProtoToString(Context(r_context))); } + VALUE Object::GetConstructorName(VALUE self) { + Object object(self); + Locker lock(object); + + return String(object.getIsolate(), object->GetConstructorName()); + } + + VALUE Object::InternalFieldCount(VALUE self) { + Object object(self); + Locker lock(object); + + return INT2FIX(object->InternalFieldCount()); + } + + VALUE Object::GetInternalField(VALUE self, VALUE index) { + Object object(self); + Locker lock(object); + + return Value(object.getIsolate(), object->GetInternalField(NUM2INT(index))); + } + + VALUE Object::SetInternalField(VALUE self, VALUE index, VALUE value) { + Object object(self); + Locker lock(object); + + object->SetInternalField(NUM2INT(index), *Value(value)); + + return Qnil; + } + + VALUE Object::HasOwnProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->HasOwnProperty(Context(r_context), *Name(key))); + } + + VALUE Object::HasRealNamedProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->HasRealNamedProperty(Context(r_context), *Name(key))); + } + + VALUE Object::HasRealIndexedProperty(VALUE self, VALUE r_context, VALUE index) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->HasRealIndexedProperty(Context(r_context), NUM2INT(index))); + } + + VALUE Object::HasRealNamedCallbackProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Bool::Maybe(object->HasRealNamedCallbackProperty(Context(r_context), *Name(key))); + } + + VALUE Object::GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Value::Maybe(object.getIsolate(), object->GetRealNamedPropertyInPrototypeChain( + Context(r_context), + *Name(key) + )); + } + + VALUE Object::GetRealNamedProperty(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Value::Maybe(object.getIsolate(), object->GetRealNamedProperty( + Context(r_context), + *Name(key) + )); + } + + VALUE Object::GetRealNamedPropertyAttributes(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Enum::Maybe(object->GetRealNamedPropertyAttributes( + Context(r_context), + *Name(key) + )); + } + + VALUE Object::GetRealNamedPropertyAttributesInPrototypeChain(VALUE self, VALUE r_context, VALUE key) { + Object object(self); + Locker lock(object); + + return Enum::Maybe(object->GetRealNamedPropertyAttributesInPrototypeChain( + Context(r_context), + *Name(key) + )); + } + + VALUE Object::HasNamedLookupInterceptor(VALUE self) { + Object object(self); + Locker lock(object); + + return Bool(object->HasNamedLookupInterceptor()); + } + + VALUE Object::HasIndexedLookupInterceptor(VALUE self) { + Object object(self); + Locker lock(object); + + return Bool(object->HasIndexedLookupInterceptor()); + } + + VALUE Object::SetHiddenValue(VALUE self, VALUE key, VALUE value) { + Object object(self); + Locker lock(object); + + return Bool(object->SetHiddenValue(String(key), Value(value))); + } + + VALUE Object::GetHiddenValue(VALUE self, VALUE key) { + Object object(self); + Locker lock(object); + + return Value(object.getIsolate(), object->GetHiddenValue(String(key))); + } + + VALUE Object::DeleteHiddenValue(VALUE self, VALUE key) { + Object object(self); + Locker lock(object); + + return Bool(object->DeleteHiddenValue(String(key))); + } + + VALUE Object::Clone(VALUE self) { + Object object(self); + Locker lock(object); + + return Object(object.getIsolate(), object->Clone()); + } + + VALUE Object::CreationContext(VALUE self) { + Object object(self); + Locker lock(object); + + return Context(object->CreationContext()); + } + + VALUE Object::IsCallable(VALUE self) { + Object object(self); + Locker lock(object); + + return Bool(object->IsCallable()); + } + + VALUE Object::CallAsFunction(int argc, VALUE* argv, VALUE self) { + VALUE r_context, recv, r_argv; + rb_scan_args(argc, argv, "30", &r_context, &recv, &r_argv); + + Object object(self); + v8::Isolate* isolate = object.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, r_argv)); + + return Value::Maybe(isolate, object->CallAsFunction( + Context(r_context), + Value(recv), + RARRAY_LENINT(r_argv), + &vector[0] + )); + } + + VALUE Object::CallAsConstructor(VALUE self, VALUE r_context, VALUE argv) { + Object object(self); + v8::Isolate* isolate = object.getIsolate(); + Locker lock(isolate); + + std::vector< v8::Handle > vector(Value::convertRubyArray(isolate, argv)); + + return Value::Maybe(isolate, object->CallAsConstructor( + Context(r_context), + RARRAY_LENINT(argv), + &vector[0] + )); + } + Object::operator VALUE() { Isolate isolate(getIsolate()); Locker lock(isolate); diff --git a/ext/v8/object.h b/ext/v8/object.h index 6d536620..0ed309f2 100644 --- a/ext/v8/object.h +++ b/ext/v8/object.h @@ -27,6 +27,35 @@ namespace rr { static VALUE SetPrototype(VALUE self, VALUE r_context, VALUE prototype); static VALUE ObjectProtoToString(VALUE self, VALUE r_context); + static VALUE GetConstructorName(VALUE self); + + static VALUE InternalFieldCount(VALUE self); + static VALUE GetInternalField(VALUE self, VALUE index); + static VALUE SetInternalField(VALUE self, VALUE index, VALUE value); + + static VALUE HasOwnProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE HasRealNamedProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE HasRealIndexedProperty(VALUE self, VALUE r_context, VALUE index); + static VALUE HasRealNamedCallbackProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyInPrototypeChain(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedProperty(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyAttributes(VALUE self, VALUE r_context, VALUE key); + static VALUE GetRealNamedPropertyAttributesInPrototypeChain(VALUE self, VALUE r_context, VALUE key); + + static VALUE HasNamedLookupInterceptor(VALUE self); + static VALUE HasIndexedLookupInterceptor(VALUE self); + + static VALUE SetHiddenValue(VALUE self, VALUE key, VALUE value); + static VALUE GetHiddenValue(VALUE self, VALUE key); + static VALUE DeleteHiddenValue(VALUE self, VALUE key); + + static VALUE Clone(VALUE self); + static VALUE CreationContext(VALUE self); + + static VALUE IsCallable(VALUE self); + + static VALUE CallAsFunction(int argc, VALUE* argv, VALUE self); + static VALUE CallAsConstructor(VALUE self, VALUE r_context, VALUE argv); inline Object(VALUE value) : Ref(value) {} inline Object(v8::Isolate* isolate, v8::Handle object) : Ref(isolate, object) {} diff --git a/spec/c/object_spec.rb b/spec/c/object_spec.rb index ef9fa038..1a43b478 100644 --- a/spec/c/object_spec.rb +++ b/spec/c/object_spec.rb @@ -272,6 +272,14 @@ expect(names).to be_successful expect(names.FromJust.Get(@ctx, 0)).to v8_eq o_key + + has_own = o.HasOwnProperty(@ctx, o_key) + expect(has_own.IsJust).to be true + expect(has_own.FromJust).to be true + + has_own = o.HasOwnProperty(@ctx, prototype_key) + expect(has_own.IsJust).to be true + expect(has_own.FromJust).to be false end it 'can enumerate all properties' do @@ -294,12 +302,138 @@ it 'can return a string representation of simple objects' do a = V8::C::Array.New(@isolate) - a.Set(@ctx, 0, 2) - a.Set(@ctx, 1, 3) str = a.ObjectProtoToString(@ctx) expect(str).to be_successful expect(str.FromJust.Utf8Value).to eq '[object Array]' end end + + describe '#GetConstructorName' do + it 'can return the constructor name of an array' do + a = V8::C::Array.New(@isolate) + + expect(a.GetConstructorName().Utf8Value).to eq 'Array' + end + end + + context 'with internal fields' do + let(:o) do + template = V8::C::ObjectTemplate.New(@isolate) + template.SetInternalFieldCount(2) + + template.NewInstance(@ctx).FromJust + end + + let(:value_one) { V8::C::String.NewFromUtf8(@isolate, 'value 1') } + let(:value_two) { V8::C::String.NewFromUtf8(@isolate, 'value 2') } + + before do + o.SetInternalField(0, value_one) + o.SetInternalField(1, value_two) + end + + it 'can get and set internal fields' do + expect(o.GetInternalField(0)).to strict_eq value_one + expect(o.GetInternalField(1)).to strict_eq value_two + end + + it 'can get the internal field count' do + expect(o.InternalFieldCount).to eq 2 + end + end + + context 'with hidden values' do + let(:o) { V8::C::Object.New @isolate } + + let(:key_one) { V8::C::String.NewFromUtf8(@isolate, 'key 1') } + let(:key_two) { V8::C::String.NewFromUtf8(@isolate, 'key 2') } + let(:value_one) { V8::C::String.NewFromUtf8(@isolate, 'value 1') } + let(:value_two) { V8::C::String.NewFromUtf8(@isolate, 'value 2') } + + before do + o.SetHiddenValue(key_one, value_one) + o.SetHiddenValue(key_two, value_two) + end + + it 'can get and set hidden values' do + expect(o.GetHiddenValue(key_one)).to strict_eq value_one + expect(o.GetHiddenValue(key_two)).to strict_eq value_two + end + + it 'can delete hidden values' do + expect(o.DeleteHiddenValue(key_one)).to be true + expect(o.GetHiddenValue(key_one)).to be nil + end + end + + describe '#Clone' do + let(:o) { V8::C::Object.New @isolate } + + let(:key) { V8::C::String.NewFromUtf8(@isolate, 'key') } + let(:value) { V8::C::String.NewFromUtf8(@isolate, 'value') } + + before do + o.Set(@ctx, key, value) + end + + it 'can clone an object' do + clone = o.Clone + + expect(clone).to_not strict_eq o + + expect(clone.Get(@ctx, key)).to strict_eq value + end + end + + describe '#CreationContext' do + it 'returns a context' do + o = V8::C::Object.New @isolate + expect(o.CreationContext).to be_a V8::C::Context + end + end + + describe '#IsCallable' do + it 'returns false for plain objects' do + o = V8::C::Object.New @isolate + expect(o.IsCallable).to be false + end + + it 'returns true for functions' do + fn = V8::C::Function.New @isolate, proc { }, V8::C::Object::New(@isolate) + expect(fn.IsCallable).to be true + end + end + + describe 'callable objects' do + let(:value) { V8::C::String.NewFromUtf8(@isolate, 'value') } + let(:one) { V8::C::String.NewFromUtf8(@isolate, 'one') } + let(:two) { V8::C::String.NewFromUtf8(@isolate, 'two') } + + let(:fn) do + source = '(function(one, two) {this.one = one; this.two = two; return this;})' + source = V8::C::String.NewFromUtf8(@isolate, source.to_s) + script = V8::C::Script.Compile(@ctx, source) + result = script.FromJust.Run(@ctx) + + result.FromJust + end + + it 'can be called as functions' do + object = V8::C::Object::New(@isolate) + result = fn.CallAsFunction(@ctx, object, [one, two]).FromJust + + expect(result).to strict_eq object + expect(object.Get(@ctx, one)).to strict_eq one + expect(object.Get(@ctx, two)).to strict_eq two + end + + it 'can be called as constructors' do + result = fn.CallAsConstructor(@ctx, [one, two]).FromJust + + expect(result).to be_a V8::C::Object + expect(result.Get(@ctx, one)).to strict_eq one + expect(result.Get(@ctx, two)).to strict_eq two + end + end end From 2c0b979a7ee376eac0dea89b9239dc6eda357089 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 12 Aug 2015 17:47:43 +0300 Subject: [PATCH 101/105] add support for TryCatch along with StackTrace --- ext/v8/class_builder.cc | 8 ++ ext/v8/class_builder.h | 1 + ext/v8/equiv.h | 4 + ext/v8/init.cc | 9 +- ext/v8/int.h | 54 +++++++++++ ext/v8/isolate.cc | 13 ++- ext/v8/isolate.h | 1 + ext/v8/message.h | 95 ++++++++++++++++++ ext/v8/rr.h | 6 ++ ext/v8/stack-frame.h | 70 ++++++++++++++ ext/v8/stack-trace.h | 70 ++++++++++++++ ext/v8/try-catch.cc | 5 + ext/v8/try-catch.h | 192 +++++++++++++++++++++++++++++++++++++ ext/v8/value.cc | 9 +- ext/v8/value.h | 2 +- spec/c/stack_trace_spec.rb | 18 ++++ spec/c/try_catch_spec.rb | 70 ++++++++++++++ 17 files changed, 619 insertions(+), 8 deletions(-) create mode 100644 ext/v8/int.h create mode 100644 ext/v8/message.h create mode 100644 ext/v8/stack-frame.h create mode 100644 ext/v8/stack-trace.h create mode 100644 ext/v8/try-catch.cc create mode 100644 ext/v8/try-catch.h create mode 100644 spec/c/stack_trace_spec.rb create mode 100644 spec/c/try_catch_spec.rb diff --git a/ext/v8/class_builder.cc b/ext/v8/class_builder.cc index 1413e1c2..00cd6f2b 100644 --- a/ext/v8/class_builder.cc +++ b/ext/v8/class_builder.cc @@ -41,6 +41,14 @@ namespace rr { return *this; } + ClassBuilder& ClassBuilder::defineConstMethod(const char* name, VALUE value) { + VALUE symbol = rb_funcall(rb_str_cat2(rb_str_new2("@"), name), rb_intern("to_sym"), 0); + VALUE singleton_class = rb_funcall(this->value, rb_intern("singleton_class"), 0); + rb_ivar_set(this->value, SYM2ID(symbol), value); + rb_funcall(singleton_class, rb_intern("attr_reader"), 1, ID2SYM(rb_intern(name))); + return *this; + } + ClassBuilder& ClassBuilder::defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)) { rb_define_method(this->value, name, (VALUE (*)(...))impl, -1); return *this; diff --git a/ext/v8/class_builder.h b/ext/v8/class_builder.h index b2358f9c..41f5a846 100644 --- a/ext/v8/class_builder.h +++ b/ext/v8/class_builder.h @@ -16,6 +16,7 @@ namespace rr { ClassBuilder(const char* name, const char* supername); ClassBuilder& defineConst(const char* name, VALUE value); + ClassBuilder& defineConstMethod(const char* name, VALUE value); ClassBuilder& defineMethod(const char* name, VALUE (*impl)(int, VALUE*, VALUE)); ClassBuilder& defineMethod(const char* name, VALUE (*impl)(VALUE)); diff --git a/ext/v8/equiv.h b/ext/v8/equiv.h index f0fa8828..82578c43 100644 --- a/ext/v8/equiv.h +++ b/ext/v8/equiv.h @@ -23,6 +23,10 @@ namespace rr { * Internally, `Equiv`s are always stored as a Ruby `VALUE`, and so * part of the job of the subclass is to have an appropriate * constructor that converts the C/C++ type to a `VALUE` + * + * It is handy to have a class to do the conversions instead of + * preprocessor macros like `NUM2INT`, et al. because classes can be + * easily used in C++ templates. */ class Equiv { public: diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 29a37886..41c97d97 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -43,15 +43,14 @@ extern "C" { ObjectTemplate::Init(); FunctionTemplate::Init(); Signature::Init(); + StackFrame::Init(); + StackTrace::Init(); + Message::Init(); + TryCatch::Init(); // Invocation::Init(); - // Signature::Init(); - // Date::Init(); // Constants::Init(); // Template::Init(); - // Stack::Init(); - // Message::Init(); - // TryCatch::Init(); // Exception::Init(); // ResourceConstraints::Init(); // HeapStatistics::Init(); diff --git a/ext/v8/int.h b/ext/v8/int.h new file mode 100644 index 00000000..92376694 --- /dev/null +++ b/ext/v8/int.h @@ -0,0 +1,54 @@ +// -*- mode: c++ -*- +#ifndef RR_INT_H +#define RR_INT_H + + +namespace rr { + /** + * Converts between Ruby `Number` and the C/C++ `int`. + * + * This allows you to easily pass in `int` values whenever a + * Ruby VALUE is expected (such as a method call) E.g. + * + * int myInt = 5; + * rb_funcall(Uint32_t(myInt), rb_intern("to_s")); //=> + * + * It also converts a Ruby `VALUE` into its corresponding + * `int`: + * + * intt myInt = Int(rb_eval_string("5")); //=> 5 + * + * Like all `Equiv`s, it stores itself internally as a Ruby `VALUE` + */ + class Int : public Equiv { + public: + /** + * Use to convert methods that return Maybe to a Ruby + * VALUE + * + * return Int::Maybe(stack_trace->GetStartColumn(context)); + */ + typedef Equiv::Maybe Maybe; + + /** + * Construct an Int from a Ruby `VALUE` + */ + Int(VALUE val) : Equiv(val) {} + + /** + * Construct an Int from a `int` by converting it into its + * corresponding `VALUE`. + */ + Int(int i) : Equiv(INT2FIX(i)) {} + + /** + * Coerce the Ruby `VALUE` into an `int`. + */ + inline operator int() { + return RTEST(value) ? NUM2UINT(value) : 0; + } + }; + +} + +#endif diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index a185128b..2a63e21e 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -1,6 +1,5 @@ // -*- mode: c++ -*- #include "rr.h" -#include "isolate.h" namespace rr { @@ -11,6 +10,7 @@ namespace rr { ClassBuilder("Isolate"). defineSingletonMethod("New", &New). + defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). store(&Class); @@ -33,6 +33,17 @@ namespace rr { return Isolate(isolate); } + VALUE Isolate::SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options) { + Isolate isolate(self); + Locker lock(isolate); + + isolate->SetCaptureStackTraceForUncaughtExceptions( + Bool(capture), + RTEST(stack_limit) ? NUM2INT(stack_limit) : 10, + Enum(options, v8::StackTrace::kOverview)); + return Qnil; + } + VALUE Isolate::IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds) { Isolate isolate(self); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 59d27821..0837bdb3 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -30,6 +30,7 @@ namespace rr { static void Init(); static VALUE New(VALUE self); + static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); inline Isolate(IsolateData* data_) : data(data_) {} inline Isolate(v8::Isolate* isolate) : diff --git a/ext/v8/message.h b/ext/v8/message.h new file mode 100644 index 00000000..20ed82c7 --- /dev/null +++ b/ext/v8/message.h @@ -0,0 +1,95 @@ +// -*- mode: c++ -*- + +#ifndef RR_MESSAGE_H +#define RR_MESSAGE_H +#include "rr.h" + +namespace rr { + class Message : public Ref { + public: + Message(VALUE self) : Ref(self) {} + Message(v8::Isolate* isolate, v8::Local msg) : + Ref(isolate, msg) {} + + static VALUE Get(VALUE self) { + Message message(self); + Locker lock(message); + return String(message, message->Get()); + } + static VALUE GetSourceLine(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return String::Maybe(message, message->GetSourceLine(Context(cxt))); + } + static VALUE GetScriptOrigin(VALUE self) { + Message message(self); + Locker lock(message); + return ScriptOrigin(message, message->GetScriptOrigin()); + } + static VALUE GetScriptResourceName(VALUE self) { + Message message(self); + Locker lock(message); + return Value(message, message->GetScriptResourceName()); + } + static VALUE GetStackTrace(VALUE self) { + Message message(self); + Locker lock(message); + return StackTrace(message, message->GetStackTrace()); + } + static VALUE GetLineNumber(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetLineNumber(Context(cxt))); + } + static VALUE GetStartPosition(VALUE self) { + Message message(self); + Locker lock(message); + return Int(message->GetStartPosition()); + } + static VALUE GetEndPosition(VALUE self) { + Message message(self); + Locker lock(message); + return Int(message->GetEndPosition()); + } + static VALUE GetStartColumn(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetStartColumn(Context(cxt))); + } + static VALUE GetEndColumn(VALUE self, VALUE cxt) { + Message message(self); + Locker lock(message); + return Int::Maybe(message->GetEndColumn(Context(cxt))); + } + static VALUE IsOpaque(VALUE self) { + Message message(self); + Locker lock(message); + return Bool(message->IsOpaque()); + } + static VALUE IsSharedCrossOrigin(VALUE self) { + Message message(self); + Locker lock(message); + return Bool(message->IsSharedCrossOrigin()); + } + + static inline void Init() { + ClassBuilder("Message"). + defineMethod("Get", &Get). + defineMethod("GetSourceLine", &GetSourceLine). + defineMethod("GetScriptOrigin", &GetScriptOrigin). + defineMethod("GetScriptResourceName", &GetScriptResourceName). + defineMethod("GetStackTrace", &GetStackTrace). + defineMethod("GetLineNumber", &GetLineNumber). + defineMethod("GetStartPosition", &GetStartPosition). + defineMethod("GetEndPosition", &GetEndPosition). + defineMethod("GetStartColumn", &GetStartColumn). + defineMethod("GetEndColumn", &GetEndColumn). + defineMethod("IsSharedCrossOrigin", &IsSharedCrossOrigin). + defineMethod("IsOpaque", &IsOpaque). + store(&Class); + } + }; +} + + +#endif /* RR_MESSAGE_H */ diff --git a/ext/v8/rr.h b/ext/v8/rr.h index e68b34ec..0d1d8c66 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -23,6 +23,7 @@ inline VALUE not_implemented(const char* message) { #include "equiv.h" #include "bool.h" +#include "int.h" #include "uint32_t.h" #include "pointer.h" #include "wrapper.h" @@ -68,4 +69,9 @@ inline VALUE not_implemented(const char* message) { #include "function-template.h" #include "object-template.h" +#include "stack-frame.h" +#include "stack-trace.h" +#include "message.h" +#include "try-catch.h" + #endif diff --git a/ext/v8/stack-frame.h b/ext/v8/stack-frame.h new file mode 100644 index 00000000..d683e09a --- /dev/null +++ b/ext/v8/stack-frame.h @@ -0,0 +1,70 @@ +// -*- mode: c++ -*- + +#ifndef RR_STACK_FRAME_H +#define RR_STACK_FRAME_H +#include "rr.h" + +namespace rr { + class StackFrame : public Ref { + public: + StackFrame(VALUE self) : Ref(self) {} + StackFrame(v8::Isolate* isolate, v8::Local frame) : + Ref(isolate, frame) {} + + static VALUE GetLineNumber(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetLineNumber()); + } + static VALUE GetColumn(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetColumn()); + } + static VALUE GetScriptId(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return INT2FIX(frame->GetScriptId()); + } + static VALUE GetScriptName(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetScriptName()); + } + static VALUE GetScriptNameOrSourceURL(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetScriptNameOrSourceURL()); + } + static VALUE GetFunctionName(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return String(frame, frame->GetFunctionName()); + } + static VALUE IsEval(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return Bool(frame->IsEval()); + } + static VALUE IsConstructor(VALUE self) { + StackFrame frame(self); + Locker lock(frame); + return Bool(frame->IsConstructor()); + } + + static inline void Init() { + ClassBuilder("StackFrame"). + defineMethod("GetLineNumber", &GetLineNumber). + defineMethod("GetColumn", &GetColumn). + defineMethod("GetScriptId", &GetScriptId). + defineMethod("GetScriptName", &GetScriptName). + defineMethod("GetScriptNameOrSourceURL", &GetScriptNameOrSourceURL). + defineMethod("GetFunctionName", &GetFunctionName). + defineMethod("IsEval", &IsEval). + defineMethod("IsConstructor", &IsConstructor). + store(&Class); + } + }; +} + +#endif /* RR_STACK_FRAME_H */ diff --git a/ext/v8/stack-trace.h b/ext/v8/stack-trace.h new file mode 100644 index 00000000..8d6c14eb --- /dev/null +++ b/ext/v8/stack-trace.h @@ -0,0 +1,70 @@ +// -*- mode: c++ -*- +#ifndef RR_STACK_TRACE_H +#define RR_STACK_TRACE_H +#include "rr.h" + +namespace rr { + class StackTrace : public Ref { + public: + StackTrace(VALUE self) : Ref(self) {} + StackTrace(v8::Isolate* isolate, v8::Local trace) : + Ref(isolate, trace) {} + + static VALUE GetFrame(VALUE self, VALUE offset) { + StackTrace stack(self); + Locker lock(stack); + + return StackFrame(stack, stack->GetFrame(NUM2INT(offset))); + } + + static VALUE GetFrameCount(VALUE self) { + StackTrace stack(self); + Locker lock(stack); + + return INT2FIX(stack->GetFrameCount()); + } + + static VALUE AsArray(VALUE self) { + StackTrace stack(self); + Locker lock(stack); + + return Array(stack, stack->AsArray()); + } + + static VALUE CurrentStackTrace(VALUE self, VALUE rb_isolate, VALUE frame_limit, VALUE options) { + Isolate isolate(rb_isolate); + Locker lock(isolate); + + return StackTrace( + isolate, + v8::StackTrace::CurrentStackTrace( + isolate, + NUM2UINT(frame_limit), + Enum(options, v8::StackTrace::kOverview))); + } + + inline static void Init() { + ClassBuilder("StackTrace"). + defineMethod("GetFrame", &GetFrame). + defineMethod("GetFrameCount", &GetFrameCount). + defineMethod("AsArray", &AsArray). + defineSingletonMethod("CurrentStackTrace", &CurrentStackTrace). + + defineConstMethod("kLineNumber", INT2FIX(v8::StackTrace::kLineNumber)). + defineConstMethod("kColumnOffset", INT2FIX(v8::StackTrace::kColumnOffset)). + defineConstMethod("kScriptName", INT2FIX(v8::StackTrace::kScriptName)). + defineConstMethod("kFunctionName", INT2FIX(v8::StackTrace::kFunctionName)). + defineConstMethod("kIsEval", INT2FIX(v8::StackTrace::kIsEval)). + defineConstMethod("kIsConstructor", INT2FIX(v8::StackTrace::kIsConstructor)). + defineConstMethod("kScriptNameOrSourceURL", INT2FIX(v8::StackTrace::kScriptNameOrSourceURL)). + defineConstMethod("kScriptId", INT2FIX(v8::StackTrace::kScriptId)). + defineConstMethod("kExposeFramesAcrossSecurityOrigins", INT2FIX(v8::StackTrace::kExposeFramesAcrossSecurityOrigins)). + defineConstMethod("kOverview", INT2FIX(v8::StackTrace::kOverview)). + defineConstMethod("kDetailed", INT2FIX(v8::StackTrace::kDetailed)). + store(&Class); + } + }; + +} + +#endif /* RR_STACK_TRACE_H */ diff --git a/ext/v8/try-catch.cc b/ext/v8/try-catch.cc new file mode 100644 index 00000000..b5f12421 --- /dev/null +++ b/ext/v8/try-catch.cc @@ -0,0 +1,5 @@ +#include "rr.h" + +namespace rr { + VALUE TryCatch::Class; +} diff --git a/ext/v8/try-catch.h b/ext/v8/try-catch.h new file mode 100644 index 00000000..cd51fa2e --- /dev/null +++ b/ext/v8/try-catch.h @@ -0,0 +1,192 @@ +// -*- mode: c++ -*- + +#ifndef RR_TRY_CATCH_H +#define RR_TRY_CATCH_H +#include "rr.h" + +namespace rr { + /** + * V8::C::TryCatch is both a class and a method. + * + * Use the method to evaluate a block of Ruby code from within a + * v8::TryCatch. The block will receive an instance of the + * `V8::C::TryCatch` class as its argument E.g. + * + * V8::C::TryCatch(isolate) do |trycatch| + * trycatch.class #=> V8::C::TryCatch + * cxt.eval('throw new Error()') + * trycatch.HasCaught() #=> true + * end + * + * Note that the `trycatch` value yielded to the block must never be + * used outside the block since it references the `v8::TryCatch` C++ + * object which is allocated on the stack. See `doTryCatch` and + * `call` for the implementation of the block parameters. + * + * Note: Ideally, we'd like to make the `Wrapper` class as a super + * class to handle the wrapping and unwrapping of the TryCatch, but + * that would require introducing pass-by-reference semantics to the + * wrapper template (since v8::TryCatch disables the copy + * constructor). Satisfying the compiler proved too challenging for + * this poor implementer. + */ + class TryCatch { + public: + TryCatch(VALUE self) : container(Container::unwrap(self)) {} + TryCatch(v8::Isolate* isolate, v8::TryCatch& trycatch) : + container(new Container(isolate, trycatch)) {} + + static VALUE HasCaught(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->HasCaught()); + } + static VALUE CanContinue(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->CanContinue()); + } + static VALUE HasTerminated(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Bool(tc->HasTerminated()); + } + static VALUE ReThrow(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Value(tc, tc->ReThrow()); + } + static VALUE Exception(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return Value(tc, tc->Exception()); + } + static VALUE StackTrace(VALUE self, VALUE context) { + TryCatch tc(self); + Locker lock(tc); + return Value::Maybe(tc, tc->StackTrace(Context(context))); + } + static VALUE Message(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + return rr::Message(tc, tc->Message()); + } + static VALUE Reset(VALUE self) { + TryCatch tc(self); + Locker lock(tc); + tc->Reset(); + return Qnil; + } + static VALUE SetVerbose(VALUE self, VALUE value) { + TryCatch tc(self); + Locker lock(tc); + tc->SetVerbose(Bool(value)); + return Qnil; + } + static VALUE SetCaptureMessage(VALUE self, VALUE value) { + TryCatch tc(self); + Locker lock(tc); + tc->SetCaptureMessage(Bool(value)); + return Qnil; + } + + /** + * Implements the V8::C::TryCatch(isolate) method. This yields the + * trycatch to the passed ruby code. + */ + static VALUE doTryCatch(int argc, VALUE argv[], VALUE self) { + if (!rb_block_given_p()) { + return Qnil; + } + int state; + VALUE isolate, code, result; + rb_scan_args(argc, argv, "10&", &isolate, &code); + + // allocate the trycatch within its own scope, so that its + // destructor is called, even if we're forced to `rb_jmp_tag` + // out of this function because of a ruby exception. + // the actual `V8::C::TryCatch` object is stored on the code in + // the `_v8_trycatch` variable, because rb_protect only accepts + // a single argument. + { + v8::TryCatch trycatch; + rb_iv_set(code, "_v8_trycatch", TryCatch(Isolate(isolate), trycatch)); + result = rb_protect(&call, code, &state); + rb_iv_set(code, "_v8_trycatch", Qnil); + } + if (state != 0) { + // ruby exception. bail! + rb_jump_tag(state); + } + return result; + } + /** + * Actually invokes the passed ruby block. The instance of + * `V8::C::TryCatch` is pulled off of the `_v8_trycatch` instance variable. + */ + static VALUE call(VALUE code) { + return rb_funcall(code, rb_intern("call"), 1, rb_iv_get(code, "_v8_trycatch")); + } + + + // What follows is a copy of the `Wrapper` template, that uses + // pass-by-reference semantics so that the actual `v8::TryCatch` + // object that is allocated on the C++ stack is the same object + // used everywhere. Ideally, we'd like to share the + // implementation, but I couldn't figure out how. + static VALUE Class; + + struct Container { + Container(v8::Isolate* isolate_, v8::TryCatch& tc) : isolate(isolate_), trycatch(tc) {} + + inline VALUE wrap() { + return Data_Wrap_Struct(Class, 0, &destroy, this); + } + + static inline Container* unwrap(VALUE object) { + Container* container; + Data_Get_Struct(object, struct Container, container); + return container; + } + + static void destroy(Container* container) { + delete container; + } + v8::Isolate* isolate; + v8::TryCatch& trycatch; + }; + + inline v8::TryCatch* operator ->() { + return &container->trycatch; + } + + inline operator v8::Isolate*() { + return container->isolate; + } + + inline operator VALUE() { + return container->wrap(); + } + static inline void Init() { + ClassBuilder("TryCatch"). + defineMethod("HasCaught", &HasCaught). + defineMethod("CanContinue", &CanContinue). + defineMethod("HasTerminated", &HasTerminated). + defineMethod("ReThrow", &ReThrow). + defineMethod("Exception", &Exception). + defineMethod("StackTrace", &StackTrace). + defineMethod("Message", &Message). + defineMethod("Reset", &Reset). + defineMethod("SetVerbose", &SetVerbose). + defineMethod("SetCaptureMessage", &SetCaptureMessage). + + store(&Class); + rb_define_singleton_method(rb_eval_string("V8::C"), "TryCatch", (VALUE (*)(...))&doTryCatch, -1); + } + + Container* container; + }; +} + + +#endif /* RR_TRY_CATCH_H */ diff --git a/ext/v8/value.cc b/ext/v8/value.cc index ce5061c8..ed59380e 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -21,7 +21,7 @@ namespace rr { // defineMethod("IsBooleanObject", &IsBooleanObject). // defineMethod("IsNumberObject", &IsNumberObject). // defineMethod("IsStringObject", &IsStringObject). - // defineMethod("IsNativeError", &IsNativeError). + defineMethod("IsNativeError", &IsNativeError). // defineMethod("IsRegExp", &IsRegExp). defineMethod("ToString", &ToString). // defineMethod("ToDetailString", &ToDetailString). @@ -120,6 +120,13 @@ namespace rr { return Bool(value->IsUint32()); } + VALUE Value::IsNativeError(VALUE self) { + Value value(self); + Locker lock(value); + + return Bool(value->IsNativeError()); + } + VALUE Value::ToString(VALUE self) { Value value(self); Locker lock(value.getIsolate()); diff --git a/ext/v8/value.h b/ext/v8/value.h index 8f353e15..b1f48a8f 100644 --- a/ext/v8/value.h +++ b/ext/v8/value.h @@ -27,7 +27,7 @@ namespace rr { // static VALUE IsBooleanObject(VALUE self); // static VALUE IsNumberObject(VALUE self); // static VALUE IsStringObject(VALUE self); - // static VALUE IsNativeError(VALUE self); + static VALUE IsNativeError(VALUE self); // static VALUE IsRegExp(VALUE self); static VALUE ToString(VALUE self); // static VALUE ToDetailString(VALUE self); diff --git a/spec/c/stack_trace_spec.rb b/spec/c/stack_trace_spec.rb new file mode 100644 index 00000000..0ac22d70 --- /dev/null +++ b/spec/c/stack_trace_spec.rb @@ -0,0 +1,18 @@ +require 'c_spec_helper' + +describe V8::C::StackTrace do + let(:const) { V8::C::StackTrace } + it "defines the stack trace description constants" do + expect(const::kLineNumber).to eql 1 + expect(const::kColumnOffset).to eql 3 + expect(const::kScriptName).to eql 4 + expect(const::kFunctionName).to eql 8 + expect(const::kIsEval).to eql 16 + expect(const::kIsConstructor).to eql 32 + expect(const::kScriptNameOrSourceURL).to eql 64 + expect(const::kScriptId).to eql 128 + expect(const::kExposeFramesAcrossSecurityOrigins).to eql 256 + expect(const::kOverview).to eql 15 + expect(const::kDetailed).to eql 127 + end +end diff --git a/spec/c/try_catch_spec.rb b/spec/c/try_catch_spec.rb new file mode 100644 index 00000000..cab4cca8 --- /dev/null +++ b/spec/c/try_catch_spec.rb @@ -0,0 +1,70 @@ +require 'c_spec_helper' + +describe V8::C::TryCatch do + requires_v8_context + + before do + @isolate.SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kDetailed) + end + + it "can catch javascript exceptions" do + V8::C::TryCatch(@isolate) do |trycatch| + source = V8::C::String::NewFromUtf8(@isolate, <<-JS) + function one() { + two() + } + function two() { + three() + } + function three() { + boom() + } + function boom() { + throw new Error('boom!') + } + eval('one()') + JS + + + filename = V8::C::String::NewFromUtf8(@isolate, "") + origin = V8::C::ScriptOrigin.new(filename) + script = V8::C::Script::Compile(@ctx, source, origin) + expect(script).to be_successful + result = script.FromJust().Run(@ctx) + trycatch.HasCaught().should be_truthy + trycatch.CanContinue().should be_truthy + exception = trycatch.Exception() + exception.should_not be_nil + exception.IsNativeError().should be_truthy + expect(trycatch.StackTrace(@ctx)).to be_successful + trace = trycatch.StackTrace(@ctx).FromJust() + trace.Utf8Value().should match /boom.*three.*two.*one/m + message = trycatch.Message(); + message.should_not be_nil + message.Get().Utf8Value().should eql "Uncaught Error: boom!" + expect(message.GetSourceLine(@cxt)).to be_successful + message.GetSourceLine(@cxt).FromJust().Utf8Value().should eql " throw new Error('boom!')" + message.GetScriptResourceName().Utf8Value().should eql "" + expect(message.GetLineNumber(@cxt)).to be_successful + expect(message.GetLineNumber(@cxt)).to eq_just 11 + + stack = message.GetStackTrace() + stack.should_not be_nil + stack.GetFrameCount().should eql 6 + frame = stack.GetFrame(0) + frame.GetLineNumber().should eql 11 + frame.GetColumn().should eql 15 + frame.GetScriptName().Utf8Value().should eql "" + frame.IsEval().should be_falsey + frame.IsConstructor().should be_falsey + end + end + + it "won't die on a ruby exception" do + expect { + V8::C::TryCatch(@isolate) do |trycatch| + fail "boom!" + end + }.to raise_error + end +end From c9b43ff46c2e8c255cef34c32cce942db8456518 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 12 Aug 2015 20:18:55 +0300 Subject: [PATCH 102/105] Throw JavaScript exceptions from Ruby --- ext/v8/exception.h | 50 ++++++++++++++++++++++++++++++++++++++++ ext/v8/init.cc | 2 +- ext/v8/isolate.cc | 8 ++++++- ext/v8/isolate.h | 1 + ext/v8/rr.h | 1 + spec/c/try_catch_spec.rb | 10 +++++++- 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 ext/v8/exception.h diff --git a/ext/v8/exception.h b/ext/v8/exception.h new file mode 100644 index 00000000..37bc6021 --- /dev/null +++ b/ext/v8/exception.h @@ -0,0 +1,50 @@ +// -*- mode: c++ -*- +#ifndef RR_EXCEPTION_H +#define RR_EXCEPTION_H +#include "rr.h" + +namespace rr { + class Exception { + public: + static inline void Init() { + ClassBuilder("Exception"). + defineSingletonMethod("RangeError", &RangeError). + defineSingletonMethod("ReferenceError", &ReferenceError). + defineSingletonMethod("SyntaxError", &SyntaxError). + defineSingletonMethod("TypeError", &TypeError). + defineSingletonMethod("Error", &Error); + } + + static VALUE RangeError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::RangeError(message)); + } + + static VALUE ReferenceError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::ReferenceError(message)); + } + + static VALUE SyntaxError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::SyntaxError(message)); + } + + static VALUE TypeError(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::TypeError(message)); + } + + static VALUE Error(VALUE self, VALUE rb_message) { + String message(rb_message); + Locker lock(message); + return Value(message, v8::Exception::Error(message)); + } + }; +} + +#endif /* RR_EXCEPTION_H */ diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 41c97d97..0a0a9a29 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -46,12 +46,12 @@ extern "C" { StackFrame::Init(); StackTrace::Init(); Message::Init(); + Exception::Init(); TryCatch::Init(); // Invocation::Init(); // Constants::Init(); // Template::Init(); - // Exception::Init(); // ResourceConstraints::Init(); // HeapStatistics::Init(); } diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 2a63e21e..97808d5c 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -9,7 +9,7 @@ namespace rr { rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). - + defineMethod("ThrowException", &ThrowException). defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). @@ -33,6 +33,12 @@ namespace rr { return Isolate(isolate); } + VALUE Isolate::ThrowException(VALUE self, VALUE error) { + Isolate isolate(self); + Locker lock(isolate); + return Value(isolate, isolate->ThrowException(Value(error))); + } + VALUE Isolate::SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options) { Isolate isolate(self); Locker lock(isolate); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 0837bdb3..9899a866 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -31,6 +31,7 @@ namespace rr { static VALUE New(VALUE self); static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); + static VALUE ThrowException(VALUE self, VALUE error); inline Isolate(IsolateData* data_) : data(data_) {} inline Isolate(v8::Isolate* isolate) : diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 0d1d8c66..37a5e4d0 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -72,6 +72,7 @@ inline VALUE not_implemented(const char* message) { #include "stack-frame.h" #include "stack-trace.h" #include "message.h" +#include "exception.h" #include "try-catch.h" #endif diff --git a/spec/c/try_catch_spec.rb b/spec/c/try_catch_spec.rb index cab4cca8..d33ccf09 100644 --- a/spec/c/try_catch_spec.rb +++ b/spec/c/try_catch_spec.rb @@ -60,11 +60,19 @@ end end + it "sees JavaScript exceptions thrown from Ruby" do + V8::C::TryCatch(@isolate) do |trycatch| + message = V8::C::String::NewFromUtf8(@isolate, "boom!") + @isolate.ThrowException(V8::C::Exception::Error(message)) + expect(trycatch.HasCaught).to be_truthy + end + end + it "won't die on a ruby exception" do expect { V8::C::TryCatch(@isolate) do |trycatch| fail "boom!" end - }.to raise_error + }.to raise_error RuntimeError, "boom!" end end From 250b8e7d506281a01369154c8310d15694362dc0 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 12 Aug 2015 21:02:39 +0300 Subject: [PATCH 103/105] Embed lambdas and procs into V8::Context This adds the ability embed basic Ruby code blocks into a javascript context and invoke it. cxt = V8::Context.new cxt['twice'] = lambda {|str| "#{str}#{str}"} cxt.eval('twice("hi")') #=> "hihi" Note that error handling is not accounted for yet. If an exception happens inside the called proc, it will cause terrible, awful things to happen because portions of the C++ stack will not be unwound leaving the Isolate in an indeterminate state. --- lib/v8/conversion.rb | 25 +++++++++++++ lib/v8/conversion/fundamental.rb | 13 ++++++- spec/v8/context_spec.rb | 60 ++++++++++++++++---------------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/lib/v8/conversion.rb b/lib/v8/conversion.rb index 64f3fdfa..6fb3af9b 100644 --- a/lib/v8/conversion.rb +++ b/lib/v8/conversion.rb @@ -140,3 +140,28 @@ def to_v8(context) V8::C::Symbol::For(context.isolate.native, V8::C::String::NewFromUtf8(isolate, to_s)) end end + +class Object + def to_v8(context) + V8::C::Object::New(context.isolate.native) + end +end + +class Proc + def to_v8(context) + isolate = context.isolate.native + callback = lambda do |info| + args = [] + arity = info.Length() + if self.arity > -1 + arity = self.arity + end + arity.times do |i| + args << context.to_ruby(info[i]) + end + result = context.to_v8 self.call(*args) + info.GetReturnValue().Set(result) + end + V8::C::Function::New(isolate, callback) + end +end diff --git a/lib/v8/conversion/fundamental.rb b/lib/v8/conversion/fundamental.rb index 9e780b88..9c9afd7d 100644 --- a/lib/v8/conversion/fundamental.rb +++ b/lib/v8/conversion/fundamental.rb @@ -43,7 +43,18 @@ def to_ruby(v8_object) # First it checks to see if there is an entry in the id map for # this object. Otherwise, it will run the default conversion. def to_v8(context, ruby_object) - rb_idmap[ruby_object.object_id] || ruby_object.to_v8(context) + if v8_object = rb_idmap[ruby_object.object_id] + v8_object + else + v8_object = ruby_object.to_v8(context) + if v8_object.kind_of? V8::C::Object + v8_object.tap do + equate ruby_object, v8_object + end + else + v8_object + end + end end ## diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 60f78a7f..d441303e 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -93,12 +93,12 @@ end end - # xit "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do - # Object.new.tap do |o| - # @cxt['get'] = lambda {o} - # @cxt.eval('get()').should be(o) - # end - # end + it "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do + Object.new.tap do |o| + @cxt['get'] = lambda {o} + @cxt.eval('get()').should be(o) + end + end it "always returns the same ruby object for a single javascript object" do obj = @cxt.eval('obj = {}') @@ -133,25 +133,25 @@ # end end -# describe "Calling Ruby Code From Within Javascript", :compat => '0.1.0' do + describe "Calling Ruby Code From Within Javascript" do -# before(:each) do + before(:each) do # @class = Class.new # @instance = @class.new -# @cxt = V8::Context.new + @cxt = V8::Context.new # @cxt['o'] = @instance -# end + end -# xit "can embed a closure into a context and call it" do -# @cxt["say"] = lambda { |*args| args[-2] * args[-1] } -# @cxt.eval("say('Hello', 2)").should == "HelloHello" -# end + it "can embed a closure into a context and call it" do + @cxt["say"] = lambda { |*args| args[-2] * args[-1] } + @cxt.eval("say('Hello', 2)").should == "HelloHello" + end -# xit "recognizes the same closure embedded into the same context as the same function object" do -# @cxt['say'] = @cxt['declare'] = lambda { |*args| args } -# @cxt.eval('say == declare').should be(true) -# @cxt.eval('say === declare').should be(true) -# end + it "recognizes the same closure embedded into the same context as the same function object" do + @cxt['say'] = @cxt['declare'] = lambda { |*args| args } + @cxt.eval('say == declare').should be(true) + @cxt.eval('say === declare').should be(true) + end # xit "translates ruby Array to Javascript Array" do # class_eval do @@ -275,17 +275,17 @@ # @cxt.eval('typeof(RObject)').should == 'function' # end -# xit "truncates lambda arguments passed in to match the arity of the function", :compat => '0.4.2' do -# @cxt['testing'] = lambda { |arg| arg } -# lambda { -# @cxt.eval('testing(1,2,3)') -# }.should_not raise_error + it "truncates lambda arguments passed in to match the arity of the function", :compat => '0.4.2' do + @cxt['testing'] = lambda { |arg| arg } + lambda { + @cxt.eval('testing(1,2,3)') + }.should_not raise_error -# @cxt['testing'] = lambda { } -# lambda { -# @cxt.eval('testing(1,2,3)') -# }.should_not raise_error -# end + @cxt['testing'] = lambda { } + lambda { + @cxt.eval('testing(1,2,3)') + }.should_not raise_error + end # xit "truncates method arguments passed in to match the arity of the function", :compat => '0.4.3' do # @instance.instance_eval do @@ -652,7 +652,7 @@ # @class.class_eval &body # end -# end + end describe "Calling JavaScript Code From Within Ruby", :compat => '0.1.0' do From 3d5516abeaf248cdf827ba93c4e05992eb684374 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 12 Aug 2015 21:32:16 +0300 Subject: [PATCH 104/105] finish out the Array C API Convert CloneElementAt to the Maybe API --- ext/v8/array.cc | 8 ++++---- ext/v8/array.h | 2 +- spec/c/array_spec.rb | 15 ++++++++++++++- spec/v8/context_spec.rb | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ext/v8/array.cc b/ext/v8/array.cc index ba3d4f6b..d3813ec1 100644 --- a/ext/v8/array.cc +++ b/ext/v8/array.cc @@ -25,16 +25,16 @@ namespace rr { VALUE Array::Length(VALUE self) { Array array(self); - Locker lock(array.getIsolate()); + Locker lock(array); return Uint32_t(array->Length()); } - VALUE Array::CloneElementAt(VALUE self, VALUE index) { + VALUE Array::CloneElementAt(VALUE self, VALUE context, VALUE index) { Array array(self); - Locker lock(array.getIsolate()); + Locker lock(array); - return Object(array.getIsolate(), array->CloneElementAt(Uint32_t(index))); + return Object::Maybe(array.getIsolate(), array->CloneElementAt(Context(context), Uint32_t(index))); } } diff --git a/ext/v8/array.h b/ext/v8/array.h index 3884e172..f872daa1 100644 --- a/ext/v8/array.h +++ b/ext/v8/array.h @@ -9,7 +9,7 @@ namespace rr { static VALUE New(int argc, VALUE argv[], VALUE self); static VALUE Length(VALUE self); - static VALUE CloneElementAt(VALUE self, VALUE index); + static VALUE CloneElementAt(VALUE self, VALUE context, VALUE index); inline Array(v8::Isolate* isolate, v8::Handle array) : Ref(isolate, array) {} inline Array(VALUE value) : Ref(value) {} diff --git a/spec/c/array_spec.rb b/spec/c/array_spec.rb index ce0551ef..fe7d58b2 100644 --- a/spec/c/array_spec.rb +++ b/spec/c/array_spec.rb @@ -17,7 +17,20 @@ it 'can be initialized with a length' do a = V8::C::Array::New(@isolate, 5) - expect(a.Length).to eq 5 end + + it 'can clone its elements' do + o = V8::C::Object::New(@isolate) + key = V8::C::String::NewFromUtf8(@isolate, "key") + o.Set(@ctx, key, key) + a = V8::C::Array::New(@isolate) + + a.Set(@ctx, 0, o) + + object = a.CloneElementAt(@ctx, 0) + expect(object).to be_successful + expect(object.FromJust().Get(@ctx, key)).to be_successful + expect(object.FromJust().Get(@ctx, key).FromJust().Utf8Value()).to eql "key" + end end diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 60f78a7f..9f955eb9 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -654,7 +654,7 @@ # end - describe "Calling JavaScript Code From Within Ruby", :compat => '0.1.0' do + describe "Calling JavaScript Code From Within Ruby" do before(:each) do @cxt = V8::Context.new From b29c94f0874c406959dda234f8c2ec9f67662651 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 14 Aug 2015 16:04:27 +0300 Subject: [PATCH 105/105] add pre-emption APIS v8 4.5 has very convenient APIs for interrupting potentially long running JavaScript code. This exposes those APIs to Ruby code so that it can place its own limits on the CPU resources consumed by V8. --- .travis.yml | 3 +-- ext/v8/isolate.cc | 16 ++++++++++++++ ext/v8/isolate.h | 5 ++++- lib/v8/isolate.rb | 49 +++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 34 ---------------------------- spec/v8/isolate_spec.rb | 43 ++++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 spec/v8/isolate_spec.rb diff --git a/.travis.yml b/.travis.yml index 1bbff0b8..8e6e8eb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,7 @@ before_install: - gem update --system 2.1.11 script: - bundle exec rake compile - - bundle exec rspec spec/c - - bundle exec rspec spec/v8/context_spec.rb + - bundle exec rspec spec/c spec/v8/context_spec.rb spec/v8/isolate_spec.rb - bundle exec rspec spec/mem sudo: false addons: diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 97808d5c..b14e532e 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -9,6 +9,9 @@ namespace rr { rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). + defineMethod("TerminateExecution", &TerminateExecution). + defineMethod("IsExecutionTerminating", &IsExecutionTerminating). + defineMethod("CancelTerminateExecution", &CancelTerminateExecution). defineMethod("ThrowException", &ThrowException). defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). @@ -33,6 +36,19 @@ namespace rr { return Isolate(isolate); } + VALUE Isolate::TerminateExecution(VALUE self) { + Isolate(self)->TerminateExecution(); + return Qnil; + }; + + VALUE Isolate::IsExecutionTerminating(VALUE self) { + return Bool(Isolate(self)->IsExecutionTerminating()); + } + VALUE Isolate::CancelTerminateExecution(VALUE self) { + Isolate(self)->CancelTerminateExecution(); + return Qnil; + } + VALUE Isolate::ThrowException(VALUE self, VALUE error) { Isolate isolate(self); Locker lock(isolate); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 9899a866..c60c571e 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -30,8 +30,11 @@ namespace rr { static void Init(); static VALUE New(VALUE self); - static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); + static VALUE TerminateExecution(VALUE self); + static VALUE IsExecutionTerminating(VALUE self); + static VALUE CancelTerminateExecution(VALUE self); static VALUE ThrowException(VALUE self, VALUE error); + static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); inline Isolate(IsolateData* data_) : data(data_) {} inline Isolate(v8::Isolate* isolate) : diff --git a/lib/v8/isolate.rb b/lib/v8/isolate.rb index b18a5db9..b294885a 100644 --- a/lib/v8/isolate.rb +++ b/lib/v8/isolate.rb @@ -1,9 +1,58 @@ module V8 + ## + # A completely encapsulated JavaScript virtual machine. No state of + # any kind is shared between isolates. + # + # The isolate is the primany API for understanding and configuring + # the runtime profile of the JavaScript interpreter. For example, it + # is through the isolate that you can take heap and stack samples + # for memory and cpu profiling. It is also through the isolate that + # you can place restraints on the amount of memory and CPU that your + # JavaScript execution will consume. + # + # An isolate can have many exceutions contexts that each have their + # own scopes, and so significant resource savings can be had by + # reusing the same isolate for multiple contexts. class Isolate + + # @!attribute [r] native + # @return [V8::C::Isolate] the underlying C++ object attr_reader :native + ## + # Create an new isolate. def initialize() @native = V8::C::Isolate::New() end + + ## + # Terminates any currently executing JavaScript in this Isolate. + # + # This will throw an uncatchable exception inside the JavaScript + # code which will forcibly unwind the stack. + # + # @return [NilClass] nil + def terminate_execution! + @native.TerminateExecution() + end + + ## + # Returns true if execution is terminating, but there are still + # JavaScript frames left on the stack. + # + # @return [TrueClass | FalseClass] whether isolate is terminating execution + def execution_terminating? + @native.IsExecutionTerminating() + end + + ## + # Cancels any scheduled termination of JavaScript. Not really sure + # why you would want to do this, but if you figure out a use-case, + # would love for you to document it here. + # + # @return [NilClass] nil + def cancel_terminate_execution! + @native.CancelTerminateExecution() + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b31ecb41..d2b1abbd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,35 +1 @@ require 'v8' - -def rputs(msg) - puts "
#{ERB::Util.h(msg)}
" - $stdout.flush -end - -module V8ContextHelpers - module GroupMethods - def requires_v8_context - around(:each) do |example| - bootstrap_v8_context(&example) - end - end - end - - def bootstrap_v8_context - V8::C::Locker() do - V8::C::HandleScope() do - @cxt = V8::C::Context::New() - begin - @cxt.Enter() - yield - ensure - @cxt.Exit() - end - end - end - end -end - -RSpec.configure do |c| - c.include V8ContextHelpers - c.extend V8ContextHelpers::GroupMethods -end diff --git a/spec/v8/isolate_spec.rb b/spec/v8/isolate_spec.rb new file mode 100644 index 00000000..3589fd8b --- /dev/null +++ b/spec/v8/isolate_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe V8::Isolate do + let(:isolate) { V8::Isolate.new } + let(:context) { V8::Context.new isolate: isolate } + + it "allows you to terminate javascript execution" do + context['doTerminate'] = lambda { isolate.terminate_execution! } + expect(isolate).to_not be_execution_terminating + expect { + context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + }.to raise_error V8::C::PendingExceptionError + end + + it "allows you to cancel terminated javascript executing" do + context['doTerminate'] = lambda do + isolate.terminate_execution! + isolate.cancel_terminate_execution! + end + result = context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + expect(result).to eql 5 + end + + +end