Subject: | Possible off-by-one bug when caller() is called from one level below the uplevel |
Dear Michael,
I'm wresting with Sub::Uplevel and the way caller() is working in calls
below the uplevel call. (All the stuff alluded to in the _private POD
section.) I think I may have found an off-by-one bug -- or I may just
be misinterpreting what the objective of uplevel is and how caller
works. I hope you might have the time to reviewing the following
questions/analysis.
The situation (in abstract) is that I'm calling a wrapper function, the
wrapper function calls uplevel 1 for the worker function, which then
calls another function to do the real work (which I call the "delegated"
function). For various reasons, each of these are in separate package
namespaces and my problem requires knowing whether the delegated
function was called from the worker function or from somewhere else.
The problem I'm finding is that calling "caller(0)" in the delegated
function is skipping two frames up and returns the package that the
wrapper is in. What I expected is that caller(0) would give me the
package of the "worker" and that caller(1) would then skip up and give
me "main" (which called the wrapper in the first place).
I wrote some sample code which replicates that calling structure and
then prints out a walk up the caller tree with both CORE::caller and
caller (i.e. the Sub::Uplevel replacement) in the delegated function.
(The test code is at the end of this email.) Here's the result:
CORE::caller:
Level 0: package: In::Worker => routine: In::Delegate::delegate
Level 1: package: Sub::Uplevel => routine: In::Worker::worker
Level 2: package: In::Wrapper => routine: Sub::Uplevel::uplevel
Level 3: package: main => routine: In::Wrapper::wrapper
caller:
Level 0: package: In::Wrapper => routine: Sub::Uplevel::uplevel
Level 1: package: main => routine: In::Wrapper::wrapper
What I was expecting to get was something that worked like this:
CORE::caller:
Level 0: package: In::Worker => routine: In::Delegate::delegate
Level 1: package: Sub::Uplevel => routine: In::Worker::worker
Level 2: package: In::Wrapper => routine: Sub::Uplevel::uplevel
Level 3: package: main => routine: In::Wrapper::wrapper
caller:
Level 0: package: In::Worker => routine: In::Delegate::delegate
Level 1: package: main => routine: In::Wrapper::wrapper
I found that I was able to get that expected result with the following
patch:
--- c:\Perl\site\lib\Sub\Uplevel.pm.orig Mon Jul 25 14:32:52 2005
+++ c:\Perl\site\lib\sub\Uplevel.pm Mon Jul 25 14:33:27 2005
@@ -118,7 +118,8 @@
my $saw_uplevel = 0;
# Yes, we need a C style for loop here since $height changes
- for( my $up = 1; $up <= $height + 1; $up++ ) {
+ # for( my $up = 1; $up <= $height + 1; $up++ ) {
+ for( my $up = 1; $up <= $height; $up++ ) {
my @caller = CORE::caller($up);
if( $caller[0] eq __PACKAGE__ ) {
$height++;
It looked like the code was looking too far up to see if it needed to
skip up past uplevel. Of course, I immediately found that the patch
broke Test::Exception -- or at least, led to
incorrect output from Carp winding up in Test::Exception instead of in
my test-file. :-( So if this is a bug, there could be some big ripple
effects.
So -- that's my diagnosis. I'm not sure if this is really a bug or if
there is some reasoning behind that skip that I'm missing. Doing it the
patch way leads to a different calling stack of functions (rather than
packages), but I'm not sure if that's what was intended and why it would
be important (and uplevel() shows up either way).
Any insight you can shed is greatly appreciated.
Regards,
David Golden
P.S. Here's the test code:
#!/usr/bin/perl
use warnings;
use strict;
package In::Wrapper;
use Sub::Uplevel;
sub wrapper { uplevel 1, \&In::Worker::worker; };
package In::Worker;
sub worker { In::Delegate::delegate() };
package In::Delegate;
sub delegate {
print "CORE::caller:\n";
for my $i (0 .. 3) {
my ($pkg, $sub ) = (CORE::caller($i))[0,3];
$pkg ||= ""; $sub ||= "";
print "Level $i: package: $pkg => routine: $sub\n";
}
print "\ncaller:\n";
for my $i (0 .. 3) {
my ($pkg, $sub ) = (caller($i))[0,3];
$pkg ||= ""; $sub ||= "";
print "Level $i: package: $pkg => routine: $sub\n";
}
}
package main;
In::Wrapper::wrapper();
__END__