Skip to content

Commit a5cadca

Browse files
committed
[JSC] OrdinarySet should invoke custom [[Set]] methods
https://bugs.webkit.org/show_bug.cgi?id=217916 Reviewed by Yusuke Suzuki. JSTests: * microbenchmarks/put-slow-no-cache-array.js: Added. * microbenchmarks/put-slow-no-cache-function.js: Added. * microbenchmarks/put-slow-no-cache-js-proxy.js: Added. * microbenchmarks/put-slow-no-cache-long-prototype-chain.js: Added. * microbenchmarks/put-slow-no-cache.js: Added. * microbenchmarks/reflect-set-with-receiver.js: Added. * stress/custom-get-set-proto-chain-put.js: * stress/module-namespace-access-set-fails.js: Added. * stress/put-non-reified-static-accessor-or-custom.js: Added. * stress/put-non-reified-static-function-or-custom.js: Added. * stress/put-to-primitive-non-reified-static-custom.js: Added. * stress/put-to-primitive.js: Added. * stress/put-to-proto-chain-overrides-put.js: Added. * stress/typed-array-canonical-numeric-index-string-set.js: Added. LayoutTests/imported/w3c: * web-platform-tests/WebIDL/ecmascript-binding/interface-object-set-receiver-expected.txt: Added. * web-platform-tests/WebIDL/ecmascript-binding/interface-object-set-receiver.html: Added. * web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver-expected.txt: * web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver.html: Source/JavaScriptCore: This patch fixes putInlineSlow() to handle special properties (like JSFunction's "name" and JSArray's "length") in prototype chain. When such property is encountered, prototype chain traversal is stopped; if it's read-only, a TypeError is thrown in strict mode. This change adds OverridesPut out of line type info flag, and utilizes it in putInlineSlow() to invoke overriden methods. While this approach requires put() methods to be aware of altered receivers, it renders several benefits: 1. put() method can be used for both "real" [[Set]] overrides and special properties, with its return value remaining `bool`; 2. it is simpler, faster, and more predictable than calling [[GetOwnProperty]] in putInlineSlow() or adding defineOwnPropertyViaPut() to the method table. Removes ordinarySetSlow() for non-index properties, which didn't invoke some [[Set]] methods as well. Instead, definePropertyOnReceiver() is introduced for altered receivers, which performs correctly when reached because: 1. all special properties were already handled (unless it's Reflect.set); 2. performing putDirectInternal() is unobservable (unless ProxyObject was seen); 3. putDirectInternal() now fully implements property definition of OrdinarySet [1]; 4. put() override is required if a spec defines custom [[DefineOwnProperty]]. Since indexed puts handle overrides / altered receivers quite differently, they will be fixed in a follow-up, completely removing ordinarySetSlow(). Also, by merging putEntry() / putToPrimitive() into putInlineSlow() and introducing putInlineFastReplacingStaticPropertyIfNeeded() helper, this patch fixes a few bugs: 1. Direct [[Set]] to non-reified static property now preserves its attributes when replacing [[Value]]. 2. Prototype chain [[Set]] to non-reified static property now throws if receiver is non-extensible. 3. Non-reified static writable property now shadows read-only one that is further in prototype chain. 4. Non-reified static properties in prototype chain of a primitive are now considered. Fixes a few issues that were previously unobservable: 1. PropertyAttribute::CustomValue is now unset when a setter-less property is reassigned. 2. uint64_t putByIndexInline() now calls put() via method table like uint32_t counterpart. Other notable refactors: 1. Inlines callCustomSetter(), dropping weird TriState return value. 2. Simplifies initialization of StringPrototype. 3. Simplifies isThisValueAltered() to pointer comparisons at non-JSProxy call sites. 4. Removes doPutPropertySecurityCheck() methods as the same checks are performed by put() methods. 5. Removes prototypeChainMayInterceptStoreTo(), which pretty much duplicated canPerformFastPutInline(). 6. Removes dummy JSArrayBufferView::put() method. 7. Removes now unused lookupPut(). Aligns JSC with V8 and SpiderMonkey. This patch carefully preserves the current behavior of Reflect.set with CustomValue and prototype chain [[Set]] to a JSCallbackObject / legacy platform object. This change is performance-neutral on /put/ microbenchmarks as it doesn't affect caching, only the slow path. Reflect.set with JSFinalObject receiver is 130% faster. putInlineSlow() microbenchmarks progress by 4-18%. [1]: https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor (step 3) * API/JSCallbackObject.h: * API/JSCallbackObjectFunctions.h: (JSC::JSCallbackObject<Parent>::put): * API/tests/testapiScripts/testapi.js: * debugger/DebuggerScope.h: * runtime/ClassInfo.h: * runtime/ClonedArguments.h: * runtime/CustomGetterSetter.cpp: (JSC::callCustomSetter): Deleted. * runtime/CustomGetterSetter.h: * runtime/ErrorConstructor.h: * runtime/ErrorInstance.h: * runtime/GenericArguments.h: * runtime/GenericArgumentsInlines.h: (JSC::GenericArguments<Type>::put): * runtime/GetterSetter.h: * runtime/JSArray.cpp: (JSC::JSArray::put): * runtime/JSArray.h: * runtime/JSArrayBufferView.cpp: (JSC::JSArrayBufferView::put): Deleted. * runtime/JSArrayBufferView.h: * runtime/JSCJSValue.cpp: (JSC::JSValue::putToPrimitive): * runtime/JSCell.cpp: (JSC::JSCell::doPutPropertySecurityCheck): Deleted. * runtime/JSCell.h: * runtime/JSFunction.cpp: (JSC::JSFunction::put): * runtime/JSFunction.h: * runtime/JSGenericTypedArrayView.h: * runtime/JSGlobalLexicalEnvironment.h: * runtime/JSGlobalObject.cpp: (JSC::JSGlobalObject::put): * runtime/JSGlobalObject.h: * runtime/JSLexicalEnvironment.h: * runtime/JSModuleEnvironment.h: * runtime/JSModuleNamespaceObject.h: * runtime/JSObject.cpp: (JSC::JSObject::getOwnPropertySlot): (JSC::JSObject::putInlineSlow): (JSC::definePropertyOnReceiverSlow): (JSC::JSObject::definePropertyOnReceiver): (JSC::JSObject::putInlineFastReplacingStaticPropertyIfNeeded): (JSC::JSObject::doPutPropertySecurityCheck): Deleted. (JSC::JSObject::prototypeChainMayInterceptStoreTo): Deleted. * runtime/JSObject.h: (JSC::JSObject::putByIndexInline): (JSC::JSObject::hasNonReifiedStaticProperties): (JSC::JSObject::getOwnPropertySlot): (JSC::JSObject::putDirect): (JSC::JSObject::doPutPropertySecurityCheck): Deleted. * runtime/JSObjectInlines.h: (JSC::JSObject::canPerformFastPutInlineExcludingProto): (JSC::JSObject::putInlineForJSObject): (JSC::JSObject::putInlineFast): (JSC::JSObject::putDirectInternal): * runtime/JSProxy.h: * runtime/JSTypeInfo.h: (JSC::TypeInfo::hasStaticPropertyTable const): (JSC::TypeInfo::overridesPut const): (JSC::TypeInfo::getOwnPropertySlotMayBeWrongAboutDontEnum const): (JSC::TypeInfo::hasPutPropertySecurityCheck const): Deleted. * runtime/Lookup.h: (JSC::putEntry): Deleted. (JSC::lookupPut): Deleted. * runtime/PropertySlot.h: * runtime/ProxyObject.cpp: (JSC::ProxyObject::put): * runtime/ProxyObject.h: * runtime/PutPropertySlot.h: (JSC::PutPropertySlot::PutPropertySlot): (JSC::PutPropertySlot::context const): (JSC::PutPropertySlot::isTaintedByOpaqueObject const): (JSC::PutPropertySlot::setIsTaintedByOpaqueObject): * runtime/ReflectObject.cpp: (JSC::JSC_DEFINE_HOST_FUNCTION): * runtime/RegExpObject.cpp: (JSC::RegExpObject::put): * runtime/RegExpObject.h: * runtime/StringObject.cpp: (JSC::StringObject::put): * runtime/StringObject.h: * runtime/StringPrototype.cpp: (JSC::StringPrototype::finishCreation): (JSC::StringPrototype::create): * runtime/StringPrototype.h: * runtime/Structure.cpp: (JSC::Structure::validateFlags): * runtime/Structure.h: (JSC::Structure::hasNonReifiedStaticProperties const): * tools/JSDollarVM.cpp: Source/WebCore: Fixes: 1. Legacy platform object's [[Set]] now guards against altered receiver [1]. (aligns with Blink). 2. Direct [[Set]] to window.%Interface% constructor now preserves DontEnum attribute [2]. (aligns with Blink and Gecko). 3. Cross-origin non-index put() now throws SecurityError instead of silently failing [3]. (aligns with Blink and Gecko). Refactors: 1. Simplifies cross-origin JSDOMWindow::put(), aligning it with JSLocation::put(). 2. Replaces lookupPut() with direct setter call in JSRemoteDOMWindow::put(). 3. Removes now unused doPutPropertySecurityCheck() methods. Tests: js/dom/script-tests/reflect-set-onto-dom.js imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/interface-object-set-receiver.html http/tests/security/cross-frame-access-object-getPrototypeOf-in-put.html [1] https://heycam.github.io/webidl/#legacy-platform-object-set (step 1) [2] https://heycam.github.io/webidl/#define-the-global-property-references (step 3.1.3) [3] https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-) (step 4) * bindings/js/JSDOMWindowCustom.cpp: (WebCore::JSDOMWindow::put): (WebCore::JSDOMWindow::doPutPropertySecurityCheck): Deleted. * bindings/js/JSLocationCustom.cpp: (WebCore::JSLocation::doPutPropertySecurityCheck): Deleted. * bindings/js/JSRemoteDOMWindowCustom.cpp: (WebCore::JSRemoteDOMWindow::put): * bindings/scripts/CodeGeneratorJS.pm: (GeneratePut): (GenerateHeader): * bindings/scripts/test/JS/*: Updated. * bridge/objc/objc_runtime.h: * bridge/runtime_array.h: * bridge/runtime_object.h: Source/WebKit: * WebProcess/Plugins/Netscape/JSNPObject.h: LayoutTests: * http/tests/security/cross-frame-access-object-getPrototypeOf-in-put-expected.txt: * http/tests/security/cross-frame-access-object-getPrototypeOf-in-put.html: * js/dom/reflect-set-onto-dom-expected.txt: * js/dom/script-tests/reflect-set-onto-dom.js: Canonical link: https://commits.webkit.org/237028@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@276592 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 2bdcfad commit a5cadca

