Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.

Commit ecaf357

Browse files
[WebIDL] %Interface%.prototype.constructor should be defined on [[Set]] receiver
https://bugs.webkit.org/show_bug.cgi?id=216533 Reviewed by Darin Adler. JSTests: * stress/custom-get-set-proto-chain-put.js: Added. LayoutTests/imported/w3c: * web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver-expected.txt: Added. * web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver.html: Added. Source/JavaScriptCore: Before this change, a [[Set]] performed on an %Interface% instance used to overwrite %Interface%.prototype.constructor instead of defining own "constructor" property. Since using CustomValue is essential for lazy initialization of WebIDL constructors, and forwarding [[Set]] with correct receiver would require further diverging CustomValue setter signature from CustomAccessor counterpart, this patch makes a CustomValue property without a setter to be treated as a data descriptor [1]. This avoids generating a "constructor" setter for every exposed WebIDL interface and making an extra put() dispatch in putInlineSlow(). Changing the semantics is safe because there were no setter-less CustomValue properties before this patch. [1]: https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor (step 3.e.ii) * bytecode/AccessCase.cpp: (JSC::AccessCase::generateImpl): * runtime/CustomGetterSetter.cpp: (JSC::callCustomSetter): * runtime/CustomGetterSetter.h: * runtime/JSCJSValue.cpp: (JSC::JSValue::putToPrimitive): Add missing exception check. * runtime/JSCustomGetterSetterFunction.cpp: (JSC::JSC_DEFINE_HOST_FUNCTION): * runtime/JSObject.cpp: (JSC::JSObject::putInlineSlow): * runtime/Lookup.h: (JSC::putEntry): * runtime/PropertySlot.cpp: (JSC::PropertySlot::customGetter const): * runtime/PropertySlot.h: * tools/JSDollarVM.cpp: Source/WebCore: 1. Create a setter-less CustomGetterSetter instead of generating "constructor" setter. 2. Remove unused $needsConstructorTable variable. 3. Remove [LegacyNoInterfaceObject] branch as it's precluded by NeedsConstructorProperty. Test: imported/w3c/web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver.html * bindings/scripts/CodeGeneratorJS.pm: (GeneratePropertiesHashTable): (GenerateImplementation): * bindings/scripts/test/JS/*: Updated. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@268710 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 6b952c8 commit ecaf357

File tree

95 files changed

+381
-1229
lines changed

Some content is hidden

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

95 files changed

+381
-1229
lines changed

JSTests/ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
2020-10-19 Alexey Shvayka <[email protected]>
2+
3+
[WebIDL] %Interface%.prototype.constructor should be defined on [[Set]] receiver
4+
https://bugs.webkit.org/show_bug.cgi?id=216533
5+
6+
Reviewed by Darin Adler.
7+
8+
* stress/custom-get-set-proto-chain-put.js: Added.
9+
110
2020-10-19 Mark Cohen <[email protected]>
211

