Skip to content

Commit e0e1a29

Browse files
isheludkotargos
andcommitted
deps: V8: backport 088b7112e7ab
Original commit message: [runtime] Fix contextual stores to global with interceptor According to the spec, contextual store in strict mode must first check whether property exists and if not, the ReferenceError should be thrown instead of calling the interceptor setter. See https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s Drive-by: - introduce new Api v8::Object::GetPropertyAttributes(..) which is able to return "property does not exist" result, which wasn't possible with the existing GetPropertyAttributes(..) Api, - update GenericInterceptor* callbacks in test-api-interceptors.cc to better suite for implementing a proxy-like interceptor. Bug: 455600234 Change-Id: I0986c18c406844f58c453e7aa7513c52a9097e04 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7718821 Reviewed-by: Leszek Swirski <[email protected]> Commit-Queue: Igor Sheludko <[email protected]> Cr-Commit-Position: refs/heads/main@{#106322} Refs: v8/v8@088b711 Co-authored-by: Michaël Zasso <[email protected]> PR-URL: #61898 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Filip Skokan <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent bdb5e67 commit e0e1a29

6 files changed

Lines changed: 237 additions & 10 deletions

File tree

common.gypi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
# Reset this number to 0 on major V8 upgrades.
4242
# Increment by one for each non-official patch applied to deps/v8.
43-
'v8_embedder_string': '-node.8',
43+
'v8_embedder_string': '-node.9',
4444

4545
##### V8 defaults for Node.js #####
4646