117 files changed

Lines changed: 1355 additions & 460 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎JSTests/ChangeLog‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
2021-04-26 Alexey Shvayka <shvaikalesh@gmail.com>
2+
3+
[JSC] OrdinarySet should invoke custom [[Set]] methods
4+
https://bugs.webkit.org/show_bug.cgi?id=217916
5+
6+
Reviewed by Yusuke Suzuki.
7+
8+
* microbenchmarks/put-slow-no-cache-array.js: Added.
9+
* microbenchmarks/put-slow-no-cache-function.js: Added.
10+
* microbenchmarks/put-slow-no-cache-js-proxy.js: Added.
11+
* microbenchmarks/put-slow-no-cache-long-prototype-chain.js: Added.
12+
* microbenchmarks/put-slow-no-cache.js: Added.
13+
* microbenchmarks/reflect-set-with-receiver.js: Added.
14+
* stress/custom-get-set-proto-chain-put.js:
15+
* stress/module-namespace-access-set-fails.js: Added.
16+
* stress/put-non-reified-static-accessor-or-custom.js: Added.
17+
* stress/put-non-reified-static-function-or-custom.js: Added.
18+
* stress/put-to-primitive-non-reified-static-custom.js: Added.
19+
* stress/put-to-primitive.js: Added.
20+
* stress/put-to-proto-chain-overrides-put.js: Added.
21+
* stress/typed-array-canonical-numeric-index-string-set.js: Added.
22+
123
2021-04-22 Yusuke Suzuki <ysuzuki@apple.com>
224

