Skip to content

fix: correct caller() line numbers for method and function calls#657

Closed
fglock wants to merge 4 commits intomasterfrom
fix/caller-line-number
Closed

fix: correct caller() line numbers for method and function calls#657
fglock wants to merge 4 commits intomasterfrom
fix/caller-line-number

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented May 1, 2026

Summary

  • Root cause: caller() inside a called subroutine was reporting the wrong source line — the line of the closing ) of the argument list rather than the line where the call begins.
  • Affected paths:
    • handleArrowOperator in Dereference.java — method calls via $obj->method(args) / callCached
    • handleApplyOperator in EmitSubroutine.java — direct function calls via foo(args) / apply
  • Fix: emit ByteCodeSourceMapper.setDebugInfoLineNumber(ctx, node.left.getIndex()) immediately before each INVOKESTATIC instruction. node.left.getIndex() is the token index of the receiver object / function name, which corresponds to the line where the call begins — matching what __LINE__ captures and what Perl's caller() is supposed to return.

The parse-time saveSourceLocation in StatementResolver already records this token index with the correct line number, so the existing debug-info infrastructure handles everything correctly once we anchor the call instruction to the right token.

Test plan

  • make (all unit tests pass)
  • ./jcpan -t Test::Unit::Lite — all 39/39 subtests pass (was 37/39; test_assert_deep_equals and test_fail_assert_not_equals had been failing because check_failures() verifies the exact caller line number from assertion exceptions)
  • Updated dev/cpan-reports/cpan-compatibility.md: Test::Unit::Lite PARTIAL → PASS

Generated with Devin

fglock and others added 4 commits May 1, 2026 23:23
1. FileSpec.splitdir scalar context: `scalar File::Spec->splitdir($dir)` was
   returning the last component instead of the component count. Fixed by adding
   a scalar-context branch that returns `dirs.length` as a RuntimeScalar.
   Affected Test::Unit::Lite::AllTests::suite() path-depth calculation.

2. Symbol::qualify_to_ref returned GLOB instead of GLOBREFERENCE: the Java
   implementation was using `new RuntimeScalar().set(new RuntimeGlob(...))` which
   creates a GLOB-typed scalar, not a reference-to-glob. Changed to
   `new RuntimeGlob(object.toString()).createReference()` which produces
   GLOBREFERENCE (type=GLOBREFERENCE), matching how gensym() works.
   Without this fix, `keys %{ *{Symbol::qualify_to_ref("Pkg::")} }` returned
   empty, so list_tests() found 0 test methods.

3. IOOperator.select auto-vivification: when `select $var` is called with an
   undefined scalar, Perl auto-vivifies it into an anonymous GLOB reference
   (enabling the idiom `select select my $fh_null; tie *$fh_null, 'Class'`).
   Added the same pattern used by open/socket/pipe: create a new RuntimeGlob,
   wrap a fresh RuntimeIO in it, create a GLOBREFERENCE, and set() it back on
   the lvalue via runtimeList.getFirst().

Also added unit tests for cases 1 and 2 in directory.t and typeglob.t.

After these fixes: Test::Unit::Lite goes from 0/39 passing to 37/39 passing.
Remaining 2 failures are a pre-existing caller() line-number off-by-one for
multi-line function calls (not introduced by this PR).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
In handleArrowOperator (Dereference.java) and handleApplyOperator
(EmitSubroutine.java), the JVM line number at the callCached/apply
INVOKESTATIC was set to the token index of the closing ')' of the
argument list, not to the token index of the call site itself.

This caused caller() inside a called subroutine to report the wrong
source line — specifically the last line of the argument list rather
than the line where the call begins.

Fix: emit setDebugInfoLineNumber with node.left.getIndex() (the token
index of the receiver object / function name) immediately before the
INVOKESTATIC instruction in both call paths. This anchors the JVM
bytecode to the Perl line where the call starts, so caller() returns
the correct line number.

Verified by ./jcpan -t Test::Unit::Lite: all 39 subtests now pass
(was 37/39; test_assert_deep_equals and test_fail_assert_not_equals
had been failing because check_failures() verified caller() line
numbers from assertion failures).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The caller() line number fix resolved the 2 remaining failures.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Same root cause as the JVM fix (af48192): CALL_SUB and CALL_METHOD
instructions in the interpreter's bytecode had their pcToTokenIndex
entries pointing to the token AFTER the closing ')' (inherited from the
statement-level emit), instead of the token at the call-site.

Fix all three interpreter call-emit sites:
- CompileBinaryOperator.java, coderef->() path: use emitWithToken with
  node.left.getIndex() (the coderef token index).
- CompileBinaryOperator.java, ->method() path: same, with the invocant's
  token index.
- CompileBinaryOperatorHelper.java, "()" switch case: change from
  emit() to emitWithToken(), driven by the call-site tokenIndex now
  passed from CompileBinaryOperator (node.left.getIndex()).

After fix: --interpreter and JVM both report the same line as system
Perl for caller() inside a called subroutine with multi-line args.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock
Copy link
Copy Markdown
Owner Author

fglock commented May 2, 2026

Superseded by PR #656 which now includes all commits from this branch.

@fglock fglock closed this May 2, 2026
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