File Coverage

blib/lib/Aspect/Pointcut.pm
Criterion Covered Total %
statement 81 91 89.0
branch 22 26 84.6
condition 16 30 53.3
subroutine 20 25 80.0
pod 8 10 80.0
total 147 182 80.7


line stmt bran cond sub pod time code
1             package Aspect::Pointcut;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Aspect::Pointcut - API for determining which events should be hooked
8              
9             =head1 DESCRIPTION
10              
11             Aspect-Oriented Programming implementations draw much of their power from the
12             flexibility that can be applied to when a function call should or should not
13             be hooked.
14              
15             B provides a robust and powerful API for defining the rules
16             for when a function call should be hooked, and then applying the rules as
17             optimally as possible. This optimisation is particularly important for any
18             pure-Perl implementation, which cannot hook deeply into the underlying
19             virtual machine as you might with a Java or Perl XS-based implementation.
20              
21             A running program can be seen as a collection of events. Events like a
22             sub returning from a call, or a package being used. These are called join
23             points. A pointcut defines a set of join points, taken from all the join
24             points in the program. Different pointcut classes allow you to define the
25             set in different ways, so you can target the exact join points you need.
26              
27             Pointcuts are constructed as trees; logical operations on pointcuts with
28             one or two arguments (not, and, or) are themselves pointcut operators.
29             You can construct them explicitly using object syntax, or you can use the
30             convenience functions exported by Aspect and the overloaded operators
31             C, C<&> and C<|>.
32              
33             =head1 METHODS
34              
35             =cut
36              
37 21     21   1194 use strict;
  21         34  
  21         2352  
38 21     21   921 use warnings;
  21         26  
  21         2764  
39 21     21   11426 use Aspect::Pointcut::Or ();
  21         41  
  21         444  
40 21     21   8171 use Aspect::Pointcut::And ();
  21         35  
  21         484  
41 21     21   7429 use Aspect::Pointcut::Not ();
  21         42  
  21         3071  
42              
43             our $VERSION = '0.97_06';
44              
45             use overload (
46             # Keep traditional Perl boolification and stringification
47             'bool' => sub () { 1 },
48 3     3   15 '""' => sub { ref $_[0] },
49              
50             # Overload bitwise boolean operators to perform logical transformations.
51 13     13   49 '|' => sub { Aspect::Pointcut::Or->new( $_[0], $_[1] ) },
52 46     46   288 '&' => sub { Aspect::Pointcut::And->new( $_[0], $_[1] ) },
53 6     6   638 '!' => sub { Aspect::Pointcut::Not->new( $_[0] ) },
54              
55             # Everything else should fail to match and throw an exception
56 21     21   107 );
  21         19  
  21         311  
