From 0cee8b218313d22dccb66bc7d998a8c980933ab6 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 31 Aug 2026 14:02:35 +0200 Subject: [PATCH] Report a prep-time arg that its check should have caught calcPrep discarded parseCalc's error, so a divergence from checkCalc would have produced a nil AST and then a nil dereference per render, with no message. atoi did the same quietly, returning zero for a length, range or decimal count. Both now panic naming the argument, so the invariant they rest on fails where it breaks. Co-Authored-By: Claude Opus 5 --- builtins.go | 12 ++++++++++-- calc.go | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/builtins.go b/builtins.go index 88627b2..943fcd3 100644 --- a/builtins.go +++ b/builtins.go @@ -93,8 +93,16 @@ func generate(f func(rng) string) func([]string) callFn { const hexDigits = "0123456789abcdef" -// atoi parses an arg already validated by a builtin's check, so it cannot fail. -func atoi(s string) int { n, _ := strconv.Atoi(s); return n } +// atoi parses an arg a builtin's check already validated. It panics rather than +// returning zero, so a check that stops covering its own args is a stack trace and +// not a silently wrong length, range or decimal count. +func atoi(s string) int { + n, err := strconv.Atoi(s) + if err != nil { + panic(fmt.Sprintf("fakes: builtin arg %q reached prep unvalidated: %v", s, err)) + } + return n +} func randBytes(r rng, n int) []byte { b := make([]byte, n) diff --git a/calc.go b/calc.go index 6f63b51..bf0c28e 100644 --- a/calc.go +++ b/calc.go @@ -96,7 +96,10 @@ func checkCalc(fields map[string]node, args []string) error { // operand name at the position expand will read it into. checkCalc proved both args // valid, so no step here can fail; dp -1 prints the minimal form. func calcPrep(args []string) callFn { - expr, _ := parseCalc(args[0]) + expr, err := parseCalc(args[0]) + if err != nil { // a nil AST would be a nil dereference per render, with no message + panic(fmt.Sprintf("fakes: calc(%q) reached prep unparsed: %v", args[0], err)) + } at := make(map[string]int) for i, name := range calcVars(expr) { at[name] = i