Skip to content

Fix block refactoring breaking return and foreach loop variable aliasing#255

Merged
fglock merged 3 commits intomasterfrom
fix-exiftool-tests
Mar 1, 2026
Merged

Fix block refactoring breaking return and foreach loop variable aliasing#255
fglock merged 3 commits intomasterfrom
fix-exiftool-tests

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Mar 1, 2026

Summary

Fixes discovered while investigating ExifTool test failures:

  • Block refactoring breaking return: ControlFlowDetectorVisitor now marks return as unsafe control flow, preventing large blocks containing return from being refactored into sub { ... }->(@_). This caused infinite loops in ExifTool's SetNewValue (Writer.pl) where return inside a while loop only returned from the anonymous wrapper sub, not the enclosing function.

  • Foreach loop variable aliasing after exit: After a foreach loop exits, the loop variable register is reset to a fresh RuntimeScalar() in both the bytecode interpreter (BytecodeInterpreter.java) and JVM emitter (EmitForeach.java). Previously the register still aliased the last array element, so subsequent writes to the variable would corrupt the source array's last element.

Test plan

  • All 154 mvn tests pass
  • ExifTool XMP.t test 18 (previously infinite loop) now passes
  • No regressions in existing test suite

Generated with Devin

fglock and others added 3 commits March 1, 2026 18:03
…sing

Two bugs fixed:

1. ControlFlowDetectorVisitor: Mark `return` as unsafe control flow.
   When large blocks are refactored into `sub { ... }->(@_)` to avoid
   JVM "Method too large" errors, a `return` inside the block would
   only return from the anonymous sub, not from the enclosing function.
   This caused infinite loops in ExifTool's SetNewValue (Writer.pl)
   where `return` inside a `while` loop was silently swallowed.

2. BytecodeInterpreter & EmitForeach: Reset loop variable register
   after foreach loop exits. Previously the register still aliased
   the last array element, so subsequent writes to the variable
   would corrupt the source array's last element.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
Replace BytecodeCompiler ad-hoc variableScopes Stack + allDeclaredVariables
HashMap with ScopedSymbolTable for proper scope-aware variable tracking.

The root cause: allDeclaredVariables was a flat map that overwrote entries when
the same variable name was declared in different scopes. eval $val would
capture the register from the LAST declaration, not the one visible at the
eval site. This broke ~30 ExifTool write tests.

Key changes:
- SymbolTable/ScopedSymbolTable: add addVariableWithIndex(), getVisibleVariableRegistry()
- BytecodeCompiler: use symbolTable for variable tracking, add per-eval-site
  registry snapshots via evalSiteRegistries list
- CompileOperator: snapshot visible variables at each EVAL_STRING emission
- InterpretedCode: carry evalSiteRegistries for runtime lookup
- SlowOpcodeHandler/EvalStringHandler: use site-specific registry with
  fallback to global registry for backward compatibility

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock merged commit ac7ec3f into master Mar 1, 2026
2 checks passed
@fglock fglock deleted the fix-exiftool-tests branch March 1, 2026 18:07
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.

1 participant