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