mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Read a spread's elements before an earlier argument converts
When the Math functions evaluate their arguments into temporaries before converting (an earlier argument whose ToNumber can run valueOf), a spread argument's array is now copied where it is evaluated. Before, the temporary held the array itself and its elements were read after the conversions, so a valueOf that changed the array changed the values passed, while JavaScript reads a spread's elements during argument evaluation.
This commit is contained in:
@@ -1429,9 +1429,12 @@ export function lowerToNumberArgument(lowerer: Lowerer, node: ts.Expression): Ir
|
||||
* any. When an argument's conversion can run program code (valueOf or
|
||||
* toString on a dynamic value) and a later argument follows, the arguments
|
||||
* evaluate into temporaries first and convert in order after. `lowerOther`
|
||||
* lowers an argument that takes no conversion (a spread), or returns null
|
||||
* for a ToNumber argument. Returns the temporaries' statements (empty when
|
||||
* none are needed) and the converted values. */
|
||||
* lowers an argument that takes no conversion (a spread's array), or returns
|
||||
* null for a ToNumber argument; with temporaries, such an array is copied
|
||||
* where it is evaluated, since JavaScript reads a spread's elements then,
|
||||
* before a later conversion's valueOf could change them. Returns the
|
||||
* temporaries' statements (empty when none are needed) and the converted
|
||||
* values. */
|
||||
export function lowerToNumberArguments(
|
||||
lowerer: Lowerer,
|
||||
nodes: readonly ts.Expression[],
|
||||
@@ -1439,7 +1442,9 @@ export function lowerToNumberArguments(
|
||||
): { stmts: IrStmt[]; values: IrExpr[] } {
|
||||
const operands = nodes.map((node): NumberOperand => {
|
||||
const other = lowerOther?.(node);
|
||||
return other ? { value: other, convert: null, runsCode: false } : numberOperand(lowerer, node);
|
||||
return other
|
||||
? { value: other, convert: null, runsCode: false, spread: true }
|
||||
: numberOperand(lowerer, node);
|
||||
});
|
||||
const late = operands.slice(0, -1).some((operand) => operand.runsCode);
|
||||
if (!late) {
|
||||
@@ -1451,7 +1456,18 @@ export function lowerToNumberArguments(
|
||||
if (operand.value.kind === "numLit") return operand.value;
|
||||
const temp = lowerer.declareHiddenLocal("%numberArgument", operand.value.type);
|
||||
const loc = operand.value.loc;
|
||||
stmts.push({ kind: "varDecl", localId: temp.id, init: operand.value, loc });
|
||||
const init: IrExpr =
|
||||
operand.spread && operand.value.type.kind === "array"
|
||||
? {
|
||||
kind: "arrIntrinsic",
|
||||
method: "slice",
|
||||
receiver: operand.value,
|
||||
args: [numLit(0, loc), numLit(Infinity, loc)],
|
||||
type: operand.value.type,
|
||||
loc,
|
||||
}
|
||||
: operand.value;
|
||||
stmts.push({ kind: "varDecl", localId: temp.id, init, loc });
|
||||
const read = varRef(temp.id, operand.value.type, loc);
|
||||
return operand.convert ? operand.convert(read) : read;
|
||||
});
|
||||
@@ -1460,11 +1476,13 @@ export function lowerToNumberArguments(
|
||||
|
||||
/** An argument a ToNumber site lowers: its evaluated value and the
|
||||
* conversion that makes it a number (null when the value already is one).
|
||||
* `runsCode`: the conversion can call program code. */
|
||||
* `runsCode`: the conversion can call program code. `spread`: the value is
|
||||
* a spread's array (lowerToNumberArguments' `lowerOther`). */
|
||||
interface NumberOperand {
|
||||
value: IrExpr;
|
||||
convert: ((value: IrExpr) => IrExpr) | null;
|
||||
runsCode: boolean;
|
||||
spread?: boolean;
|
||||
}
|
||||
|
||||
function numberOperand(lowerer: Lowerer, node: ts.Expression): NumberOperand {
|
||||
|
||||
@@ -75,3 +75,23 @@ try {
|
||||
} catch (e) {
|
||||
console.log(e.message, order);
|
||||
}
|
||||
|
||||
// A spread reads its elements where it is evaluated, before an earlier
|
||||
// argument's valueOf runs.
|
||||
const values = [1, 2];
|
||||
const mutator = {
|
||||
valueOf() {
|
||||
values[0] = 100;
|
||||
values.push(50);
|
||||
return 3;
|
||||
},
|
||||
};
|
||||
console.log(Math.max(mutator, ...values), values.join(","));
|
||||
const more = [4];
|
||||
const resetter = {
|
||||
valueOf() {
|
||||
more[0] = -1;
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
console.log(Math.hypot(resetter, ...more, 3), more.join(","));
|
||||
|
||||
Reference in New Issue
Block a user