57              
58              
59              
60              
61              
62             ######################################################################
63             # Constructor
64              
65             =pod
66              
67             =head2 new
68              
69             The C constructor creates new pointcut objects.
70              
71             All pointcut classes define their own rules around the parameters that are
72             provided, but once created these pointcuts can then all be mixed together in
73             an arbitrary fashion.
74              
75             Note: Unlike most Perl objects the default and recommended underlying datatype
76             for pointcut objects is an C reference rather than C references.
77             This is done because pointcut code can directly impact the speed of function
78             calls, and so is extremely performance sensitive.
79              
80             =cut
81              
82             sub new {
83 146     146 1 195 my $class = shift;
84 146         1053 bless [ @_ ], $class;
85             }
86              
87              
88              
89              
90              
91             ######################################################################
92             # Weaving Methods
93              
94             my %PRUNE;
95             my %IGNORE;
96             BEGIN {
97             # Classes we should not recurse down into
98 21     21   3931 %PRUNE = map { $_ => 1 } qw{
  84         175  
99             main
100             CORE
101             DB
102             Aspect
103             };
104              
105             # Classes we should not hook functions in
106 21         34 %IGNORE = map { $_ => 1 } qw{
  462         2286  
107             Aspect
108             Carp
109             Carp::Heavy
110             Config
111             CORE
112             DB
113             DynaLoader
114             Exporter
115             Exporter::Heavy
116             IO
117             IO::Handle
118             Regexp
119             Sub::Uplevel
120             UNIVERSAL
121             attributes
122             base
123             feature
124             fields
125             lib
126             strict
127             warnings
128             warnings::register
129             };
130             }
131              
132             =pod
133              
134             =head2 match_all
135              
136             my @fully_resolved_function_names = $pointcut->match_all;
137              
138             The C method is the primary compile-time function called on the
139             pointcut model by the core Aspect library.
140              
141             It will examine the list of all loaded functions and identify those which
142             could potentially match, and will need to have hooks installed to intercept
143             calls to those functions.
144              
145             These functions will not necesarily all result in Aspect code being run.
146             Some functions may be called in all cases, but often further run-time
147             analyis needs to be done before we can be sure the particular function call
148             respresents a match.
149              
150             Returns a list of fully-resolved function names
151             (e.g. "Module::Name::function")
152              
153             =cut
154              
155             sub match_all {
156 117     117 1 535 my $self = shift;
157 117         248 my @matches = ();
158              
159             # Curry the pointcut and compile the weave-time function
160 117         438 my $curried = $self->curry_weave;
161 117 50       2357 my $compiled = $curried ? $self->compiled_weave : sub () { 1 };
162 117 50       377 unless ( $compiled ) {
163 0         0 die "Failed to generate weave filter";
164             }
165              
166             # Quick initial root package scan to remove the need
167             # for special-casing of main:: in the recursive scan.
168 21     21   103 no strict 'refs';
  21         20  
  21         4598  
169 117         226 my @search = ();
170 117         153 my ($key,$value);
171 117         180 while ( ($key,$value) = each %{*{"::"}} ) {
  31421         17596  
  31421         95980  
172 31304 50       40245 next unless defined $value;
173 31304         38061 local (*ENTRY) = $value;
174 31304 100       47496 next unless defined *ENTRY{HASH};
175 8293 100       18951 next unless $key =~ /^([^\W\d]\w*)::\z/;
176              
177             # Suppress aggressively ignored things
178 7591 100 66     14490 if ( $IGNORE{$1} and $PRUNE{$1} ) {
179 351         446 next;
180             }
181              
182 7240         9801 push @search, $1;
183             }
184              
185             # Search using a simple package list-recursion
186 117         422 while ( my $package = shift @search ) {
187 21     21   129 no strict 'refs';
  21         32  
  21         12411  
188 23393         18222 my ($key,$value);
189 23393         16065 while ( ($key,$value) = each %{*{"$package\::"}} ) {
  472294         293674  
  472294         1906107  
190 448901 100       934084 next if $key =~ /[^\w:]/;
191 430293 50       514772 next unless defined $value;
192 430293         435301 $_ = "$package\::$key";
193 430293         867780 local(*ENTRY) = $value;
194              
195             # Is this a matched function?
196 430293 100 100     6365989 if (
      100        
      100        
197             defined *ENTRY{CODE}
198             and
199             not $IGNORE{$package}
200             and
201             not $Aspect::EXPORTED{$_}
202             and
203             $compiled->()
204             ) {
205 137         269 push @matches, $_;
206             }
207              
208             # Is this a package we should recurse into?
209 430293 100 100     2077842 if (
      66        
210             not $PRUNE{$package}
211             and
212             s/::\z//
213             and
214             defined *ENTRY{HASH}
215             ) {
216 16153         22491 push @search, $_;
217             }
218             }
219             }
220              
221 117         2085 return @matches;
222             }
223              
224             =pod
225              
226             =head2 match_define
227              
228             my $should_hook = $pointcut->match_define;
229              
230             At compile time, the only common factor in predicting the future state of
231             a function call is the name of the function itself.
232              
233             The C method is called on the pointcut for each
234             theoretically-matchable function in the entire Perl namespace that part of
235             an ignored namespace, passing a single parameter of the fully-resolved
236             function name.
237              
238             The method will determine if the function B match, and needs to be
239             hooked for further checking at run-time, potentially calling C
240             on child objects as well.
241              
242             Returns true if the function might match the pointcut, or false if the
243             function can never possibly match the pointcut and should never be checked
244             at run-time.
245              
246             =cut
247              
248             sub match_define {
249 0   0 0 1 0 my $class = ref $_[0] || $_[0];
250 0         0 die("Method 'match_define' not implemented in class '$class'");
251             }
252              
253             =pod
254              
255             =head2 compile_weave
256              
257             The C method generates a custom function that is used to test
258             if a particular named function should be hooked as a potential join point.
259              
260             =cut
261              
262             # Most pointcut conditions always match at weave time, so default to that
263             sub compile_weave {
264             return 1;
265             }
266              
267             sub compiled_weave {
268 120     120 0 193 my $self = shift;
269 120         572 my $code = $self->compile_weave;
270 120 100       286 return $code if ref $code;
271 119         8303 return eval "sub () { $code }";
272             }
273              
274             =pod
275              
276             =head2 compile_runtime
277              
278             The C method generates a custom function that is used to test
279             if a particular named function should be hooked as a potential join point.
280              
281             =cut
282              
283             sub compile_runtime {
284 0   0 0 1 0 my $class = ref $_[0] || $_[0];
285 0         0 die "Missing compile_runtime method for $class";
286             }
287              
288             sub compiled_runtime {
289 63     63 0 949 my $self = shift;
290 63         269 my $code = $self->compile_runtime;
291 63 100       233 return $code if ref $code;
292 51         4431 return eval "sub () { $code }";
293             }
294              
295             =pod
296              
297             =head2 match_contains
298              
299             my $calls = $pointcut->match_contains('Aspect::Pointcut::Call');
300              
301             The C method provides a convenience for the validation and
302             optimisation systems. It is used to check for the existance of a particular
303             condition type anywhere within the pointcut object tree.
304              
305             Returns the number of instances of a particular pointcut type within the tree.
306              
307             =cut
308              
309             sub match_contains {
310 398     398 1 379 my $self = shift;
311 398 100       1467 return 1 if $self->isa($_[0]);
312 394         854 return 0;
313             }
314              
315             =pod
316              
317             =head2 match_always
318              
319             my $always = $pointcut->match_contains('Aspect::Pointcut::Throwing');
320              
321             The C method provides a convenience for the validation and
322             optimisation systems. It is used to check that a particular condition type will
323             be tested at least once for a matching join point, regardless of which path
324             the match takes through branching pointcut logic.
325              
326             Returns true if an expression type is encounter at least once in all branches,
327             or false if there is any branch path that can be taken in which the condition
328             won't be encountered.
329              
330             =cut
331              
332             sub match_always {
333 0     0 1   die "CODE NOT IMPLEMENTED";
334             }
335              
336             =pod
337              
338             =head2 curry_runtime
339              
340             my $optimized_pointcut = $raw_pointcut->curry_runtime;
341              
342             In a production system, pointcut declarations can result in large and
343             complex B object trees.
344              
345             Because this tree can contain a large amount of structure that is no longer
346             relevant at run-time, it can end up making a long series of prohibitively
347             expensive cascading method or function calls before every single regular
348             function call.
349              
350             To reduce this cost down to something more reasonable, pointcuts are run
351             through a currying process (see L).
352              
353             A variety of optimisations are used to simplify boolean nesting, to remove
354             tests that are irrelevant once the compile-time hooks have all been set up,
355             and to remove other tests that the currying process can determine will
356             never need to be tested.
357              
358             The currying process will generate and return a new pointcut tree that is
359             independent from the original, and that can perform a match test at the
360             structurally minimum computational cost.
361              
362             Returns a new optimised B object if any further testing
363             needs to be done at run-time for the pointcut. Returns null (C in
364             scalar context or C<()> in list context) if the pointcut can be curried
365             away to nothing, and no further testing needs to be done at run-time.
366              
367             =cut
368              
369             sub curry_runtime {
370 0   0 0 1   my $class = ref $_[0] || $_[0];
371 0           die("Method 'curry_runtime' not implemented in class '$class'");
372             }
373              
374             =pod
375              
376             =head2 curry_weave
377              
378             The C method is similar to the C method, except
379             that instead of reducing the pointcut to only elements that are relevant at
380             run-time, it reduces the pointcut to only elements that are relevant at weave
381             time.
382              
383             By remove purely run-time elements, the compile weave test code is made both
384             faster and more accurate (some complicated situations can occur when there is
385             a L in the tree).
386              
387             =cut
388              
389             sub curry_weave {
390 0   0 0 1   my $class = ref $_[0] || $_[0];
391 0           die("Method 'curry_weave' not implemented in class '$class'");
392             }
393              
394             sub match_runtime {
395             return 1;
396             }
397              
398              
399              
400              
401              
402             ######################################################################
403             # Optional XS Acceleration
404              
405             BEGIN {
406 21     21   40 local $@;
407 21     21   1660 eval <<'END_PERL';
  21         10610  
  21         72290  
  21         138  
408             use Class::XSAccessor::Array 1.08 {
409             replace => 1,
410             true => [ 'compile_weave', 'match_runtime' ],
411             };
412             END_PERL
413             }
414              
415             1;
416              
417             __END__