v8/test/mjsunit/regress/regress-v8-12421.js
Joyee Cheung 4ee68d81b9 [ic] fix handling of existing properties in Define{Keyed|Named}OwnIC
- When the property being defined with DefineKeyedOwnIC or
  DefineNamedOwnIC already exists, we should use the slow path to
  check if the operation is allowed in case the property is
  non-configurable or Object.preventExtensions() has been called on
  the property.
- Since KeyedStoreIC:Store() reuses StoreIC::Store() when the key is a
  name, we should use Runtime::DefineObjectOwnProperty() for
  DefineKeyedOwnIC too.
- When dealing with public fields, Runtime::DefineObjectOwnProperty()
  should use JSReceiver::CreateDataProperty() instead of
  Object::SetProperty() for the specified semantics. This patch also
  adds JSReceiver::AddPrivateField() for it and StoreIC::Store to
  define private fields without triggering traps or checking
  extensibility.
- To emit a more specific error message when redefining properties
  on non-extensible objects, Object::AddDataProperty() now also takes
  a EnforceDefineSemantics enum to distinguish between set and define.
- Drive-by: fix JSReceiver::CheckIfCanDefine() which should check for
  extensibility even if the configurability check passes.

Bug: chromium:1259950, v8:9888
Change-Id: Ib1bc851ffd4b9c3a0e98cac96dafe743c08ee37e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3517934
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Cr-Commit-Position: refs/heads/main@{#79603}
2022-03-24 12:36:42 +00:00

331 lines
7.6 KiB
JavaScript

// 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.
{
class X {
static name = "name";
static length = 15;
}
assertEquals({
"value": "name",
"writable": true,
"enumerable": true,
"configurable": true
}, Object.getOwnPropertyDescriptor(X, "name"));
assertEquals({
"value": 15,
"writable": true,
"enumerable": true,
"configurable": true
}, Object.getOwnPropertyDescriptor(X, "length"));
}
{
class X {
field = Object.preventExtensions(this);
}
assertThrows(() => {
new X();
}, TypeError, /Cannot define property field, object is not extensible/);
}
{
class X {
field = Object.defineProperty(
this,
"field2",
{ writable: false, configurable: true, value: 1}
);
field2 = 2;
}
let x = new X();
assertEquals(2, x.field2);
}
{
class X {
field = Object.defineProperty(
this,
"field2",
{ writable: false, configurable: false, value: 1}
);
field2 = true;
}
assertThrows(() => {
new X();
}, TypeError, /Cannot redefine property: field2/);
}
{
class X {
field = Object.defineProperty(
this,
"field2",
{ writable: true, configurable: false, value: 1}
);
field2 = true;
}
assertThrows(() => {
new X();
}, TypeError, /Cannot redefine property: field2/);
}
{
let setterCalled = false;
class X {
field = Object.defineProperty(
this,
"field2",
{
configurable: true,
set(val) {
setterCalled = true;
}
}
);
field2 = 2;
}
let x = new X();
assertFalse(setterCalled);
assertEquals({
"value": 2,
"writable": true,
"enumerable": true,
"configurable": true
}, Object.getOwnPropertyDescriptor(x, 'field2'));
}
{
let setterCalled = false;
class X {
field = Object.defineProperty(
this,
"field2",
{
configurable: false,
set(val) {
setterCalled = true;
}
}
);
field2 = 2;
}
assertThrows(() => {
new X();
}, TypeError, /Cannot redefine property: field2/);
assertFalse(setterCalled);
}
{
class Base {
constructor(arg) {
return arg;
}
}
class ClassWithNormalField extends Base {
field = (() => {
Object.defineProperty(
this,
"normalField",
{ writable: true, configurable: true, value: "initial"}
);
return 1;
})();
normalField = "written";
constructor(arg) {
super(arg);
}
}
let setterCalled = false;
class ClassWithSetterField extends Base {
field = (() => {
Object.defineProperty(
this,
"setterField",
{ configurable: true, set(val) { setterCalled = true; } }
);
return 1;
})();
setterField = "written";
constructor(arg) {
super(arg);
}
}
class ClassWithReadOnlyField extends Base {
field = (() => {
Object.defineProperty(
this,
"readOnlyField",
{ writable: false, configurable: true, value: "initial"}
);
return 1;
})();
readOnlyField = "written";
constructor(arg) {
super(arg);
}
}
class ClassWithNonConfigurableField extends Base {
field = (() => {
Object.defineProperty(
this,
"nonConfigurableField",
{ writable: false, configurable: false, value: "initial"}
);
return 1;
})();
nonConfigurableField = "configured";
constructor(arg) {
super(arg);
}
}
class ClassNonExtensible extends Base {
field = (() => {
Object.preventExtensions(this);
return 'defined';
})();
nonExtensible = 4;
constructor(arg) {
super(arg);
}
}
// Test dictionary objects.
function testObject(getObject) {
let obj = getObject();
new ClassWithNormalField(obj);
assertEquals(1, obj.field);
assertEquals("written", obj.normalField);
obj = getObject();
new ClassWithSetterField(obj);
assertEquals(1, obj.field);
assertFalse(setterCalled);
obj = getObject();
new ClassWithReadOnlyField(obj);
assertEquals(1, obj.field);
assertEquals("written", obj.readOnlyField);
obj = getObject();
assertThrows(() => {
new ClassWithNonConfigurableField(obj);
}, TypeError, /Cannot redefine property: nonConfigurableField/);
assertEquals("initial", obj.nonConfigurableField);
assertEquals(1, obj.field);
obj = getObject();
if (Object.hasOwn(obj, 'field')) {
assertThrows(() => {
new ClassNonExtensible(obj);
}, TypeError, /Cannot define property nonExtensible, object is not extensible/);
assertEquals({
"value": 'defined',
"writable": true,
"enumerable": true,
"configurable": true
}, Object.getOwnPropertyDescriptor(obj, 'field'));
} else {
assertThrows(() => {
new ClassNonExtensible(obj);
}, TypeError, /Cannot define property field, object is not extensible/);
assertFalse(Object.hasOwn(obj, 'field'));
}
assertFalse(Object.hasOwn(obj, 'nonExtensible'));
return obj;
}
testObject(() => Object.create(null));
testObject( () => { return {field: 1000 } });
// Test proxies.
{
let trapCalls = [];
function getProxy() {
trapCalls = [];
let target = {};
return new Proxy(target, {
get(oTarget, sKey) {
return oTarget[sKey];
},
defineProperty(oTarget, sKey, oDesc) {
trapCalls.push(sKey);
Object.defineProperty(oTarget, sKey, oDesc);
return oTarget;
}
});
}
let proxy = getProxy();
new ClassWithNormalField(proxy);
assertEquals(1, proxy.field);
assertEquals("written", proxy.normalField);
assertEquals(["normalField", "field", "normalField"], trapCalls);
proxy = getProxy();
new ClassWithSetterField(proxy);
assertFalse(setterCalled);
assertEquals("written", proxy.setterField);
assertEquals(["setterField", "field", "setterField"], trapCalls);
proxy = getProxy();
new ClassWithReadOnlyField(proxy);
assertEquals("written", proxy.readOnlyField);
assertEquals(["readOnlyField", "field", "readOnlyField"], trapCalls);
proxy = getProxy();
assertThrows(() => {
new ClassWithNonConfigurableField(proxy);
}, TypeError, /Cannot redefine property: nonConfigurableField/);
assertEquals("initial", proxy.nonConfigurableField);
assertEquals(["nonConfigurableField", "field", "nonConfigurableField"], trapCalls);
proxy = getProxy();
assertThrows(() => {
new ClassNonExtensible(proxy);
}, TypeError, /Cannot define property field, object is not extensible/);
assertFalse(Object.hasOwn(proxy, 'field'));
assertFalse(Object.hasOwn(proxy, 'nonExtensible'));
assertEquals(["field"], trapCalls);
}
// Test globalThis.
{
new ClassWithNormalField(globalThis);
assertEquals(1, field);
assertEquals("written", normalField);
new ClassWithSetterField(globalThis);
assertFalse(setterCalled);
assertEquals("written", setterField);
new ClassWithReadOnlyField(globalThis);
assertEquals("written", readOnlyField);
assertThrows(() => {
new ClassWithNonConfigurableField(globalThis);
}, TypeError, /Cannot redefine property: nonConfigurableField/);
assertEquals("initial", nonConfigurableField);
assertThrows(() => {
new ClassNonExtensible(globalThis);
}, TypeError, /Cannot define property nonExtensible, object is not extensible/);
assertEquals("undefined", typeof nonExtensible);
}
}