312
test262: test/language/expressions/conditional/in-branch-1.js
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"use strict";
2+
3+
function assert(x) {
4+
if (!x)
5+
throw new Error("Bad assert!");
6+
}
7+
8+
function shouldThrow(func, expectedError) {
9+
var errorThrown = false;
10+
try {
11+
func();
12+
} catch (error) {
13+
errorThrown = true;
14+
if (String(error) !== expectedError)
15+
throw new Error(`Bad error: ${error}`);
16+
}
17+
if (!errorThrown)
18+
throw new Error("Didn't throw!");
19+
}
20+
21+
const customTestGetterSetter = $vm.createCustomTestGetterSetter();
22+
Object.setPrototypeOf(customTestGetterSetter, {
23+
set customValue(_v) { throw new Error("Should be unreachable!"); },
24+
set customValueGlobalObject(_v) { throw new Error("Should be unreachable!"); },
25+
set customAccessor(_v) { throw new Error("Should be unreachable!"); },
26+
set customAccessorGlobalObject(_v) { throw new Error("Should be unreachable!"); },
27+
});
28+
29+
const primitives = [true, 1, "", Symbol(), 1n];
30+
for (let primitive of primitives) {
31+
let prototype = Object.getPrototypeOf(primitive);
32+
Object.setPrototypeOf(prototype, customTestGetterSetter);
33+
}
34+
35+
function getObjects() {
36+
return [
37+
...primitives.map(Object),
38+
Object.create(customTestGetterSetter),
39+
Object.create(Object.create(customTestGetterSetter)),
40+
// FIXME: ordinarySetSlow() should handle Custom{Accessor,Value} descriptors.
41+
// new Proxy(customTestGetterSetter, {}),
42+
];
43+
}
44+
45+
function getBases() {
46+
return [
47+
...primitives,
48+
...getObjects(),
49+
];
50+
}
51+
52+
// CustomAccessor: exception check
53+
(() => {
54+
for (let base of getBases()) {
55+
for (let i = 0; i < 100; ++i) {
56+
let value = {};
57+
base.customAccessor = value;
58+
assert(value.hasOwnProperty("result"));
59+
}
60+
assert(Reflect.set(Object(base), "customAccessor", {}));
61+
}
62+
})();
63+
64+
// CustomValue: exception check
65+
(() => {
66+
for (let base of getBases()) {
67+
for (let i = 0; i < 100; ++i) {
68+
let value = {};
69+
base.customValue = value;
70+
assert(value.hasOwnProperty("result"));
71+
}
72+
assert(Reflect.set(Object(base), "customValue", {}));
73+
}
74+
})();
75+
76+
// CustomAccessor: setter returns |false|
77+
(() => {
78+
for (let base of getBases()) {
79+
for (let i = 0; i < 100; ++i)
80+
base.customAccessor = 1;
81+
// This is intentional:
82+
assert(!base.hasOwnProperty("customAccessor"));
83+
assert(Reflect.set(Object(base), "customAccessor", 1));
84+
}
85+
})();
86+
87+
// CustomValue: setter returns |false|
88+
(() => {
89+
for (let base of getBases()) {
90+
for (let i = 0; i < 100; ++i)
91+
base.customValue = 1;
92+
// This is weird, but it isn't exposed to userland code:
93+
assert(!base.hasOwnProperty("customValue"));
94+
assert(!Reflect.set(Object(base), "customValue", 1));
95+
}
96+
})();
97+
98+
// CustomAccessor: no setter
99+
(() => {
100+
for (let base of getBases()) {
101+
for (let i = 0; i < 100; ++i)
102+
shouldThrow(() => { base.customAccessorGlobalObject = {}; }, "TypeError: Attempted to assign to readonly property.");
103+
assert(!Reflect.set(Object(base), "customAccessorGlobalObject", {}));
104+
}
105+
})();
106+
107+
// CustomValue: no setter
108+
(() => {
109+
for (let primitive of primitives) {
110+
for (let i = 0; i < 100; ++i)
111+
shouldThrow(() => { primitive.customValueGlobalObject = {}; }, "TypeError: Attempted to assign to readonly property.");
112+
}
113+
114+
for (let object of getObjects()) {
115+
for (let i = 0; i < 100; ++i)
116+
object.customValueGlobalObject = {};
117+
assert(object.hasOwnProperty("customValueGlobalObject"));
118+
assert(Reflect.set(object, "customValueGlobalObject", {}));
119+
}
120+
121+
for (let object of getObjects()) {
122+
Object.preventExtensions(object);
123+
for (let i = 0; i < 100; ++i) {
124+
shouldThrow(() => { object.customValueGlobalObject = {}; }, "TypeError: Attempted to assign to readonly property.");
125+
assert(!Reflect.set(object, "customValueGlobalObject", {}));
126+
}
127+
}
128+
})();

LayoutTests/imported/w3c/ChangeLog

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
2020-10-19 Alexey Shvayka <[email protected]>
2+
3+
[WebIDL] %Interface%.prototype.constructor should be defined on [[Set]] receiver
4+
https://bugs.webkit.org/show_bug.cgi?id=216533
5+
6+
Reviewed by Darin Adler.
7+
8+
* web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver-expected.txt: Added.
9+
* web-platform-tests/WebIDL/ecmascript-binding/interface-prototype-constructor-set-receiver.html: Added.
10+
111
2020-10-19 Alexey Shvayka <[email protected]>
212

