Skip to content

Commit 718b019

Browse files
authored
bugc: revert on out-of-bounds indexing (#344)
* bugc: add an assert instruction that reverts with Panic An `assert` instruction takes a condition and a panic code. Code generation jumps over a revert when the condition holds; otherwise the code reverts with Solidity's `Panic(uint256)` error data, and the REVERT carries a `revert` context with the panic code and a pointer to that data. Dead code elimination keeps asserts, and constant folding removes an assert whose condition is a true constant. * bugc: give an array literal a memory reference type An array literal's value had a scalar type, so code generation took its length as the scalar's size: [1, 2, 3].length was 32. * bugc: require an unsigned integer index As in Solidity, an array or bytes index and a slice bound must be an unsigned integer. Before, a signed index compiled, and since #339 its comparisons are signed, so -1 would pass a bounds check. * bugc: revert on out-of-bounds indexing Indexing an array or bytes in memory, or a fixed-size array in storage, now reverts with Panic(0x32) when the index is not less than the length, as Solidity does. This holds for reads and writes, at every level of a nested array. A slice x[a:b] reverts unless a <= b <= x.length. The check compares as unsigned words whatever the index's type, so a negative value cast to an index is a huge index and reverts. Before, an index past the end read or wrote whatever memory followed: writing m[0][1] on [[1], [2]] overwrote m[1]'s length word. The locals soundness test that sliced msg.data[0:4] runs with no calldata, so it slices msg.data[0:0] now. * bugc: document bounds checks
1 parent be3ee87 commit 718b019

24 files changed

Lines changed: 855 additions & 26 deletions

‎packages/bugc/CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ support. Changes to the specification itself are tracked in the root
5050
- A cast from dynamic `bytes` to an integer or `address` is now a type
5151
error, as in Solidity: cast to `bytesN` first, as in
5252
`msg.data[4:36] as bytes32 as uint256` ([#334]).
53+
- An index out of bounds now reverts with Solidity's `Panic(0x32)`, as
54+
in Solidity: `a[i]` on an array or `bytes` in memory, or on a
55+
fixed-size array in storage, reverts unless `i` is less than the
56+
length, for reads and writes, at each level of a nested array. A slice
57+
`b[s:e]` reverts unless `s <= e <= b.length`, so `msg.data[0:4]`
58+
reverts on shorter calldata. The `REVERT` has a `revert` context with
59+
the panic code. Before, an index past the end read or wrote the memory
60+
that follows, such as the next inner array's length word. A dynamic
61+
array in storage is not checked ([#344]).
62+
- An array or bytes index and a slice bound must now be an unsigned
63+
integer, as in Solidity; cast a signed one, as in `a[i as uint256]`.
64+
Before, a signed index compiled ([#344]).
5365

5466
### Fixed
5567

@@ -187,6 +199,9 @@ support. Changes to the specification itself are tracked in the root
187199
skipped no length word and read a whole word, so it gave the length's
188200
bytes; a write failed with error EVM999, as an array element write did
189201
([#343]).
202+
- `.length` of an array literal, as in `[1, 2, 3].length`, is now the
203+
number of elements. Before, at every optimization level, it was 32
204+
([#344]).
190205

191206
## 0.1.0-preview.1 — 2026-10-03
192207

@@ -240,3 +255,4 @@ First publication.
240255
[#340]: https://github.com/ethdebug/format/pull/340
241256
[#341]: https://github.com/ethdebug/format/pull/341
242257
[#343]: https://github.com/ethdebug/format/pull/343
258+
[#344]: https://github.com/ethdebug/format/pull/344

‎packages/bugc/src/evmgen/analysis/liveness.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ function getUsedValues(inst: Ir.Instruction): Set<string> {
284284
case "length":
285285
addValue(inst.object);
286286
break;
287+
case "assert":
288+
addValue(inst.condition);
289+
break;
287290
case "allocate":
288291
addValue(inst.size);
289292
break;
@@ -338,6 +341,7 @@ function getDefinedValue(inst: Ir.Instruction): string | undefined {
338341
return inst.dest;
339342
// These instructions don't define values
340343
case "write": // NEW: unified write
344+
case "assert":
341345
return undefined;
342346
}
343347
}

‎packages/bugc/src/evmgen/analysis/memory.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ function simulateInstruction(stack: string[], inst: Ir.Instruction): string[] {
265265
case "unary":
266266
case "cast":
267267
case "length":
268+
case "assert":
268269
newStack.pop(); // One operand
269270
break;
270271
// NEW: unified read - pops slot/offset/length as needed
@@ -356,6 +357,9 @@ function getUsedValues(inst: Ir.Instruction): Set<string> {
356357
case "hash":
357358
addValue(inst.value);
358359
break;
360+
case "assert":
361+
addValue(inst.condition);
362+
break;
359363
// Call instruction removed - calls are now block terminators
360364
}
361365

@@ -412,6 +416,7 @@ function definedType(inst: Ir.Instruction): Ir.Type | undefined {
412416
case "compute_offset":
413417
return Ir.Type.Scalar.word;
414418
case "write":
419+
case "assert":
415420
return undefined;
416421
}
417422
}

0 commit comments

Comments
 (0)