File Coverage

blib/lib/Aspect/Advice/Around.pm
Criterion Covered Total %
statement 63 69 91.3
branch 20 24 83.3
condition n/a
subroutine 14 14 100.0
pod n/a
total 97 107 90.6


line stmt bran cond sub pod time code
1             package Aspect::Advice::Around;
2              
3 21     21   84 use strict;
  21         27  
  21         543  
4 21     21   76 use warnings;
  21         22  
  21         487  
5              
6             # Added by eilara as hack around caller() core dump
7             # NOTE: Now we've switched to Sub::Uplevel can this be removed? --ADAMK
8 21     21   95 use Carp::Heavy ();
  21         25  
  21         269  
9 21     21   73 use Carp ();
  21         22  
  21         254  
10 21     21   72 use Sub::Uplevel ();
  21         34  
  21         299  
11 21     21   72 use Aspect::Hook ();
  21         21  
  21         308  
12 21     21   79 use Aspect::Advice ();
  21         30  
  21         467  
13 21     21   8864 use Aspect::Point::Around ();
  21         38  
  21         2471  
14              
15             our $VERSION = '0.97_06';
16             our @ISA = 'Aspect::Advice';
17              
18             sub _install {
19 27     27   39 my $self = shift;
20 27         61 my $pointcut = $self->pointcut;
21 27         96 my $code = $self->code;
22 27         89 my $lexical = $self->lexical;
23              
24             # Get the curried version of the pointcut we will use for the
25             # runtime checks instead of the original.
26             # Because $MATCH_RUN is used in boolean conditionals, if there
27             # is nothing to do the compiler will optimise away the code entirely.
28 27         99 my $curried = $pointcut->curry_runtime;
29 27 100       120 my $compiled = $curried ? $curried->compiled_runtime : undef;
30 27 100       78 my $MATCH_RUN = $compiled ? '$compiled->()' : 1;
31              
32             # When an aspect falls out of scope, we don't attempt to remove
33             # the generated hook code, because it might (for reasons potentially
34             # outside our control) have been recursively hooked several times
35             # by both Aspect and other modules.
36             # Instead, we store an "out of scope" flag that is used to shortcut
37             # past the hook as quickely as possible.
38             # This flag is shared between all the generated hooks for each
39             # installed Aspect.
40             # If the advice is going to last lexical then we don't need to
41             # check or use the $out_of_scope variable.
42 27         34 my $out_of_scope = undef;
43 27 100       62 my $MATCH_DISABLED = $lexical ? '$out_of_scope' : '0';
44              
45             # Find all pointcuts that are statically matched
46             # wrap the method with advice code and install the wrapper
47 27         123 foreach my $name ( $pointcut->match_all ) {
48 32         64 my $NAME = $name; # For completeness
49              
50 21     21   105 no strict 'refs';
  21         28  
  21         1726  
51 32         127 my $original = *$name{CODE};
52 32 50       98 unless ( $original ) {
53 0         0 Carp::croak("Can't wrap non-existent subroutine ", $name);
54             }
55              
56             # Any way to set prototypes other than eval?
57 32         59 my $PROTOTYPE = prototype($original);
58 32 100       101 $PROTOTYPE = defined($PROTOTYPE) ? "($PROTOTYPE)" : '';
59              
60             # Generate the new function
61 21     21   94 no warnings 'redefine';
  21         30  
  21         4395  
62 32 50   19   6079 eval <<"END_PERL"; die $@ if $@;
  32 100       105  
  19 100       2776  
  17 100       27  
  17         107  
  17         78  
  9         25  
  0         0  
  0         0  
  0         0  
  9         30  
  9         92  
63             package Aspect::Hook;
64              
65             *$NAME = sub $PROTOTYPE {
66             # Is this a lexically scoped hook that has finished
67             goto &\$original if $MATCH_DISABLED;
68              
69             # Apply any runtime-specific context checks
70             my \$wantarray = wantarray;
71             local \$_ = bless {
72             sub_name => \$name,
73             wantarray => \$wantarray,
74             args => \\\@_,
75             return_value => \$wantarray ? [ ] : undef,
76             pointcut => \$pointcut,
77             original => \$original,
78             }, 'Aspect::Point::Around';
79              
80             goto &\$original unless $MATCH_RUN;
81              
82             # Array context needs some special return handling
83             if ( \$wantarray ) {
84             # Run the advice code
85             Sub::Uplevel::uplevel(
86             1, \$code, \$_,
87             );
88              
89             # Don't run the original
90             return \@{\$_->{return_value}};
91             }
92              
93             # Scalar and void have the same return handling.
94             Sub::Uplevel::uplevel(
95             1, \$code, \$_,
96             );
97              
98             return \$_->{return_value};
99             };
100             END_PERL
101 32         161 $self->{installed}++;
102             }
103              
104             # If this will run lexical we don't need a descoping hook
105 27 100       130 return unless $lexical;
106              
107             # Return the lexical descoping hook.
108             # This MUST be stored and run at DESTROY-time by the
109             # parent object calling _install. This is less bullet-proof
110             # than the DESTROY-time self-executing blessed coderef
111 15     15   119 return sub { $out_of_scope = 1 };
  15         97  
112             }
113              
114             # Check for pointcut usage not supported by the advice type
115             sub _validate {
116 27     27   42 my $self = shift;
117 27         149 my $pointcut = $self->pointcut;
118              
119             # Pointcuts using "throwing" are irrelevant in before advice
120 27 50       183 if ( $pointcut->match_contains('Aspect::Pointcut::Throwing') ) {
121 0         0 return 'The pointcut throwing is illegal when used by around advice';
122             }
123              
124             # Pointcuts using "throwing" are irrelevant in before advice
125 27 50       81 if ( $pointcut->match_contains('Aspect::Pointcut::Returning') ) {
126 0         0 return 'The pointcut returning is illegal when used by around advice';
127             }
128              
129 27         149 $self->SUPER::_validate(@_);
130             }
131              
132             1;
133              
134             =pod
135              
136             =head1 NAME
137              
138             Aspect::Advice::Around - Execute code both before and after a function
139              
140             =head1 SYNOPSIS
141              
142             use Aspect;
143            
144             around {
145             # Trace all calls to your module
146             print STDERR "Called my function " . $_->sub_name . "\n";
147            
148             # Lexically alter a global for this function
149             local $MyModule::MAXSIZE = 1000;
150            
151             # Continue and execute the function
152             $_->run_original;
153            
154             # Suppress exceptions for the call
155             $_->return_value(1) if $_->exception;
156            
157             } call qr/^ MyModule::\w+ $/;
158              
159             =head1 DESCRIPTION
160              
161             The C advice type is used to execute code on either side of a
162             function, allowing deep and precise control of how the function will be
163             called when none of the other advice types are good enough.
164              
165             Using C advice is also critical if you want to lexically alter
166             the environment in which the call will be made (as in the example above
167             where a global variable is temporarily changed).
168              
169             This advice type is also the most computationally expensive to run, so if
170             your problem can be solved with the use of a different advice type,
171             particularly C, you should use that instead.
172              
173             Please note that unlike the other advice types, your code in C is
174             required to trigger the execution of the target function yourself with the
175             C method. If you do not C and also do not set either a
176             C or C, the function call will return C
177             in scalar context or the null list C<()> in list context.
178              
179             =head1 AUTHORS
180              
181             Adam Kennedy Eadamk@cpan.orgE
182              
183             =head1 COPYRIGHT AND LICENSE
184              
185             Copyright 2010 Adam Kennedy.
186              
187             This library is free software; you can redistribute it and/or modify
188             it under the same terms as Perl itself.
189              
190             =cut