Функция caller () Perl возвращает неправильный номер строки

у меня есть следующий скрипт, работающий на Perl 5.10.1:

#!/usr/bin/perl
use strict;
use warnings;

foreach( my $x =0 ; $x < 1; $x++) {   # Line 5
  print_line();                       # Line 6
} 

sub print_line {
  print "Function call from line: " . [caller(0)]->[2] . "n";
}

несмотря на вызов подпрограммы из строки 6, скрипт выводит номер строки начала оператора C-style for:

Function call from line: 5

что действительно странно, если я бросаю случайное утверждение в одну из пустых строк в цикле C-style for,caller возвращает правильный номер строки:

#!/usr/bin/perl
use strict;
use warnings;

foreach( my $x =0 ; $x < 1; $x++) {
  my $x = 3;
  print_line();  # Line 7
}

sub print_line {
  print "Function call from line: " . [caller(0)]->[2] . "n";
}

приведенный выше скрипт корректно выводит:

Function call from line: 7

это какая-то ошибка или что я могу сделать, чтобы получить caller точно сообщить номер строки?

4 ответов


$ perl -MO=Concise a.pl
j  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 6 a.pl:5) v:*,&,{,x*,x&,x$,$ ->3
5     <2> sassign vKS/2 ->6
3        <$> const[IV 0] s ->4
4        <0> padsv[$x:3,5] sRM*/LVINTRO ->5
6     <0> unstack v* ->7
i     <2> leaveloop vK/2 ->j
7        <{> enterloop(next->b last->i redo->8) v ->e
-        <1> null vK/1 ->i
h           <|> and(other->8) vK/1 ->i
g              <2> lt sK/2 ->h
e                 <0> padsv[$x:3,5] s ->f
f                 <$> const[IV 1] s ->g
-              <@> lineseq vK ->-
-                 <@> scope vK ->b                       <---
-                    <0> ex-nextstate v ->8              <---
a                    <1> entersub[t5] vKS/TARG,2 ->b
-                       <1> ex-list K ->a
8                          <0> pushmark s ->9
-                          <1> ex-rv2cv sK/2 ->-
9                             <#> gv[*print_line] s/EARLYCV ->a
c                 <1> preinc[t2] vK/1 ->d
b                    <0> padsv[$x:3,5] sRM ->c
d                 <0> unstack v ->e
a.pl syntax OK

происходит некоторая оптимизация. The scope был признан ненужным и оптимизирован. (Обратите внимание на "- " это означает, что он никогда не достигнет.)

но в то же время, что удалены nextstate op, который устанавливает номер строки для предупреждений и для caller.

так, это ошибка, которая является результатом неправильной оптимизации.


Я думаю, что потенциально это ошибка, потому что такое же поведение не возникает, если вы замените

foreach (my $x = 0 ; $x < 1 ; $x++) {

С

foreach my $x (0 .. 0) {

Я не понимаю ровно что происходит, но сравнивая optrees двух разных версий, я думаю, что a nextstate op неправильно оптимизирован. Моя версия

<;> nextstate(main 4 lineno.pl:11) v:*,&,x*,x&,x$,$ ->8

как левый брат entersub op, который вызывает print_line, в то время как ваш

<0> ex-nextstate v ->8

, который имеет был выведен из потока казни.

не помешало бы записать это как perlbug.


Я подозреваю, что это может быть вплоть до разделителей операторов (точек с запятой). Как вы, возможно, заметили - с кодом, который вы используете, номер строки, сообщенный caller совпадает с foreach петли.

Я думаю то, что происходит, потому что нет точек с запятой.

если вы должны были сделать многострочный суб-вызов,caller сообщит первую строку:

print "first call:", __LINE__, "\n";
print "Start of statement\n",
"a bit more on line ", __LINE__, "\n",
print_line(
    1,

    2,

    3,

    5,

);

вы получаете номер строки начала вызова, а не конец. Так Что Я ... подумайте, что это то, что вы получаете - оператор начинается, когда происходит разделитель оператора точки с запятой - который является foreach строка в первом примере.

так что в качестве обходного пути - я мог бы предложить использовать __LINE__. Хотя я бы также, возможно, предложил не беспокоиться об этом слишком много, потому что он все равно укажет вам на нужное место в коде.

вы получаете что-то подобное, если вы используете croak, по-видимому, то же причина.


Как было указано, это действительно ошибка в Perl, возвращающаяся по крайней мере к 5.10 или 11 годам, но на самом деле я думаю дольше.

было сообщено как ошибка Perl на Perl #133239 и хотя утверждается, что это не это трудно исправить, это не было. Может быть, это и не так!--7-->легко чтобы исправить, имеет последствия для производительности, так как добавление COP замедляет работу, и, возможно, потребуется некоторая административная работа для корректировки тестов.

и даже если бы эта ошибка была исправлена, она была бы исправлена только в версиях Perl 5.29 и позже или около того. Это не поможет тебе с 5.10.

Итак, вот еще один ТЭК, который не полагается на изменение ядра Perl и, следовательно, больше контролирует пользователей. Тем не менее, я скажу, что это немного экспериментально, и если люди не готовы тратить на это усилия по кодированию, это вряд ли вернется к 5.10. Сейчас ранние версии Perl у меня работает 5.14, 7 лет назад, как будто этого письма.

используя B:: DeparseTree вы можете написать другой, и я думаю, лучше caller () который может показать вам местоположение вызывающего абонента с более подробной информацией. Вот ваша программа, измененная для этого:

#!/usr/bin/perl
use strict;
use warnings;
use B::DeparseTree::Fragment;
use Devel::Callsite;

sub dt_caller
{
    my $level = $_ ? $_ : 0;
    # Pick up the right caller's OP address.
    my $addr =  callsite($level+1);
    # Hack alert 'main::main' should be replaced with the function name if not the top level. caller() is a little off-sync here.
    my $op_info = deparse_offset('main::main', $addr);
    # When Perl is in the middle of call, it has already advanced the PC,
    # so we need to go back to the preceding op. 
    $op_info = get_prev_addr_info($op_info);
    my $extract_texts = extract_node_info($op_info);
    print join("\n", @$extract_texts), "\n";
}

foreach( my $x =0 ; $x < 1; $x++) {
  print_line();
}

sub print_line {
    dt_caller();
}

при запуске он печатает:

$ perl bug-caller.pl
print_line()
------------

dt_caller() может и должен быть завернут в пакет, как карп, чтобы вы не видели все это уродство. Однако я оставлю это для кого-то еще. И я упомяну, что просто, чтобы получить эту работу, были некоторые исправления ошибок, которые я должен был сделать, поэтому это работает только начиная с версии 3.4.0 B:: DeparseTree.