Skip to content

B3: EmptyBlock keeps finally statements and switch selector side effects, and leaves synchronized alone - #1117

Merged
timtebeek merged 3 commits into
mainfrom
tim/b3-issue-fix
Oct 25, 2026
Merged

timtebeek merged 3 commits into
mainfrom
tim/b3-issue-fix

Conversation

@timtebeek

@timtebeek timtebeek commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Fixes B3 of #1116: EmptyBlock (in CommonStaticAnalysis and CodeCleanup) dropped code that runs, not just empty blocks.

  • try { } finally { r.run(); } was deleted whole. It is now replaced by the finally statements, or by a plain { … } block when those declare variables, classes or pattern variables that could clash with later declarations, or when the block ends in comments that would otherwise be lost.
  • switch (sel()) {} was deleted along with its selector call. It now keeps the side effects, the same way the empty-if path already did.
  • Empty synchronized blocks are no longer removed at all. Acquiring the monitor waits for other holders and establishes a happens-before edge, so an empty block can be a deliberate barrier, and no rewrite preserves that.
  • The empty-if path hoisted every side effect out of the condition, so if (m.find() && m.group(1) != null) {} became m.find(); m.group(1);, which throws when nothing matches. A condition is now left alone when getSideEffects() would make a call unconditional (right of &&/||) or would miss one (ternaries, array indexes, …). Side effects on the left of && are still extracted.
  • Inverting if (d > 0) {} else { … } to d <= 0 changes the result for NaN, so relational comparisons with a float/double (or untyped) operand are no longer inverted. ==/!= inversion is unaffected.

Verified with ./gradlew test --tests EmptyBlockTest (new probe tests for each case fail on main and pass here) and a full ./gradlew test. The only failures were the python() tests, which can't install the pip dev build on a local Python 3.9, and a few Kotlin tests of recipes that don't use EmptyBlockVisitor, which flaked and passed on rerun.

…ized` and `switch`

Replace an empty `try` with its `finally` statements, extract side effects
from the `synchronized` lock and the `switch` selector as is done for `if`
conditions, leave conditions alone whose side effects would become
unconditional (right of `&&`/`||`, ternaries), and do not invert
floating-point comparisons, which changes the result for NaN.

Fixes B3 of #1116
Acquiring the monitor waits for other holders and establishes a
happens-before edge, so removing even an empty block changes behaviour.
@timtebeek timtebeek changed the title B3: EmptyBlock keeps finally statements and side effects of synchronized and switch B3: EmptyBlock keeps finally statements and switch selector side effects, and leaves synchronized alone Oct 25, 2026
…visit

Replacements now come from the already-visited children, so nested fixes
inside `finally` survive in one cycle without an extra after-visit per
statement. Flattened `finally` statements are only re-indented rather than
auto-formatted, and `finally` stays a block when it declares pattern
variables or ends in comments.
@timtebeek
timtebeek merged commit 30f7995 into main Oct 25, 2026
3 checks passed
@timtebeek
timtebeek deleted the tim/b3-issue-fix branch October 25, 2026 17:20
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant