Skip to content

Avoid 'result' name clash when a user parameter is named result - #431

Open
tautschnig wants to merge 1 commit into
strata-org:mainfrom
tautschnig:pr/avoid-result-param-name-clash
Open

Avoid 'result' name clash when a user parameter is named result#431
tautschnig wants to merge 1 commit into
strata-org:mainfrom
tautschnig:pr/avoid-result-param-name-clash

Conversation

@tautschnig

@tautschnig tautschnig commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

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:

  • 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 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.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0.

Copilot AI review requested due to automatic review settings June 9, 2026 07:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 result and 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>
@tautschnig
tautschnig force-pushed the pr/avoid-result-param-name-clash branch from 4885412 to 92c97bb Compare June 9, 2026 09:53

@keyboardDrummer keyboardDrummer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a fix in Laurel? To allow local variables to be named result ?

@tautschnig

Copy link
Copy Markdown
Collaborator Author

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 fabiomadge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 resultnot 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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

tautschnig added a commit to tautschnig/jverify that referenced this pull request Jun 10, 2026
…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>
@fabiomadge

Copy link
Copy Markdown
Collaborator

Shouldn't this be a fix in Laurel? To allow local variables to be named result?

Agree it's a Laurel scoping issue. When a procedure has a returnType but no explicit return param, Strata's ConcreteToAbstractTreeTranslator (~line 477) injects an output param literally named result into the procedure scope — so a user var/param named result collides ("Duplicate definition"). The robust fix is there: give the synthesized return binding a name users can't write. The front-end rename in this PR is a workaround.

tautschnig added a commit to tautschnig/jverify that referenced this pull request Jun 25, 2026
…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>
tautschnig added a commit to tautschnig/jverify that referenced this pull request Jun 30, 2026
…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>
tautschnig added a commit that referenced this pull request Jul 1, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants