From 6b90cbe4994ce51b5fd6bb506a976177e33a780b Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 30 Sep 2020 01:04:59 +0200 Subject: [PATCH] add Object + ObjectTemplate internal field support (#477) The rusty_v8 API deviates slightly from the V8 C++ API because the latter is definitely unsound when you pass in out-of-range indexes. --- src/binding.cc | 23 ++++++++++++++++++++ src/object.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++ src/template.rs | 29 ++++++++++++++++++++++++++ tests/test_api.rs | 32 ++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) diff --git a/src/binding.cc b/src/binding.cc index eb3926d7..f8d3dac5 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -733,6 +733,15 @@ const v8::Object* v8__ObjectTemplate__NewInstance( ptr_to_local(&self)->NewInstance(ptr_to_local(&context))); } +int v8__ObjectTemplate__InternalFieldCount(const v8::ObjectTemplate& self) { + return ptr_to_local(&self)->InternalFieldCount(); +} + +void v8__ObjectTemplate__SetInternalFieldCount( + const v8::ObjectTemplate& self, int value) { + ptr_to_local(&self)->SetInternalFieldCount(value); +} + const v8::Object* v8__Object__New(v8::Isolate* isolate) { return local_to_ptr(v8::Object::New(isolate)); } @@ -866,6 +875,20 @@ MaybeBool v8__Object__DeleteIndex(const v8::Object& self, ptr_to_local(&self)->Delete(ptr_to_local(&context), index)); } +int v8__Object__InternalFieldCount(const v8::Object& self) { + return ptr_to_local(&self)->InternalFieldCount(); +} + +const v8::Value* v8__Object__GetInternalField(const v8::Object& self, + int index) { + return local_to_ptr(ptr_to_local(&self)->GetInternalField(index)); +} + +void v8__Object__SetInternalField(const v8::Object& self, int index, + const v8::Value& value) { + ptr_to_local(&self)->SetInternalField(index, ptr_to_local(&value)); +} + const v8::Array* v8__Array__New(v8::Isolate* isolate, int length) { return local_to_ptr(v8::Array::New(isolate, length)); } diff --git a/src/object.rs b/src/object.rs index ee8c339f..d84bdced 100644 --- a/src/object.rs +++ b/src/object.rs @@ -13,6 +13,7 @@ use crate::Name; use crate::Object; use crate::PropertyAttribute; use crate::Value; +use std::convert::TryFrom; extern "C" { fn v8__Object__New(isolate: *mut Isolate) -> *const Object; @@ -107,6 +108,16 @@ extern "C" { context: *const Context, index: u32, ) -> MaybeBool; + fn v8__Object__InternalFieldCount(this: *const Object) -> int; + fn v8__Object__GetInternalField( + this: *const Object, + index: int, + ) -> *const Value; + fn v8__Object__SetInternalField( + this: *const Object, + index: int, + value: *const Value, + ); fn v8__Array__New(isolate: *mut Isolate, length: int) -> *const Array; fn v8__Array__New_with_elements( @@ -404,6 +415,48 @@ impl Object { } .into() } + + /// Gets the number of internal fields for this Object. + pub fn internal_field_count(&self) -> usize { + let count = unsafe { v8__Object__InternalFieldCount(self) }; + usize::try_from(count).expect("bad internal field count") // Can't happen. + } + + /// Gets the value from an internal field. + pub fn get_internal_field<'s>( + &self, + scope: &mut HandleScope<'s>, + index: usize, + ) -> Option> { + // Trying to access out-of-bounds internal fields makes V8 abort + // in debug mode and access out-of-bounds memory in release mode. + // The C++ API takes an i32 but doesn't check for indexes < 0, which + // results in an out-of-bounds access in both debug and release mode. + if index < self.internal_field_count() { + if let Ok(index) = int::try_from(index) { + return unsafe { + scope.cast_local(|_| v8__Object__GetInternalField(self, index)) + }; + } + } + None + } + + /// Sets the value in an internal field. Returns false when the index + /// is out of bounds, true otherwise. + pub fn set_internal_field(&self, index: usize, value: Local) -> bool { + // Trying to access out-of-bounds internal fields makes V8 abort + // in debug mode and access out-of-bounds memory in release mode. + // The C++ API takes an i32 but doesn't check for indexes < 0, which + // results in an out-of-bounds access in both debug and release mode. + if index < self.internal_field_count() { + if let Ok(index) = int::try_from(index) { + unsafe { v8__Object__SetInternalField(self, index, &*value) }; + return true; + } + } + false + } } impl Array { diff --git a/src/template.rs b/src/template.rs index b4c0e56a..368ade50 100644 --- a/src/template.rs +++ b/src/template.rs @@ -4,6 +4,7 @@ use crate::data::Name; use crate::data::ObjectTemplate; use crate::data::Template; use crate::isolate::Isolate; +use crate::support::int; use crate::support::MapFnTo; use crate::Context; use crate::Function; @@ -14,6 +15,7 @@ use crate::Object; use crate::PropertyAttribute; use crate::String; use crate::NONE; +use std::convert::TryFrom; extern "C" { fn v8__Template__Set( @@ -44,6 +46,12 @@ extern "C" { this: *const ObjectTemplate, context: *const Context, ) -> *const Object; + fn v8__ObjectTemplate__InternalFieldCount(this: *const ObjectTemplate) + -> int; + fn v8__ObjectTemplate__SetInternalFieldCount( + this: *const ObjectTemplate, + value: int, + ); } impl Template { @@ -132,4 +140,25 @@ impl ObjectTemplate { }) } } + + /// Gets the number of internal fields for objects generated from + /// this template. + pub fn internal_field_count(&self) -> usize { + let count = unsafe { v8__ObjectTemplate__InternalFieldCount(self) }; + usize::try_from(count).expect("bad internal field count") // Can't happen. + } + + /// Sets the number of internal fields for objects generated from + /// this template. + pub fn set_internal_field_count(&self, value: usize) -> bool { + // The C++ API takes an i32 but trying to set a value < 0 + // results in unpredictable behavior, hence we disallow it. + match int::try_from(value) { + Err(_) => false, + Ok(value) => { + unsafe { v8__ObjectTemplate__SetInternalFieldCount(self, value) }; + true + } + } + } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 23db2fe3..b396b235 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -1132,6 +1132,25 @@ fn json() { } } +#[test] +fn no_internal_field() { + 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); + let object = v8::Object::new(scope); + let value = v8::Integer::new(scope, 42).into(); + assert_eq!(0, object.internal_field_count()); + for index in &[0, 1, 1337] { + assert!(object.get_internal_field(scope, *index).is_none()); + assert_eq!(false, object.set_internal_field(*index, value)); + assert!(object.get_internal_field(scope, *index).is_none()); + } + } +} + #[test] fn object_template() { let _setup_guard = setup(); @@ -1142,11 +1161,23 @@ fn object_template() { let function_templ = v8::FunctionTemplate::new(scope, fortytwo_callback); let name = v8::String::new(scope, "f").unwrap(); let attr = v8::READ_ONLY + v8::DONT_ENUM + v8::DONT_DELETE; + object_templ.set_internal_field_count(1); object_templ.set_with_attr(name.into(), function_templ.into(), attr); let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); + let object = object_templ.new_instance(scope).unwrap(); assert!(!object.is_null_or_undefined()); + assert_eq!(1, object.internal_field_count()); + + let value = object.get_internal_field(scope, 0).unwrap(); + assert!(value.is_undefined()); + + let fortytwo = v8::Integer::new(scope, 42).into(); + assert_eq!(true, object.set_internal_field(0, fortytwo)); + let value = object.get_internal_field(scope, 0).unwrap(); + assert!(value.same_value(fortytwo)); + let name = v8::String::new(scope, "g").unwrap(); context.global(scope).define_own_property( scope, @@ -1189,6 +1220,7 @@ fn object_template_from_function_template() { function_templ.set_class_name(expected_class_name); let object_templ = v8::ObjectTemplate::new_from_template(scope, function_templ); + assert_eq!(0, object_templ.internal_field_count()); let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); let object = object_templ.new_instance(scope).unwrap();