Perl Debuggers in Detail
(Page 1 of 6 )
In this second part of a two-part series on debugging Perl, we will go beyond the author's favorite debugger and look at a number of alternative Perl debuggers. This article is excerpted from chapter four of the book
Mastering Perl, written by Brian D Foy (O'Reilly; ISBN: 0596527241). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.
Wrapping Subroutines
I don’t have to copy a module file to change its behavior. I can override parts of it directly in my code. Damian Conway wrote a wonderful module called Hook::LexWrap to wrap a subroutine around another subroutine. That means that my wrapper subroutine can see the arguments coming in and the return values going out. I can inspect the values, or even change them if I like.
I’ll start with my simple example program that adds a couple of numbers. As before, it has some problems because I’m passing it the wrong arguments since I can’t tell the difference between $n and $m, and have used $n twice in my call to add. Just running the program gives me the wrong answer, but I don’t know where the problem is:
#!/usr/bin/perl
# @ARGV = qw( 5 6 );
my $n = shift @ARGV;
my $m = $ARGV[0];
print "The sum of $n and $m is " . add( $n, $n ) . "\n";
sub add
{
my( $n, $m ) = @_;
my $sum = $n + $m;
return $sum;
}
I don’t want to change anything in the code, or, I should say, I want to look at what’s happening without affecting the statements that are already there. As before, I want everything back to normal when I’m finished debugging. Not editing the subroutine makes that easier.
The Hook::LexWrap gives me a chance to do something right after I make a subroutine call and right before the subroutine returns. As the name suggests, it wraps the subroutine with another one to provide the magic. The Hook::LexWrap::wrap function takes the name of the subroutine it will wrap, add in this case, and then anonymous subroutines as pre- and posthandlers:
#!/usr/bin/perl
use Hook::LexWrap qw(wrap);
my $n = shift @ARGV;
my $m = $ARGV[0];
wrap add,
pre => sub { print "I got the arguments: [@_]\n" },
post => sub { print "The return value is going to be $_[-1]\n" }
;
# this line has the error
print "The sum of $n and $m is " . add( $n, $n ) . "\n";
sub add
{
my( $n, $m ) = @_;
my $sum = $n + $m;
return $sum;
}
The prehandler sees the same argument list as my call to add. In this case I just output the list so I can see what it is. The posthandler gets the same arguments, but Hook::Lex Wrap adds another element, the return value, on the end of @_. In the posthandler,
$_[-1] is always the return value. My program now outputs some useful debugging output, and I see that I’m passing the same argument twice:
$ perl add_numbers.pl 5 6
I got the arguments: [5 5 ]
The return value is going to be 10
The sum of 5 and 6 is 10
In that output, notice the space after the last 5. Since wrap added an element to @_, even though it’s undef, I get a space between it and the preceding 5 when I interpolate the array in the double-quoted string.
Hook::LexWrap has the magic to handle all the calling contexts too. It’s smart enough to handle scalar, list, and void contexts. In list context, that last element of @_ in the posthandler will be an array reference. In void context, it won’t be anything.
It gets even better than that, though. Hook::LexWrap actually adds that extra element to @_ before it does anything. Look at the last output carefully. After the second argument, there’s a space between the second 5 and the closing square bracket. That’s the space between 5 and the undef value of the extra element in @_.
In the prehandler, I can assign to that element, signaling to Hook::LexWrap that it should assume that it already has the return value, so it doesn’t need to actually run the original subroutine. If the subroutine isn’t doing what I need, I can force it to return the right value:
#!/usr/bin/perl
use Hook::LexWrap;
my $n = shift @ARGV;
my $m = $ARGV[0];
{
wrap add,
pre => sub {
print "I got the arguments: [@_]\n";
$_[-1] = "11";
},
post => sub { print "The return value is going to be $_[-1]\n" }
;
print "The sum of $n and $m is " . add( $n, $m ) . "\n";
}
sub add
{
my( $n, $m ) = @_;
my $sum = $n + $m;
return $sum;
}
Now that I’ve assigned to $_[-1] in my prehandler, the output is different. It doesn’t run the subroutine or the posthandler, and I get back 11:
$ perl add_numbers.pl 5 6
I got the arguments: [5 6 ]
The sum of 5 and 6 is 11
With my fake return value, I can give myself the right answer and get on with the right program, and do it without changing the subroutine I want to investigate. This can be especially handy if I’m working on a big problem where other things are broken, too. I know what I need to return from the subroutine so I make it do that until I fix the other parts, or at least investigate the rest of the program while the subroutine returns what it should. Sometimes eliminating a source of error, even temporarily, makes it easier to fix other things.
Next: perl5db.pl >>
More Perl Articles
More By O'Reilly Media
|
This article is excerpted from chapter four of the book Mastering Perl, written by Brian D Foy (O'Reilly; ISBN: 0596527241). Check it out today at your favorite bookstore. Buy this book now.
|
|