Commit 0ba88594 authored by Igor Sheludko's avatar Igor Sheludko Committed by V8 LUCI CQ

[ic] Make DefineOwnIC throw if the private symbol already exists

Drive-by:
* don't create proto handlers for DefineOwnIC and StoreOwnIC,
* make sure that none of the DefineOwnIC and StoreOwnIC handlers are
  leaked into StoreIC's megamorphic stub cache.

Bug: v8:9888, chromium:1259950
Change-Id: I9db538e6ed14bc578aa80df037ffebd9e8c3c649
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3250641
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77632}
parent 43253f7a
......@@ -714,6 +714,8 @@ void Symbol::SymbolPrint(std::ostream& os) {
os << " (" << PrivateSymbolToName() << ")";
}
os << "\n - private: " << is_private();
os << "\n - private_name: " << is_private_name();
os << "\n - private_brand: " << is_private_brand();
os << "\n";
}
......
......@@ -15,6 +15,7 @@
#include "src/ic/stub-cache.h"
#include "src/logging/counters.h"
#include "src/objects/cell.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/foreign.h"
#include "src/objects/heap-number.h"
#include "src/objects/megadom-handler.h"
......@@ -1223,6 +1224,10 @@ void AccessorAssembler::HandleStoreICHandlerCase(
properties, CAST(p->name()), &dictionary_found, &var_name_index, miss);
BIND(&dictionary_found);
{
if (p->IsDefineOwn()) {
// Take slow path to throw if a private name already exists.
GotoIf(IsPrivateSymbol(CAST(p->name())), &if_slow);
}
Label if_constant(this), done(this);
TNode<Uint32T> details =
LoadDetailsByKeyIndex(properties, var_name_index.value());
......@@ -1340,6 +1345,9 @@ void AccessorAssembler::HandleStoreICHandlerCase(
{
TNode<PropertyCell> property_cell = CAST(map_or_property_cell);
ExitPoint direct_exit(this);
// StoreGlobalIC_PropertyCellCase doesn't properly handle private names
// but they are not expected here anyway.
CSA_DCHECK(this, BoolConstant(!p->IsDefineOwn()));
StoreGlobalIC_PropertyCellCase(property_cell, p->value(), &direct_exit,
miss);
}
......@@ -1347,7 +1355,9 @@ void AccessorAssembler::HandleStoreICHandlerCase(
{
TNode<Map> map = CAST(map_or_property_cell);
HandleStoreICTransitionMapHandlerCase(p, map, miss,
kCheckPrototypeValidity);
p->IsAnyStoreOwn()
? kDontCheckPrototypeValidity
: kCheckPrototypeValidity);
Return(p->value());
}
}
......@@ -1771,6 +1781,11 @@ void AccessorAssembler::HandleStoreICProtoHandler(
if (ic_mode == ICMode::kGlobalIC) {
TailCallRuntime(Runtime::kStoreGlobalIC_Slow, p->context(), p->value(),
p->slot(), p->vector(), p->receiver(), p->name());
} else if (p->IsAnyStoreOwn()) {
// DefineOwnIC and StoreOwnIC shouldn't be using slow proto handlers,
// otherwise proper slow function must be called.
CSA_DCHECK(this, BoolConstant(!p->IsAnyStoreOwn()));
Unreachable();
} else {
TailCallRuntime(Runtime::kKeyedStoreIC_Slow, p->context(), p->value(),
p->receiver(), p->name());
......@@ -1850,6 +1865,9 @@ void AccessorAssembler::HandleStoreICProtoHandler(
BIND(&if_store_global_proxy);
{
ExitPoint direct_exit(this);
// StoreGlobalIC_PropertyCellCase doesn't properly handle private names
// but they are not expected here anyway.
CSA_DCHECK(this, BoolConstant(!p->IsDefineOwn()));
StoreGlobalIC_PropertyCellCase(CAST(holder), p->value(), &direct_exit,
miss);
}
......
......@@ -229,6 +229,7 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
bool IsStoreOwn() const { return mode_ == StoreICMode::kStoreOwn; }
bool IsDefineOwn() const { return mode_ == StoreICMode::kDefineOwn; }
bool IsAnyStoreOwn() const { return IsStoreOwn() || IsDefineOwn(); }
private:
TNode<Context> context_;
......@@ -247,6 +248,7 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
const StoreICParameters* p, TNode<MaybeObject> handler, Label* miss,
ICMode ic_mode, ElementSupport support_elements = kOnlyProperties);
enum StoreTransitionMapFlags {
kDontCheckPrototypeValidity = 0,
kCheckPrototypeValidity = 1 << 0,
kValidateTransitionHandler = 1 << 1,
kStoreTransitionMapFlagsMask =
......
......@@ -219,6 +219,40 @@ Handle<Object> StoreHandler::StoreElementTransition(
return handler;
}
// static
MaybeObjectHandle StoreHandler::StoreOwnTransition(Isolate* isolate,
Handle<Map> transition_map) {
bool is_dictionary_map = transition_map->is_dictionary_map();
#ifdef DEBUG
if (!is_dictionary_map) {
InternalIndex descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(
transition_map->instance_descriptors(isolate), isolate);
PropertyDetails details = descriptors->GetDetails(descriptor);
if (descriptors->GetKey(descriptor).IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
} else {
DCHECK_EQ(NONE, details.attributes());
}
Representation representation = details.representation();
DCHECK(!representation.IsNone());
}
#endif
// Declarative handlers don't support access checks.
DCHECK(!transition_map->is_access_check_needed());
// StoreOwnTransition does not involve any prototype checks.
if (is_dictionary_map) {
DCHECK(!transition_map->IsJSGlobalObjectMap());
int config = KindBits::encode(Kind::kNormal);
return MaybeObjectHandle(Smi::FromInt(config), isolate);
} else {
return MaybeObjectHandle::Weak(transition_map);
}
}
// static
MaybeObjectHandle StoreHandler::StoreTransition(Isolate* isolate,
Handle<Map> transition_map) {
bool is_dictionary_map = transition_map->is_dictionary_map();
......@@ -227,6 +261,8 @@ MaybeObjectHandle StoreHandler::StoreTransition(Isolate* isolate,
InternalIndex descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(
transition_map->instance_descriptors(isolate), isolate);
// Private fields must be added via StoreOwnTransition handler.
DCHECK(!descriptors->GetKey(descriptor).IsPrivateName());
PropertyDetails details = descriptors->GetDetails(descriptor);
if (descriptors->GetKey(descriptor).IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
......
......@@ -301,6 +301,11 @@ class StoreHandler final : public DataHandler {
PropertyConstness constness,
Representation representation);
// Create a store transition handler which doesn't check prototype chain.
static MaybeObjectHandle StoreOwnTransition(Isolate* isolate,
Handle<Map> transition_map);
// Create a store transition handler with prototype chain validity cell check.
static MaybeObjectHandle StoreTransition(Isolate* isolate,
Handle<Map> transition_map);
......
......@@ -114,13 +114,12 @@ void IC::TraceIC(const char* type, Handle<Object> name, State old_state,
} else if (IsKeyedLoadIC()) {
KeyedAccessLoadMode mode = nexus()->GetKeyedAccessLoadMode();
modifier = GetModifier(mode);
} else if (IsKeyedStoreIC() || IsStoreInArrayLiteralICKind(kind()) ||
IsDefineOwnIC()) {
} else if (IsKeyedStoreIC() || IsStoreInArrayLiteralIC() || IsDefineOwnIC()) {
KeyedAccessStoreMode mode = nexus()->GetKeyedAccessStoreMode();
modifier = GetModifier(mode);
}
bool keyed_prefix = is_keyed() && !IsStoreInArrayLiteralICKind(kind());
bool keyed_prefix = is_keyed() && !IsStoreInArrayLiteralIC();
if (!(TracingFlags::ic_stats.load(std::memory_order_relaxed) &
v8::tracing::TracingCategoryObserver::ENABLED_BY_TRACING)) {
......@@ -831,7 +830,10 @@ void LoadIC::UpdateCaches(LookupIterator* lookup) {
}
StubCache* IC::stub_cache() {
// HasICs and each of the store own ICs require its own stub cache.
// Until we create them, don't allow accessing the load/store stub caches.
DCHECK(!IsAnyHas());
DCHECK(!is_any_store_own());
if (IsAnyLoad()) {
return isolate()->load_stub_cache();
} else {
......@@ -842,7 +844,7 @@ StubCache* IC::stub_cache() {
void IC::UpdateMegamorphicCache(Handle<Map> map, Handle<Name> name,
const MaybeObjectHandle& handler) {
if (!IsAnyHas()) {
if (!IsAnyHas() && !is_any_store_own()) {
stub_cache()->Set(*name, *map, *handler);
}
}
......@@ -1833,6 +1835,13 @@ MaybeObjectHandle StoreIC::ComputeHandler(LookupIterator* lookup) {
#endif
return StoreHandler::StoreGlobal(lookup->transition_cell());
}
if (IsDefineOwnIC()) {
// Private field can't be deleted from this global object and can't
// be overwritten, so install slow handler in order to make store IC
// throw if a private name already exists.
TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub);
return MaybeObjectHandle(StoreHandler::StoreSlow(isolate()));
}
Handle<Smi> smi_handler = StoreHandler::StoreGlobalProxy(isolate());
Handle<Object> handler = StoreHandler::StoreThroughPrototype(
......@@ -1845,7 +1854,10 @@ MaybeObjectHandle StoreIC::ComputeHandler(LookupIterator* lookup) {
!lookup_start_object_map()->is_dictionary_map());
DCHECK(lookup->IsCacheableTransition());
if (IsAnyStoreOwn()) {
return StoreHandler::StoreOwnTransition(isolate(),
lookup->transition_map());
}
return StoreHandler::StoreTransition(isolate(), lookup->transition_map());
}
......@@ -2072,7 +2084,7 @@ void KeyedStoreIC::UpdateStoreElement(Handle<Map> receiver_map,
for (const MapAndHandler& map_and_handler : target_maps_and_handlers) {
Handle<Map> map = map_and_handler.first;
if (!map.is_null() && map->instance_type() == JS_PRIMITIVE_WRAPPER_TYPE) {
DCHECK(!IsStoreInArrayLiteralICKind(kind()));
DCHECK(!IsStoreInArrayLiteralIC());
set_slow_stub_reason("JSPrimitiveWrapper");
return;
}
......@@ -2163,13 +2175,13 @@ void KeyedStoreIC::UpdateStoreElement(Handle<Map> receiver_map,
return;
} else if (map->has_typed_array_or_rab_gsab_typed_array_elements()) {
DCHECK(!IsStoreInArrayLiteralICKind(kind()));
DCHECK(!IsStoreInArrayLiteralIC());
external_arrays++;
}
}
if (external_arrays != 0 &&
external_arrays != target_maps_and_handlers.size()) {
DCHECK(!IsStoreInArrayLiteralICKind(kind()));
DCHECK(!IsStoreInArrayLiteralIC());
set_slow_stub_reason(
"unsupported combination of external and normal arrays");
return;
......@@ -2197,7 +2209,7 @@ Handle<Object> KeyedStoreIC::StoreElementHandler(
DCHECK_IMPLIES(
!receiver_map->has_dictionary_elements() &&
receiver_map->MayHaveReadOnlyElementsInPrototypeChain(isolate()),
IsStoreInArrayLiteralICKind(kind()));
IsStoreInArrayLiteralIC());
if (receiver_map->IsJSProxyMap()) {
return StoreHandler::StoreProxy(isolate());
......@@ -2218,7 +2230,7 @@ Handle<Object> KeyedStoreIC::StoreElementHandler(
if (receiver_map->has_typed_array_or_rab_gsab_typed_array_elements()) {
return code;
}
} else if (IsStoreInArrayLiteralICKind(kind())) {
} else if (IsStoreInArrayLiteralIC()) {
// TODO(jgruber): Update counter name.
TRACE_HANDLER_STATS(isolate(), StoreInArrayLiteralIC_SlowStub);
return StoreHandler::StoreSlow(isolate(), store_mode);
......@@ -2230,7 +2242,7 @@ Handle<Object> KeyedStoreIC::StoreElementHandler(
code = StoreHandler::StoreSlow(isolate(), store_mode);
}
if (IsStoreInArrayLiteralICKind(kind())) return code;
if (is_any_store_own() || IsStoreInArrayLiteralIC()) return code;
Handle<Object> validity_cell;
if (!prev_validity_cell.ToHandle(&validity_cell)) {
validity_cell =
......
......@@ -122,13 +122,17 @@ class IC {
bool IsStoreIC() const { return IsStoreICKind(kind_); }
bool IsStoreOwnIC() const { return IsStoreOwnICKind(kind_); }
bool IsDefineOwnIC() const { return IsDefineOwnICKind(kind_); }
bool IsStoreInArrayLiteralIC() const {
return IsStoreInArrayLiteralICKind(kind_);
}
bool IsKeyedStoreIC() const { return IsKeyedStoreICKind(kind_); }
bool IsKeyedHasIC() const { return IsKeyedHasICKind(kind_); }
bool IsKeyedDefineOwnIC() const { return IsKeyedDefineOwnICKind(kind_); }
bool is_keyed() const {
return IsKeyedLoadIC() || IsKeyedStoreIC() ||
IsStoreInArrayLiteralICKind(kind_) || IsKeyedHasIC() ||
IsKeyedDefineOwnICKind(kind_);
return IsKeyedLoadIC() || IsKeyedStoreIC() || IsStoreInArrayLiteralIC() ||
IsKeyedHasIC() || IsKeyedDefineOwnIC();
}
bool is_any_store_own() const { return IsStoreOwnIC() || IsDefineOwnIC(); }
bool ShouldRecomputeHandler(Handle<String> name);
Handle<Map> lookup_start_object_map() { return lookup_start_object_map_; }
......
......@@ -154,8 +154,13 @@ class KeyedStoreGenericAssembler : public AccessorAssembler {
bool ShouldCallSetter() const { return IsKeyedStore() || IsKeyedStoreOwn(); }
bool ShouldCheckPrototypeValidity() const {
// We don't do this for "in-literal" stores, because it is impossible for
// the target object to be a "prototype"
return !IsStoreInLiteral() && !IsKeyedStoreOwn();
// the target object to be a "prototype".
// We don't need the prototype validity check for "own" stores, because
// we don't care about the prototype chain.
// Thus, we need the prototype check only for ordinary stores.
DCHECK_IMPLIES(!IsKeyedStore(), IsStoreInLiteral() || IsKeyedStoreOwn() ||
IsKeyedDefineOwn());
return IsKeyedStore();
}
};
......@@ -813,7 +818,7 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&descriptor_found);
{
if (IsKeyedDefineOwn()) {
// Take slow path to throw if a private name already exists
// Take slow path to throw if a private name already exists.
GotoIf(IsPrivateSymbol(name), slow);
}
TNode<IntPtrT> name_index = var_name_index.value();
......@@ -875,6 +880,7 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
{
Label check_const(this), overwrite(this), done(this);
if (IsKeyedDefineOwn()) {
// Take slow path to throw if a private name already exists.
GotoIf(IsPrivateSymbol(name), slow);
}
TNode<Uint32T> details =
......@@ -1071,6 +1077,7 @@ void KeyedStoreGenericAssembler::KeyedStoreGeneric(
BIND(&slow);
{
if (IsKeyedStore() || IsKeyedStoreOwn()) {
CSA_DCHECK(this, BoolConstant(!IsKeyedStoreOwn()));
Comment("KeyedStoreGeneric_slow");
TailCallRuntime(Runtime::kSetKeyedProperty, context, receiver, key,
value);
......
// Copyright 2021 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.
function test(obj) {
class Base {
constructor() {
return obj;
}
}
class C extends Base {
#x = 1;
}
let c = new C();
c = new C();
}
assertThrows(
() => test(globalThis),
TypeError, "Cannot initialize #x twice on the same object");
assertThrows(
() => test(Object.create(null)),
TypeError, "Cannot initialize #x twice on the same object");
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