Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for external v8 strings #641

Merged
merged 9 commits into from
Mar 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}