Skip to content

Fix caller() returning incorrect frames in interpreter mode#307

Closed
fglock wants to merge 1 commit into
masterfrom
fix/caller-interpreter-frames
Closed

Fix caller() returning incorrect frames in interpreter mode#307
fglock wants to merge 1 commit into
masterfrom
fix/caller-interpreter-frames

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 13, 2026

Summary

Fix the interpreter's caller() function which was returning incorrect stack frames in two scenarios:

  1. Regular subroutine calls (main calls outer() calls inner()): caller() returned no frames because all BytecodeInterpreter.execute frames were consecutive and were treated as one call.

  2. use/require with import(): The stack order was wrong - the CallerStack entry was being added after interpreter frames instead of at the correct position.

Root Cause

The previous fix removed the use of InterpreterState.currentPackage.get() for the innermost frame, which broke Exporter's caller() calls. This caused @EXPORT_OK to be populated in the wrong package, making all ExifTool tests fail with "Symbol not allowed for export" errors.

Fix

  • Use InterpretedCode.apply as the boundary marker for Perl call levels
  • Track addedFrameForCurrentLevel flag to add only one interpreter frame per call level
  • Critical: For the innermost frame (index 0), use runtime currentPackage to reflect package Foo; declarations that executed at runtime

Test Plan

  • ./gradlew test passes
  • ExifTool tests pass (ExifTool.t: 35/35, Writer.t: 61/61)
  • Regular sub calls: caller(0)=main, caller(1)=main, depth=1
  • Verified Exporter works correctly with use Image::ExifTool qw(ImageInfo)

Generated with Devin

The interpreter's caller() function was returning incorrect stack frames
in two scenarios:

1. For regular subroutine calls (main calls outer() calls inner()),
   caller() returned no frames because all BytecodeInterpreter.execute
   frames were consecutive and the previous fix treated them as one call.

2. For use/require with import(), the stack order was wrong - the
   CallerStack entry (from parseUseDeclaration) was being added after
   interpreter frames instead of at the correct position.

Fix: Use InterpretedCode.apply as the boundary marker. Each apply() call
marks the END of a Perl subroutine execution. Multiple execute() frames
within an apply() share one interpreter frame.

Key changes to ExceptionFormatter.formatException():
- Track addedFrameForCurrentLevel flag
- Only add one interpreter frame per call level
- When we see InterpretedCode.apply, increment to next frame index
- For innermost frame (index 0), use runtime currentPackage to reflect
  package declarations that executed at runtime

Now both test cases work correctly:
- Regular sub calls: caller(0)=main, caller(1)=main, depth=1
- use/import: caller(0)=NestedInstance, caller(1)=main

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

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock
Copy link
Copy Markdown
Owner Author

fglock commented Mar 13, 2026

Merged into PR #306 which contains both fixes

@fglock fglock closed this Mar 13, 2026
@fglock fglock deleted the fix/caller-interpreter-frames branch March 13, 2026 13:03
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