Skip to content

Commit

Permalink
Support for external v8 strings (denoland#641)
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronO authored Mar 7, 2021
1 parent a6d36d1 commit f2766ed
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
62 changes: 62 additions & 0 deletions src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<Local<'s, String>> {
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,
Expand Down
38 changes: 38 additions & 0 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

0 comments on commit f2766ed

Please sign in to comment.