fix(template): only auto-call callables that take zero arguments - #56
Merged
Conversation
The nightly `template_parse_render` fuzz target aborted on `<%= patch%>`:
panicked at src/interpreter/builtins/request_helpers.rs:73:
index out of bounds: the len is 0 but the index is 0
Omitting parentheses is documented as a zero-argument form (`<%= now.to_iso
%>`), but the renderer auto-called *every* callable it evaluated, passing an
empty argument list. So naming a helper that needs arguments ran its body with
nothing in it. The request helpers are registered variadic — `arity: None`
means "the runtime does not check the count", not "takes no arguments" — so
`patch` read `args[0]` on an empty slice. A panic aborts without unwinding, out
of reach of the per-request `catch_unwind`.
Two fixes, at the choke point and at the destination:
- `auto_call_if_callable` now invokes only callables that genuinely accept zero
arguments, using the same predicate as a bare name in code
(`can_auto_invoke_with_no_args`, which `try_auto_invoke` also calls now so
the two surfaces cannot drift). A callable that needs arguments renders as
its value instead of being called. This closes the whole class from the
template surface, not just `patch`.
- The request helpers bounds-check their arguments (`arg_string`), so calling
one directly with too few raises "… is missing a required argument" rather
than panicking.
The minimized reproducer is a tracked fuzz seed. Regression tests cover both
directions: a helper needing arguments does not panic, and a genuine zero-arg
method is still called without parentheses.
Note: ~760 other builtins still index `args[N]` unchecked, reachable by calling
one with too few arguments from Soli code. Not touched here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t 241 Two things that made the fuzz job red for reasons unrelated to the code under test. LeakSanitizer. cargo-fuzz builds with AddressSanitizer, which enables LSan too, and nothing set ASAN_OPTIONS. These targets exist to catch panics, stack overflows and UB in the surfaces that take untrusted input — not to audit allocation lifetimes. The interpreter is `Rc<RefCell<…>>`-based, so a closure capturing its defining environment forms a reference cycle that LSan correctly calls an unreachable "direct leak"; it is bounded and harmless in a process that exits. On PR #55 that failed the job *after* a completely clean fuzz — `Done 72817 runs in 21 second(s)`, then 446 bytes in 36 allocations — so every PR touching a fuzzed path inherited a red X that had nothing to do with its diff. Leak detection is now off for the fuzz step. The backtick guard in `template_parse_render` predates this and was partly justified by the same LSan behaviour; its other reason — rendering a backtick tag runs `sh -c <fuzzer bytes>` on the host — stands on its own, so the guard stays and the comment now says why. Unwrap ratchet. The regression tests added in the previous commit pushed `src/template` from 241 to 244 `.unwrap()`/`.expect()` calls and failed `scripts/lint_unwraps.sh`. The ratchet is meant to move in one direction, so the tests were rewritten to not need them (`let Ok(…) else`, and comparing against `Ok("ADA".to_string())`) rather than raising the baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The nightly
template_parse_renderfuzz target aborted on<%= patch%>:Omitting parentheses is documented as a zero-argument form (
<%= now.to_iso %>), but the renderer auto-called every callable it evaluated, passing an empty argument list. So naming a helper that needs arguments ran its body with nothing in it. The request helpers are registered variadic —arity: Nonemeans "the runtime does not check the count", not "takes no arguments" — sopatchreadargs[0]on an empty slice. A panic aborts without unwinding, out of reach of the per-requestcatch_unwind.Two fixes, at the choke point and at the destination:
auto_call_if_callablenow invokes only callables that genuinely accept zero arguments, using the same predicate as a bare name in code (can_auto_invoke_with_no_args, whichtry_auto_invokealso calls now so the two surfaces cannot drift). A callable that needs arguments renders as its value instead of being called. This closes the whole class from the template surface, not justpatch.arg_string), so calling one directly with too few raises "… is missing a required argument" rather than panicking.The minimized reproducer is a tracked fuzz seed. Regression tests cover both directions: a helper needing arguments does not panic, and a genuine zero-arg method is still called without parentheses.
Note: ~760 other builtins still index
args[N]unchecked, reachable by calling one with too few arguments from Soli code. Not touched here.