deps/v8/include/v8-object.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,24 @@ class V8_EXPORT Object : public Value {
340340
* Gets the property attributes of a property which can be None or
341341
* any combination of ReadOnly, DontEnum and DontDelete. Returns
342342
* None when the property doesn't exist.
343+
*
344+
* This method will be deprecated soon, since it doesn't provide a way
345+
* to return "property does not exist" result. Use GetPropertyAttributes with
346+
* PropertyAttribute* instead.
343347
*/
344348
V8_WARN_UNUSED_RESULT Maybe<PropertyAttribute> GetPropertyAttributes(
345349
Local<Context> context, Local<Value> key);
350+
/**
351+
* Gets the property attributes of a property which can be None or
352+
* any combination of ReadOnly, DontEnum and DontDelete.
353+
*
354+
* Returns true and sets *out_attributes if the property exists, false if
355+
* not or empty Maybe if an exception is thrown. In the latter two cases,
356+
* the value of *out_attributes is not modified.
357+
*/
358+
V8_WARN_UNUSED_RESULT Maybe<bool> GetPropertyAttributes(
359+
Local<Context> context, Local<Value> key,
360+
PropertyAttribute* out_attributes);
346361

347362
/**
348363
* Implements Object.getOwnPropertyDescriptor(O, P), see

deps/v8/src/api/api.cc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4831,6 +4831,16 @@ MaybeLocal<Value> v8::Object::GetPrivate(Local<Context> context,
48314831

48324832
Maybe<PropertyAttribute> v8::Object::GetPropertyAttributes(
48334833
Local<Context> context, Local<Value> key) {
4834+
PropertyAttribute attributes = PropertyAttribute::None;
4835+
auto result = GetPropertyAttributes(context, key, &attributes);
4836+
if (result.IsNothing()) return {};
4837+
// This will confusingly return None when the property doesn't exist.
4838+
return Just(attributes);
4839+
}
4840+
4841+
Maybe<bool> v8::Object::GetPropertyAttributes(
4842+
Local<Context> context, Local<Value> key,
4843+
PropertyAttribute* out_attributes) {
48344844
auto i_isolate = i::Isolate::Current();
48354845
EnterV8Scope<> api_scope{i_isolate, context,
48364846
RCCId::kAPI_Object_GetPropertyAttributes};
@@ -4843,9 +4853,10 @@ Maybe<PropertyAttribute> v8::Object::GetPropertyAttributes(
48434853
auto result = i::JSReceiver::GetPropertyAttributes(i_isolate, self, key_name);
48444854
if (result.IsNothing()) return {};
48454855
if (result.FromJust() == i::ABSENT) {
4846-
return Just(static_cast<PropertyAttribute>(i::NONE));
4856+
return Just(false);
48474857
}
4848-
return Just(static_cast<PropertyAttribute>(result.FromJust()));
4858+
*out_attributes = static_cast<PropertyAttribute>(result.FromJust());
4859+
return Just(true);
48494860
}
48504861

48514862
MaybeLocal<Value> v8::Object::GetOwnPropertyDescriptor(Local<Context> context,

deps/v8/src/ic/ic.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,8 @@ bool StoreIC::LookupForWrite(LookupIterator* it, DirectHandle<Object> value,
18991899
case LookupIterator::NOT_FOUND:
19001900
// If we are in StoreGlobal then check if we should throw on
19011901
// non-existent properties.
1902+
// TODO(ishell): make store global ic kind reliable, currently it is
1903+
// not if the feedback vector is not available.
19021904
if (IsStoreGlobalIC() &&
19031905
(GetShouldThrow(it->isolate(), Nothing<ShouldThrow>()) ==
19041906
ShouldThrow::kThrowOnError)) {
@@ -2170,6 +2172,8 @@ MaybeDirectHandle<Object> StoreIC::Store(Handle<JSAny> object,
21702172
Nothing<ShouldThrow>(), store_origin));
21712173
}
21722174
} else {
2175+
// TODO(ishell): deduce should throw from IC kind once it's reliable,
2176+
// currently it is not if the feedback vector is not available.
21732177
MAYBE_RETURN_NULL(Object::SetProperty(&it, value, store_origin));
21742178
}
21752179
return value;
@@ -2252,6 +2256,20 @@ MaybeObjectHandle StoreIC::ComputeHandler(LookupIterator* lookup) {
22522256
isolate());
22532257

22542258
if (lookup->HolderIsReceiverOrHiddenPrototype()) {
2259+
// TODO(ishell): make store global ic kind reliable, currently it is
2260+
// not if the feedback vector is not available.
2261+
if (IsStoreGlobalIC() &&
2262+
(GetShouldThrow(isolate(), Nothing<ShouldThrow>()) ==
2263+
ShouldThrow::kThrowOnError)) {
2264+
DCHECK(IsJSGlobalObject(*lookup->GetHolder<Object>()));
2265+
// This is a contextual store to an interceptor object. Use slow
2266+
// handler because before calling the setter callback we need to
2267+
// check whether the property exists. See
2268+
// https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s
2269+
Handle<Smi> smi_handler = StoreHandler::StoreSlow(isolate());
2270+
return MaybeObjectHandle(smi_handler);
2271+
}
2272+
22552273
if (info->has_setter() && !IsAnyDefineOwn()) {
22562274
TRACE_HANDLER_STATS(isolate(), StoreIC_StoreInterceptorDH);
22572275
if (info->non_masking()) {

deps/v8/src/objects/objects.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,6 +2385,28 @@ Maybe<bool> Object::SetPropertyInternal(LookupIterator* it,
23852385

23862386
case LookupIterator::INTERCEPTOR: {
23872387
if (it->HolderIsReceiverOrHiddenPrototype()) {
2388+
// In case we are executing contextual store to a global object with
2389+
// an interceptor in strict mode, we need to check that the property
2390+
// actually exists before calling the setter. See
2391+
// https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s
2392+
if (IsJSGlobalObject(*it->GetReceiver())) {
2393+
Isolate* isolate = it->isolate();
2394+
auto should_throw_value = GetShouldThrow(isolate, should_throw);
2395+
should_throw = Just(should_throw_value);
2396+
if (should_throw_value == kThrowOnError) {
2397+
Maybe<PropertyAttributes> maybe_attributes =
2398+
JSObject::GetPropertyAttributesWithInterceptor(it);
2399+
if (maybe_attributes.IsNothing()) return Nothing<bool>();
2400+
if ((maybe_attributes.FromJust() & READ_ONLY) != 0) {
2401+
return WriteToReadOnlyProperty(it, value, should_throw);
2402+
}
2403+
if (maybe_attributes.FromJust() == ABSENT) {
2404+
// Interceptor doesn't have the property, continue lookup.
2405+
continue;
2406+
}
2407+
}
2408+
}
2409+
23882410
InterceptorResult result;
23892411
if (!JSObject::SetPropertyWithInterceptor(it, should_throw, value)
23902412
.To(&result)) {

deps/v8/test/cctest/test-api-interceptors.cc

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ v8::Intercepted GenericInterceptorGetter(
201201
Local<String> name = generic_name.As<String>();
202202
String::Utf8Value utf8(isolate, name);
203203
char* name_str = *utf8;
204-
if (*name_str == '_') return v8::Intercepted::kNo;
204+
if (*name_str != '$') return v8::Intercepted::kNo;
205205
str = String::Concat(isolate, v8_str("_str_"), name);
206206
}
207207

@@ -211,6 +211,35 @@ v8::Intercepted GenericInterceptorGetter(
211211
return v8::Intercepted::kYes;
212212
}
213213

214+
v8::Intercepted GenericInterceptorQuery(
215+
Local<Name> generic_name,
216+
const v8::PropertyCallbackInfo<v8::Integer>& info) {
217+
v8::Isolate* isolate = info.GetIsolate();
218+
Local<String> str;
219+
if (generic_name->IsSymbol()) {
220+
Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
221+
if (name->IsUndefined()) return v8::Intercepted::kNo;
222+
str = String::Concat(isolate, v8_str("_sym_"), name.As<String>());
223+
} else {
224+
Local<String> name = generic_name.As<String>();
225+
String::Utf8Value utf8(isolate, name);
226+
char* name_str = *utf8;
227+
if (*name_str != '$') return v8::Intercepted::kNo;
228+
str = String::Concat(isolate, v8_str("_str_"), name);
229+
}
230+
231+
Local<Object> self = info.HolderV2();
232+
v8::PropertyAttribute attributes;
233+
bool exists;
234+
if (self->GetPropertyAttributes(isolate->GetCurrentContext(), str,
235+
&attributes)
236+
.To(&exists)) {
237+
if (!exists) return v8::Intercepted::kNo;
238+
info.GetReturnValue().Set(attributes);
239+
}
240+
return v8::Intercepted::kYes;
241+
}
242+
214243
v8::Intercepted GenericInterceptorSetter(
215244
Local<Name> generic_name, Local<Value> value,
216245
const v8::PropertyCallbackInfo<Boolean>& info) {
@@ -224,7 +253,7 @@ v8::Intercepted GenericInterceptorSetter(
224253
Local<String> name = generic_name.As<String>();
225254
String::Utf8Value utf8(info.GetIsolate(), name);
226255
char* name_str = *utf8;
227-
if (*name_str == '_') return v8::Intercepted::kNo;
256+
if (*name_str != '$') return v8::Intercepted::kNo;
228257
str = String::Concat(info.GetIsolate(), v8_str("_str_"), name);
229258
}
230259

@@ -1639,9 +1668,9 @@ void InterceptorLoadICGlobalWithInterceptor(bool masking) {
16391668
ExpectInt32(
16401669
"(function() {"
16411670
" var f = function(obj) { "
1642-
" return obj.foo;"
1671+
" return obj.$foo;"
16431672
" };"
1644-
" this._str_foo = 42;"
1673+
" this._str_$foo = 42;"
16451674
" var obj = { __proto__: this };"
16461675
" for (var i = 0; i < 1500; i++) obj['p' + i] = 0;"
16471676
" /* Ensure that |obj| is in dictionary mode. */"
@@ -2068,6 +2097,138 @@ THREADED_TEST(EmptyInterceptorVsStoreGlobalICs) {
20682097
120);
20692098
}
20702099

