Commit 83a6c8b26a for perl
commit 83a6c8b26a8823eb966652414864eb07a3d2c6d4
Author: Richard Leach <rich+perl@hyphen-dash-hyphen.info>
Date: Wed Oct 21 17:52:06 2026 +0000
Enable B::Xref to use ex-COPs for filename/line numbers
`B::Xref` operates by walking the live optree, which has
traditionally meant that is has no visibility of nulled COPs.
This sometimes caused inaccurate reporting of line numbers.
For example:
my $a = 1;
if ($a) { # STDOUT stated to appear on this line
print STDOUT "This line is misreported\n";
}
With this commit, `xref` peeks into branches to attempt to find
applicable ex-COPs. If it finds one, the file and line detail
contained within are used to update `$file` and `$line`.
diff --git a/ext/B/B/Xref.pm b/ext/B/B/Xref.pm
index 000790a269..91f96561ef 100644
--- a/ext/B/B/Xref.pm
+++ b/ext/B/B/Xref.pm
@@ -1,6 +1,6 @@
package B::Xref;
-our $VERSION = '1.07';
+our $VERSION = '1.08';
=head1 NAME
@@ -215,6 +215,20 @@ sub load_pad {
}
}
+# The pp_nextstate sub will update $file, $line, and $top for any live
+# nextstate OPs. This sub checks for and handles ex-nextstate OPs.
+sub cop_or_ex_cop {
+ my $op = shift;
+ return if (!$op || $op->isa('B::NULL') || $op->name eq 'enter');
+ return unless $op->name eq 'scope';
+ $op = $op->first;
+ if ($op->name eq 'null' && $op->isa('B::COP')) {
+ $file = $op->file;
+ $line = $op->line;
+ $top = UNKNOWN;
+ }
+}
+
sub xref {
my $start = shift;
my $op;
@@ -224,7 +238,13 @@ sub xref {
warn peekop($op), "\n" if $debug_op;
my $opname = $op->name;
if ($opname =~ /^(or|and|mapwhile|grepwhile|range|cond_expr)$/) {
+ my $op_true = $op->first->sibling;
+ cop_or_ex_cop($op_true);
xref($op->other);
+ unless ($op_true->isa('B::NULL')) {
+ my $op_false = $op_true->sibling;
+ cop_or_ex_cop($op_false);
+ }
} elsif ($opname eq "match" || $opname eq "subst") {
xref($op->pmreplstart);
} elsif ($opname eq "substcont") {
diff --git a/ext/B/t/xref.t b/ext/B/t/xref.t
index 3e201c12e1..07e7cbdec0 100644
--- a/ext/B/t/xref.t
+++ b/ext/B/t/xref.t
@@ -12,7 +12,7 @@ BEGIN {
use strict;
use warnings;
no warnings 'once';
-use Test::More tests => 14;
+use Test::More tests => 16;
# line 50
use_ok( 'B::Xref' );
@@ -102,6 +102,17 @@ is(
'1001', 'glob FOO used in subroutine foo'
);
+# GH #7974 (Xref ignores nulled COPs)
+is(
+ $xreftable{$thisfile}{'Testing::Xref::baz'}{'main'}{'*STDOUT'}, '1005',
+ 'STDOUT used at line 1005 in subroutine baz'
+);
+
+is(
+ $xreftable{$thisfile}{'Testing::Xref::baz'}{'main'}{'*STDERR'}, '1007',
+ 'STDERR used at line 1007 in subroutine baz'
+);
+
END {
1 while unlink $file;
}
@@ -113,4 +124,11 @@ END {
package Testing::Xref;
sub foo { print FOO %::xreftable; }
sub bar { print FOO foo; }
+sub baz {
+ if ($_[0]) {
+ print STDOUT "This line shouldn't be misreported\n";
+ } else {
+ print STDERR "Nor this one\n";
+ }
+}
diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index cee4f0981a..a55eee3d5c 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -373,6 +373,10 @@ XXX
L<perlfunc/caller> is better able to identify the correct call site when
multiple branches of an C<if>/C<elsif>/C<else> chain call the same function.
+=item *
+
+L<B::Xref> is able to use line numbers stored within nulled COPs. [GH #7947]
+
=back
=head1 Known Problems