313
[WebIDL] convertRecord() should handle duplicate keys for USVString records
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
PASS Window, Location, Navigator, HTMLDocument, and HTMLHeadElement
3+
PASS All window.* constructors
4+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>Interface.prototype.constructor is defined on [[Set]] receiver</title>
4+
<link rel="help" href="https://heycam.github.io/webidl/#interface-prototype-object">
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script>
8+
"use strict";
9+
10+
test(() => {
11+
window.constructor = null;
12+
assert_equals(Window.prototype.constructor, Window);
13+
14+
location.constructor = false;
15+
assert_equals(Location.prototype.constructor, Location);
16+
17+
navigator.constructor = 1;
18+
assert_equals(Navigator.prototype.constructor, Navigator);
19+
20+
document.constructor = {};
21+
assert_equals(HTMLDocument.prototype.constructor, HTMLDocument);
22+
23+
document.head.constructor = [];
24+
assert_equals(HTMLHeadElement.prototype.constructor, HTMLHeadElement);
25+
}, "Window, Location, Navigator, HTMLDocument, and HTMLHeadElement");
26+
27+
test(() => {
28+
for (let key of Object.getOwnPropertyNames(window)) {
29+
if (!/^[A-Z]/.test(key)) continue;
30+
31+
let desc = Object.getOwnPropertyDescriptor(window, key);
32+
if (!desc || desc.enumerable) continue;
33+
let {value} = desc;
34+
if (typeof value !== "function") continue;
35+
let {prototype} = value;
36+
if (!prototype) continue;
37+
38+
let heir = Object.create(prototype);
39+
let newConstructor = function() {};
40+
heir.constructor = newConstructor;
41+
42+
assert_not_equals(prototype.constructor, newConstructor, key);
43+
assert_own_property(heir, "constructor", key);
44+
assert_equals(heir.constructor, newConstructor, key);
45+
}
46+
}, "All window.* constructors");
47+
</script>

Source/JavaScriptCore/ChangeLog

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1+
2020-10-19 Alexey Shvayka <[email protected]>
2+
3+
[WebIDL] %Interface%.prototype.constructor should be defined on [[Set]] receiver
4+
https://bugs.webkit.org/show_bug.cgi?id=216533
5+
6+
Reviewed by Darin Adler.
7+
8+
Before this change, a [[Set]] performed on an %Interface% instance used to overwrite
9+
%Interface%.prototype.constructor instead of defining own "constructor" property.
10+
11+
Since using CustomValue is essential for lazy initialization of WebIDL constructors,
12+
and forwarding [[Set]] with correct receiver would require further diverging
13+
CustomValue setter signature from CustomAccessor counterpart, this patch makes a
14+
CustomValue property without a setter to be treated as a data descriptor [1].
15+
16+
This avoids generating a "constructor" setter for every exposed WebIDL interface and
17+
making an extra put() dispatch in putInlineSlow(). Changing the semantics is safe
18+
because there were no setter-less CustomValue properties before this patch.
19+
20+
[1]: https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor (step 3.e.ii)
21+
22+
* bytecode/AccessCase.cpp:
23+
(JSC::AccessCase::generateImpl):
24+
* runtime/CustomGetterSetter.cpp:
25+
(JSC::callCustomSetter):
26+
* runtime/CustomGetterSetter.h:
27+
* runtime/JSCJSValue.cpp:
28+
(JSC::JSValue::putToPrimitive): Add missing exception check.
29+
* runtime/JSCustomGetterSetterFunction.cpp:
30+
(JSC::JSC_DEFINE_HOST_FUNCTION):
31+
* runtime/JSObject.cpp:
32+
(JSC::JSObject::putInlineSlow):
33+
* runtime/Lookup.h:
34+
(JSC::putEntry):
35+
* runtime/PropertySlot.cpp:
36+
(JSC::PropertySlot::customGetter const):
37+
* runtime/PropertySlot.h:
38+
* tools/JSDollarVM.cpp:
39+
140
2020-10-19 Mark Cohen <[email protected]>
241

342
test262: test/language/expressions/conditional/in-branch-1.js

Source/JavaScriptCore/bytecode/AccessCase.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,10 +1715,8 @@ void AccessCase::generateImpl(AccessGenerationState& state)
17151715
GPRReg receiverForCustomGetGPR = baseGPR != thisGPR ? thisGPR : receiverGPR;
17161716

17171717
// getter: EncodedJSValue (*GetValueFunc)(JSGlobalObject*, EncodedJSValue thisValue, PropertyName);
1718-
// setter: void (*PutValueFunc)(JSGlobalObject*, EncodedJSValue thisObject, EncodedJSValue value);
1719-
// Custom values are passed the slotBase (the property holder), custom accessors are passed the thisVaule (receiver).
1720-
// FIXME: Remove this differences in custom values and custom accessors.
1721-
// https://bugs.webkit.org/show_bug.cgi?id=158014
1718+
// setter: bool (*PutValueFunc)(JSGlobalObject*, EncodedJSValue thisObject, EncodedJSValue value);
1719+
// Custom values are passed the slotBase (the property holder), custom accessors are passed the thisValue (receiver).
17221720
GPRReg baseForCustom = takesPropertyOwnerAsCFunctionArgument ? propertyOwnerGPR : receiverForCustomGetGPR;
17231721
// We do not need to keep globalObject alive since the owner CodeBlock (even if JSGlobalObject* is one of CodeBlock that is inlined and held by DFG CodeBlock)
17241722
// must keep it alive.

