mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
The spec for PropertyDefinitionEvaluation requires that when evaluating a property definition with a computed key (PropertyDefinition : PropertyName : AssignmentExpression), the PropertyName is fully evaluated (including ToPropertyKey, which calls ToPrimitive) before the value's AssignmentExpression is evaluated. Our bytecode compiler was evaluating the key expression first, then the value expression, and only performing ToPropertyKey later inside PutByValue at runtime. This meant user-observable side effects from ToPrimitive (such as calling Symbol.toPrimitive or toString on the key object) would fire after the value expression had already been evaluated. Fix this by using a new ToPrimitiveWithStringHint instruction that performs ToPrimitive with string hint(!), and emitting it between the key and value evaluations in ObjectExpression codegen. After ToPrimitive, the key is already a primitive, so the subsequent ToPropertyKey inside PutByValue becomes a no-op from the perspective of user-observable side effects. Also update an existing test that was asserting the old (incorrect) evaluation order, and add comprehensive new tests for computed property key evaluation order.
146 lines
3.6 KiB
JavaScript
146 lines
3.6 KiB
JavaScript
let calledToStringError = {};
|
|
let throwingToString = {
|
|
toString: () => {
|
|
throw calledToStringError;
|
|
},
|
|
};
|
|
let calledValueOfError = {};
|
|
let throwingValueOf = {
|
|
toString: undefined,
|
|
valueOf: () => {
|
|
throw calledValueOfError;
|
|
},
|
|
};
|
|
let calledToStringAccessorError = {};
|
|
let throwingToStringAccessor = {
|
|
get toString() {
|
|
throw calledToStringAccessorError;
|
|
},
|
|
};
|
|
let calledValueOfAccessorError = {};
|
|
let throwingValueOfAccessor = {
|
|
toString: undefined,
|
|
get valueOf() {
|
|
throw calledValueOfAccessorError;
|
|
},
|
|
};
|
|
|
|
test("Exceptions thrown by computed properties are caught", () => {
|
|
var i = 0;
|
|
var j = 0;
|
|
var k = 0;
|
|
// ToPropertyKey of the computed key happens before the value expression is evaluated,
|
|
// so when toString() throws, the value expression (i++) should NOT have executed.
|
|
expect(() => {
|
|
return { first: k++, [throwingToString]: i++, second: j++ };
|
|
}).toThrow(calledToStringError);
|
|
expect(i).toBe(0);
|
|
expect(j).toBe(0);
|
|
expect(k).toBe(1);
|
|
expect(() => {
|
|
return { first: k++, [throwingValueOf]: i++, second: j++ };
|
|
}).toThrow(calledValueOfError);
|
|
expect(i).toBe(0);
|
|
expect(j).toBe(0);
|
|
expect(k).toBe(2);
|
|
expect(() => {
|
|
return { first: k++, [throwingToStringAccessor]: i++, second: j++ };
|
|
}).toThrow(calledToStringAccessorError);
|
|
expect(i).toBe(0);
|
|
expect(j).toBe(0);
|
|
expect(k).toBe(3);
|
|
expect(() => {
|
|
return { first: k++, [throwingValueOfAccessor]: i++, second: j++ };
|
|
}).toThrow(calledValueOfAccessorError);
|
|
expect(i).toBe(0);
|
|
expect(j).toBe(0);
|
|
expect(k).toBe(4);
|
|
});
|
|
|
|
test("Test toString and valueOf are only called once", () => {
|
|
var counter = 0;
|
|
var key1 = {
|
|
valueOf: function () {
|
|
expect(counter++).toBe(0);
|
|
return "a";
|
|
},
|
|
toString: null,
|
|
};
|
|
var key2 = {
|
|
valueOf: function () {
|
|
expect(counter++).toBe(1);
|
|
return "b";
|
|
},
|
|
toString: null,
|
|
};
|
|
var key3 = {
|
|
get toString() {
|
|
expect(counter++).toBe(2);
|
|
return function () {
|
|
return "c";
|
|
};
|
|
},
|
|
};
|
|
var key4 = {
|
|
get valueOf() {
|
|
expect(counter++).toBe(3);
|
|
return function () {
|
|
return "d";
|
|
};
|
|
},
|
|
toString: null,
|
|
};
|
|
var key5 = {
|
|
valueOf: function () {
|
|
expect(counter++).toBe(4);
|
|
return 0;
|
|
},
|
|
toString: null,
|
|
};
|
|
var key6 = {
|
|
valueOf: function () {
|
|
expect(counter++).toBe(5);
|
|
return 2;
|
|
},
|
|
toString: null,
|
|
};
|
|
var key7 = {
|
|
get toString() {
|
|
expect(counter++).toBe(6);
|
|
return function () {
|
|
return 4;
|
|
};
|
|
},
|
|
};
|
|
var key8 = {
|
|
get valueOf() {
|
|
expect(counter++).toBe(7);
|
|
return function () {
|
|
return 8;
|
|
};
|
|
},
|
|
toString: null,
|
|
};
|
|
|
|
var object = {
|
|
[key1]: "a",
|
|
[key2]: "b",
|
|
[key3]: "c",
|
|
[key4]: "d",
|
|
[key5]: "e",
|
|
[key6]: "f",
|
|
[key7]: "g",
|
|
[key8]: "h",
|
|
};
|
|
expect(counter).toBe(8);
|
|
expect(object.a).toBe("a");
|
|
expect(object.b).toBe("b");
|
|
expect(object.c).toBe("c");
|
|
expect(object.d).toBe("d");
|
|
expect(object[0]).toBe("e");
|
|
expect(object[2]).toBe("f");
|
|
expect(object[4]).toBe("g");
|
|
expect(object[8]).toBe("h");
|
|
expect(Object.getOwnPropertyNames(object) + "").toBe(["0", "2", "4", "8", "a", "b", "c", "d"] + "");
|
|
});
|