Commit 81c14eb8 authored by Joyee Cheung's avatar Joyee Cheung Committed by V8 LUCI CQ

[runtime] return when failed access callback doesn't throw

When the failed access callback is configured but it doesn't throw,
we should return instead of expecting an exception, otherwise
it would crash because there isn't one.

This patch also adds --throw-on-failed-access-check and
--noop-on-failed-access-check in d8 to mimic the behavior
of the failed access check callback in chromium.

Bug: chromium:1339722
Change-Id: Ie1db9d2fb364c6f8259eb9b8d81a21071c280a80
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3737305
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81557}
parent 3ec0f936
...@@ -3091,6 +3091,39 @@ Local<FunctionTemplate> Shell::CreateNodeTemplates( ...@@ -3091,6 +3091,39 @@ Local<FunctionTemplate> Shell::CreateNodeTemplates(
return div_element; return div_element;
} }
static bool D8AccessCheckCallback(Local<Context> accessing_context,
Local<Object> accessed_object,
Local<Value> data) {
return Isolate::GetCurrent()
->GetCurrentContext()
->GetSecurityToken()
->StrictEquals(
accessed_object->GetCreationContextChecked()->GetSecurityToken());
}
static void AccessNamedGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {}
static void AccessNamedSetter(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {}
static void AccessNamedQuery(Local<Name> property,
const PropertyCallbackInfo<Integer>& info) {}
static void AccessNamedDeleter(Local<Name> property,
const PropertyCallbackInfo<Boolean>& info) {}
static void AccessNamedEnumerator(const PropertyCallbackInfo<Array>& info) {}
static void AccessIndexedGetter(uint32_t index,
const PropertyCallbackInfo<Value>& info) {}
static void AccessIndexedSetter(uint32_t index, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {}
static void AccessIndexedQuery(uint32_t index,
const PropertyCallbackInfo<Integer>& info) {}
static void AccessIndexedDeleter(uint32_t index,
const PropertyCallbackInfo<Boolean>& info) {}
static void AccessIndexedEnumerator(const PropertyCallbackInfo<Array>& info) {}
Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) { Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate); Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
global_template->Set(Symbol::GetToStringTag(isolate), global_template->Set(Symbol::GetToStringTag(isolate),
...@@ -3144,6 +3177,18 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) { ...@@ -3144,6 +3177,18 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
Shell::CreateAsyncHookTemplate(isolate)); Shell::CreateAsyncHookTemplate(isolate));
} }
if (options.throw_on_failed_access_check ||
options.noop_on_failed_access_check) {
global_template->SetAccessCheckCallbackAndHandler(
D8AccessCheckCallback,
v8::NamedPropertyHandlerConfiguration(
AccessNamedGetter, AccessNamedSetter, AccessNamedQuery,
AccessNamedDeleter, AccessNamedEnumerator),
v8::IndexedPropertyHandlerConfiguration(
AccessIndexedGetter, AccessIndexedSetter, AccessIndexedQuery,
AccessIndexedDeleter, AccessIndexedEnumerator));
}
return global_template; return global_template;
} }
...@@ -3412,6 +3457,11 @@ void Shell::PromiseRejectCallback(v8::PromiseRejectMessage data) { ...@@ -3412,6 +3457,11 @@ void Shell::PromiseRejectCallback(v8::PromiseRejectMessage data) {
isolate_data->AddUnhandledPromise(promise, message, exception); isolate_data->AddUnhandledPromise(promise, message, exception);
} }
static void ThrowOnFailedAccessCheck(Local<Object> host, v8::AccessType type,
Local<Value> data) {
Isolate::GetCurrent()->ThrowError("Error in failed access check callback");
}
void Shell::Initialize(Isolate* isolate, D8Console* console, void Shell::Initialize(Isolate* isolate, D8Console* console,
bool isOnMainThread) { bool isOnMainThread) {
isolate->SetPromiseRejectCallback(PromiseRejectCallback); isolate->SetPromiseRejectCallback(PromiseRejectCallback);
...@@ -3435,6 +3485,13 @@ void Shell::Initialize(Isolate* isolate, D8Console* console, ...@@ -3435,6 +3485,13 @@ void Shell::Initialize(Isolate* isolate, D8Console* console,
isolate->SetHostCreateShadowRealmContextCallback( isolate->SetHostCreateShadowRealmContextCallback(
Shell::HostCreateShadowRealmContext); Shell::HostCreateShadowRealmContext);
if (options.throw_on_failed_access_check) {
isolate->SetFailedAccessCheckCallbackFunction(ThrowOnFailedAccessCheck);
} else if (options.noop_on_failed_access_check) {
isolate->SetFailedAccessCheckCallbackFunction(
[](Local<Object> host, v8::AccessType type, Local<Value> data) {});
}
#ifdef V8_FUZZILLI #ifdef V8_FUZZILLI
// Let the parent process (Fuzzilli) know we are ready. // Let the parent process (Fuzzilli) know we are ready.
if (options.fuzzilli_enable_builtins_coverage) { if (options.fuzzilli_enable_builtins_coverage) {
...@@ -4800,6 +4857,12 @@ bool Shell::SetOptions(int argc, char* argv[]) { ...@@ -4800,6 +4857,12 @@ bool Shell::SetOptions(int argc, char* argv[]) {
options.enable_sandbox_crash_filter = true; options.enable_sandbox_crash_filter = true;
argv[i] = nullptr; argv[i] = nullptr;
#endif // V8_ENABLE_SANDBOX #endif // V8_ENABLE_SANDBOX
} else if (strcmp(argv[i], "--throw-on-failed-access-check") == 0) {
options.throw_on_failed_access_check = true;
argv[i] = nullptr;
} else if (strcmp(argv[i], "--noop-on-failed-access-check") == 0) {
options.noop_on_failed_access_check = true;
argv[i] = nullptr;
} else { } else {
#ifdef V8_TARGET_OS_WIN #ifdef V8_TARGET_OS_WIN
PreProcessUnicodeFilenameArg(argv, i); PreProcessUnicodeFilenameArg(argv, i);
...@@ -4812,6 +4875,13 @@ bool Shell::SetOptions(int argc, char* argv[]) { ...@@ -4812,6 +4875,13 @@ bool Shell::SetOptions(int argc, char* argv[]) {
FATAL("Flag --no-always-turbofan is incompatible with --stress-opt."); FATAL("Flag --no-always-turbofan is incompatible with --stress-opt.");
} }
if (options.throw_on_failed_access_check &&
options.noop_on_failed_access_check && check_d8_flag_contradictions) {
FATAL(
"Flag --throw-on-failed-access-check is incompatible with "
"--noop-on-failed-access-check.");
}
const char* usage = const char* usage =
"Synopsis:\n" "Synopsis:\n"
" shell [options] [--shell] [<file>...]\n" " shell [options] [--shell] [<file>...]\n"
......
...@@ -482,6 +482,10 @@ class ShellOptions { ...@@ -482,6 +482,10 @@ class ShellOptions {
DisallowReassignment<bool> enable_sandbox_crash_filter = { DisallowReassignment<bool> enable_sandbox_crash_filter = {
"enable-sandbox-crash-filter", false}; "enable-sandbox-crash-filter", false};
#endif // V8_ENABLE_SANDBOX #endif // V8_ENABLE_SANDBOX
DisallowReassignment<bool> throw_on_failed_access_check = {
"throw-on-failed-access-check", false};
DisallowReassignment<bool> noop_on_failed_access_check = {
"noop-on-failed-access-check", false};
}; };
class Shell : public i::AllStatic { class Shell : public i::AllStatic {
......
...@@ -1852,8 +1852,11 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name, ...@@ -1852,8 +1852,11 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
if (name->IsPrivate()) { if (name->IsPrivate()) {
if (name->IsPrivateName()) { if (name->IsPrivateName()) {
DCHECK(!IsDefineNamedOwnIC()); DCHECK(!IsDefineNamedOwnIC());
if (!JSReceiver::CheckPrivateNameStore(&it, IsDefineKeyedOwnIC())) { Maybe<bool> can_store =
return MaybeHandle<Object>(); JSReceiver::CheckPrivateNameStore(&it, IsDefineKeyedOwnIC());
MAYBE_RETURN_NULL(can_store);
if (!can_store.FromJust()) {
return isolate()->factory()->undefined_value();
} }
} }
...@@ -1877,8 +1880,9 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name, ...@@ -1877,8 +1880,9 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
!Handle<JSObject>::cast(object)->HasNamedInterceptor()) { !Handle<JSObject>::cast(object)->HasNamedInterceptor()) {
Maybe<bool> can_define = JSReceiver::CheckIfCanDefine( Maybe<bool> can_define = JSReceiver::CheckIfCanDefine(
isolate(), &it, value, Nothing<ShouldThrow>()); isolate(), &it, value, Nothing<ShouldThrow>());
if (can_define.IsNothing() || !can_define.FromJust()) { MAYBE_RETURN_NULL(can_define);
return MaybeHandle<Object>(); if (!can_define.FromJust()) {
return isolate()->factory()->undefined_value();
} }
} }
......
...@@ -188,14 +188,13 @@ Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate, ...@@ -188,14 +188,13 @@ Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate,
} }
// static // static
bool JSReceiver::CheckPrivateNameStore(LookupIterator* it, bool is_define) { Maybe<bool> JSReceiver::CheckPrivateNameStore(LookupIterator* it,
bool is_define) {
DCHECK(it->GetName()->IsPrivateName()); DCHECK(it->GetName()->IsPrivateName());
Isolate* isolate = it->isolate(); Isolate* isolate = it->isolate();
Handle<String> name_string( Handle<String> name_string(
String::cast(Handle<Symbol>::cast(it->GetName())->description()), String::cast(Handle<Symbol>::cast(it->GetName())->description()),
isolate); isolate);
bool should_throw = GetShouldThrow(isolate, Nothing<ShouldThrow>()) ==
ShouldThrow::kThrowOnError;
for (; it->IsFound(); it->Next()) { for (; it->IsFound(); it->Next()) {
switch (it->state()) { switch (it->state()) {
case LookupIterator::TRANSITION: case LookupIterator::TRANSITION:
...@@ -209,31 +208,30 @@ bool JSReceiver::CheckPrivateNameStore(LookupIterator* it, bool is_define) { ...@@ -209,31 +208,30 @@ bool JSReceiver::CheckPrivateNameStore(LookupIterator* it, bool is_define) {
if (!it->HasAccess()) { if (!it->HasAccess()) {
isolate->ReportFailedAccessCheck( isolate->ReportFailedAccessCheck(
Handle<JSObject>::cast(it->GetReceiver())); Handle<JSObject>::cast(it->GetReceiver()));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, false); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
return false; return Just(false);
} }
break; break;
case LookupIterator::DATA: case LookupIterator::DATA:
if (is_define && should_throw) { if (is_define) {
MessageTemplate message = MessageTemplate message =
it->GetName()->IsPrivateBrand() it->GetName()->IsPrivateBrand()
? MessageTemplate::kInvalidPrivateBrandReinitialization ? MessageTemplate::kInvalidPrivateBrandReinitialization
: MessageTemplate::kInvalidPrivateFieldReinitialization; : MessageTemplate::kInvalidPrivateFieldReinitialization;
isolate->Throw(*(isolate->factory()->NewTypeError( RETURN_FAILURE(isolate,
message, name_string, it->GetReceiver()))); GetShouldThrow(isolate, Nothing<ShouldThrow>()),
return false; NewTypeError(message, name_string, it->GetReceiver()));
} }
return true; return Just(true);
} }
} }
DCHECK(!it->IsFound()); DCHECK(!it->IsFound());
if (!is_define && should_throw) { if (!is_define) {
isolate->Throw(*(isolate->factory()->NewTypeError( RETURN_FAILURE(isolate, GetShouldThrow(isolate, Nothing<ShouldThrow>()),
MessageTemplate::kInvalidPrivateMemberWrite, name_string, NewTypeError(MessageTemplate::kInvalidPrivateMemberWrite,
it->GetReceiver()))); name_string, it->GetReceiver()));
return false;
} }
return true; return Just(true);
} }
// static // static
...@@ -1749,7 +1747,7 @@ Maybe<bool> JSReceiver::AddPrivateField(LookupIterator* it, ...@@ -1749,7 +1747,7 @@ Maybe<bool> JSReceiver::AddPrivateField(LookupIterator* it,
if (!it->HasAccess()) { if (!it->HasAccess()) {
it->isolate()->ReportFailedAccessCheck(it->GetHolder<JSObject>()); it->isolate()->ReportFailedAccessCheck(it->GetHolder<JSObject>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>()); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
return Just(true); return Just(false);
} }
break; break;
} }
...@@ -2553,6 +2551,9 @@ bool JSObject::AllCanRead(LookupIterator* it) { ...@@ -2553,6 +2551,9 @@ bool JSObject::AllCanRead(LookupIterator* it) {
// which have already been checked. // which have already been checked.
DCHECK(it->state() == LookupIterator::ACCESS_CHECK || DCHECK(it->state() == LookupIterator::ACCESS_CHECK ||
it->state() == LookupIterator::INTERCEPTOR); it->state() == LookupIterator::INTERCEPTOR);
if (it->IsPrivateName()) {
return false;
}
for (it->Next(); it->IsFound(); it->Next()) { for (it->Next(); it->IsFound(); it->Next()) {
if (it->state() == LookupIterator::ACCESSOR) { if (it->state() == LookupIterator::ACCESSOR) {
auto accessors = it->GetAccessors(); auto accessors = it->GetAccessors();
...@@ -2638,6 +2639,9 @@ Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithFailedAccessCheck( ...@@ -2638,6 +2639,9 @@ Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithFailedAccessCheck(
// static // static
bool JSObject::AllCanWrite(LookupIterator* it) { bool JSObject::AllCanWrite(LookupIterator* it) {
if (it->IsPrivateName()) {
return false;
}
for (; it->IsFound() && it->state() != LookupIterator::JSPROXY; it->Next()) { for (; it->IsFound() && it->state() != LookupIterator::JSPROXY; it->Next()) {
if (it->state() == LookupIterator::ACCESSOR) { if (it->state() == LookupIterator::ACCESSOR) {
Handle<Object> accessors = it->GetAccessors(); Handle<Object> accessors = it->GetAccessors();
......
...@@ -162,9 +162,10 @@ class JSReceiver : public TorqueGeneratedJSReceiver<JSReceiver, HeapObject> { ...@@ -162,9 +162,10 @@ class JSReceiver : public TorqueGeneratedJSReceiver<JSReceiver, HeapObject> {
PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw); PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw);
// Check if private name property can be store on the object. It will return // Check if private name property can be store on the object. It will return
// false with an error when it cannot. // false with an error when it cannot but didn't throw, or a Nothing if
V8_WARN_UNUSED_RESULT static bool CheckPrivateNameStore(LookupIterator* it, // it throws.
bool is_define); V8_WARN_UNUSED_RESULT static Maybe<bool> CheckPrivateNameStore(
LookupIterator* it, bool is_define);
// Check if a data property can be created on the object. It will fail with // Check if a data property can be created on the object. It will fail with
// an error when it cannot. // an error when it cannot.
......
...@@ -185,6 +185,10 @@ bool LookupIterator::IsElement(JSReceiver object) const { ...@@ -185,6 +185,10 @@ bool LookupIterator::IsElement(JSReceiver object) const {
object.map().has_any_typed_array_or_wasm_array_elements()); object.map().has_any_typed_array_or_wasm_array_elements());
} }
bool LookupIterator::IsPrivateName() const {
return !IsElement() && name()->IsPrivateName(isolate());
}
bool LookupIterator::is_dictionary_holder() const { bool LookupIterator::is_dictionary_holder() const {
return !holder_->HasFastProperties(isolate_); return !holder_->HasFastProperties(isolate_);
} }
......
...@@ -1409,6 +1409,11 @@ LookupIterator::State LookupIterator::LookupInRegularHolder( ...@@ -1409,6 +1409,11 @@ LookupIterator::State LookupIterator::LookupInRegularHolder(
Handle<InterceptorInfo> LookupIterator::GetInterceptorForFailedAccessCheck() Handle<InterceptorInfo> LookupIterator::GetInterceptorForFailedAccessCheck()
const { const {
DCHECK_EQ(ACCESS_CHECK, state_); DCHECK_EQ(ACCESS_CHECK, state_);
// Skip the interceptors for private
if (IsPrivateName()) {
return Handle<InterceptorInfo>();
}
DisallowGarbageCollection no_gc; DisallowGarbageCollection no_gc;
AccessCheckInfo access_check_info = AccessCheckInfo access_check_info =
AccessCheckInfo::Get(isolate_, Handle<JSObject>::cast(holder_)); AccessCheckInfo::Get(isolate_, Handle<JSObject>::cast(holder_));
......
...@@ -115,6 +115,8 @@ class V8_EXPORT_PRIVATE LookupIterator final { ...@@ -115,6 +115,8 @@ class V8_EXPORT_PRIVATE LookupIterator final {
// any integer for JSTypedArrays). // any integer for JSTypedArrays).
inline bool IsElement(JSReceiver object) const; inline bool IsElement(JSReceiver object) const;
inline bool IsPrivateName() const;
bool IsFound() const { return state_ != NOT_FOUND; } bool IsFound() const { return state_ != NOT_FOUND; }
void Next(); void Next();
void NotFound() { void NotFound() {
......
...@@ -559,9 +559,12 @@ MaybeHandle<Object> Runtime::SetObjectProperty( ...@@ -559,9 +559,12 @@ MaybeHandle<Object> Runtime::SetObjectProperty(
PropertyKey lookup_key(isolate, key, &success); PropertyKey lookup_key(isolate, key, &success);
if (!success) return MaybeHandle<Object>(); if (!success) return MaybeHandle<Object>();
LookupIterator it(isolate, object, lookup_key); LookupIterator it(isolate, object, lookup_key);
if (key->IsSymbol() && Symbol::cast(*key).is_private_name() && if (key->IsSymbol() && Symbol::cast(*key).is_private_name()) {
!JSReceiver::CheckPrivateNameStore(&it, false)) { Maybe<bool> can_store = JSReceiver::CheckPrivateNameStore(&it, false);
return MaybeHandle<Object>(); MAYBE_RETURN_NULL(can_store);
if (!can_store.FromJust()) {
return isolate->factory()->undefined_value();
}
} }
MAYBE_RETURN_NULL( MAYBE_RETURN_NULL(
...@@ -589,10 +592,15 @@ MaybeHandle<Object> Runtime::DefineObjectOwnProperty(Isolate* isolate, ...@@ -589,10 +592,15 @@ MaybeHandle<Object> Runtime::DefineObjectOwnProperty(Isolate* isolate,
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN); LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
if (key->IsSymbol() && Symbol::cast(*key).is_private_name()) { if (key->IsSymbol() && Symbol::cast(*key).is_private_name()) {
if (!JSReceiver::CheckPrivateNameStore(&it, true)) { Maybe<bool> can_store = JSReceiver::CheckPrivateNameStore(&it, true);
return MaybeHandle<Object>(); MAYBE_RETURN_NULL(can_store);
// If the state is ACCESS_CHECK, the faliled access check callback
// is configured but it did't throw.
DCHECK_IMPLIES(it.IsFound(), it.state() == LookupIterator::ACCESS_CHECK &&
!can_store.FromJust());
if (!can_store.FromJust()) {
return isolate->factory()->undefined_value();
} }
DCHECK(!it.IsFound());
MAYBE_RETURN_NULL( MAYBE_RETURN_NULL(
JSReceiver::AddPrivateField(&it, value, Nothing<ShouldThrow>())); JSReceiver::AddPrivateField(&it, value, Nothing<ShouldThrow>()));
} else { } else {
......
...@@ -9,13 +9,4 @@ const realm = Realm.createAllowCrossRealmAccess(); ...@@ -9,13 +9,4 @@ const realm = Realm.createAllowCrossRealmAccess();
const detached = Realm.global(realm); const detached = Realm.global(realm);
Realm.detachGlobal(realm); Realm.detachGlobal(realm);
assertThrows(() => new B(detached), Error, /no access/); checkNoAccess(detached, /no access/);
assertThrows(() => new C(detached), Error, /no access/);
assertThrows(() => new D(detached), Error, /no access/);
assertThrows(() => new E(detached), Error, /no access/);
assertThrows(() => B.setField(detached), Error, /no access/);
assertThrows(() => C.setField(detached), Error, /no access/);
assertThrows(() => D.setAccessor(detached), Error, /no access/);
assertThrows(() => E.setMethod(detached), Error, /no access/);
assertThrows(() => D.getAccessor(detached), Error, /no access/);
assertThrows(() => E.getMethod(detached), Error, /no access/);
...@@ -8,58 +8,9 @@ d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js'); ...@@ -8,58 +8,9 @@ d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
const realm = Realm.createAllowCrossRealmAccess(); const realm = Realm.createAllowCrossRealmAccess();
const globalProxy = Realm.global(realm); const globalProxy = Realm.global(realm);
assertThrows(() => B.setField(globalProxy), TypeError, /Cannot write private member #b to an object whose class did not declare it/); checkHasAccess(globalProxy);
assertThrows(() => B.getField(globalProxy), TypeError, /Cannot read private member #b from an object whose class did not declare it/);
new B(globalProxy);
assertEquals(B.getField(globalProxy), 1);
B.setField(globalProxy);
assertEquals(B.getField(globalProxy), 'b'); // Fast case
B.setField(globalProxy); // Fast case
assertEquals(B.getField(globalProxy), 'b'); // Fast case
assertThrows(() => new B(globalProxy), TypeError, /Cannot initialize #b twice on the same object/);
assertThrows(() => C.setField(globalProxy), TypeError, /Cannot write private member #c to an object whose class did not declare it/);
assertThrows(() => C.getField(globalProxy), TypeError, /Cannot read private member #c from an object whose class did not declare it/);
new C(globalProxy);
assertEquals(C.getField(globalProxy), undefined);
C.setField(globalProxy);
assertEquals(C.getField(globalProxy), 'c'); // Fast case
C.setField(globalProxy); // Fast case
assertEquals(C.getField(globalProxy), 'c'); // Fast case
assertThrows(() => new C(globalProxy), TypeError, /Cannot initialize #c twice on the same object/);
assertThrows(() => D.setAccessor(globalProxy), TypeError, /Receiver must be an instance of class D/);
assertThrows(() => D.getAccessor(globalProxy), TypeError, /Receiver must be an instance of class D/);
new D(globalProxy);
assertEquals(D.getAccessor(globalProxy), 0);
D.setAccessor(globalProxy);
assertEquals(D.getAccessor(globalProxy), 'd'); // Fast case
D.setAccessor(globalProxy); // Fast case
assertEquals(D.getAccessor(globalProxy), 'd'); // Fast case
assertThrows(() => new D(globalProxy), TypeError, /Cannot initialize private methods of class D twice on the same object/);
assertThrows(() => E.setMethod(globalProxy), TypeError, /Receiver must be an instance of class E/);
assertThrows(() => E.getMethod(globalProxy), TypeError, /Receiver must be an instance of class E/);
new E(globalProxy);
assertEquals(E.getMethod(globalProxy)(), 0);
assertThrows(() => E.setMethod(globalProxy), TypeError, /Private method '#e' is not writable/);
assertEquals(E.getMethod(globalProxy)(), 0); // Fast case
assertThrows(() => new E(globalProxy), TypeError, /Cannot initialize private methods of class E twice on the same object/);
// Access should fail after detaching // Access should fail after detaching
Realm.detachGlobal(realm); Realm.detachGlobal(realm);
assertThrows(() => new B(globalProxy), Error, /no access/); checkNoAccess(globalProxy, /no access/);
assertThrows(() => new C(globalProxy), Error, /no access/);
assertThrows(() => new D(globalProxy), Error, /no access/);
assertThrows(() => new E(globalProxy), Error, /no access/);
assertThrows(() => B.setField(globalProxy), Error, /no access/);
assertThrows(() => C.setField(globalProxy), Error, /no access/);
assertThrows(() => D.setAccessor(globalProxy), Error, /no access/);
assertThrows(() => E.setMethod(globalProxy), Error, /no access/);
assertThrows(() => D.getAccessor(globalProxy), Error, /no access/);
assertThrows(() => E.getMethod(globalProxy), Error, /no access/);
...@@ -7,13 +7,4 @@ d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js'); ...@@ -7,13 +7,4 @@ d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
const realm = Realm.create(); const realm = Realm.create();
const globalProxy = Realm.global(realm); const globalProxy = Realm.global(realm);
assertThrows(() => new B(globalProxy), Error, /no access/); checkNoAccess(globalProxy, /no access/);
assertThrows(() => new C(globalProxy), Error, /no access/);
assertThrows(() => new D(globalProxy), Error, /no access/);
assertThrows(() => new E(globalProxy), Error, /no access/);
assertThrows(() => B.setField(globalProxy), Error, /no access/);
assertThrows(() => C.setField(globalProxy), Error, /no access/);
assertThrows(() => D.setAccessor(globalProxy), Error, /no access/);
assertThrows(() => E.setMethod(globalProxy), Error, /no access/);
assertThrows(() => D.getAccessor(globalProxy), Error, /no access/);
assertThrows(() => E.getMethod(globalProxy), Error, /no access/);
...@@ -19,6 +19,9 @@ class B extends A { ...@@ -19,6 +19,9 @@ class B extends A {
static getField(obj) { static getField(obj) {
return obj.#b; return obj.#b;
} }
static hasField(obj) {
return #b in obj;
}
} }
class C extends A { class C extends A {
...@@ -32,12 +35,15 @@ class C extends A { ...@@ -32,12 +35,15 @@ class C extends A {
static getField(obj) { static getField(obj) {
return obj.#c; return obj.#c;
} }
static hasField(obj) {
return #c in obj;
}
} }
let d = 0; let d = 0;
class D extends A { class D extends A {
get #d() { return d; } get #d() { return d; }
set #d(val) { d = val;} set #d(val) { d = val; }
constructor(arg) { constructor(arg) {
super(arg); // KeyedStoreIC for private brand super(arg); // KeyedStoreIC for private brand
} }
...@@ -47,6 +53,9 @@ class D extends A { ...@@ -47,6 +53,9 @@ class D extends A {
static getAccessor(obj) { static getAccessor(obj) {
return obj.#d; // KeyedLoadIC for private brand return obj.#d; // KeyedLoadIC for private brand
} }
static hasAccessor(obj) {
return #d in obj;
}
} }
class E extends A { class E extends A {
...@@ -60,4 +69,104 @@ class E extends A { ...@@ -60,4 +69,104 @@ class E extends A {
static getMethod(obj) { static getMethod(obj) {
return obj.#e; // KeyedLoadIC for private brand return obj.#e; // KeyedLoadIC for private brand
} }
static hasMethod(obj) {
return #e in obj;
}
}
function checkHasAccess(object) {
assertThrows(() => B.setField(globalProxy), TypeError, /Cannot write private member #b to an object whose class did not declare it/);
assertThrows(() => B.getField(globalProxy), TypeError, /Cannot read private member #b from an object whose class did not declare it/);
assertFalse(B.hasField(globalProxy));
new B(globalProxy);
assertEquals(B.getField(globalProxy), 1);
B.setField(globalProxy);
assertEquals(B.getField(globalProxy), 'b'); // Fast case
B.setField(globalProxy); // Fast case
assertEquals(B.getField(globalProxy), 'b'); // Fast case
assertThrows(() => new B(globalProxy), TypeError, /Cannot initialize #b twice on the same object/);
assertTrue(B.hasField(globalProxy));
assertTrue(B.hasField(globalProxy)); // Fast case
assertThrows(() => C.setField(globalProxy), TypeError, /Cannot write private member #c to an object whose class did not declare it/);
assertThrows(() => C.getField(globalProxy), TypeError, /Cannot read private member #c from an object whose class did not declare it/);
assertFalse(C.hasField(globalProxy));
new C(globalProxy);
assertEquals(C.getField(globalProxy), undefined);
C.setField(globalProxy);
assertEquals(C.getField(globalProxy), 'c'); // Fast case
C.setField(globalProxy); // Fast case
assertEquals(C.getField(globalProxy), 'c'); // Fast case
assertThrows(() => new C(globalProxy), TypeError, /Cannot initialize #c twice on the same object/);
assertTrue(C.hasField(globalProxy));
assertTrue(C.hasField(globalProxy)); // Fast case
assertThrows(() => D.setAccessor(globalProxy), TypeError, /Receiver must be an instance of class D/);
assertThrows(() => D.getAccessor(globalProxy), TypeError, /Receiver must be an instance of class D/);
assertFalse(D.hasAccessor(globalProxy));
new D(globalProxy);
assertEquals(D.getAccessor(globalProxy), 0);
D.setAccessor(globalProxy);
assertEquals(D.getAccessor(globalProxy), 'd'); // Fast case
D.setAccessor(globalProxy); // Fast case
assertEquals(D.getAccessor(globalProxy), 'd'); // Fast case
assertThrows(() => new D(globalProxy), TypeError, /Cannot initialize private methods of class D twice on the same object/);
assertTrue(D.hasAccessor(globalProxy));
assertTrue(D.hasAccessor(globalProxy)); // Fast case
assertThrows(() => E.setMethod(globalProxy), TypeError, /Receiver must be an instance of class E/);
assertThrows(() => E.getMethod(globalProxy), TypeError, /Receiver must be an instance of class E/);
assertFalse(E.hasMethod(globalProxy));
new E(globalProxy);
assertEquals(E.getMethod(globalProxy)(), 0);
assertThrows(() => E.setMethod(globalProxy), TypeError, /Private method '#e' is not writable/);
assertEquals(E.getMethod(globalProxy)(), 0); // Fast case
assertThrows(() => new E(globalProxy), TypeError, /Cannot initialize private methods of class E twice on the same object/);
assertTrue(E.hasMethod(globalProxy));
assertTrue(E.hasMethod(globalProxy)); // Fast case
}
function checkNoAccess(object, message) {
assertThrows(() => new B(object), Error, message);
assertThrows(() => new C(object), Error, message);
assertThrows(() => new D(object), Error, message);
assertThrows(() => new E(object), Error, message);
assertThrows(() => B.setField(object), Error, message);
assertThrows(() => B.getField(object), Error, message);
assertThrows(() => B.hasField(object), Error, message);
assertThrows(() => C.setField(object), Error, message);
assertThrows(() => C.getField(object), Error, message);
assertThrows(() => C.hasField(object), Error, message);
assertThrows(() => D.setAccessor(object), Error, message);
assertThrows(() => D.getAccessor(object), Error, message);
assertThrows(() => D.hasAccessor(object), Error, message);
assertThrows(() => E.setMethod(object), Error, message);
assertThrows(() => E.getMethod(object), Error, message);
assertThrows(() => E.hasMethod(object), Error, message);
}
function checkNoAccessNoThrow(object) {
// The failed access check callback is supposed to throw.
// If it doesn't the behavior is quite quirky.
// This only documents the current behavior.
new B(object);
new C(object);
new D(object);
new E(object);
B.setField(object);
assertEquals(undefined, B.getField(object));
assertFalse(B.hasField(object));
C.setField(object);
assertEquals(undefined, C.getField(object));
assertFalse(C.hasField(object));
D.setAccessor(object)
assertEquals("d", D.getAccessor(object));
assertFalse(D.hasAccessor(object));
assertThrows(() => E.setMethod(object), TypeError, /Private method '#e' is not writable/);
assertEquals(0, E.getMethod(object)());
assertFalse(E.hasMethod(object));
} }
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-lazy-feedback-allocation --throw-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1339722.js');
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --throw-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
// Attached global should have access
const realm = Realm.createAllowCrossRealmAccess();
const globalProxy = Realm.global(realm);
checkHasAccess(globalProxy);
// Access should fail after detaching
Realm.navigate(realm);
checkNoAccess(globalProxy, /Error in failed access check callback/);
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-lazy-feedback-allocation --throw-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1339722-3.js');
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --noop-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
const realm = Realm.create();
const globalProxy = Realm.global(realm);
new B(globalProxy);
B.setField(globalProxy);
assertEquals(undefined, B.getField(globalProxy));
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-lazy-feedback-allocation --noop-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1339722-5.js');
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --noop-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
const realm = Realm.createAllowCrossRealmAccess();
const globalProxy = Realm.global(realm);
checkHasAccess(globalProxy);
Realm.navigate(realm);
checkNoAccessNoThrow(globalProxy);
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-lazy-feedback-allocation --noop-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1339722-7.js');
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --throw-on-failed-access-check
d8.file.execute('test/mjsunit/regress/regress-crbug-1321899.js');
const realm = Realm.create();
const globalProxy = Realm.global(realm);
checkNoAccess(globalProxy, /Error in failed access check callback/);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment