File Coverage

blib/lib/Aspect/Pointcut.pm
Criterion Covered Total %
statement 78 88 88.6
branch 22 26 84.6
condition 17 30 56.6
subroutine 19 24 79.1
pod 8 10 80.0
total 144 178 80.9


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 26     26   157 use strict;
  26         64  
  26         955  
38 26     26   15905 use Aspect::Pointcut::Or ();
  26         68  
  26         595  
39 26     26   15701 use Aspect::Pointcut::And ();
  26         71  
  26         570  
40 26     26   17875 use Aspect::Pointcut::Not ();
  26         65  
  26         10807  
41              
42             our $VERSION = '1.04';
43              
44             use overload (
45             # Keep traditional Perl boolification and stringification
46             'bool' => sub () { 1 },
47 3     3   21 '""' => sub { ref $_[0] },
48              
49             # Overload bitwise boolean operators to perform logical transformations.
50 13     13   96 '|' => sub { Aspect::Pointcut::Or->new( $_[0], $_[1] ) },
51 57     57   542 '&' => sub { Aspect::Pointcut::And->new( $_[0], $_[1] ) },
52 7     7   1107 '!' => sub { Aspect::Pointcut::Not->new( $_[0] ) },
53              
54             # Everything else should fail to match and throw an exception
55 26     26   174 );
  26         53  
  26         421  
