From 73c91e70b5bbc2edafb4b114a8a14e32c7502494 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 18 Mar 2026 12:41:01 +0000 Subject: [PATCH] fix: ci, anchorShare, discoveryDepth casts are unguarded for the same literal problem (#905) Co-Authored-By: Claude Sonnet 4.6 --- tools/push3-transpiler/src/index.ts | 43 ++++++ .../test_transpiler_clamping.sh | 141 +++++++++++++++--- 2 files changed, 165 insertions(+), 19 deletions(-) diff --git a/tools/push3-transpiler/src/index.ts b/tools/push3-transpiler/src/index.ts index 2cce36e..2a0bc01 100644 --- a/tools/push3-transpiler/src/index.ts +++ b/tools/push3-transpiler/src/index.ts @@ -13,6 +13,44 @@ import * as path from 'path'; import { parse } from './parser.js'; import { transpile } from './transpiler.js'; +const INT_LITERAL_RE = /^-?\d+$/; + +/** + * Validates that a transpiler output variable is a non-negative integer literal + * (or a Solidity expression, which is unchecked). Throws a clear error rather + * than silently emitting `uint256()` which Solidity would reject with a + * cryptic compile-time message. + */ +function assertNonNegativeLiteral(value: string, name: string): void { + if (INT_LITERAL_RE.test(value)) { + const n = BigInt(value); + if (n < 0n) { + throw new Error( + `Transpiler error: ${name} is a negative literal (${value}). ` + + `uint256(${value}) is rejected by the Solidity compiler. ` + + `Fix the Push3 program to produce a non-negative value for ${name}.`, + ); + } + } +} + +/** + * Validates that a transpiler output variable for a uint24 field is within the + * valid range [0, 16777215] when it is a literal constant. Large literals that + * are computed at runtime are still wrapped with % (2**24) in the generated code. + */ +function assertUint24Range(value: string, name: string): void { + if (INT_LITERAL_RE.test(value)) { + const n = BigInt(value); + if (n < 0n || n > 16777215n) { + throw new Error( + `Transpiler error: ${name} literal (${value}) is outside uint24 range [0, 16777215]. ` + + `Fix the Push3 program to produce a value in [0, 16777215] for ${name}.`, + ); + } + } +} + function main(): void { const args = process.argv.slice(2); if (args.length < 2) { @@ -31,6 +69,11 @@ function main(): void { const { functionBody, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar } = transpile(ast); + assertNonNegativeLiteral(ciVar, 'ci'); + assertNonNegativeLiteral(anchorShareVar, 'anchorShare'); + assertUint24Range(anchorWidthVar, 'anchorWidth'); + assertNonNegativeLiteral(discoveryDepthVar, 'discoveryDepth'); + const solidityLines = [ '// SPDX-License-Identifier: GPL-3.0-or-later', 'pragma solidity ^0.8.19;', diff --git a/tools/push3-transpiler/test_transpiler_clamping.sh b/tools/push3-transpiler/test_transpiler_clamping.sh index 2010fd4..93da1b7 100755 --- a/tools/push3-transpiler/test_transpiler_clamping.sh +++ b/tools/push3-transpiler/test_transpiler_clamping.sh @@ -1,7 +1,10 @@ #!/usr/bin/env bash -# test_transpiler_clamping.sh — Tests that the transpiler clamps anchorWidth to uint24 bounds. -# Verifies that a Push3 program producing 1e18 for anchorWidth generates -# `uint24(... % (2**24))` rather than a raw overflowing literal. +# test_transpiler_clamping.sh — Tests transpiler validation of output register values. +# Verifies that: +# - negative literal outputs for ci, anchorShare, anchorWidth, discoveryDepth produce +# a transpiler-level error with a clear message (not a silent Solidity compile failure). +# - an anchorWidth literal outside [0, 16777215] produces a transpiler-level error. +# - a valid anchorWidth literal within range is accepted and still uses % (2**24). # Exit: 0 if all tests pass, 1 if any fail. set -euo pipefail @@ -42,45 +45,145 @@ assert_not_contains() { fi } +assert_exits_nonzero() { + local name="$1" + local stderr_output="$2" + local expected_fragment="$3" + # stderr_output is non-empty only if the command failed (captured separately) + if echo "$stderr_output" | grep -qF "$expected_fragment"; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected stderr to contain: $expected_fragment" + echo " actual stderr:" + echo "$stderr_output" | sed 's/^/ /' + FAIL=$((FAIL + 1)) + fi +} + # ── Ensure node_modules exist ──────────────────────────────────────────────── if [ ! -d "$SCRIPT_DIR/node_modules" ]; then (cd "$SCRIPT_DIR" && npm install --silent) fi -# ── Test 5: anchorWidth = 1e18 is clamped to uint24 range ─────────────────── -echo "Test 5: anchorWidth overflow clamped via % (2**24)" - TMPDIR_T=$(mktemp -d) trap 'rm -rf "$TMPDIR_T"' EXIT -INPUT_PUSH3="$TMPDIR_T/overflow.push3" -OUTPUT_SOL="$TMPDIR_T/overflow.sol" +run_transpiler() { + local push3_content="$1" + local input_file="$TMPDIR_T/test_input.push3" + local output_file="$TMPDIR_T/test_output.sol" + printf '%s\n' "$push3_content" > "$input_file" + rm -f "$output_file" + local stderr_out + # Capture stderr; allow non-zero exit without aborting + if stderr_out=$(cd "$SCRIPT_DIR" && node --loader ts-node/esm src/index.ts "$input_file" "$output_file" 2>&1 >/dev/null); then + echo "OK" + else + printf '%s' "$stderr_out" + fi +} + +# ── Test 5: anchorWidth literal > uint24 max → transpiler error ────────────── +echo "Test 5: anchorWidth = 1e18 (literal) → transpiler error, not silent Solidity failure" # Push3 program: push 4 values (discoveryDepth, anchorWidth=1e18, anchorShare, ci). # The transpiler pops top-4 from DYADIC stack; these literals sit on top of the # primed input slots, so they become the 4 output vars. -cat > "$INPUT_PUSH3" <<'PUSH3EOF' -( +STDERR=$(run_transpiler "( 300000000000000000 1000000000000000000 300000000000000000 0 +)") + +assert_contains \ + "anchorWidth 1e18: transpiler emits range error message" \ + "$STDERR" \ + "anchorWidth literal (1000000000000000000) is outside uint24 range [0, 16777215]" + +# ── Test 6: valid anchorWidth literal (in range) → accepted, wraps with % (2**24) ─ +echo "Test 6: anchorWidth = 100 (valid literal) → accepted, output uses % (2**24)" + +INPUT_6="$TMPDIR_T/valid_aw.push3" +OUTPUT_6="$TMPDIR_T/valid_aw.sol" +cat > "$INPUT_6" <<'PUSH3EOF' +( + 300000000000000000 + 100 + 300000000000000000 + 0 ) PUSH3EOF -(cd "$SCRIPT_DIR" && node --loader ts-node/esm src/index.ts "$INPUT_PUSH3" "$OUTPUT_SOL" 2>/dev/null) +(cd "$SCRIPT_DIR" && node --loader ts-node/esm src/index.ts "$INPUT_6" "$OUTPUT_6" 2>/dev/null) -SOL_CONTENT=$(cat "$OUTPUT_SOL") +SOL_6=$(cat "$OUTPUT_6") +assert_contains \ + "anchorWidth 100: output uses % (2**24)" \ + "$SOL_6" \ + "uint24(100 % (2**24))" -assert_not_contains \ - "anchorWidth: no raw uint24(1000000000000000000) literal" \ - "$SOL_CONTENT" \ - "uint24(1000000000000000000)" +# ── Test 7: negative ci literal → transpiler error ─────────────────────────── +echo "Test 7: ci = -1 (negative literal) → transpiler error" + +STDERR=$(run_transpiler "( + 300000000000000000 + 100 + 300000000000000000 + -1 +)") assert_contains \ - "anchorWidth: output uses % (2**24) clamping" \ - "$SOL_CONTENT" \ - "uint24(1000000000000000000 % (2**24))" + "negative ci: transpiler emits error message" \ + "$STDERR" \ + "ci is a negative literal (-1)" + +# ── Test 8: negative anchorShare literal → transpiler error ────────────────── +echo "Test 8: anchorShare = -5 (negative literal) → transpiler error" + +STDERR=$(run_transpiler "( + 300000000000000000 + 100 + -5 + 0 +)") + +assert_contains \ + "negative anchorShare: transpiler emits error message" \ + "$STDERR" \ + "anchorShare is a negative literal (-5)" + +# ── Test 9: negative discoveryDepth literal → transpiler error ─────────────── +echo "Test 9: discoveryDepth = -99 (negative literal) → transpiler error" + +STDERR=$(run_transpiler "( + -99 + 100 + 300000000000000000 + 0 +)") + +assert_contains \ + "negative discoveryDepth: transpiler emits error message" \ + "$STDERR" \ + "discoveryDepth is a negative literal (-99)" + +# ── Test 10: negative anchorWidth literal → transpiler error ───────────────── +echo "Test 10: anchorWidth = -1 (negative literal) → transpiler error" + +STDERR=$(run_transpiler "( + 300000000000000000 + -1 + 300000000000000000 + 0 +)") + +assert_contains \ + "negative anchorWidth: transpiler emits range error message" \ + "$STDERR" \ + "anchorWidth literal (-1) is outside uint24 range [0, 16777215]" # ── Summary ────────────────────────────────────────────────────────────────── echo ""