diff --git a/BUILD.gn b/BUILD.gn index 4ebaf9b2..bca1e075 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -10,10 +10,11 @@ v8_static_library("rusty_v8") { "src/inspector/client.cc", "src/isolate.cc", "src/locker.cc", + "src/number.cc", "src/platform/mod.cc", "src/platform/task.cc", + "src/string.cc", "src/string_buffer.cc", - "src/value.cc", ] deps = [ ":v8", diff --git a/Cargo.lock b/Cargo.lock index 4b783b5e..09b67277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,11 @@ dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cargo_gn" version = "0.0.13" @@ -62,6 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "rusty_v8" version = "0.0.7" dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_gn 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", @@ -80,6 +86,7 @@ dependencies = [ [metadata] "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum cargo_gn 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0a679c108cad6ee19a25f4dbaf1b4e1fadf303e707fa1e2d060b4c169faeb836" "checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" diff --git a/Cargo.toml b/Cargo.toml index 97cd57da..14382c2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ exclude = [ [dependencies] lazy_static = "1.4.0" libc = "0.2.65" +bitflags = "1.2.1" [build-dependencies] cargo_gn = "0.0.13" diff --git a/src/inspector/channel.cc b/src/inspector/channel.cc index 39f936e0..b17984fe 100644 --- a/src/inspector/channel.cc +++ b/src/inspector/channel.cc @@ -55,4 +55,4 @@ void v8_inspector__V8Inspector__Channel__flushProtocolNotifications( V8Inspector::Channel& self) { self.flushProtocolNotifications(); } -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/src/inspector/client.cc b/src/inspector/client.cc index 26dfacb1..6848f139 100644 --- a/src/inspector/client.cc +++ b/src/inspector/client.cc @@ -51,4 +51,4 @@ void v8_inspector__V8InspectorClient__runIfWaitingForDebugger( int contextGroupId) { self.runIfWaitingForDebugger(contextGroupId); } -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/src/lib.rs b/src/lib.rs index 68083d7b..b59b2a39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,8 @@ #![allow(clippy::new_without_default)] #![allow(dead_code)] +#[macro_use] +extern crate bitflags; #[macro_use] extern crate lazy_static; extern crate libc; @@ -14,13 +16,14 @@ mod inspector; mod isolate; mod local; mod locker; +mod number; mod string_buffer; mod string_view; mod support; -mod value; pub mod array_buffer; pub mod platform; +pub mod string; // This module is intentionally named "V8" rather than "v8" to match the // C++ namespace "v8::V8". #[allow(non_snake_case)] @@ -30,6 +33,7 @@ pub use handle_scope::HandleScope; pub use isolate::Isolate; pub use local::Local; pub use locker::Locker; +pub use number::{Integer, Number}; +pub use string::String; pub use string_buffer::StringBuffer; pub use string_view::StringView; -pub use value::{Integer, Number}; diff --git a/src/value.cc b/src/number.cc similarity index 100% rename from src/value.cc rename to src/number.cc diff --git a/src/value.rs b/src/number.rs similarity index 100% rename from src/value.rs rename to src/number.rs diff --git a/src/string.cc b/src/string.cc new file mode 100644 index 00000000..3539bf18 --- /dev/null +++ b/src/string.cc @@ -0,0 +1,29 @@ +#include + +#include "support.h" +#include "v8/include/v8.h" + +using namespace v8; +using namespace support; + +extern "C" { +String* v8__String__NewFromUtf8(Isolate* isolate, + const char* data, + NewStringType type, + int length) { + return maybe_local_ptr(String::NewFromUtf8(isolate, data, type, length)); +} + +int v8__String__Utf8Length(const String& self, Isolate* isolate) { + return self.Utf8Length(isolate); +} + +int v8__String__WriteUtf8(const String& self, + Isolate* isolate, + char* buffer, + int length, + int* nchars_ref, + int options) { + return self.WriteUtf8(isolate, buffer, length, nchars_ref, options); +} +} diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 00000000..9b3564e9 --- /dev/null +++ b/src/string.rs @@ -0,0 +1,135 @@ +use std::convert::TryInto; +use std::default::Default; +use std::mem::forget; +use std::slice; + +use crate::isolate::CxxIsolate; +use crate::isolate::LockedIsolate; +use crate::support::char; +use crate::support::int; +use crate::support::Opaque; +use crate::HandleScope; +use crate::Local; + +extern "C" { + fn v8__String__NewFromUtf8( + isolate: *mut CxxIsolate, + data: *const char, + new_type: NewStringType, + length: int, + ) -> *const String; + + fn v8__String__Utf8Length(this: &String, isolate: *mut CxxIsolate) -> int; + + fn v8__String__WriteUtf8( + this: &String, + isolate: *mut CxxIsolate, + buffer: *mut char, + length: int, + nchars_ref: *mut int, + options: WriteOptions, + ) -> int; +} + +#[repr(C)] +pub enum NewStringType { + Normal, + Internalized, +} + +impl Default for NewStringType { + fn default() -> Self { + NewStringType::Normal + } +} + +bitflags! { + #[derive(Default)] + #[repr(transparent)] + pub struct WriteOptions: int { + const NO_OPTIONS = 0; + const HINT_MANY_WRITES_EXPECTED = 1; + const NO_NULL_TERMINATION = 2; + const PRESERVE_ONE_BYTE_NULL = 4; + // Used by WriteUtf8 to replace orphan surrogate code units with the + // unicode replacement character. Needs to be set to guarantee valid UTF-8 + // output. + const REPLACE_INVALID_UTF8 = 8; + } +} + +#[repr(C)] +pub struct String(Opaque); + +impl String { + pub fn new_from_utf8<'sc>( + scope: &mut HandleScope<'sc>, + buffer: &[u8], + new_type: NewStringType, + ) -> Option> { + unsafe { + let ptr = v8__String__NewFromUtf8( + scope.cxx_isolate(), + buffer.as_ptr() as *const char, + new_type, + buffer.len().try_into().ok()?, + ); + Local::from_raw(ptr) + } + } + + pub fn utf8_length(&self, isolate: &mut impl LockedIsolate) -> usize { + unsafe { v8__String__Utf8Length(self, isolate.cxx_isolate()) as usize } + } + + pub fn write_utf8( + &self, + isolate: &mut impl LockedIsolate, + buffer: &mut [u8], + nchars_ref: Option<&mut usize>, + options: WriteOptions, + ) -> usize { + let mut nchars_ref_int: int = 0; + let bytes = unsafe { + v8__String__WriteUtf8( + self, + isolate.cxx_isolate(), + buffer.as_mut_ptr() as *mut char, + buffer.len().try_into().unwrap_or(int::max_value()), + &mut nchars_ref_int, + options, + ) + }; + if let Some(r) = nchars_ref { + *r = nchars_ref_int as usize; + } + bytes as usize + } + + // Convenience function not present in the original V8 API. + pub fn new<'sc>( + scope: &mut HandleScope<'sc>, + value: &str, + new_type: NewStringType, + ) -> Option> { + Self::new_from_utf8(scope, value.as_ref(), new_type) + } + + // Convenience function not present in the original V8 API. + pub fn to_rust_string_lossy( + &self, + isolate: &mut impl LockedIsolate, + ) -> std::string::String { + let capacity = self.utf8_length(isolate); + let mut string = std::string::String::with_capacity(capacity); + let data = string.as_mut_ptr(); + forget(string); + let length = self.write_utf8( + isolate, + unsafe { slice::from_raw_parts_mut(data, capacity) }, + None, + WriteOptions::NO_NULL_TERMINATION | WriteOptions::REPLACE_INVALID_UTF8, + ); + unsafe { std::string::String::from_raw_parts(data, length, capacity) } + } +} diff --git a/src/support.h b/src/support.h index 63cb3919..fc362b54 100644 --- a/src/support.h +++ b/src/support.h @@ -7,6 +7,8 @@ #include #include +#include "v8/include/v8.h" + // Check assumptions made in binding code. // TODO(ry) re-enable the following // static_assert(sizeof(bool) == sizeof(uint8_t)); @@ -33,4 +35,9 @@ void construct_in_place(uninit_t& buf, Args... args) { } } // namespace support -#endif // SUPPORT_H_ \ No newline at end of file +template +inline static T* maybe_local_ptr(v8::MaybeLocal value) { + return *value.FromMaybe(v8::Local()); +} + +#endif // SUPPORT_H_ diff --git a/src/support.rs b/src/support.rs index 4b15513e..8f188350 100644 --- a/src/support.rs +++ b/src/support.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; +pub use std::os::raw::c_char as char; pub use std::os::raw::c_int as int; pub type Opaque = [usize; 0]; diff --git a/tests/test_api.rs b/tests/test_api.rs index 688065a6..a6345f1d 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -76,6 +76,24 @@ fn handle_scope_numbers() { drop(g); } +#[test] +fn test_string() { + setup(); + let mut params = v8::Isolate::create_params(); + params.set_array_buffer_allocator( + v8::array_buffer::Allocator::new_default_allocator(), + ); + let mut isolate = v8::Isolate::new(params); + let mut locker = v8::Locker::new(&mut isolate); + v8::HandleScope::enter(&mut locker, |scope| { + let reference = "Hello 🦕 world!"; + let local = + v8::String::new(scope, reference, v8::string::NewStringType::Normal) + .unwrap(); + assert_eq!(reference, local.to_rust_string_lossy(scope)); + }); +} + #[test] fn isolate_new() { let g = setup();