Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the Sub-Uplevel CPAN distribution.

Report information
The Basics
Id: 13893
Status: resolved
Priority: 0/
Queue: Sub-Uplevel

People
Owner: dagolden [...] cpan.org
Requestors: dagolden [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: 0.11_01



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__
I'd sent a patch by email last year but no action has happened on it. I'm reposting it here for the record. It implements a proper stack of uplevel requests for each call to uplevel, allowing proper nesting of uplevel and non-uplevel function calls. I've also updated the patch to fix the warnings bugs for 5.8.8. David
=== t/nested-uplevels.t ================================================================== --- t/nested-uplevels.t (revision 2970) +++ t/nested-uplevels.t (patch uplevel-stack-patch level 2) @@ -0,0 +1,79 @@ +#!perl +use strict; +use warnings; +use Test::More; + +use Sub::Uplevel; + +package Wrap; +use Sub::Uplevel; + +sub wrap { + my ($n, $f, $depth, $up, @case) = @_; + + if ($n > 1) { + $n--; + return wrap( $n, $f, $depth, $up, @case ); + } + else { + return uplevel( $up , $f, $depth, $up, @case ); + } +} + +package Call; + +sub recurse_call_check { + my ($depth, $up, @case) = @_; + + if ( $depth ) { + $depth--; + my @result; + push @result, recurse_call_check($depth, $up, @case, 'Call' ); + for my $n ( 1 .. $up ) { + push @result, Wrap::wrap( $n, \&recurse_call_check, + $depth, $n, @case, + $n == 1 ? "Wrap(Call)" : "Wrap(Call) x $n" ), + ; + } + return @result; + } + else { + my (@uplevel_callstack, @real_callstack); + my $i = 0; + while ( defined( my $caller = caller($i++) ) ) { + push @uplevel_callstack, $caller; + } + $i = 0; + while ( defined( my $caller = CORE::caller($i++) ) ) { + push @real_callstack, $caller; + } + return [ + join( q{, }, @case ), + join( q{, }, reverse @uplevel_callstack ), + join( q{, }, reverse @real_callstack ), + ]; + } +} + +package main; + +my $depth = 4; +my $up = 3; +my $cases = 104; + +plan tests => $cases; + +my @results = Call::recurse_call_check( $depth, $up, 'Call' ); + +is( scalar @results, $cases, + "Right number of cases" +); + +my $expected = shift @results; + +for my $got ( @results ) { + is( $got->[1], $expected->[1], + "Case: $got->[0]" + ) or diag( "Real callers: $got->[2]" ); +} + === t/Uplevel.t ================================================================== --- t/Uplevel.t (revision 2970) +++ t/Uplevel.t (patch uplevel-stack-patch level 2) @@ -2,7 +2,7 @@ use lib qw(t/lib); use strict; -use Test::More tests => 18; +use Test::More tests => 20; BEGIN { use_ok('Sub::Uplevel'); } can_ok('Sub::Uplevel', 'uplevel'); @@ -62,10 +62,12 @@ # Carp? use Carp; sub try_croak { - croak("You couldn't fool me on the foolingest day of the year!"); +# line 64 + croak("Now we can fool croak!"); } sub wrap_croak { +# line 68 uplevel 1, \&try_croak; } @@ -74,7 +76,7 @@ # line 72 eval { wrap_croak() }; is( $@, <<CARP, 'croak() fooled'); -You couldn't fool me on the foolingest day of the year! at $0 line 68 +Now we can fool croak! at $0 line 64 main::wrap_croak() called at $0 line 72 $croak_diag called at $0 line 72 CARP @@ -89,7 +91,8 @@ # how about carp? sub try_carp { - carp "HA! You don't fool me!"; +# line 88 + carp "HA! Even carp is fooled!"; } sub wrap_carp { @@ -104,7 +107,7 @@ wrap_carp(); } is( $warning, <<CARP, 'carp() fooled' ); -HA! You don't fool me! at $0 line 92 +HA! Even carp is fooled! at $0 line 88 main::wrap_carp() called at $0 line 98 CARP @@ -118,11 +121,11 @@ } sub caller_check { - return caller(0); + return caller(shift); } -ok( eq_array([caller_check()], - ['main', $0, 122, 'main::caller_check', (caller_check)[4..9]]), +ok( eq_array([caller_check(0)], + ['main', $0, 122, 'main::caller_check', (caller_check(0))[4..9]]), 'caller check' ); sub deep_caller { @@ -154,3 +157,23 @@ sub hock { uplevel 1, \&yarrow } ok( eq_array([(hock)], ['main', $0, 154]), 'nested uplevel()s' ); + +# Deep caller inside uplevel +package Delegator; +# line 159 +sub delegate { main::caller_check(shift) } + +package Wrapper; +use Sub::Uplevel; +sub wrap { uplevel 1, \&Delegator::delegate, @_ } + +package main; + +is( (Wrapper::wrap(0))[0], 'Delegator', + 'deep caller check of parent sees real calling package' +); + +is( (Wrapper::wrap(1))[0], 'main', + 'deep caller check of grandparent sees package above uplevel' +); + === lib/Sub/Uplevel.pm ================================================================== --- lib/Sub/Uplevel.pm (revision 2970) +++ lib/Sub/Uplevel.pm (patch uplevel-stack-patch level 2) @@ -48,7 +48,7 @@ Makes the given function think it's being executed $num_frames higher than the current stack level. So when they use caller($frames) it -will actually caller($frames + $num_frames) for them. +will actually give caller($frames + $num_frames) for them. C<uplevel(1, \&some_func, @_)> is effectively C<goto &some_func> but you don't immediately exit the current subroutine. So while you can't @@ -72,11 +72,12 @@ =cut -our $Up_Frames = 0; +our @Up_Frames; # uplevel stack + sub uplevel { my($num_frames, $func, @args) = @_; - local $Up_Frames = $num_frames + $Up_Frames; - + + local @Up_Frames = ($num_frames, @Up_Frames ); return $func->(@args); } @@ -84,9 +85,13 @@ sub _setup_CORE_GLOBAL { no warnings 'redefine'; - *CORE::GLOBAL::caller = sub { + *CORE::GLOBAL::caller = sub(;$) { my $height = $_[0] || 0; + # shortcut if no uplevels have been called + # always add +1 to CORE::caller to skip this function's caller + return CORE::caller( $height + 1 ) if ! @Up_Frames; + =begin _private So it has to work like this: @@ -112,25 +117,50 @@ =end _private +=begin _dagolden + +I found the description above a bit confusing. Instead, this is the logic +that I found clearer when CORE::GLOBAL::caller is invoked and we have to +walk up the call stack: + +* if searching up to the requested height in the real call stack doesn't find +a call to uplevel, then we can return the result at that height in the +call stack + +* if we find a call to uplevel, we need to keep searching upwards beyond the +requested height at least by the amount of upleveling requested for that +call to uplevel (from the Up_Frames stack set during the uplevel call) + +* additionally, we need to hide the uplevel subroutine call, too, so we search +upwards one more level for each call to uplevel + +* when we've reached the top of the search, we want to return that frame +in the call stack, i.e. the requested height plus any uplevel adjustments +found during the search + +=end _dagolden + =cut - $height++; # up one to avoid this wrapper function. my $saw_uplevel = 0; - # Yes, we need a C style for loop here since $height changes - for( my $up = 1; $up <= $height + 1; $up++ ) { - my @caller = CORE::caller($up); - if( $caller[0] eq __PACKAGE__ ) { - $height++; - $height += $Up_Frames unless $saw_uplevel; - $saw_uplevel = 1; + my $adjust = 0; + + # walk up the call stack to fight the right package level to return; + # look one higher than requested for each call to uplevel found + # and adjust by the amount found in the Up_Frames stack for that call + for ( my $up = 0; $up <= $height + $adjust; $up++ ) { + my @caller = CORE::caller($up + 1); + if( defined $caller[0] && $caller[0] eq __PACKAGE__ ) { + # add one for each uplevel call seen + # and look into the uplevel stack for the offset + $adjust += 1 + $Up_Frames[$saw_uplevel]; + $saw_uplevel++; } } + my @caller = CORE::caller($height + $adjust + 1); - return undef if $height < 0; - my @caller = CORE::caller($height); - if( wantarray ) { if( !@_ ) { @caller = @caller[0..2]; @@ -144,7 +174,6 @@ } - =back =head1 EXAMPLE