diff --git a/common.gypi b/common.gypi index bae09d8a5d6300..40797a9f7ca0b0 100644 --- a/common.gypi +++ b/common.gypi @@ -41,7 +41,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.24', + 'v8_embedder_string': '-node.25', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-template.h b/deps/v8/include/v8-template.h index 35c3c34e8badbc..299e17f86aa052 100644 --- a/deps/v8/include/v8-template.h +++ b/deps/v8/include/v8-template.h @@ -683,6 +683,13 @@ enum class PropertyHandlerFlags { */ kHasNoSideEffect = 1 << 2, + /** + * The interceptor may return non-configurable (PropertyAttribute::DontDelete) + * properties. When set on a global object's interceptor, it will be consulted + * during HasRestrictedGlobalProperty checks for lexical declarations. + */ + kHasDontDeleteProperty = 1 << 3, + /** * This flag is used to distinguish which callbacks were provided - * GenericNamedPropertyXXXCallback (old signature) or diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index bdb9f715de95b4..fbd628370c0b1b 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -1613,6 +1613,8 @@ i::DirectHandle CreateInterceptorInfo( !(flags & PropertyHandlerFlags::kOnlyInterceptStrings)); obj->set_non_masking(flags & PropertyHandlerFlags::kNonMasking); obj->set_has_no_side_effect(flags & PropertyHandlerFlags::kHasNoSideEffect); + obj->set_has_dont_delete_property( + flags & PropertyHandlerFlags::kHasDontDeleteProperty); if (data.IsEmpty()) { data = v8::Undefined(reinterpret_cast(i_isolate)); diff --git a/deps/v8/src/execution/execution.cc b/deps/v8/src/execution/execution.cc index 37c313309e1e24..4dfd84f842e061 100644 --- a/deps/v8/src/execution/execution.cc +++ b/deps/v8/src/execution/execution.cc @@ -230,18 +230,14 @@ MaybeDirectHandle NewScriptContext( } if (IsLexicalVariableMode(mode)) { - LookupIterator lookup_it(isolate, global_object, name, global_object, - LookupIterator::OWN_SKIP_INTERCEPTOR); - Maybe maybe = - JSReceiver::GetPropertyAttributes(&lookup_it); - // Can't fail since the we looking up own properties on the global object - // skipping interceptors. - CHECK(!maybe.IsNothing()); - if ((maybe.FromJust() & DONT_DELETE) != 0) { - // ES#sec-globaldeclarationinstantiation 5.a: + Maybe has_restricted = JSGlobalObject::HasRestrictedGlobalProperty( + isolate, global_object, name); + if (has_restricted.IsNothing()) return MaybeDirectHandle(); + if (has_restricted.FromJust()) { + // https://tc39.es/ecma262/#sec-globaldeclarationinstantiation 3.a: // If envRec.HasVarDeclaration(name) is true, throw a SyntaxError // exception. - // ES#sec-globaldeclarationinstantiation 5.d: + // https://tc39.es/ecma262/#sec-globaldeclarationinstantiation 3.d: // If hasRestrictedGlobal is true, throw a SyntaxError exception. MessageLocation location(script, 0, 1); isolate->ThrowAt(isolate->factory()->NewSyntaxError( diff --git a/deps/v8/src/objects/api-callbacks-inl.h b/deps/v8/src/objects/api-callbacks-inl.h index f08e91f563cf2c..7c28fedd9a2c17 100644 --- a/deps/v8/src/objects/api-callbacks-inl.h +++ b/deps/v8/src/objects/api-callbacks-inl.h @@ -203,6 +203,8 @@ BOOL_ACCESSORS(InterceptorInfo, flags, has_no_side_effect, // TODO(ishell): remove once all the Api changes are done. BOOL_ACCESSORS(InterceptorInfo, flags, has_new_callbacks_signature, HasNewCallbacksSignatureBit::kShift) +BOOL_ACCESSORS(InterceptorInfo, flags, has_dont_delete_property, + HasDontDeletePropertyBit::kShift) void InterceptorInfo::RemoveCallbackRedirectionForSerialization( IsolateForSandbox isolate) { diff --git a/deps/v8/src/objects/api-callbacks.h b/deps/v8/src/objects/api-callbacks.h index f6c7c35a4dc7f6..f55cf0881555b6 100644 --- a/deps/v8/src/objects/api-callbacks.h +++ b/deps/v8/src/objects/api-callbacks.h @@ -182,6 +182,7 @@ class InterceptorInfo // TODO(ishell): remove support for old signatures once they go through // Api deprecation process. DECL_BOOLEAN_ACCESSORS(has_new_callbacks_signature) + DECL_BOOLEAN_ACCESSORS(has_dont_delete_property) DEFINE_TORQUE_GENERATED_INTERCEPTOR_INFO_FLAGS() diff --git a/deps/v8/src/objects/api-callbacks.tq b/deps/v8/src/objects/api-callbacks.tq index 6c4946e941c78e..bb5c3b9aaf54da 100644 --- a/deps/v8/src/objects/api-callbacks.tq +++ b/deps/v8/src/objects/api-callbacks.tq @@ -8,6 +8,7 @@ bitfield struct InterceptorInfoFlags extends uint31 { named: bool: 1 bit; has_no_side_effect: bool: 1 bit; has_new_callbacks_signature: bool: 1 bit; + has_dont_delete_property: bool: 1 bit; } extern class InterceptorInfo extends HeapObject { diff --git a/deps/v8/src/objects/js-objects.cc b/deps/v8/src/objects/js-objects.cc index 8e18b4fe6602db..9c1f619fd0d75e 100644 --- a/deps/v8/src/objects/js-objects.cc +++ b/deps/v8/src/objects/js-objects.cc @@ -5785,6 +5785,24 @@ void JSGlobalObject::InvalidatePropertyCell(DirectHandle global, value); } +// static +Maybe JSGlobalObject::HasRestrictedGlobalProperty( + Isolate* isolate, DirectHandle global, + DirectHandle name) { + LookupIterator::Configuration config = LookupIterator::OWN_SKIP_INTERCEPTOR; + if (global->HasNamedInterceptor() && + global->GetNamedInterceptor()->has_dont_delete_property()) { + config = LookupIterator::OWN; + } + LookupIterator it(isolate, global, name, global, config); + Maybe maybe = JSReceiver::GetPropertyAttributes(&it); + if (maybe.IsNothing()) return Nothing(); + // Global var and function bindings (except those that are introduced by + // non-strict direct eval) are non-configurable and are therefore restricted + // global properties. + return Just((maybe.FromJust() & DONT_DELETE) != 0); +} + // static MaybeDirectHandle JSDate::New(Isolate* isolate, DirectHandle constructor, diff --git a/deps/v8/src/objects/js-objects.h b/deps/v8/src/objects/js-objects.h index e289aae8aca06b..fa7c2ec4179abd 100644 --- a/deps/v8/src/objects/js-objects.h +++ b/deps/v8/src/objects/js-objects.h @@ -1218,6 +1218,11 @@ class JSGlobalObject static void InvalidatePropertyCell(DirectHandle object, DirectHandle name); + // https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty + static Maybe HasRestrictedGlobalProperty( + Isolate* isolate, DirectHandle global, + DirectHandle name); + inline bool IsDetached(); inline Tagged native_context(); diff --git a/deps/v8/test/cctest/test-api-interceptors.cc b/deps/v8/test/cctest/test-api-interceptors.cc index 2867809be4091e..6e801321c2abb3 100644 --- a/deps/v8/test/cctest/test-api-interceptors.cc +++ b/deps/v8/test/cctest/test-api-interceptors.cc @@ -834,6 +834,49 @@ THREADED_TEST(InterceptorFunctionRedeclareWithQueryCallback) { v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).ToLocalChecked(); } +// A lexical declaration must throw when the interceptor reports the property +// as non-configurable (DontDelete), per HasRestrictedGlobalProperty checks in +// https://tc39.es/ecma262/#sec-globaldeclarationinstantiation. +THREADED_TEST(LexicalDeclThrowsForRestrictedGlobalViaInterceptor) { + v8::HandleScope scope(CcTest::isolate()); + LocalContext env; + v8::Local templ = + v8::FunctionTemplate::New(CcTest::isolate()); + + v8::Local object_template = templ->InstanceTemplate(); + object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( + nullptr, nullptr, QueryCallbackSetDontDelete, nullptr, nullptr, + v8::Local(), + v8::PropertyHandlerFlags::kHasDontDeleteProperty)); + v8::Local ctx = + v8::Context::New(CcTest::isolate(), nullptr, object_template); + + v8::TryCatch try_catch(CcTest::isolate()); + v8::Local code = v8_str("let x;"); + CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty()); + CHECK(try_catch.HasCaught()); +} + +// Without kHasDontDeleteProperty, the interceptor is not consulted for +// HasRestrictedGlobalProperty and lexical declarations succeed. +THREADED_TEST(LexicalDeclSucceedsWithoutRestrictedGlobalFlag) { + v8::HandleScope scope(CcTest::isolate()); + LocalContext env; + v8::Local templ = + v8::FunctionTemplate::New(CcTest::isolate()); + + v8::Local object_template = templ->InstanceTemplate(); + object_template->SetHandler(v8::NamedPropertyHandlerConfiguration( + nullptr, nullptr, QueryCallbackSetDontDelete)); + v8::Local ctx = + v8::Context::New(CcTest::isolate(), nullptr, object_template); + + v8::TryCatch try_catch(CcTest::isolate()); + v8::Local code = v8_str("let x;"); + CHECK(!v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty()); + CHECK(!try_catch.HasCaught()); +} + // Regression test for chromium bug 656648. // Do not crash on non-masking, intercepting setter callbacks. THREADED_TEST(NonMaskingInterceptor) { diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 40ed019a73d83b..f10498efc20187 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -170,16 +170,18 @@ void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { Local global_object_template = global_func_template->InstanceTemplate(); - NamedPropertyHandlerConfiguration config( - PropertyGetterCallback, - PropertySetterCallback, - PropertyQueryCallback, - PropertyDeleterCallback, - PropertyEnumeratorCallback, - PropertyDefinerCallback, - PropertyDescriptorCallback, - {}, - PropertyHandlerFlags::kHasNoSideEffect); + PropertyHandlerFlags flags = static_cast( + static_cast(PropertyHandlerFlags::kHasNoSideEffect) | + static_cast(PropertyHandlerFlags::kHasDontDeleteProperty)); + NamedPropertyHandlerConfiguration config(PropertyGetterCallback, + PropertySetterCallback, + PropertyQueryCallback, + PropertyDeleterCallback, + PropertyEnumeratorCallback, + PropertyDefinerCallback, + PropertyDescriptorCallback, + {}, + flags); IndexedPropertyHandlerConfiguration indexed_config( IndexedPropertyGetterCallback, @@ -190,7 +192,7 @@ void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { IndexedPropertyDefinerCallback, IndexedPropertyDescriptorCallback, {}, - PropertyHandlerFlags::kHasNoSideEffect); + flags); global_object_template->SetHandler(config); global_object_template->SetHandler(indexed_config); diff --git a/test/parallel/test-vm-global-restricted-property.js b/test/parallel/test-vm-global-restricted-property.js new file mode 100644 index 00000000000000..e57cc8dc854613 --- /dev/null +++ b/test/parallel/test-vm-global-restricted-property.js @@ -0,0 +1,20 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +const ctx = vm.createContext({}); + +// Define a non-configurable ("restricted") property on the context global. +vm.runInContext( + "Object.defineProperty(this, 'foo', { value: 1, configurable: false });", + ctx +); + +// https://tc39.es/ecma262/#sec-globaldeclarationinstantiation 3.d: +// If hasRestrictedGlobal is true, throw a SyntaxError exception. +assert.throws( + () => vm.runInContext('let foo = 2;', ctx), + vm.runInContext('SyntaxError', ctx), +);