NAME
Devel::DumpTrace::PPI - PPI-based version of Devel::DumpTrace
VERSION
0.27
SYNOPSIS
perl -d:DumpTrace::PPI demo.pl
>>>>> demo.pl:3:[__top__]: $a:1 = 1;
>>>>> demo.pl:4:[__top__]: $b:3 = 3;
>>>>> demo.pl:5:[__top__]: $c:23 = 2 * $a:1 + 7 * $b:3;
>>>>> demo.pl:6:[__top__]: @d:(1,3,26) = ($a:1, $b:3, $c:23 + $b:3);
perl -d:DumpTrace::PPI=verbose demo.pl
>> demo.pl:3:[__top__]:
>>> $a = 1;
>>>>> 1 = 1;
------------------------------------------
>> demo.pl:4:[__top__]:
>>> $b = 3;
>>>>> 3 = 3;
------------------------------------------
>> demo.pl:5:[__top__]:
>>> $c = 2 * $a + 7 * $b;
>>>> $c = 2 * 1 + 7 * 3;
>>>>> 23 = 2 * 1 + 7 * 3;
------------------------------------------
>> demo.pl:6:[__top__]:
>>> @d = ($a, $b, $c + $b);
>>>> @d = (1, 3, 23 + 3);
>>>>> (1,3,26) = (1, 3, 23 + 3);
------------------------------------------
DESCRIPTION
Devel::DumpTrace::PPI
is a near drop-in replacement to Devel::DumpTrace that uses the PPI module for parsing the source code. With PPI, this module overcomes some of the limitations of the original Devel::DumpTrace
parser and makes a few other features available, including
handling statements with chained assignments of complex assignment expressions
$ perl -d:DumpTrace::noPPI=verbose -e '$a=$b[$c=2]="foo"' >> -e:1:[__top__]: >>> $a=$b[$c=2]="foo" >>>> $a=()[undef=2]="foo" >>>>> 'foo'=()[undef=2]="foo" ------------------------------------------- $ perl -d:DumpTrace::PPI=verbose -e '$a=$b[$c=2]="foo"' >> -e:1:[__top__]: >>> $a=$b[$c=2]="foo" >>>>> 'foo'=(undef,undef,'foo')[$c=2]="foo" ------------------------------------------
multi-line statements
$ cat multiline.pl $b = 4; @a = (1 + 2, 3 + $b); $ perl -d:DumpTrace::noPPI=verbose multiline.pl >> multiline.pl:1:[__top__]: >>> $b = 4; >>>>> 4 = 4; ------------------------------------------- >> multiline.pl:2:[__top__]: >>> @a = (1 + 2, >>>>> (3,7) = (1 + 2, ------------------------------------------- $ perl -d:DumpTrace::PPI=verbose multiline.pl >> multiline.pl:1:[__top__]: >>> $b = 4; >>>>> 4 = 4; ------------------------------------------ >> multiline.pl:2:[__top__]: >>> @a = (1 + 2, 3 + $b); >>>> @a = (1 + 2, 3 + 4); >>>>> (3,7) = (1 + 2, 3 + 4); ------------------------------------------
string literals with variable names
$ perl -d:DumpTrace::noPPI=verbose -e '$email = q/mob@cpan.org/' >> -e:1:[__top__]: >>> $email = q/mob@cpan.org/ >>>> $email = q/mob().org/ >>>>> "mob\@cpan.org" = q/mob().org/ ------------------------------------------- $ perl -d:DumpTrace::PPI=verbose -e '$email = q/mob@cpan.org/' >> -e:1:[__top__]: >>> $email = q/mob@cpan.org/ >>>>> "mob\@cpan.org" = q/mob@cpan.org/ ------------------------------------------
Better recognition of Perl's magic variables
$ perl -d:DumpTrace::noPPI=verbose -e '$"="\t";' -e 'print join $", 3, 4, 5' >> -e:1:[__top__]: >>> $"="\t"; >>>>> $"="\t"; ------------------------------------------- >> -e:2:[__top__]: >>> print join $", 3, 4, 5 ------------------------------------------- 3 4 5 $ perl -d:DumpTrace::PPI=verbose -e '$"="\t";' -e 'print join $", 3, 4, 5' >> -e:1:[__top__]: >>> $"="\t"; >>>>> "\t"="\t"; ------------------------------------------ >> -e:2:[__top__]: >>> print join $", 3, 4, 5 >>>> print join "\t", 3, 4, 5 ---------------------------------------------- 3 4 5
Can insert implicit
$_
,@_
,$.
,@ARGV
variables$_
is often used as the implicit target of regular expressions or an implicit argument to many standard functions. Since Perl v5.10,$_
can take on the value in agiven
expression and used in an implicit smart match of awhen
expression.@_
and@ARGV
are often implicitly used as arguments toshift
orpop
. This module can identify some places where these variables are used implicitly and include their values in the trace output.$ perl -d:DumpTrace::PPI=verbose -e '$_=pop;' \ -e 'print m/hello/ && sin' hello >> -e:1:[__top__]: >>> $_=pop; >>>> $_=pop ('hello'); >>>>> 'hello'=pop ('hello'); ------------------------------------------- >> -e:2:[__top__]: >>> print m/hello/ && sin >>>> print 'hello'=~m/hello/ && sin $_ 0>>>>> print 'hello'=~m/hello/ && sin 'hello' -------------------------------------------
Since v0.13 there is limited support for inserting the implicit smartmatch (
~~
) operations in agiven/when
construction.Since v0.19 this feature includes the implicit assignment to
$_
in awhile (<HANDLE>)
oruntil (<HANDLE>)
construction.This feature includes limited support for the implicit
$.
comparison with the flip-flop operators. The three-dot operator...
has been supported since v0.19, and support for the two-dot operator..
was introduced in v0.20.Smarter abbreviation of large arrays and hashes
When displaying the contents of a large array or hash table,
Devel::DumpTrace
can abbreviate the output.When displaying the contents of an array or hash table, the PPI-based parser can sometimes evaluate the expressions inside subscripts. When the array or hash is large and abbreviated output is used, the abbreviation can use the value of the subscript expression to provide better context.
$ perl -d:DumpTrace::noPPI=quiet -e '@r=(0..99);' -e '$s=$r[50];' >>>>> -e:1:[__top__]: @r:(0,1,2,3,4,5,6,7,...)=(0..99); >>>>> -e:2:[__top__]: $s:50=$r:(0,1,2,3,4,5,6,7,...)[50];
In some cases, the PPI-based parser can evaluate the expressions inside subscripts. This value can be used to produce an abbreviation with some context:
$ perl -Ilib -d:DumpTrace::PPI=quiet -e '@r=(0..99);' -e '$s=$r[50];' >>>>> -e:1:[__top__]: @r:(0,1,2,3,4,5,6,7,...)=(0..99); >>>>> -e:2:[__top__]: $s:50=$r:(0,1,2,...,50,...,99)[50];
For some complex cases (like programs with
tie
'd variables where just reading a variable's value can have side effects) you may want to disable the context-sensitive abbreviation of large arrays and hashes. This can be done by passing a true value in the environment variableDUMPTRACE_DUMB_ABBREV
.
The PPI-based parser has more overhead than the simpler parser from Devel::DumpTrace (benchmarks forthcoming), so you may not want to always favor Devel::DumpTrace::PPI
over Devel::DumpTrace
. Plus it requires PPI to be installed. You can force the basic (non-PPI) parser to be used by either invoking your program with the -d:DumpTrace::noPPI
switch, or by setting the environment variable DUMPTRACE_NOPPI
to a true value.
See Devel::DumpTrace for far more information about what this module is supposed to do, including the variables and configuration settings.
SPECIAL HANDLING FOR FLOW CONTROL STRUCTURES
Inside a Perl debugger, there are many expressions evaluated inside Perl flow control structures that "cannot hold a breakpoint" (to use the language of perldebguts). As a result, these expressions never appear in a normal trace ouptut (using -d:Trace
, for example).
For example, a trace for a line containing a C-style for
loop typically appears only once, during the first iteration of the loop:
$ perl -d:Trace -e 'for ($i=0; $i<3; $i++) {' -e '$j = $i ** 2;' -e '}'
>> -e:3: }
>> -e:1: for ($i=0; $i<3; $i++) {
>> -e:2: $j = $i ** 2;
>> -e:2: $j = $i ** 2;
>> -e:2: $j = $i ** 2;
Perl still evaluates the expressions $i++
and $i<3
at each iteration, but those steps are optimized out of the trace output.
Or for another example, a trace through a complex if-elsif-else
structure may only produce the conditional expression for the initial if
statement:
$ perl -d:Trace -e '$a=3;
> if ($a==1) {
> $b=$a;
> } elsif ($a==2) {
> $b=0;
> } else {
> $b=9;
> }'
>> -e:1: $a=3;
>> -e:2: if ($a==1) {
>> -e:7: $b=9;
To get to the assignment $b=9
, Perl needed to have evaluated the expression $a==2
, but this step did not make it to the trace output.
There's a lot of value in seeing these expressions, however, so Devel::DumpTrace::PPI
takes steps to attach these expressions to the existing source code and to display and evaluate these expressions when they would have been evaluated in the Perl program.
Special handling for C-style for loops
A C-style for loop has the structure
for ( INITIALIZER ; CONDITION ; UPDATE ) BLOCK
In debugging a program with such a control structure, it is helpful to observe how the CONDITION
and UPDATE
expressions are evaluated at each iteration of the loop. At times the first statement of a BLOCK
inside a for loop will be decorated with the relevant expressions from the for
loop:
$ cat simple-for.pl
for ($i=0; $i<3; $i++) {
$y += $i;
}
print $y;
$ perl -d:DumpTrace::PPI simple-for.pl
>>>>> simple-for.pl:1:[__top__]: for ($i:0=0; $i:undef<3; $i:0++) {
>>>>> simple-for.pl:2:[__top__]: $y:0 += $i:0;
>>>>> simple-for.pl:2:[__top__]: FOR-UPDATE: {$i:2++ } FOR-COND: {$i:1<3; }
$y:1 += $i:1;
>>>>> simple-for.pl:2:[__top__]: FOR-UPDATE: {$i:3++ } FOR-COND: {$i:2<3; }
$y:3 += $i:2;
>>> simple-for.pl:4:[__top__]: FOR-COND: {$i:3<3;}
print $y:3;
$ perl -d:DumpTrace::PPI=verbose simple-for.pl
>> simple-for.pl:1:[__top__]:
>>> for ($i=0; $i<3; $i++) {
>>>> for ($i=0; undef<3; $i++) {
>>>>> for (0=0; undef<3; 0++) {
-------------------------------------------
>> simple-for.pl:2:[__top__]:
>>> $y += $i;
>>>> $y += 0;
>>>>> 0 += 0;
-------------------------------------------
>> simple-for.pl:2:[__top__]:
>>> FOR-UPDATE: {$i++ } FOR-COND: {$i<3; } $y += $i;
>>>> FOR-UPDATE: {$i++ } FOR-COND: {1<3; } $y += 1;
>>>>> FOR-UPDATE: {2++ } FOR-COND: {1<3; } 1 += 1;
-------------------------------------------
>> simple-for.pl:2:[__top__]:
>>> FOR-UPDATE: {$i++ } FOR-COND: {$i<3; } $y += $i;
>>>> FOR-UPDATE: {$i++ } FOR-COND: {2<3; } $y += 2;
>>>>> FOR-UPDATE: {3++ } FOR-COND: {2<3; } 3 += 2;
-------------------------------------------
>> simple-for.pl:4:[__top__]:
>>> FOR-COND: {$i<3;} print $y;
>>>> FOR-COND: {3<3;} print 3;
-------------------------------------------
The first time the loop's block code is executed, there is no need to evaluate the conditional or the update expression, because they were just evaluated in the previous line. But the second and third time through the loop, the original source code is decorated with FOR-UPDATE: {
expression }
and FOR-COND: {
expression }
, showing what code was executed when the previous iteration finished, and what expression was evaluated to determine whether to continue with the for
loop, respectively.
Note: the final FOR-COND ...
statement, where the condition is false and Perl breaks out of the loop, will only be displayed when the compound for
statement is not the last statement in the current block, as this feature works by attaching additional information to the statement that follows the end of the for
loop.
Special handling for other foreach loops
When a program containing the regular foreach [$var] LIST
construction is traced, the foreach ...
statement only appears in the trace output for the first iteration of the loop, just like the C-style for loop construct. For all subsequent iterations the Devel::DumpTrace::PPI
module will prepend the first statement in the block with FOREACH: {
loop-variable }
to show the new value of the loop variable at the beginning of each iteration.
$ perl -d:DumpTrace::PPI -e '
for (1 .. 6) {
$n += 2 * $_ - 1;
print $_, "\t", $n, "\n"
}
'
>>>>> -e:2:[__top__]: for $_:1 (1 .. 6) {
>>>>> -e:3:[__top__]: $n:1 += 2 * $_:1 - 1;
>>> -e:4:[__top__]: print $_:1, "\t", $n:1, "\n"
1 1
>>>>> -e:3:[__top__]: FOREACH: {$_:2} $n:4 += 2 * $_:2 - 1;
>>> -e:4:[__top__]: print $_:2, "\t", $n:4, "\n"
2 4
>>>>> -e:3:[__top__]: FOREACH: {$_:3} $n:9 += 2 * $_:3 - 1;
>>> -e:4:[__top__]: print $_:3, "\t", $n:9, "\n"
3 9
>>>>> -e:3:[__top__]: FOREACH: {$_:4} $n:16 += 2 * $_:4 - 1;
>>> -e:4:[__top__]: print $_:4, "\t", $n:16, "\n"
4 16
>>>>> -e:3:[__top__]: FOREACH: {$_:5} $n:25 += 2 * $_:5 - 1;
>>> -e:4:[__top__]: print $_:5, "\t", $n:25, "\n"
5 25
>>>>> -e:3:[__top__]: FOREACH: {$_:6} $n:36 += 2 * $_:6 - 1;
>>> -e:4:[__top__]: print $_:6, "\t", $n:36, "\n"
6 36
Special handling for while/until loops
As with a for
loop, the conditional expression of a while
or until
loop is only included in trace output on the initial entrance to the loop. Devel::DumpTrace::PPI
decorates the first statement of the block inside the while/until
loop to show how the conditional expression is evaluated at the beginning of every iteration of the loop:
$ cat ./simple-while.pl
my ($i, $j, $l) = (0, 9, 0);
while ($i++ < 6) {
my $k = $i * $j--;
next if $k % 5 == 1;
$l = $l + $k;
}
print "L is $l\n";
$ perl -d:DumpTrace::PPI ./simple-while.pl
>>>>> simple-while.pl:1:[__top__]: my ($i:0, $j:9, $l:0) = (0, 9, 0);
>>>>> simple-while.pl:2:[__top__]: while ($i:1++ < 6) {
>>>>> simple-while.pl:3:[__top__]: my $k:9 = $i:1 * $j:8--;
>>> simple-while.pl:4:[__top__]: next if $k:9 % 5 == 1;
>>>>> simple-while.pl:5:[__top__]: $l:9 = $l:0 + $k:9;
>>>>> simple-while.pl:3:[__top__]: WHILE: ($i:2++ < 6)
my $k:16 = $i:2 * $j:7--;
>>> simple-while.pl:4:[__top__]: next if $k:16 % 5 == 1;
>>>>> simple-while.pl:3:[__top__]: WHILE: ($i:3++ < 6)
my $k:21 = $i:3 * $j:6--;
>>> simple-while.pl:4:[__top__]: next if $k:21 % 5 == 1;
>>>>> simple-while.pl:3:[__top__]: WHILE: ($i:4++ < 6)
my $k:24 = $i:4 * $j:5--;
>>> simple-while.pl:4:[__top__]: next if $k:24 % 5 == 1;
>>>>> simple-while.pl:5:[__top__]: $l:33 = $l:9 + $k:24;
>>>>> simple-while.pl:3:[__top__]: WHILE: ($i:5++ < 6)
my $k:25 = $i:5 * $j:4--;
>>> simple-while.pl:4:[__top__]: next if $k:25 % 5 == 1;
>>>>> simple-while.pl:5:[__top__]: $l:58 = $l:33 + $k:25;
>>>>> simple-while.pl:3:[__top__]: WHILE: ($i:6++ < 6)
my $k:24 = $i:6 * $j:3--;
>>> simple-while.pl:4:[__top__]: next if $k:24 % 5 == 1;
>>>>> simple-while.pl:5:[__top__]: $l:82 = $l:58 + $k:24;
L is 82
>>>>> simple-while.pl:7:[__top__]: WHILE: {($i:7++ < 6)}
print "L is $l\n";
In this example, a WHILE: {
expression }
decorator (capitalized to indicate that it is not a part of the actual source code) shows how the conditional statement was evaluated prior to each iteration of the loop (the output is a little misleading because the conditional expression contains a ++
postfix operator, but this module does not evaluate the expression until after the real conditional expression has actually been evaluated).
Note: Again, the output for the evaluation that breaks out of the while
loop can only be displayed when the compound while
or until
statement is not the last statement in the block.
do-while and do-until loops
Like regular while
and until
loops, the do-while
and do-until
constructions do not include evaluation of the final conditional expression in the trace output. So Devel::DumpTrace::PPI
decorates the last statement of a do-while
or do-until
block to print out the condition:
$ perl -d:DumpTrace::PPI -e 'do {
> $k++;
> $l += $k;
> } while $l < 40'
>>> -e:1:[__top__]: do {
>>>>> -e:2:[__top__]: $k:1++;
>>>>> -e:3:[__top__]: $l:1 += $k:1;
DO-WHILE: { $l:1 < 40}
>>>>> -e:2:[__top__]: $k:2++;
>>>>> -e:3:[__top__]: $l:3 += $k:2;
DO-WHILE: { $l:3 < 40}
>>>>> -e:2:[__top__]: $k:3++;
>>>>> -e:3:[__top__]: $l:6 += $k:3;
DO-WHILE: { $l:6 < 40}
>>>>> -e:2:[__top__]: $k:4++;
>>>>> -e:3:[__top__]: $l:10 += $k:4;
DO-WHILE: { $l:10 < 40}
>>>>> -e:2:[__top__]: $k:5++;
>>>>> -e:3:[__top__]: $l:15 += $k:5;
DO-WHILE: { $l:15 < 40}
>>>>> -e:2:[__top__]: $k:6++;
>>>>> -e:3:[__top__]: $l:21 += $k:6;
DO-WHILE: { $l:21 < 40}
>>>>> -e:2:[__top__]: $k:7++;
>>>>> -e:3:[__top__]: $l:28 += $k:7;
DO-WHILE: { $l:28 < 40}
>>>>> -e:2:[__top__]: $k:8++;
>>>>> -e:3:[__top__]: $l:36 += $k:8;
DO-WHILE: { $l:36 < 40}
>>>>> -e:2:[__top__]: $k:9++;
>>>>> -e:3:[__top__]: $l:45 += $k:9;
DO-WHILE: { $l:45 < 40}
The conditional expression is displayed and evaluated after the last statement of the block has been executed but before the actual while/until
condition has been evaluated. The trace output in the expression labeled DO-WHILE:
or DO-UNTIL:
may be misleading if the conditional expression makes function calls or has any other side-effects.
Complex if - elsif - ... - else blocks
Although a long sequence of expressions might need to be evaluated to determine program flow through a complex if
- elsif
- ... - else
statement, the normal trace output will always only show the initial condition (that is, the condition associated with the if
keyword). Devel::DumpTrace::PPI
will decorate the first statement in blocks after the elsif
or else
keywords to show all of the expressions that had to be evaluated to get to a particular point of execution, and how (subject to side-effects of the conditional expressions) those expressions were evaluated:
$ cat iffy.pl
for ($a=-1; $a<=3; $a++) {
if ($a == 1) {
$b = 1;
} elsif ($a == 2) {
$b = 4;
} elsif ($a == 3) {
$b = 9;
} elsif ($a < 0) {
$b = 5;
$b++;
} else {
$b = 20;
}
}
$ perl -d:DumpTrace::PPI iffy.pl
>>> iffy.pl:14:[__top__]:
>>>>> iffy.pl:1:[__top__]: for ($a:-1=-1; $a:-1<=3; $a:-1++) {
>>> iffy.pl:2:[__top__]: if ($a:-1 == 1) {
>>>>> iffy.pl:9:[__top__]: ELSEIF ($a:-1 == 1)
ELSEIF ($a:-1 == 2)
ELSEIF ($a:-1 == 3)
ELSEIF ($a:-1 < 0)
$b:5 = 5;
>>> iffy.pl:10:[__top__]: $b:5++;
>>> iffy.pl:2:[__top__]: FOR-UPDATE: {$a:0++ } FOR-COND: {$a:0<=3; }
if ($a:0 == 1) {
>>>>> iffy.pl:12:[__top__]: ELSEIF ($a:0 == 1)
ELSEIF ($a:0 == 2)
ELSEIF ($a:0 == 3)
ELSEIF ($a:0 < 0)
ELSE
$b:20 = 20;
>>> iffy.pl:2:[__top__]: FOR-UPDATE: {$a:1++ } FOR-COND: {$a:1<=3; }
if ($a:1 == 1) {
>>>>> iffy.pl:3:[__top__]: $b:1 = 1;
>>> iffy.pl:2:[__top__]: FOR-UPDATE: {$a:2++ } FOR-COND: {$a:2<=3; }
if ($a:2 == 1) {
>>>>> iffy.pl:5:[__top__]: ELSEIF ($a:2 == 1)
ELSEIF ($a:2 == 2)
$b:4 = 4;
>>> iffy.pl:2:[__top__]: FOR-UPDATE: {$a:3++ } FOR-COND: {$a:3<=3; }
if ($a:3 == 1) {
>>>>> iffy.pl:7:[__top__]: ELSEIF ($a:3 == 1)
ELSEIF ($a:3 == 2)
ELSEIF ($a:3 == 3)
$b:9 = 9;
In this example, the ELSEIF
(expression) and ELSE
decorators indicate what expressions must have been evaluated to reach the particular block of the statement that is to be executed.
SUBROUTINES/METHODS
None to worry about.
EXPORT
Nothing is or can be exported from this module.
DIAGNOSTICS
All output from this module is for diagnostics.
CONFIGURATION AND ENVIRONMENT
This module reads and respects the same environment variables as Devel::DumpTrace
. See Devel::DumpTrace for more information.
DEPENDENCIES
PPI for understanding the structure of your Perl script.
PadWalker for arbitrary access to lexical variables.
Scalar::Util for the reference identification convenience methods.
Text::Shorten (bundled with this distribution) for abbreviating long output, when desired.
INCOMPATIBILITIES
None known.
BUGS AND LIMITATIONS
The PPI-based parser in this module runs 3-6 times slower than the basic parser (which runs 7-10 times slower than the basic Devel::Trace).
See "SUPPORT" in Devel::DumpTrace for other support information. Report issues for this module with the Devel-DumpTrace
distribution.
AUTHOR
Marty O'Brien, <mob at cpan.org>
LICENSE AND COPYRIGHT
Copyright 2010-2018 Marty O'Brien.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.