Source/JavaScriptCore/runtime/CustomGetterSetter.cpp

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,19 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(CustomGetterSetter);
3535

3636
const ClassInfo CustomGetterSetter::s_info = { "CustomGetterSetter", nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(CustomGetterSetter) };
3737

38-
bool callCustomSetter(JSGlobalObject* globalObject, CustomGetterSetter::CustomSetter setter, bool isAccessor, JSValue thisValue, JSValue value)
38+
TriState callCustomSetter(JSGlobalObject* globalObject, CustomGetterSetter::CustomSetter setter, bool isAccessor, JSObject* slotBase, JSValue thisValue, JSValue value)
3939
{
40-
ASSERT(setter);
41-
bool result = setter(globalObject, JSValue::encode(thisValue), JSValue::encode(value));
42-
// Always return true if there is a setter and it is observed as an accessor to users.
43-
if (isAccessor)
44-
return true;
45-
return result;
46-
}
40+
if (isAccessor) {
41+
if (!setter)
42+
return TriState::False;
43+
setter(globalObject, JSValue::encode(thisValue), JSValue::encode(value));
44+
// Always return true if there is a setter and it is observed as an accessor to users.
45+
return TriState::True;
46+
}
4747

48-
bool callCustomSetter(JSGlobalObject* globalObject, JSValue customGetterSetter, bool isAccessor, JSObject* base, JSValue thisValue, JSValue value)
49-
{
50-
CustomGetterSetter::CustomSetter setter = jsCast<CustomGetterSetter*>(customGetterSetter)->setter();
51-
// Return false since there is no setter.
5248
if (!setter)
53-
return false;
54-
// FIXME: Remove this differences in custom values and custom accessors.
55-
// https://bugs.webkit.org/show_bug.cgi?id=158014
56-
if (!isAccessor)
57-
thisValue = base;
58-
return callCustomSetter(globalObject, setter, isAccessor, thisValue, value);
49+
return TriState::Indeterminate;
50+
return triState(setter(globalObject, JSValue::encode(slotBase), JSValue::encode(value)));
5951
}
6052

6153
} // namespace JSC

Source/JavaScriptCore/runtime/CustomGetterSetter.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ class CustomGetterSetter : public JSCell {
7676
CustomSetter m_setter;
7777
};
7878

79-
JS_EXPORT_PRIVATE bool callCustomSetter(JSGlobalObject*, CustomGetterSetter::CustomSetter, bool isAccessor, JSValue thisValue, JSValue);
80-
JS_EXPORT_PRIVATE bool callCustomSetter(JSGlobalObject*, JSValue customGetterSetter, bool isAccessor, JSObject* slotBase, JSValue thisValue, JSValue);
79+
JS_EXPORT_PRIVATE TriState callCustomSetter(JSGlobalObject*, CustomGetterSetter::CustomSetter, bool isAccessor, JSObject* slotBase, JSValue thisValue, JSValue);
8180

8281
} // namespace JSC

Source/JavaScriptCore/runtime/JSCJSValue.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,13 @@ bool JSValue::putToPrimitive(JSGlobalObject* globalObject, PropertyName property
213213
if (gs.isGetterSetter())
214214
RELEASE_AND_RETURN(scope, callSetter(globalObject, *this, gs, value, slot.isStrictMode() ? ECMAMode::strict() : ECMAMode::sloppy()));
215215

216-
if (gs.isCustomGetterSetter())
217-
return callCustomSetter(globalObject, gs, attributes & PropertyAttribute::CustomAccessor, obj, slot.thisValue(), value);
216+
if (gs.isCustomGetterSetter()) {
217+
auto setter = jsCast<CustomGetterSetter*>(gs.asCell())->setter();
218+
bool isAccessor = attributes & PropertyAttribute::CustomAccessor;
219+
auto result = callCustomSetter(globalObject, setter, isAccessor, obj, slot.thisValue(), value);
220+
if (result != TriState::Indeterminate)
221+
RELEASE_AND_RETURN(scope, result == TriState::True);
222+
}
218223

219224
// If there's an existing property on the object or one of its
220225
// prototypes it should be replaced, so break here.

0 commit comments

Comments
 (0)