mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Keep the while-loop bound off counters written inside expressions
flow follows a while body's statements and applies each increment it meets as a statement. An increment inside an expression, such as an if condition's comma expression `if ((i++, i > 0))`, lowers to a statement nested in the expression; whileBound accepted it as an increment, but flow never applied it, so a read after it kept the pre-increment bound and lost its bounds check. whileBound now gives no bound when any expression in the body holds a write of the counter.
This commit is contained in:
@@ -300,10 +300,18 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly
|
||||
const receiver = length.receiver;
|
||||
const comparison = lengthComparison(cond, { receiver, counter, offset: 0, minimum });
|
||||
if (comparison?.op !== "<" && comparison?.op !== "<=") return null;
|
||||
// An increment is a statement of the body: one inside an expression
|
||||
// (a comma expression's statements) would run where flow does not
|
||||
// follow it.
|
||||
const writesOk = everyStmtList(s.body, {
|
||||
expr: (e) =>
|
||||
(e.kind !== "assignExpr" && e.kind !== "incDec") ||
|
||||
(e.localId !== counter && e.localId !== receiver),
|
||||
((e.kind !== "assignExpr" && e.kind !== "incDec") ||
|
||||
(e.localId !== counter && e.localId !== receiver)) &&
|
||||
everyExprChild(
|
||||
e,
|
||||
() => true,
|
||||
(child) => !writes(child, counter),
|
||||
),
|
||||
stmt: (child) => {
|
||||
if (child.kind === "assign" && child.localId === counter)
|
||||
return increment(child, counter) !== null;
|
||||
|
||||
@@ -119,13 +119,27 @@ function nested(line: Uint8Array): number {
|
||||
}
|
||||
return total;
|
||||
}
|
||||
function comma(line: Uint8Array): number {
|
||||
const len = line.length;
|
||||
let i = 0;
|
||||
let total = 0;
|
||||
while (i < len) {
|
||||
if ((i++, i > 0)) total += line[len - 1 - i]!;
|
||||
total += line[len - 1 - i]!;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
console.log(
|
||||
comma(new Uint8Array(2)),
|
||||
inclusive(new Uint8Array(2)),
|
||||
unguarded(new Uint8Array(2)),
|
||||
ahead(new Uint16Array(3)),
|
||||
nested(new Uint8Array(3)),
|
||||
);
|
||||
`);
|
||||
// The increment in the comma expression is not a statement of the
|
||||
// body, so the loop has no bound and both reads keep their checks.
|
||||
expect(checks(ll, "comma")).toBe(2);
|
||||
expect(checks(ll, "inclusive")).toBe(1);
|
||||
// The read before the increment is in bounds; the one after it is not.
|
||||
expect(checks(ll, "unguarded")).toBe(1);
|
||||
|
||||
@@ -166,3 +166,18 @@ function throwing(line: Uint8Array): string {
|
||||
return String(total);
|
||||
}
|
||||
console.log(throwing(new Uint8Array([4, 0, 2, 3])), throwing(new Uint8Array([1, 2])));
|
||||
|
||||
// An increment inside an if condition's comma expression: both reads are
|
||||
// at index -1 on the last pass.
|
||||
function commaIncrement(line: Uint8Array): string {
|
||||
const len = line.length;
|
||||
let i = 0;
|
||||
const out: (number | undefined)[] = [];
|
||||
while (i < len) {
|
||||
if ((i++, i > 0)) out.push(line[len - 1 - i]);
|
||||
const v = line[len - 1 - i]!;
|
||||
out.push(v);
|
||||
}
|
||||
return JSON.stringify(out);
|
||||
}
|
||||
console.log(commaIncrement(new Uint8Array([1, 2, 3])), commaIncrement(new Uint8Array(0)));
|
||||
|
||||
Reference in New Issue
Block a user