diff --git a/src/binding.cc b/src/binding.cc index 4dd6a9cc0c..d87ec74324 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -832,6 +832,32 @@ int v8__String__WriteUtf8(const v8::String& self, v8::Isolate* isolate, return self.WriteUtf8(isolate, buffer, length, nchars_ref, options); } +class ExternalStaticOneByteStringResource: public v8::String::ExternalOneByteStringResource { + public: + ExternalStaticOneByteStringResource(const char *data, int length): + _data(data), _length(length) {} + const char* data() const override { return _data; } + size_t length() const override { return _length; } + + private: + const char* _data; + const int _length; +}; + +const v8::String* v8__String__NewExternalOneByteStatic(v8::Isolate *isolate, + const char *data, int length) { + return maybe_local_to_ptr( + v8::String::NewExternalOneByte( + isolate, new ExternalStaticOneByteStringResource(data, length) + ) + ); +} + +bool v8__String__IsExternal(const v8::String& self) { return self.IsExternal(); } +bool v8__String__IsExternalOneByte(const v8::String& self) { return self.IsExternalOneByte(); } +bool v8__String__IsExternalTwoByte(const v8::String& self) { return self.IsExternalTwoByte(); } +bool v8__String__IsOneByte(const v8::String& self) { return self.IsOneByte(); } + const v8::Symbol* v8__Symbol__New(v8::Isolate* isolate, const v8::String& description) { return local_to_ptr(v8::Symbol::New(isolate, ptr_to_local(&description))); diff --git a/src/string.rs b/src/string.rs index cff389d605..2ca96f2bc6 100644 --- a/src/string.rs +++ b/src/string.rs @@ -32,6 +32,17 @@ extern "C" { nchars_ref: *mut int, options: WriteOptions, ) -> int; + + fn v8__String__NewExternalOneByteStatic( + isolate: *mut Isolate, + buffer: *const char, + length: int, + ) -> *const String; + + fn v8__String__IsExternal(this: *const String) -> bool; + fn v8__String__IsExternalOneByte(this: *const String) -> bool; + fn v8__String__IsExternalTwoByte(this: *const String) -> bool; + fn v8__String__IsOneByte(this: *const String) -> bool; } #[repr(C)] @@ -134,6 +145,57 @@ impl String { Self::new_from_utf8(scope, value.as_ref(), NewStringType::Normal) } + // Creates a v8::String from a `&'static str`, + // must be Latin-1 or ASCII, not UTF-8 ! + pub fn new_external_onebyte_static<'s>( + scope: &mut HandleScope<'s, ()>, + value: &'static str, + ) -> Option> { + let buffer: &[u8] = value.as_ref(); + if buffer.is_empty() { + return None; + } + let buffer_len = buffer.len().try_into().ok()?; + unsafe { + scope.cast_local(|sd| { + v8__String__NewExternalOneByteStatic( + sd.get_isolate_ptr(), + buffer.as_ptr() as *const char, + buffer_len, + ) + }) + } + } + + /// True if string is external + pub fn is_external(&self) -> bool { + // TODO: re-enable on next v8-release + // Right now it fallbacks to Value::IsExternal, which is incorrect + // See: https://source.chromium.org/chromium/_/chromium/v8/v8.git/+/1dd8624b524d14076160c1743f7da0b20fbe68e0 + // unsafe { v8__String__IsExternal(self) } + + // Fallback for now (though functionally identical) + self.is_external_onebyte() || self.is_external_twobyte() + } + + /// True if string is external & one-byte + /// (e.g: created with new_external_onebyte_static) + pub fn is_external_onebyte(&self) -> bool { + unsafe { v8__String__IsExternalOneByte(self) } + } + + /// True if string is external & two-byte + /// NOTE: can't yet be created via rusty_v8 + pub fn is_external_twobyte(&self) -> bool { + unsafe { v8__String__IsExternalTwoByte(self) } + } + + /// True if string is known to contain only one-byte data + /// doesn't read the string so can return false positives + pub fn is_onebyte(&self) -> bool { + unsafe { v8__String__IsExternalOneByte(self) } + } + /// Convenience function not present in the original V8 API. pub fn to_rust_string_lossy( &self, diff --git a/tests/test_api.rs b/tests/test_api.rs index d376a1070e..d354553b4b 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -4959,3 +4959,41 @@ fn code_cache_script() { let ret = script.run(scope).unwrap(); assert_eq!(ret.uint32_value(scope).unwrap(), 2); } + +#[test] +fn external_strings() { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + // Parse JSON from an external string + let json_static = "{\"a\": 1, \"b\": 2}"; + let json_external = + v8::String::new_external_onebyte_static(scope, json_static).unwrap(); + let maybe_value = v8::json::parse(scope, json_external); + assert!(maybe_value.is_some()); + // Check length + assert!(json_external.length() == 16); + // Externality checks + assert!(json_external.is_external()); + assert!(json_external.is_external_onebyte()); + assert!(json_external.is_onebyte()); + + // In & out + let hello = + v8::String::new_external_onebyte_static(scope, "hello world").unwrap(); + let rust_str = hello.to_rust_string_lossy(scope); + assert_eq!(rust_str, "hello world"); + // Externality checks + assert!(hello.is_external()); + assert!(hello.is_external_onebyte()); + assert!(hello.is_onebyte()); + + // two-byte "internal" test + let gradients = v8::String::new(scope, "∇gradients").unwrap(); + assert!(!gradients.is_external()); + assert!(!gradients.is_external_onebyte()); + assert!(!gradients.is_onebyte()); +}