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

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. PPI 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=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=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=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=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 implicitly used $_, @_, @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'
        -------------------------------------------

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 is supposed to be evaluating 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 only produces the condition 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;
    }

    $ perl -d:DumpTrace::PPI ./simple-for.pl
    >>>   ./simple-for.pl:3:[__top__]:
    >>>>> ./simple-for.pl:1:[__top__]:      for ($i:0=0; $i:0<3; $i:0++) {
    >>>>> ./simple-for.pl:2:[__top__]:      $y:0 += $i:0;
    >>>>> ./simple-for.pl:2:[__top__]:      FOR-UPDATE: {$i:1++ } FOR-COND: {$i:1<3; }
                                            $y:1 += $i:1;
    >>>>> ./simple-for.pl:2:[__top__]:      FOR-UPDATE: {$i:2++ } FOR-COND: {$i:2<3; }
                                            $y:3 += $i:2;

    $ perl -d:DumpTrace::PPI=verbose ./simple-for.pl
    >>    ./simple-for.pl:3:[__top__]:
    >>>
    -------------------------------------------
    >>    ./simple-for.pl:1:[__top__]:
    >>>              for ($i=0; $i<3; $i++) {
    >>>>             for ($i=0; 0<3; 0++) {
    >>>>>            for (0=0; 0<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: {1++ } FOR-COND: {1<3; } $y += 1;
    >>>>>            FOR-UPDATE: {1++ } FOR-COND: {1<3; } 1 += 1;
    -------------------------------------------
    >>    ./simple-for.pl:2:[__top__]:
    >>>              FOR-UPDATE: {$i++ } FOR-COND: {$i<3; } $y += $i;
    >>>>             FOR-UPDATE: {2++ } FOR-COND: {2<3; } $y += 2;
    >>>>>            FOR-UPDATE: {2++ } FOR-COND: {2<3; } 3 += 2;
    -------------------------------------------

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.

Unfortunately, this example still does not show the expressions that were evaluated at the end of the third loop, when the update and condition expressions are evaluated another time. In the last iteration, the condition expression evaluates to false and the program breaks out of the 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 handle 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;
    }

    $ perl -d:DumpTrace::PPI ./simple-while.pl   
    >>>   /tmp/while3.pl:1:[__top__]:
    >>>   /tmp/while3.pl:2:[__top__]:       while ($i:0++ < 6) {
    >>>>> /tmp/while3.pl:3:[__top__]:       my $k:9 = $i:1 * $j:9--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:9 % 5 == 1;
    >>>>> /tmp/while3.pl:5:[__top__]:       $l:9 = $l:0 + $k:9;
    >>>>> /tmp/while3.pl:3:[__top__]:       WHILE: ($i:2++ < 6)
                                            my $k:16 = $i:2 * $j:8--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:16 % 5 == 1;
    >>>>> /tmp/while3.pl:3:[__top__]:       WHILE: ($i:3++ < 6)
                                            my $k:21 = $i:3 * $j:7--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:21 % 5 == 1;
    >>>>> /tmp/while3.pl:3:[__top__]:       WHILE: ($i:4++ < 6)
                                            my $k:24 = $i:4 * $j:6--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:24 % 5 == 1;
    >>>>> /tmp/while3.pl:5:[__top__]:       $l:33 = $l:9 + $k:24;
    >>>>> /tmp/while3.pl:3:[__top__]:       WHILE: ($i:5++ < 6)
                                            my $k:25 = $i:5 * $j:5--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:25 % 5 == 1;
    >>>>> /tmp/while3.pl:5:[__top__]:       $l:58 = $l:33 + $k:25;
    >>>>> /tmp/while3.pl:3:[__top__]:       WHILE: ($i:6++ < 6)
                                            my $k:24 = $i:6 * $j:4--;
    >>>   /tmp/while3.pl:4:[__top__]:       next if $k:24 % 5 == 1;
    >>>>> /tmp/while3.pl:5:[__top__]:       $l:82 = $l:58 + $k:24;

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

Again, the output does not show the conditional expression being evaluated for the final time, right before the program breaks out of the while loop control structure.

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 ./if-elsif-else.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 ./if-elsif-else.pl
    >>>   /tmp/if4.pl:14:[__top__]:
    >>>>> /tmp/if4.pl:1:[__top__]:  for ($a:-1=-1; $a:-1<=3; $a:-1++) {
    >>>   /tmp/if4.pl:2:[__top__]:  if ($a:-1 == 1) {
    >>>>> /tmp/if4.pl:9:[__top__]:  ELSEIF ($a:-1 == 1)
                                            ELSEIF ($a:-1 == 2)
                                            ELSEIF ($a:-1 == 3)
                                            ELSEIF ($a:-1 < 0)
                                            $b:5 = 5;
    >>>   /tmp/if4.pl:10:[__top__]: $b:5++;
    >>>   /tmp/if4.pl:2:[__top__]:  FOR-UPDATE: {$a:0++ } FOR-COND: {$a:0<=3; }
                                            if ($a:0 == 1) {
    >>>>> /tmp/if4.pl:12:[__top__]: ELSEIF ($a:0 == 1)
                                            ELSEIF ($a:0 == 2)
                                            ELSEIF ($a:0 == 3)
                                            ELSEIF ($a:0 < 0)
                                            ELSE
                                            $b:20 = 20;
    >>>   /tmp/if4.pl:2:[__top__]:  FOR-UPDATE: {$a:1++ } FOR-COND: {$a:1<=3; }
                                            if ($a:1 == 1) {
    >>>>> /tmp/if4.pl:3:[__top__]:  $b:1 = 1;
    >>>   /tmp/if4.pl:2:[__top__]:  FOR-UPDATE: {$a:2++ } FOR-COND: {$a:2<=3; }
                                            if ($a:2 == 1) {
    >>>>> /tmp/if4.pl:5:[__top__]:  ELSEIF ($a:2 == 1)
                                            ELSEIF ($a:2 == 2)
                                            $b:4 = 4;
    >>>   /tmp/if4.pl:2:[__top__]:  FOR-UPDATE: {$a:3++ } FOR-COND: {$a:3<=3; }
                                            if ($a:3 == 1) {
    >>>>> /tmp/if4.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.

Some TODOs

Other flow control structures in Perl could benefit from similar treatment. These will be addressed in future versions of this distribution.

do-while and do-until loops
for[each] [variable] LIST loops
map BLOCK LIST and grep BLOCK LIST blocks
postfix EXPR for LIST, EXPR while/until CONDITION statements

Postfix expressions are typically executed as a single operation (with no place to hold a breakpoint). This item might need some B-fu.

OTHER FEATURES

"Smart" 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.

SUBROUTINES/METHODS

None to worry about.

EXPORT

Nothing is or can be exported from this module.

DIAGNOSTICS

None

CONFIGURATION AND ENVIRONMENT

Like Devel::DumpTrace, this module also reads the DUMPTRACE_FH and DUMPTRACE_LEVEL environment variables. See Devel::DumpTrace for details about how to use these variables.

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