From d3c6d2adeef826d33bf9e65bbfaa8a8d655ba738 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Thu, 8 Oct 2026 10:26:41 +0200 Subject: [PATCH] fix(runtime): preserve Carp call sites through eval wrappers Use the call-site package captured when generated methods are entered while Carp walks caller frames. This lets Carp recognize the user package boundary and report the location that invoked Moo-generated methods, without changing ordinary caller frame numbering. Add a Moo eval-wrapper regression test and a changelog entry. Generated with [OpenAI Codex](https://openai.com/codex) Co-Authored-By: OpenAI Codex --- docs/about/changelog.md | 1 + .../runtime/runtimetypes/RuntimeCode.java | 4 +- .../unit/carp_moo_eval_wrapper_location.t | 45 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/unit/carp_moo_eval_wrapper_location.t diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 0277c4f019..3265b85ded 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -28,6 +28,7 @@ priorities and future plans. nested signature calls, and keep Test2 negation accessors visible to the interpreter. - Preserve call-site package information in generated subroutine frames so Moo destructor croaks report the correct source location. +- Preserve generated-method call-site packages while Carp selects croak locations through eval wrappers. - Pass the Image::ExifTool 13.55 test suite by preserving escaped transliteration ranges and numeric string flags, and allowing valid nested conditional `goto` targets. diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index 0e9300103b..733df2e5e1 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -5825,9 +5825,7 @@ public static RuntimeList callerWithSub(RuntimeList args, int ctx, RuntimeScalar boolean carpTrustedFrame = isCarpStackActive() && reportedCode != null && isCarpTrustedCallerPackage(reportedCode.packageName); if ((reportedCode != null && "DESTROY".equals(reportedCode.subName)) - || (isCarpStackActive() - && isDestroyActiveOnStack() - && !carpTrustedFrame)) { + || (isCarpStackActive() && !carpTrustedFrame)) { String callSitePackage = getCallerPackageAtCallerFrame(trackedActiveCodeFrame); if (callSitePackage != null && !callSitePackage.isEmpty()) { pkg = callSitePackage; diff --git a/src/test/resources/unit/carp_moo_eval_wrapper_location.t b/src/test/resources/unit/carp_moo_eval_wrapper_location.t new file mode 100644 index 0000000000..804a6c1390 --- /dev/null +++ b/src/test/resources/unit/carp_moo_eval_wrapper_location.t @@ -0,0 +1,45 @@ +use strict; +use warnings; +use Test::More; +use Carp (); + +if (!eval { require Moo; 1 }) { + plan skip_all => 'Moo is required for the eval-wrapper croak location regression'; +} + +my $package = 'CarpMooEvalWrapperLocation'; +my $code = <<'MOO_CODE'; +use Moo; +has attr => (is => 'rwp', lazy => 1, default => 1); +my $obj = $PACKAGE->new; +package Elsewhere; +$obj->attr(5); +MOO_CODE +$code =~ s/\$PACKAGE/$package/g; + +my $source = $code; +$source =~ s/\n\z//; +my $expected_line = 1 + ($source =~ tr/\n//); +my $test_sub = eval( + "sub {\n" + . "package $package;\n" + . "#line 1 CarpMooEvalWrapperLocationTest\n" + . $code + . "\n}\n" +); +BAIL_OUT("could not compile Moo croak location reproducer: $@") unless $test_sub; + +my $error; +{ + local $@; + eval { $test_sub->(); 1 }; + $error = $@; +} + +like( + $error, + qr/at CarpMooEvalWrapperLocationTest line \Q$expected_line\E\.?\n?\z/s, + 'Carp::croak reports the generated method call site through the eval wrapper', +) or diag("error: $error"); + +done_testing();