325
[JSC] Baseline should have fast path for switch_imm
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(function() {
2+
var array = [];
3+
4+
for (var j = 0; j < 1e5; j++)
5+
array["foo" + j] = j;
6+
})();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(function() {
2+
class A {}
3+
class B extends A {}
4+
class C extends B {}
5+
6+
for (var j = 0; j < 1e5; j++)
7+
C["foo" + j] = j;
8+
})();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(function() {
2+
var proxy = $vm.createProxy({ set foo(_v) {} });
3+
4+
for (var j = 0; j < 1e5; j++)
5+
proxy["foo" + j] = j;
6+
})();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(function() {
2+
var base = { set foo(_v) {} };
3+
for (var i = 0; i < 1e3; i++)
4+
base = Object.create(base);
5+
6+
for (var j = 0; j < 3e3; j++)
7+
base["foo" + j] = j;
8+
})();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(function() {
2+
var base = { set foo(_v) {} };
3+
4+
for (var j = 0; j < 1e5; j++)
5+
base["foo" + j] = j;
6+
})();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(function() {
2+
var target = new (class {});
3+
4+
for (var i = 0; i < 1e6; i++)
5+
Reflect.set(target, "foo", 1, {});
6+
})();

‎JSTests/stress/custom-get-set-proto-chain-put.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ function getObjects() {
3737
...primitives.map(Object),
3838
Object.create(customTestGetterSetter),
3939
Object.create(Object.create(customTestGetterSetter)),
40-
// FIXME: ordinarySetSlow() should handle Custom{Accessor,Value} descriptors.
41-
// new Proxy(customTestGetterSetter, {}),
40+
Object.create(new Proxy(customTestGetterSetter, {})),
41+
Object.create($vm.createProxy(customTestGetterSetter)),
4242
];
4343
}
4444

