diff --git a/src/binding.cc b/src/binding.cc index 4725239355..24f75b52a6 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,5 +1,6 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. #include +#include #include #include #include @@ -850,7 +851,8 @@ two_pointers_t v8__ArrayBuffer__GetBackingStore(const v8::ArrayBuffer& self) { return make_pod(ptr_to_local(&self)->GetBackingStore()); } -bool v8__BackingStore__IsResizableByUserJavaScript(const v8::BackingStore& self) { +bool v8__BackingStore__IsResizableByUserJavaScript( + const v8::BackingStore& self) { return ptr_to_local(&self)->IsResizableByUserJavaScript(); } @@ -1013,6 +1015,33 @@ class ExternalStaticOneByteStringResource const int _length; }; +// NOTE: This class is never used and only serves as a reference for +// the OneByteConst struct created on Rust-side. +class ExternalConstOneByteStringResource + : public v8::String::ExternalOneByteStringResource { + public: + ExternalConstOneByteStringResource(int length) + : _length(length) { + static_assert(offsetof(ExternalConstOneByteStringResource, _length) == 16, + "ExternalConstOneByteStringResource's length was not at offset 16"); + static_assert(sizeof(ExternalConstOneByteStringResource) == 24, + "ExternalConstOneByteStringResource size was not 24"); + static_assert(alignof(ExternalConstOneByteStringResource) == 8, + "ExternalConstOneByteStringResource align was not 8"); + } + const char* data() const override { return nullptr; } + size_t length() const override { return _length; } + void Dispose() override {} + + private: + const int _length; +}; + +const v8::String* v8__String__NewExternalOneByte( + v8::Isolate* isolate, v8::String::ExternalOneByteStringResource* resource) { + return maybe_local_to_ptr(v8::String::NewExternalOneByte(isolate, resource)); +} + const v8::String* v8__String__NewExternalOneByteStatic(v8::Isolate* isolate, const char* data, int length) { @@ -1150,8 +1179,7 @@ void v8__ObjectTemplate__SetNamedPropertyHandler( v8::GenericNamedPropertyEnumeratorCallback enumerator, v8::GenericNamedPropertyDefinerCallback definer, v8::GenericNamedPropertyDescriptorCallback descriptor, - const v8::Value* data_or_null, - v8::PropertyHandlerFlags flags) { + const v8::Value* data_or_null, v8::PropertyHandlerFlags flags) { ptr_to_local(&self)->SetHandler(v8::NamedPropertyHandlerConfiguration( getter, setter, query, deleter, enumerator, definer, descriptor, ptr_to_local(data_or_null), flags)); @@ -1165,8 +1193,7 @@ void v8__ObjectTemplate__SetIndexedPropertyHandler( v8::IndexedPropertyEnumeratorCallback enumerator, v8::IndexedPropertyDefinerCallback definer, v8::IndexedPropertyDescriptorCallback descriptor, - const v8::Value* data_or_null, - v8::PropertyHandlerFlags flags) { + const v8::Value* data_or_null, v8::PropertyHandlerFlags flags) { ptr_to_local(&self)->SetHandler(v8::IndexedPropertyHandlerConfiguration( getter, setter, query, deleter, enumerator, definer, descriptor, ptr_to_local(data_or_null), flags)); @@ -1269,9 +1296,9 @@ MaybeBool v8__Object__DefineOwnProperty(const v8::Object& self, } MaybeBool v8__Object__DefineProperty(const v8::Object& self, - const v8::Context& context, - const v8::Name& key, - v8::PropertyDescriptor& desc) { + const v8::Context& context, + const v8::Name& key, + v8::PropertyDescriptor& desc) { return maybe_to_maybe_bool(ptr_to_local(&self)->DefineProperty( ptr_to_local(&context), ptr_to_local(&key), desc)); } @@ -1285,7 +1312,7 @@ MaybeBool v8__Object__SetAccessor(const v8::Object& self, v8::PropertyAttribute attr) { return maybe_to_maybe_bool(ptr_to_local(&self)->SetAccessor( ptr_to_local(&context), ptr_to_local(&key), getter, setter, - ptr_to_local(data_or_null), v8::AccessControl::DEFAULT,attr)); + ptr_to_local(data_or_null), v8::AccessControl::DEFAULT, attr)); } v8::Isolate* v8__Object__GetIsolate(const v8::Object& self) { @@ -1430,16 +1457,16 @@ MaybeBool v8__Object__HasPrivate(const v8::Object& self, ptr_to_local(&context), ptr_to_local(&key))); } -void v8__Object__GetPropertyAttributes( - const v8::Object& self, const v8::Context& context, - const v8::Value& key, v8::Maybe* out) { +void v8__Object__GetPropertyAttributes(const v8::Object& self, + const v8::Context& context, + const v8::Value& key, + v8::Maybe* out) { *out = ptr_to_local(&self)->GetPropertyAttributes(ptr_to_local(&context), ptr_to_local(&key)); } const v8::Value* v8__Object__GetOwnPropertyDescriptor( - const v8::Object& self, const v8::Context& context, - const v8::Name& key) { + const v8::Object& self, const v8::Context& context, const v8::Name& key) { return maybe_local_to_ptr(ptr_to_local(&self)->GetOwnPropertyDescriptor( ptr_to_local(&context), ptr_to_local(&key))); } @@ -1450,7 +1477,6 @@ const v8::Array* v8__Object__PreviewEntries( return maybe_local_to_ptr(ptr_to_local(&self)->PreviewEntries(is_key_value)); } - const v8::Array* v8__Array__New(v8::Isolate* isolate, int length) { return local_to_ptr(v8::Array::New(isolate, length)); } @@ -1536,7 +1562,7 @@ void v8__Set__Clear(const v8::Set& self) { } v8::Set* v8__Set__Add(const v8::Set& self, const v8::Context& context, - const v8::Value& key) { + const v8::Value& key) { return maybe_local_to_ptr( ptr_to_local(&self)->Add(ptr_to_local(&context), ptr_to_local(&key))); } @@ -1787,8 +1813,7 @@ const v8::Value* v8__Context__GetSecurityToken(const v8::Context& self) { return local_to_ptr(value); } -void v8__Context__SetSecurityToken(v8::Context& self, - const v8::Value* token) { +void v8__Context__SetSecurityToken(v8::Context& self, const v8::Value* token) { auto c = ptr_to_local(&self); c->SetSecurityToken(ptr_to_local(token)); } @@ -1802,7 +1827,7 @@ void v8__Context__AllowCodeGenerationFromStrings(v8::Context& self, bool allow) } bool v8__Context_IsCodeGenerationFromStringsAllowed(v8::Context& self) { - return ptr_to_local(&self)->IsCodeGenerationFromStringsAllowed(); + return ptr_to_local(&self)->IsCodeGenerationFromStringsAllowed(); } const v8::Context* v8__Context__FromSnapshot(v8::Isolate* isolate, @@ -1962,9 +1987,10 @@ int v8__Function__ScriptId(const v8::Function& self) { return ptr_to_local(&self)->ScriptId(); } -const v8::ScriptOrigin* v8__Function__GetScriptOrigin(const v8::Function& self) { - std::unique_ptr u = - std::make_unique(ptr_to_local(&self)->GetScriptOrigin()); +const v8::ScriptOrigin* v8__Function__GetScriptOrigin( + const v8::Function& self) { + std::unique_ptr u = std::make_unique( + ptr_to_local(&self)->GetScriptOrigin()); return u.release(); } @@ -1993,10 +2019,9 @@ v8::CTypeInfo* v8__CTypeInfo__New__From__Slice(unsigned int len, return v; } -v8::CFunctionInfo* v8__CFunctionInfo__New(const v8::CTypeInfo& return_info, - unsigned int args_len, - v8::CTypeInfo* args_info, - v8::CFunctionInfo::Int64Representation repr) { +v8::CFunctionInfo* v8__CFunctionInfo__New( + const v8::CTypeInfo& return_info, unsigned int args_len, + v8::CTypeInfo* args_info, v8::CFunctionInfo::Int64Representation repr) { std::unique_ptr info = std::make_unique( v8::CFunctionInfo(return_info, args_len, args_info, repr)); return info.release(); @@ -2120,7 +2145,7 @@ const v8::StackTrace* v8__StackTrace__CurrentStackTrace(v8::Isolate* isolate, } const v8::String* v8__StackTrace__CurrentScriptNameOrSourceURL( - v8::Isolate* isolate) { + v8::Isolate* isolate) { return local_to_ptr(v8::StackTrace::CurrentScriptNameOrSourceURL(isolate)); } @@ -2300,13 +2325,11 @@ int v8__ScriptOrigin__ScriptId(const v8::ScriptOrigin& self) { return ptr_to_local(&self)->ScriptId(); } -const v8::Value* v8__ScriptOrigin__ResourceName( - const v8::ScriptOrigin& self) { +const v8::Value* v8__ScriptOrigin__ResourceName(const v8::ScriptOrigin& self) { return local_to_ptr(ptr_to_local(&self)->ResourceName()); } -const v8::Value* v8__ScriptOrigin__SourceMapUrl( - const v8::ScriptOrigin& self) { +const v8::Value* v8__ScriptOrigin__SourceMapUrl(const v8::ScriptOrigin& self) { return local_to_ptr(ptr_to_local(&self)->SourceMapUrl()); } @@ -3429,8 +3452,7 @@ bool v8__PropertyDescriptor__has_enumerable( return self->has_enumerable(); } -bool v8__PropertyDescriptor__has_writable( - const v8::PropertyDescriptor* self) { +bool v8__PropertyDescriptor__has_writable(const v8::PropertyDescriptor* self) { return self->has_writable(); } @@ -3447,12 +3469,12 @@ bool v8__PropertyDescriptor__has_set(const v8::PropertyDescriptor* self) { } void v8__PropertyDescriptor__set_enumerable(v8::PropertyDescriptor* self, - bool enumurable) { + bool enumurable) { self->set_enumerable(enumurable); } void v8__PropertyDescriptor__set_configurable(v8::PropertyDescriptor* self, - bool configurable) { + bool configurable) { self->set_configurable(configurable); } diff --git a/src/lib.rs b/src/lib.rs index 6b59205be7..c38e541e33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,7 @@ pub use script_compiler::CachedData; pub use snapshot::FunctionCodeHandling; pub use snapshot::StartupData; pub use string::NewStringType; +pub use string::OneByteConst; pub use string::WriteOptions; pub use support::SharedPtr; pub use support::SharedRef; diff --git a/src/string.rs b/src/string.rs index 2e0f8a51f8..7a4ee9314a 100644 --- a/src/string.rs +++ b/src/string.rs @@ -69,6 +69,11 @@ extern "C" { options: WriteOptions, ) -> int; + fn v8__String__NewExternalOneByte( + isolate: *mut Isolate, + onebyte_const: *const OneByteConst, + ) -> *const String; + fn v8__String__NewExternalOneByteStatic( isolate: *mut Isolate, buffer: *const char, @@ -90,6 +95,102 @@ extern "C" { fn v8__String__ContainsOnlyOneByte(this: *const String) -> bool; } +#[repr(C)] +#[derive(Debug)] +pub struct OneByteConst { + vtable: *const OneByteConstNoOp, + cached_data: *const char, + length: int, +} + +// SAFETY: The vtable for OneByteConst is an immutable static and all +// of the included functions are thread-safe, the cached_data pointer +// is never changed and points to a static ASCII string, and the +// length is likewise never changed. Thus, it is safe to share the +// OneByteConst across threads. This means that multiple isolates +// can use the same OneByteConst statics simultaneously. +unsafe impl Sync for OneByteConst {} + +extern "C" fn one_byte_const_no_op(_this: *const OneByteConst) {} +extern "C" fn one_byte_const_is_cacheable(_this: *const OneByteConst) -> bool { + true +} +extern "C" fn one_byte_const_data(this: *const OneByteConst) -> *const char { + // SAFETY: Only called from C++ with a valid OneByteConst pointer. + unsafe { (*this).cached_data } +} +extern "C" fn one_byte_const_length(this: *const OneByteConst) -> usize { + // SAFETY: Only called from C++ with a valid OneByteConst pointer. + unsafe { (*this).length as usize } +} + +type OneByteConstNoOp = extern "C" fn(*const OneByteConst); +type OneByteConstIsCacheable = extern "C" fn(*const OneByteConst) -> bool; +type OneByteConstData = extern "C" fn(*const OneByteConst) -> *const char; +type OneByteConstLength = extern "C" fn(*const OneByteConst) -> usize; + +#[repr(C)] +struct OneByteConstVtable { + #[cfg(target_family = "windows")] + // In SysV / Itanium ABI -0x10 offset of the vtable + // tells how many bytes the vtable pointer pointing to + // this vtable is offset from the base class. For + // single inheritance this is always 0. + _offset_to_top: usize, + // In Itanium ABI the -0x08 offset contains the type_info + // pointer, and in MSVC it contains the RTTI Complete Object + // Locator pointer. V8 is normally compiled with `-fno-rtti` + // meaning that this pointer is a nullptr on both + // Itanium and MSVC. + _typeinfo: *const (), + // After the metadata fields come the virtual function + // pointers. The vtable pointer in a class instance points + // to the first virtual function pointer, making this + // the 0x00 offset of the table. + // The order of the virtual function pointers is determined + // by their order of declaration in the classes. + delete1: OneByteConstNoOp, + // In SysV / Itanium ABI, a class vtable includes the + // deleting destructor and the compete object destructor. + // In MSVC, it only includes the deleting destructor. + #[cfg(not(target_family = "windows"))] + delete2: OneByteConstNoOp, + is_cacheable: OneByteConstIsCacheable, + dispose: OneByteConstNoOp, + lock: OneByteConstNoOp, + unlock: OneByteConstNoOp, + data: OneByteConstData, + length: OneByteConstLength, +} + +const ONE_BYTE_CONST_VTABLE: OneByteConstVtable = OneByteConstVtable { + #[cfg(target_family = "windows")] + _offset_to_top: 0, + _typeinfo: std::ptr::null(), + delete1: one_byte_const_no_op, + #[cfg(not(target_family = "windows"))] + delete2: one_byte_const_no_op, + is_cacheable: one_byte_const_is_cacheable, + dispose: one_byte_const_no_op, + lock: one_byte_const_no_op, + unlock: one_byte_const_no_op, + data: one_byte_const_data, + length: one_byte_const_length, +}; + +/// Compile-time function to determine if a string is ASCII. Note that UTF-8 chars +/// longer than one byte have the high-bit set and thus, are not ASCII. +const fn is_ascii(s: &'static [u8]) -> bool { + let mut i = 0; + while i < s.len() { + if !s[i].is_ascii() { + return false; + } + i += 1; + } + true +} + #[repr(C)] #[derive(Debug, Default)] pub enum NewStringType { @@ -332,6 +433,34 @@ impl String { Self::new_from_utf8(scope, value.as_ref(), NewStringType::Normal) } + // Compile-time function to create an external string resource. + // The buffer is checked to contain only ASCII characters. + #[inline(always)] + pub const fn create_external_onebyte_const( + buffer: &'static [u8], + ) -> OneByteConst { + is_ascii(buffer); + OneByteConst { + vtable: &ONE_BYTE_CONST_VTABLE.delete1, + cached_data: buffer.as_ptr() as *const char, + length: buffer.len() as i32, + } + } + + // Creates a v8::String from a `&'static OneByteConst` + // which is guaranteed to be Latin-1 or ASCII. + #[inline(always)] + pub fn new_from_onebyte_const<'s>( + scope: &mut HandleScope<'s, ()>, + onebyte_const: &'static OneByteConst, + ) -> Option> { + unsafe { + scope.cast_local(|sd| { + v8__String__NewExternalOneByte(sd.get_isolate_ptr(), onebyte_const) + }) + } + } + // Creates a v8::String from a `&'static [u8]`, // must be Latin-1 or ASCII, not UTF-8 ! #[inline(always)] diff --git a/tests/test_api.rs b/tests/test_api.rs index 1327951d24..385a8c9347 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -8509,6 +8509,9 @@ fn compile_function() { assert_eq!(42 * 1337, result.int32_value(scope).unwrap()); } +static EXAMPLE_STRING: v8::OneByteConst = + v8::String::create_external_onebyte_const(b"const static"); + #[test] fn external_strings() { let _setup_guard = setup::parallel_test(); @@ -8575,6 +8578,17 @@ fn external_strings() { assert!(!latin1.is_external_twobyte()); assert!(latin1.is_onebyte()); assert!(latin1.contains_only_onebyte()); + + // one-byte "const" test + let const_ref_string = + v8::String::new_from_onebyte_const(scope, &EXAMPLE_STRING).unwrap(); + assert!(const_ref_string.is_external()); + assert!(const_ref_string.is_external_onebyte()); + assert!(!const_ref_string.is_external_twobyte()); + assert!(const_ref_string.is_onebyte()); + assert!(const_ref_string.contains_only_onebyte()); + assert!(const_ref_string + .strict_equals(v8::String::new(scope, "const static").unwrap().into())); } #[test]