Merge pull request 'fix: ci, anchorShare, discoveryDepth casts are unguarded for the same literal problem (#905)' (#959) from fix/issue-905 into master

This commit is contained in:
johba 2026-03-18 13:57:55 +01:00
commit f4201ee7ef
2 changed files with 165 additions and 19 deletions

View file

@ -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(<negative>)` 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;',

View file

@ -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 ""