@@ -85,13 +85,13 @@ function getBases() {
8585
})();
8686

8787
// CustomValue: setter returns |false|
88+
// FIXME: Once legacy RegExp features are implemented, there would be no use case for calling CustomValue setter if receiver is altered.
8889
(() => {
8990
for (let base of getBases()) {
9091
for (let i = 0; i < 100; ++i)
9192
base.customValue = 1;
92-
// This is weird, but it isn't exposed to userland code:
9393
assert(!base.hasOwnProperty("customValue"));
94-
assert(!Reflect.set(Object(base), "customValue", 1));
94+
assert(Reflect.set(Object(base), "customValue", 1));
9595
}
9696
})();
9797

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const existingKeys = ["cocoa", "test", Symbol.toStringTag];
2+
const nonExistingKeys = ["foo", Symbol("foo")];
3+
const allKeys = [...existingKeys, ...nonExistingKeys];
4+
const testValue = Object.freeze({});
5+
6+
function assert(x, key) {
7+
if (!x)
8+
throw new Error(`Bad assertion! Key: ${String(key)}`);
9+
}
10+
11+
function shouldThrow(func, errorMessage) {
12+
let errorThrown = false;
13+
try {
14+
func();
15+
} catch (error) {
16+
errorThrown = true;
17+
if (String(error) !== errorMessage)
18+
throw new Error(`Bad error: ${error}`);
19+
}
20+
if (!errorThrown)
21+
throw new Error("Didn't throw!");
22+
}
23+
24+
function testDirectSet(ns) {
25+
for (const key of allKeys) {
26+
ns[key] = testValue;
27+
assert(ns[key] !== testValue, key);
28+
29+
shouldThrow(() => { "use strict"; ns[key] = testValue; }, "TypeError: Attempted to assign to readonly property.");
30+
assert(ns[key] !== testValue, key);
31+
}
32+
}
33+
34+
function testPrototypeChainSet(ns) {
35+
for (const key of allKeys) {
36+
const heir = Object.create(ns);
37+
38+
heir[key] = testValue;
39+
assert(Object.getOwnPropertyDescriptor(heir, key) === undefined, key);
40+
41+
shouldThrow(() => { "use strict"; heir[key] = testValue; }, "TypeError: Attempted to assign to readonly property.");
42+
assert(Object.getOwnPropertyDescriptor(heir, key) === undefined, key);
43+
}
44+
}
45+
46+
function testReflectSet(ns) {
47+
"use strict";
48+
49+
for (const existingKey of existingKeys) {
50+
assert(!Reflect.set(ns, existingKey, testValue), existingKey);
51+
52+
const target = {};
53+
assert(!Reflect.set(target, existingKey, testValue, ns), existingKey);
54+
55+
const isWritable = existingKey !== Symbol.toStringTag;
56+
assert(Reflect.set(target, existingKey, ns[existingKey], ns) === isWritable, existingKey);
57+
58+
assert(ns[existingKey] !== testValue, existingKey);
59+
assert(target[existingKey] === undefined, existingKey);
60+
}
61+
62+
for (const nonExistingKey of nonExistingKeys) {
63+
assert(!Reflect.set(ns, nonExistingKey, testValue), nonExistingKey);
64+
assert(ns[nonExistingKey] === undefined, nonExistingKey);
65+
66+
const target = {};
67+
assert(!Reflect.set(target, nonExistingKey, testValue, ns), nonExistingKey);
68+
assert(target[nonExistingKey] === undefined, nonExistingKey);
69+
}
70+
71+
for (const key of allKeys) {
72+
const target = Object.create(ns);
73+
const receiver = {};
74+
75+
assert(!Reflect.set(target, key, testValue, receiver), key);
76+
assert(Object.getOwnPropertyDescriptor(target, key) === undefined, key);
77+
assert(Object.getOwnPropertyDescriptor(receiver, key) === undefined, key);
78+
}
79+
}
80+
81+
import("./resources/module-namespace-access.js").then(ns => {
82+
testDirectSet(ns);
83+
testPrototypeChainSet(ns);
84+
testReflectSet(ns);
85+
}).catch(err => {
86+
print(`!!! ${err}\n${err.stack}`);
87+
$vm.abort();
88+
});
89+
90+
drainMicrotasks();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use strict";
2+
const testValue = Object.freeze({});
3+
4+
function assert(x, key) {
5+
if (!x)
6+
throw new Error(`Key: "${key}". Bad assertion!`);
7+
}
8+
9+
function* testCases() {
10+
for (const key of ["description"])
11+
yield [$vm.createGlobalObject().Symbol.prototype, key];
12+
for (const key of ["buffer", "byteLength", "byteOffset"])
13+
yield [$vm.createGlobalObject().DataView.prototype, key];
14+
for (const key of ["baseName", "caseFirst", "hourCycle", "numeric", "region"])
15+
yield [$vm.createGlobalObject().Intl.Locale.prototype, key];
16+
17+
for (const key of ["testStaticAccessorDontEnum", "testStaticAccessorReadOnly"])
18+
yield [$vm.createStaticCustomAccessor(), key];
19+
yield [$vm.createStaticCustomValue(), "testStaticValueReadOnly"];
20+
}
21+
22+
for (const [object, key] of testCases()) {
23+
const target = {};
24+
assert(!Reflect.set(target, key, testValue, object), key);
25+
assert(!target.hasOwnProperty(key), key);
26+
assert(Object.getOwnPropertyDescriptor(object, key).value !== testValue, key);
27+
}
28+
29+
for (const [object, key] of testCases()) {
30+
Object.defineProperty(object, key, { value: {}, writable: false });
31+
const target = {};
32+
assert(!Reflect.set(target, key, testValue, object), key);
33+
assert(!target.hasOwnProperty(key), key);
34+
assert(Object.getOwnPropertyDescriptor(object, key).value !== testValue, key);
35+
}
36+
37+
for (const [object, key] of testCases()) {
38+
Object.defineProperty(object, key, { value: {}, writable: true, configurable: false });
39+
object[key] = testValue;
40+
41+
const desc = Object.getOwnPropertyDescriptor(object, key);
42+
assert(desc.value === testValue, key);
43+
assert(desc.writable, key);
44+
assert(!desc.enumerable, key);
45+
assert(!desc.configurable, key);
46+
}
47+
48+
for (const [object, key] of testCases()) {
49+
Object.defineProperty(object, key, { value: {}, writable: true, enumerable: true });
50+
const target = {};
51+
assert(Reflect.set(target, key, testValue, object), key);
52+
53+
const desc = Object.getOwnPropertyDescriptor(object, key);
54+
assert(desc.value === testValue, key);
55+
assert(desc.writable, key);
56+
assert(desc.enumerable, key);
57+
assert(desc.configurable, key);
58+
}

0 commit comments

Comments
 (0)