56              
57              
58              
59              
60              
61             ######################################################################
62             # Constructor
63              
64             =pod
65              
66             =head2 new
67              
68             The C constructor creates new pointcut objects.
69              
70             All pointcut classes define their own rules around the parameters that are
71             provided, but once created these pointcuts can then all be mixed together in
72             an arbitrary fashion.
73              
74             Note: Unlike most Perl objects the default and recommended underlying datatype
75             for pointcut objects is an C reference rather than C references.
76             This is done because pointcut code can directly impact the speed of function
77             calls, and so is extremely performance sensitive.
78              
79             =cut
80              
81             sub new {
82 160     160 1 305 my $class = shift;
83 160         1833 bless [ @_ ], $class;
84             }
85              
86              
87              
88              
89              
90             ######################################################################
91             # Weaving Methods
92              
93             my %PRUNE;
94             my %IGNORE;
95             BEGIN {
96             # Classes we should not recurse down into
97 26     26   7374 %PRUNE = map { $_ => 1 } qw{
  130         457  
98             main
99             B
100             CORE
101             DB
102             Aspect
103             };
104              
105             # Classes we should not hook functions in
106 26         76 %IGNORE = map { $_ => 1 } qw{
  598         4202  
107             Aspect
108             B
109             Carp
110             Carp::Heavy
111             Config
112             CORE
113             DB
114             DynaLoader
115             Exporter
116             Exporter::Heavy
117             IO
118             IO::Handle
119             Regexp
120             Sub::Uplevel
121             UNIVERSAL
122             attributes
123             base
124             feature
125             fields
126             lib
127             strict
128             warnings
129             warnings::register
130             };
131             }
132              
133             =pod
134              
135             =head2 match_all
136              
137             my @fully_resolved_function_names = $pointcut->match_all;
138              
139             The C method is the primary compile-time function called on the
140             pointcut model by the core Aspect library.
141              
142             It will examine the list of all loaded functions and identify those which
143             could potentially match, and will need to have hooks installed to intercept
144             calls to those functions.
145              
146             These functions will not necesarily all result in Aspect code being run.
147             Some functions may be called in all cases, but often further run-time
148             analyis needs to be done before we can be sure the particular function call
149             respresents a match.
150              
151             Returns a list of fully-resolved function names
152             (e.g. "Module::Name::function")
153              
154             =cut
155              
156             sub match_all {
157 157     157 1 807 my $self = shift;
158 157         329 my @matches = ();
159              
160             # Curry the pointcut and compile the weave-time function
161 157         647 my $curried = $self->curry_weave;
162 157 50       8409 my $compiled = $curried ? $self->compiled_weave : sub () { 1 };
163 157 50       605 unless ( $compiled ) {
164 0         0 die "Failed to generate weave filter";
165             }
166              
167             # Quick initial root package scan to remove the need
168             # for special-casing of main:: in the recursive scan.
169 26     26   145 no strict 'refs';
  26         58  
  26         35256  
170 157         373 my @search = ();
171 157         1500 my ($key,$value);
172 157         302 while ( ($key,$value) = each %{*{"::"}} ) {
  47138         45461  
  47138         244842  
173 46981 50       111773 next unless defined $value;
174 46981         136347 local (*ENTRY) = $value;
175 46981 100       160279 next unless defined *ENTRY{HASH};
176 11195 100       39541 next unless $key =~ /^([^\W\d]\w*)::\z/;
177              
178             # Suppress aggressively ignored things
179 10253 100 100     36669 if ( $IGNORE{$1} and $PRUNE{$1} ) {
180 628         1019 next;
181             }
182              
183 9625         21483 push @search, $1;
184             }
185              
186             # Search using a simple package list-recursion
187 157         770 while ( my $package = shift @search ) {
188 26     26   266 no strict 'refs';
  26         99  
  26         21963  
189 24237         26349 my ($key,$value);
190 24237         31220 while ( ($key,$value) = each %{*{"$package\::"}} ) {
  398334         462350  
  398334         2865498  
191 374097 100       1416243 next if $key =~ /[^\w:]/;
192 344434 50       673802 next unless defined $value;
193 344434         588268 $_ = "$package\::$key";
194 344434         2326849 local(*ENTRY) = $value;
195              
196             # Is this a matched function?
197 344434 100 100     6673932 if (
      100        
      100        
198             defined *ENTRY{CODE}
199             and
200             not $IGNORE{$package}
201             and
202             not $Aspect::EXPORTED{$_}
203             and
204             $compiled->()
205             ) {
206 193         483 push @matches, $_;
207             }
208              
209             # Is this a package we should recurse into?
210 344434 100 100     3900708 if (
      66        
211             not $PRUNE{$package}
212             and
213             s/::\z//
214             and
215             defined *ENTRY{HASH}
216             ) {
217 14612         30752 push @search, $_;
218             }
219             }
220             }
221              
222 157         3219 return @matches;
223             }
224              
225             =pod
226              
227             =head2 match_define
228              
229             my $should_hook = $pointcut->match_define;
230              
231             At compile time, the only common factor in predicting the future state of
232             a function call is the name of the function itself.
233              
234             The C method is called on the pointcut for each
235             theoretically-matchable function in the entire Perl namespace that part of
236             an ignored namespace, passing a single parameter of the fully-resolved
237             function name.
238              
239             The method will determine if the function B match, and needs to be
240             hooked for further checking at run-time, potentially calling C
241             on child objects as well.
242              
243             Returns true if the function might match the pointcut, or false if the
244             function can never possibly match the pointcut and should never be checked
245             at run-time.
246              
247             =cut
248              
249             sub match_define {
250 0   0 0 1 0 my $class = ref $_[0] || $_[0];
251 0         0 die("Method 'match_define' not implemented in class '$class'");
252             }
253              
254             =pod
255              
256             =head2 compile_weave
257              
258             The C method generates a custom function that is used to test
259             if a particular named function should be hooked as a potential join point.
260              
261             =cut
262              
263             # Most pointcut conditions always match at weave time, so default to that
264             sub compile_weave {
265             return 1;
266             }
267              
268             sub compiled_weave {
269 160     160 0 315 my $self = shift;
270 160         637 my $code = $self->compile_weave;
271 160 100       506 return $code if ref $code;
272 159         13336 return eval "sub () { $code }";
273             }
274              
275             =pod
276              
277             =head2 compile_runtime
278              
279             The C method generates a custom function that is used to test
280             if a particular named function should be hooked as a potential join point.
281              
282             =cut
283              
284             sub compile_runtime {
285 0   0 0 1 0 my $class = ref $_[0] || $_[0];
286 0         0 die "Missing compile_runtime method for $class";
287             }
288              
289             sub compiled_runtime {
290 73     73 0 930 my $self = shift;
291 73         356 my $code = $self->compile_runtime;
292 73 100       324 return $code if ref $code;
293 58         5401 return eval "sub () { $code }";
294             }
295              
296             =pod
297              
298             =head2 match_contains
299              
300             my $calls = $pointcut->match_contains('Aspect::Pointcut::Call');
301              
302             The C method provides a convenience for the validation and
303             optimisation systems. It is used to check for the existance of a particular
304             condition type anywhere within the pointcut object tree.
305              
306             Returns the number of instances of a particular pointcut type within the tree.
307              
308             =cut
309              
310             sub match_contains {
311 545     545 1 856 my $self = shift;
312 545 100       3603 return 1 if $self->isa($_[0]);
313 541         1879 return 0;
314             }
315              
316             =pod
317              
318             =head2 match_always
319              
320             my $always = $pointcut->match_contains('Aspect::Pointcut::Throwing');
321              
322             The C method provides a convenience for the validation and
323             optimisation systems. It is used to check that a particular condition type will
324             be tested at least once for a matching join point, regardless of which path
325             the match takes through branching pointcut logic.
326              
327             Returns true if an expression type is encounter at least once in all branches,
328             or false if there is any branch path that can be taken in which the condition
329             won't be encountered.
330              
331             =cut
332              
333             sub match_always {
334 0     0 1   die "CODE NOT IMPLEMENTED";
335             }
336              
337             =pod
338              
339             =head2 curry_runtime
340              
341             my $optimized_pointcut = $raw_pointcut->curry_runtime;
342              
343             In a production system, pointcut declarations can result in large and
344             complex B object trees.
345              
346             Because this tree can contain a large amount of structure that is no longer
347             relevant at run-time, it can end up making a long series of prohibitively
348             expensive cascading method or function calls before every single regular
349             function call.
350              
351             To reduce this cost down to something more reasonable, pointcuts are run
352             through a currying process (see L).
353              
354             A variety of optimisations are used to simplify boolean nesting, to remove
355             tests that are irrelevant once the compile-time hooks have all been set up,
356             and to remove other tests that the currying process can determine will
357             never need to be tested.
358              
359             The currying process will generate and return a new pointcut tree that is
360             independent from the original, and that can perform a match test at the
361             structurally minimum computational cost.
362              
363             Returns a new optimised B object if any further testing
364             needs to be done at run-time for the pointcut. Returns null (C in
365             scalar context or C<()> in list context) if the pointcut can be curried
366             away to nothing, and no further testing needs to be done at run-time.
367              
368             =cut
369              
370             sub curry_runtime {
371 0   0 0 1   my $class = ref $_[0] || $_[0];
372 0           die("Method 'curry_runtime' not implemented in class '$class'");
373             }
374              
375             =pod
376              
377             =head2 curry_weave
378              
379             The C method is similar to the C method, except
380             that instead of reducing the pointcut to only elements that are relevant at
381             run-time, it reduces the pointcut to only elements that are relevant at weave
382             time.
383              
384             By remove purely run-time elements, the compile weave test code is made both
385             faster and more accurate (some complicated situations can occur when there is
386             a L in the tree).
387              
388             =cut
389              
390             sub curry_weave {
391 0   0 0 1   my $class = ref $_[0] || $_[0];
392 0           die("Method 'curry_weave' not implemented in class '$class'");
393             }
394              
395             sub match_runtime {
396             return 1;
397             }
398              
399              
400              
401              
402              
403             ######################################################################
404             # Optional XS Acceleration
405              
406             BEGIN {
407 26     26   88 local $@;
408 26     26   3443 eval <<'END_PERL';
  26         28963  
  26         144690  
  26         242  
409             use Class::XSAccessor::Array 1.08 {
410             replace => 1,
411             true => [ 'compile_weave', 'match_runtime' ],
412             };
413             END_PERL
414             }
415              
416             1;
417              
418             __END__