| File: | local/lib/perl5/Sub/Uplevel.pm |
| Coverage: | 38.9% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package Sub::Uplevel; | |||||
| 2 | 6 6 | 49 10 | use 5.006; | |||
| 3 | 6 6 6 | 8 5 160 | use strict; | |||
| 4 | # ABSTRACT: apparently run a function in a higher stack frame | |||||
| 5 | our $VERSION = '0.24'; # VERSION | |||||
| 6 | ||||||
| 7 | # Frame check global constant | |||||
| 8 | our $CHECK_FRAMES; | |||||
| 9 | BEGIN { | |||||
| 10 | 6 | 55 | $CHECK_FRAMES = !! $CHECK_FRAMES; | |||
| 11 | } | |||||
| 12 | 6 6 6 | 10 4 338 | use constant CHECK_FRAMES => $CHECK_FRAMES; | |||
| 13 | ||||||
| 14 | # We must override *CORE::GLOBAL::caller if it hasn't already been | |||||
| 15 | # overridden or else Perl won't see our local override later. | |||||
| 16 | ||||||
| 17 | if ( not defined *CORE::GLOBAL::caller{CODE} ) { | |||||
| 18 | *CORE::GLOBAL::caller = \&_normal_caller; | |||||
| 19 | } | |||||
| 20 | ||||||
| 21 | # modules to force reload if ":aggressive" is specified | |||||
| 22 | my @reload_list = qw/Exporter Exporter::Heavy/; | |||||
| 23 | ||||||
| 24 | sub import { | |||||
| 25 | 6 6 6 | 13 2 426 | no strict 'refs'; ## no critic | |||
| 26 | 12 | 12 | my ($class, @args) = @_; | |||
| 27 | 12 | 13 | for my $tag ( @args, 'uplevel' ) { | |||
| 28 | 18 | 18 | if ( $tag eq 'uplevel' ) { | |||
| 29 | 18 | 16 | my $caller = caller(0); | |||
| 30 | 18 18 | 11 44 | *{"$caller\::uplevel"} = \&uplevel; | |||
| 31 | } | |||||
| 32 | elsif( $tag eq ':aggressive' ) { | |||||
| 33 | 0 | 0 | _force_reload( @reload_list ); | |||
| 34 | } | |||||
| 35 | else { | |||||
| 36 | 0 | 0 | die qq{"$tag" is not exported by the $class module\n} | |||
| 37 | } | |||||
| 38 | } | |||||
| 39 | 12 | 358 | return; | |||
| 40 | } | |||||
| 41 | ||||||
| 42 | sub _force_reload { | |||||
| 43 | 6 6 6 | 13 4 1565 | no warnings 'redefine'; | |||
| 44 | 0 | 0 | local $^W = 0; | |||
| 45 | 0 | 0 | for my $m ( @_ ) { | |||
| 46 | 0 | 0 | $m =~ s{::}{/}g; | |||
| 47 | 0 | 0 | $m .= ".pm"; | |||
| 48 | 0 | 0 | require $m if delete $INC{$m}; | |||
| 49 | } | |||||
| 50 | } | |||||
| 51 | ||||||
| 52 | ||||||
| 53 | # @Up_Frames -- uplevel stack | |||||
| 54 | # $Caller_Proxy -- whatever caller() override was in effect before uplevel | |||||
| 55 | our (@Up_Frames, $Caller_Proxy); | |||||
| 56 | ||||||
| 57 | sub _apparent_stack_height { | |||||
| 58 | 0 | 0 | my $height = 1; # start above this function | |||
| 59 | 0 | 0 | while ( 1 ) { | |||
| 60 | 0 | 0 | last if ! defined scalar $Caller_Proxy->($height); | |||
| 61 | 0 | 0 | $height++; | |||
| 62 | } | |||||
| 63 | 0 | 0 | return $height - 1; # subtract 1 for this function | |||
| 64 | } | |||||
| 65 | ||||||
| 66 | sub uplevel { | |||||
| 67 | # Backwards compatible version of "no warnings 'redefine'" | |||||
| 68 | 0 | 0 | my $old_W = $^W; | |||
| 69 | 0 | 0 | $^W = 0; | |||
| 70 | ||||||
| 71 | # Update the caller proxy if the uplevel override isn't in effect | |||||
| 72 | 0 | 0 | local $Caller_Proxy = *CORE::GLOBAL::caller{CODE} | |||
| 73 | if *CORE::GLOBAL::caller{CODE} != \&_uplevel_caller; | |||||
| 74 | 0 | 0 | local *CORE::GLOBAL::caller = \&_uplevel_caller; | |||
| 75 | ||||||
| 76 | # Restore old warnings state | |||||
| 77 | 0 | 0 | $^W = $old_W; | |||
| 78 | ||||||
| 79 | 0 | 0 | if ( CHECK_FRAMES and $_[0] >= _apparent_stack_height() ) { | |||
| 80 | require Carp; | |||||
| 81 | Carp::carp("uplevel $_[0] is more than the caller stack"); | |||||
| 82 | } | |||||
| 83 | ||||||
| 84 | 0 | 0 | local @Up_Frames = (shift, @Up_Frames ); | |||
| 85 | ||||||
| 86 | 0 | 0 | my $function = shift; | |||
| 87 | 0 | 0 | return $function->(@_); | |||
| 88 | } | |||||
| 89 | ||||||
| 90 | sub _normal_caller (;$) { ## no critic Prototypes | |||||
| 91 | 71 | 44 | my ($height) = @_; | |||
| 92 | 71 | 47 | $height++; | |||
| 93 | 71 | 223 | my @caller = CORE::caller($height); | |||
| 94 | 71 | 107 | if ( CORE::caller() eq 'DB' ) { | |||
| 95 | # Oops, redo picking up @DB::args | |||||
| 96 | package DB; | |||||
| 97 | 0 | 0 | @caller = CORE::caller($height); | |||
| 98 | } | |||||
| 99 | ||||||
| 100 | 71 | 100 | return if ! @caller; # empty | |||
| 101 | 71 | 129 | return $caller[0] if ! wantarray; # scalar context | |||
| 102 | 31 | 65 | return @_ ? @caller : @caller[0..2]; # extra info or regular | |||
| 103 | } | |||||
| 104 | ||||||
| 105 | sub _uplevel_caller (;$) { ## no critic Prototypes | |||||
| 106 | 0 | my $height = $_[0] || 0; | ||||
| 107 | ||||||
| 108 | # shortcut if no uplevels have been called | |||||
| 109 | # always add +1 to CORE::caller (proxy caller function) | |||||
| 110 | # to skip this function's caller | |||||
| 111 | 0 | return $Caller_Proxy->( $height + 1 ) if ! @Up_Frames; | ||||
| 112 | ||||||
| 113 | ||||||
| 114 | 0 | my $saw_uplevel = 0; | ||||
| 115 | 0 | my $adjust = 0; | ||||
| 116 | ||||||
| 117 | # walk up the call stack to fight the right package level to return; | |||||
| 118 | # look one higher than requested for each call to uplevel found | |||||
| 119 | # and adjust by the amount found in the Up_Frames stack for that call. | |||||
| 120 | # We *must* use CORE::caller here since we need the real stack not what | |||||
| 121 | # some other override says the stack looks like, just in case that other | |||||
| 122 | # override breaks things in some horrible way | |||||
| 123 | ||||||
| 124 | 0 | for ( my $up = 0; $up <= $height + $adjust; $up++ ) { | ||||
| 125 | 0 | my @caller = CORE::caller($up + 1); | ||||
| 126 | 0 | if( defined $caller[0] && $caller[0] eq __PACKAGE__ ) { | ||||
| 127 | # add one for each uplevel call seen | |||||
| 128 | # and look into the uplevel stack for the offset | |||||
| 129 | 0 | $adjust += 1 + $Up_Frames[$saw_uplevel]; | ||||
| 130 | 0 | $saw_uplevel++; | ||||
| 131 | } | |||||
| 132 | } | |||||
| 133 | ||||||
| 134 | # For returning values, we pass through the call to the proxy caller | |||||
| 135 | # function, just at a higher stack level | |||||
| 136 | 0 | my @caller = $Caller_Proxy->($height + $adjust + 1); | ||||
| 137 | 0 | if ( CORE::caller() eq 'DB' ) { | ||||
| 138 | # Oops, redo picking up @DB::args | |||||
| 139 | package DB; | |||||
| 140 | 0 | @caller = $Sub::Uplevel::Caller_Proxy->($height + $adjust + 1); | ||||
| 141 | } | |||||
| 142 | ||||||
| 143 | 0 | return if ! @caller; # empty | ||||
| 144 | 0 | return $caller[0] if ! wantarray; # scalar context | ||||
| 145 | 0 | return @_ ? @caller : @caller[0..2]; # extra info or regular | ||||
| 146 | } | |||||
| 147 | ||||||
| 148 | ||||||
| 149 | 1; | |||||
| 150 | ||||||