The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Devel::DumpTrace::PPI - PPI-based version of Devel::DumpTrace

VERSION

0.29

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 or 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 a given expression and used in an implicit smart match of a when expression. @_ and @ARGV are often implicitly used as arguments to shift or pop. 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 a given/when construction.

    Since v0.19 this feature includes the implicit assignment to $_ in a while (<HANDLE>) or until (<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 variable DUMPTRACE_DUMB_ABBREV.

The PPI-based parser has significantly more overhead than the simpler parser from Devel::DumpTrace (benchmarks show that the PPI-based parser runs 1.5-6 times slower than the regular parser [which already runs 6-20 times slower than Devel::Trace]). If this is too much of a disadvantage for your use case, you can force the basic parser to be used by either

invoking your program with the -d:DumpTrace::noPPI switch, or
setting the environment value DUMPTRACE_NOPPI to a true value
and also by not having PPI installed

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

See "BUGS AND LIMITATIONS" section in Devel::DumpTrace for description of some known issues in both the PPI parser and the basic parser.

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-2019 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.