2100+
namespace {
2101+
2102+
void CheckGlobalInterceptorIC(v8::NamedPropertyGetterCallback getter,
2103+
v8::NamedPropertySetterCallback setter,
2104+
v8::NamedPropertyQueryCallback query,
2105+
v8::PropertyHandlerFlags flags,
2106+
const char* source, std::optional<int> expected) {
2107+
v8::Isolate* isolate = CcTest::isolate();
2108+
v8::HandleScope scope(isolate);
2109+
v8::Local<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate);
2110+
v8::NamedPropertyHandlerConfiguration config(
2111+
getter, setter, query, nullptr /* deleter */, nullptr /* enumerator */,
2112+
nullptr /* definer */, nullptr /* descriptor */, {}, flags);
2113+
templ_global->SetHandler(config);
2114+
2115+
LocalContext context(nullptr, templ_global);
2116+
i::DirectHandle<i::JSReceiver> global_proxy =
2117+
v8::Utils::OpenDirectHandle<Object, i::JSReceiver>(context->Global());
2118+
CHECK(IsJSGlobalProxy(*global_proxy));
2119+
i::DirectHandle<i::JSGlobalObject> global(
2120+
i::Cast<i::JSGlobalObject>(global_proxy->map()->prototype()),
2121+
CcTest::i_isolate());
2122+
CHECK(global->map()->has_named_interceptor());
2123+
2124+
v8::Local<Value> value = CompileRun(source);
2125+
if (expected) {
2126+
CHECK_EQ(*expected, value->Int32Value(context.local()).FromJust());
2127+
} else {
2128+
CHECK(value.IsEmpty());
2129+
}
2130+
}
2131+
2132+
void StoreGlobalICWithGlobalInterceptor(bool masking) {
2133+
v8::PropertyHandlerFlags flags = v8::PropertyHandlerFlags::kNone;
2134+
if (!masking) {
2135+
flags = v8::PropertyHandlerFlags::kNonMasking;
2136+
}
2137+
2138+
// In sloppy mode storing to global must succeed.
2139+
CheckGlobalInterceptorIC( //
2140+
GenericInterceptorGetter, //
2141+
GenericInterceptorSetter, //
2142+
GenericInterceptorQuery, //
2143+
flags, //
2144+
R"(
2145+
// Make interceptor behave like it has a read-only property "$y".
2146+
Object.defineProperty(globalThis, '_str_$y',
2147+
{value: 153, writable: false});
2148+
2149+
let result = 0;
2150+
2151+
result += (typeof($x) === 'undefined' ? 0 : 10000);
2152+
result += ($y === 153 ? 0 : 10000);
2153+
2154+
for (var i = 0; i < 20; i++) {
2155+
try {
2156+
$x = i; // not intercepted, absent variable
2157+
result++;
2158+
} catch (e) {
2159+
}
2160+
}
2161+
for (var i = 0; i < 20; i++) {
2162+
try {
2163+
$y = i; // intercepted, read only property
2164+
result++;
2165+
} catch (e) {
2166+
}
2167+
}
2168+
2169+
// Check contextual stores succeeded.
2170+
result += ($x === 19 ? 0 : 100);
2171+
result += ($y === 153 ? 0 : 1000);
2172+
2173+
result
2174+
)",
2175+
40);
2176+
2177+
// In strict mode storing to global must throw.
2178+
CheckGlobalInterceptorIC( //
2179+
GenericInterceptorGetter, //
2180+
GenericInterceptorSetter, //
2181+
GenericInterceptorQuery, //
2182+
flags,
2183+
R"(
2184+
'use strict';
2185+
2186+
// Make interceptor behave like it has a read-only property "$y".
2187+
Object.defineProperty(globalThis, '_str_$y',
2188+
{value: 153, writable: false});
2189+
2190+
let result = 0;
2191+
2192+
result += (typeof($x) === 'undefined' ? 0 : 10000);
2193+
result += ($y === 153 ? 0 : 10000);
2194+
2195+
for (var i = 0; i < 20; i++) {
2196+
try {
2197+
$x = i; // not intercepted, absent variable
2198+
} catch (e) {
2199+
result++;
2200+
}
2201+
}
2202+
for (var i = 0; i < 20; i++) {
2203+
try {
2204+
$y = i; // intercepted, read only property
2205+
} catch (e) {
2206+
result++;
2207+
}
2208+
}
2209+
2210+
// Check contextual stores did not happen.
2211+
result += (typeof($x) === 'undefined' ? 0 : 100);
2212+
result += ($y === 153 ? 0 : 1000);
2213+
2214+
result
2215+
)",
2216+
40);
2217+
}
2218+
2219+
} // namespace
2220+
2221+
// Checks correctness of global objects with interceptor vs contextual stores.
2222+
THREADED_TEST(StoreGlobalICWithGlobalNonMaskingInterceptor) {
2223+
const bool masking = false;
2224+
StoreGlobalICWithGlobalInterceptor(masking);
2225+
}
2226+
2227+
THREADED_TEST(StoreGlobalICWithGlobalMaskingInterceptor) {
2228+
const bool masking = true;
2229+
StoreGlobalICWithGlobalInterceptor(masking);
2230+
}
2231+
20712232
THREADED_TEST(LegacyInterceptorDoesNotSeeSymbols) {
20722233
LocalContext env;
20732234
v8::Isolate* isolate = CcTest::isolate();
@@ -2120,9 +2281,9 @@ THREADED_TEST(GenericInterceptorDoesSeeSymbols) {
21202281
ExpectInt32("child._sym_age", 10);
21212282

21222283
// Check that it also sees strings.
2123-
CompileRun("child.foo = 47");
2124-
ExpectInt32("child.foo", 47);
2125-
ExpectInt32("child._str_foo", 47);
2284+
CompileRun("child.$foo = 47");
2285+
ExpectInt32("child.$foo", 47);
2286+
ExpectInt32("child._str_$foo", 47);
21262287

21272288
// Check that the interceptor can punt (in this case, on anonymous symbols).
21282289
CompileRun("child[anon] = 31337");

0 commit comments

Comments
 (0)