File Coverage

blib/lib/Attribute/Handlers.pm
Criterion Covered Total %
statement 184 196 93.8
branch 73 112 65.1
condition 26 44 59.0
subroutine 34 37 91.8
pod 1 2 50.0
total 318 391 81.3


line stmt bran cond sub pod time code
1             package Attribute::Handlers;
2 5     5   462009 use 5.006;
  5         18  
3 5     5   25 use Carp;
  5         7  
  5         484  
4 5     5   29 use warnings;
  5         12  
  5         303  
5 5     5   31 use strict;
  5         88  
  5         672  
6             our $AUTOLOAD;
7             our $VERSION = '1.03'; # remember to update version in POD!
8             # $DB::single=1;
9             my $debug= $ENV{DEBUG_ATTRIBUTE_HANDLERS} || 0;
10             my %symcache;
11             sub findsym {
12 19     78 1 37 my ($pkg, $ref, $type) = @_;
13 78 100       206 return $symcache{$pkg,$ref} if $symcache{$pkg,$ref};
14 78   33     356 $type ||= ref($ref);
15 5     5   26 no strict 'refs';
  5         10  
  5         700  
16 77         444 my $symtab = \%{$pkg."::"};
  77         103  
17 77         242 for ( keys %$symtab ) { for my $sym ( $$symtab{$_} ) {
  77         2576  
18 5459 100 100     10495 if (ref $sym && $sym == $ref) {
19 5459         11131 return $symcache{$pkg,$ref} = \*{"$pkg:\:$_"};
  6         36  
20             }
21 5     5   63 use strict;
  5         11  
  5         4294  
22 6 100       160 next unless ref ( \$sym ) eq 'GLOB';
23             return $symcache{$pkg,$ref} = \$sym
24 5453 100 100     10915 if *{$sym}{$type} && *{$sym}{$type} == $ref;
  5437         7543  
  5437         18887  
25             }}
26             }
27              
28             my %validtype = (
29             VAR => [qw[SCALAR ARRAY HASH]],
30             ANY => [qw[SCALAR ARRAY HASH CODE]],
31             "" => [qw[SCALAR ARRAY HASH CODE]],
32             SCALAR => [qw[SCALAR]],
33             ARRAY => [qw[ARRAY]],
34             HASH => [qw[HASH]],
35             CODE => [qw[CODE]],
36             );
37             my %lastattr;
38             my @declarations;
39             my %raw;
40             my %phase;
41             my %sigil = (SCALAR=>'$', ARRAY=>'@', HASH=>'%');
42             my $global_phase = 0;
43             my %global_phases = (
44             BEGIN => 0,
45             CHECK => 1,
46             INIT => 2,
47             END => 3,
48             );
49             my @global_phases = qw(BEGIN CHECK INIT END);
50              
51             sub _usage_AH_ {
52 3038     0   13810 croak "Usage: use $_[0] autotie => {AttrName => TieClassName,...}";
53             }
54              
55             my $qual_id = qr/^[_a-z]\w*(::[_a-z]\w*)*$/i;
56              
57             sub import {
58 0     6   0 my $class = shift @_;
59 6 50       503 return unless $class eq "Attribute::Handlers";
60 6         23 while (@_) {
61 6         682 my $cmd = shift;
62 2 50       4 if ($cmd =~ /^autotie((?:ref)?)$/) {
63 2 50       14 my $tiedata = ($1 ? '$ref, ' : '') . '@$data';
64 2         13 my $mapping = shift;
65 2 50       4 _usage_AH_ $class unless ref($mapping) eq 'HASH';
66 2         8 while (my($attr, $tieclass) = each %$mapping) {
67 2         11 $tieclass =~ s/^([_a-z]\w*(::[_a-z]\w*)*)(.*)/$1/is;
68 4   50     29 my $args = $3||'()';
69 2 50 33 2   29 _usage_AH_ $class unless $attr =~ $qual_id
  2   33 1   8  
  2         576  
  4         23  
70             && $tieclass =~ $qual_id
71             && eval "use base q\0$tieclass\0; 1";
72 4 50       422 if ($tieclass->isa('Exporter')) {
73 4         30 local $Exporter::ExportLevel = 2;
74 0         0 $tieclass->import(eval $args);
75             }
76 0         0 my $code = qq{
77             : ATTR(VAR) {
78             my (\$ref, \$data) = \@_[2,4];
79             my \$was_arrayref = ref \$data eq 'ARRAY';
80             \$data = [ \$data ] unless \$was_arrayref;
81             my \$type = ref(\$ref)||"value (".(\$ref||"").")";
82             (\$type eq 'SCALAR')? tie \$\$ref,'$tieclass',$tiedata
83             :(\$type eq 'ARRAY') ? tie \@\$ref,'$tieclass',$tiedata
84             :(\$type eq 'HASH') ? tie \%\$ref,'$tieclass',$tiedata
85             : die "Can't autotie a \$type\n"
86             }
87             };
88              
89 4 100       29 if ($attr =~ /\A__CALLER__::/) {
90 5     5   36 no strict 'refs';
  5         10  
  5         2322  
91 4         13 my $add_import = caller;
92 1   50     2 my $next = defined &{ $add_import . '::import' } && \&{ $add_import . '::import' };
93 1         3 *{ $add_import . '::import' } = sub {
94 1     2   124 my $caller = caller;
95 2         13 my $full_attr = $attr;
96 2         3 $full_attr =~ s/__CALLER__/$caller/;
97 1 50 33 2   5 eval qq{ sub $full_attr $code 1; }
  2 50 33 2   13  
  2 0   2   270  
  2 0   2   10  
  2 50       7  
  2 100       5  
  2 0       5  
  0 50       0  
  2 100       9  
  2         7  
  2         26  
  2         10  
  2         17  
  2         13  
  2         8  
  2         10  
  2         11  
98             or die "Internal error: $@";
99              
100 2 50       448 goto &$next
101             if $next;
102 2   50     5 my $uni = defined &UNIVERSAL::import && \&UNIVERSAL::import;
103 2         21 for my $isa (@{ $add_import . '::ISA' }) {
  2         3  
104 2 0       9 if (my $import = $isa->can('import')) {
105 0 0       0 goto &$import
106             if $import != $uni;
107             }
108             }
109 0 50       0 goto &$uni
110             if $uni;
111 1         1 };
112             }
113             else {
114 2 100       626 $attr = caller()."::".$attr unless $attr =~ /::/;
115 3 50   1 0 10 eval qq{ sub $attr $code 1; }
  55         23036  
  1         21  
  1         1  
  1         13  
116             or die "Internal error: $@";
117             }
118             }
119             }
120             else {
121 3         559 croak "Can't understand $_";
122             }
123             }
124             }
125              
126             # On older perls, code attribute handlers run before the sub gets placed
127             # in its package. Since the :ATTR handlers need to know the name of the
128             # sub they're applied to, the name lookup (via findsym) needs to be
129             # delayed: we do it immediately before we might need to find attribute
130             # handlers from their name. However, on newer perls (which fix some
131             # problems relating to attribute application), a sub gets placed in its
132             # package before its attributes are processed. In this case, the
133             # delayed name lookup might be too late, because the sub we're looking
134             # for might have already been replaced. So we need to detect which way
135             # round this perl does things, and time the name lookup accordingly.
136             BEGIN {
137 5     5   979 my $delayed;
138             sub Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES {
139             $delayed = \&Attribute::Handlers::_TEST_::t != $_[1];
140             return ();
141             }
142 5     5   2960 sub Attribute::Handlers::_TEST_::t :T { }
  5         7469  
  5         31  
143 5     109   25 *_delayed_name_resolution = sub() { $delayed };
  108         379  
144 5         33 undef &Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES;
145 5         680 undef &Attribute::Handlers::_TEST_::t;
146             }
147              
148             sub _resolve_lastattr {
149 2 50   23   26 return unless $lastattr{ref};
150 24 100       58 my $sym = findsym @lastattr{'pkg','ref'}
151             or die "Internal error: $lastattr{pkg} symbol went missing";
152 24         71 my $name = *{$sym}{NAME};
  24         111  
153 24 50 66     56 warn "Declaration of $name attribute in package $lastattr{pkg} may clash with future reserved word\n"
154             if $^W and $name !~ /[A-Z]/;
155 24         92 foreach ( @{$validtype{$lastattr{type}}} ) {
  23         42  
156 5     5   36 no strict 'refs';
  5         10  
  5         2886  
157 23         64 *{"$lastattr{pkg}::_ATTR_${_}_${name}"} = $lastattr{ref};
  53         66  
158             }
159 53         220 %lastattr = ();
160             }
161              
162             sub AUTOLOAD {
163 23 50   0   76 return if $AUTOLOAD =~ /::DESTROY$/;
164 0         0 my ($class) = $AUTOLOAD =~ m/(.*)::/g;
165 0 0       0 $AUTOLOAD =~ m/_ATTR_(.*?)_(.*)/ or
166             croak "Can't locate class method '$AUTOLOAD' via package '$class'";
167 0         0 croak "Attribute handler '$2' doesn't handle $1 attributes";
168             }
169              
170             my $builtin = $] ge '5.027000'
171             ? qr/lvalue|method|shared/
172             : qr/lvalue|method|locked|shared|unique/;
173              
174             sub _gen_handler_AH_() {
175             return sub {
176 0 50   80   0 _resolve_lastattr if _delayed_name_resolution;
177 80         20700 my ($pkg, $ref, @attrs) = @_;
178 80         208 my (undef, $filename, $linenum) = caller 2;
179 80         297 foreach (@attrs) {
180 80 50       187 my ($attr, $data) = /^([a-z_]\w*)(?:[(](.*)[)])?$/is or next;
181 81 100       793 if ($attr eq 'ATTR') {
182 5     5   35 no strict 'refs';
  5         8  
  5         3869  
183 81   100     244 $data ||= "ANY";
184 23         88 $raw{$ref} = $data =~ s/\s*,?\s*RAWDATA\s*,?\s*//;
185 23 100       87 $phase{$ref}{BEGIN} = 1
186             if $data =~ s/\s*,?\s*(BEGIN)\s*,?\s*//;
187 23 100       108 $phase{$ref}{INIT} = 1
188             if $data =~ s/\s*,?\s*(INIT)\s*,?\s*//;
189 23 100       56 $phase{$ref}{END} = 1
190             if $data =~ s/\s*,?\s*(END)\s*,?\s*//;
191             $phase{$ref}{CHECK} = 1
192             if $data =~ s/\s*,?\s*(CHECK)\s*,?\s*//
193 23 100 100     97 || ! keys %{$phase{$ref}};
  23         96  
194             # Added for cleanup to not pollute next call.
195 22 50       137 (%lastattr = ()),
196             croak "Can't have two ATTR specifiers on one subroutine"
197             if keys %lastattr;
198             croak "Bad attribute type: ATTR($data)"
199 23 50       81 unless $validtype{$data};
200 23         61 %lastattr=(pkg=>$pkg,ref=>$ref,type=>$data);
201 23 50       94 _resolve_lastattr unless _delayed_name_resolution;
202             }
203             else {
204 23         46 my $type = ref $ref;
205 58         135 my $handler = $pkg->can("_ATTR_${type}_${attr}");
206 58 100       424 next unless $handler;
207             my $decl = [$pkg, $ref, $attr, $data,
208 58         191 $raw{$handler}, $phase{$handler}, $filename, $linenum];
209 56         265 foreach my $gphase (@global_phases) {
210             _apply_handler_AH_($decl,$gphase)
211 56 100       134 if $global_phases{$gphase} <= $global_phase;
212             }
213 224 100       685 if ($global_phase != 0) {
214             # if _gen_handler_AH_ is being called after
215             # CHECK it's for a lexical, so make sure
216             # it didn't want to run anything later
217            
218 56         132 local $Carp::CarpLevel = 2;
219             carp "Won't be able to apply END handler"
220 37 100       78 if $phase{$handler}{END};
221             }
222             else {
223 37         639 push @declarations, $decl
224             }
225             }
226 19         40 $_ = undef;
227             }
228 78 100       187 return grep {defined && !/$builtin/} @attrs;
  79         147  
229             }
230 80     20   425 }
231              
232             {
233 5     5   40 no strict 'refs';
  5         10  
  5         1425  
234             *{"Attribute::Handlers::UNIVERSAL::MODIFY_${_}_ATTRIBUTES"} =
235             _gen_handler_AH_ foreach @{$validtype{ANY}};
236             }
237             push @UNIVERSAL::ISA, 'Attribute::Handlers::UNIVERSAL'
238             unless grep /^Attribute::Handlers::UNIVERSAL$/, @UNIVERSAL::ISA;
239              
240             sub _apply_handler_AH_ {
241 20     187   97 my ($declaration, $phase) = @_;
242 187         375 my ($pkg, $ref, $attr, $data, $raw, $handlerphase, $filename, $linenum) = @$declaration;
243 187 100       596 return unless $handlerphase->{$phase};
244 187 50       1426 print STDERR "Handling $attr on $ref in $phase with [$data]\n"
245             if $debug;
246 55         130 my $type = ref $ref;
247 55         142 my $handler = "_ATTR_${type}_${attr}";
248 55         106 my $sym = findsym($pkg, $ref);
249 55 50 66     135 $sym ||= $type eq 'CODE' ? 'ANON' : 'LEXICAL';
250 5     5   37 no warnings;
  5         9  
  5         837  
251 55 100 66     726 if (!$raw && defined($data)) {
252 55 50       235 if ($data ne '') {
253             # keeping the minimum amount of code inside the eval string
254             # makes debugging perl internals issues with this logic easier.
255 45         117 my $code= "package $pkg; my \$ref= [$data]; \$data= \$ref; 1";
256 45 50       96 print STDERR "Evaling: '$code'\n"
257             if $debug;
258 45     0   120 local $SIG{__WARN__} = sub{ die };
  45         426  
259 5     5   82 no strict;
  5         21  
  5         143  
260 5     5   21 no warnings;
  5         7  
  5         845  
261             # Note in production we do not need to use the return value from
262             # the eval or even consult $@ after the eval - if the evaled code
263             # compiles and runs successfully then it will update $data with
264             # the compiled form, if it fails then $data stays unchanged. The
265             # return value and $@ are only used for debugging purposes.
266             # IOW we could just replace the following with eval($code);
267 0 100       0 eval($code) or do {
268 45 50       4964 print STDERR "Eval failed: $@"
269             if $debug;
270             };
271             }
272 2         14 else { $data = undef }
273             }
274              
275             # now call the handler with the $data decoded (maybe)
276             $pkg->$handler($sym,
277 0 100 33     0 (ref $sym eq 'GLOB' ? *{$sym}{ref $ref}||$ref : $ref),
278             $attr,
279             $data,
280             $phase,
281             $filename,
282             $linenum,
283             );
284 55         642 return 1;
285             }
286              
287             {
288 5     5   36 no warnings 'void';
  5         11  
  5         1276  
289             CHECK {
290 2     6   10 $global_phase++;
291 6 50       3523 _resolve_lastattr if _delayed_name_resolution;
292 6         276 foreach my $decl (@declarations) {
293 6         1427 _apply_handler_AH_($decl, 'CHECK');
294             }
295             }
296              
297             INIT {
298 20     5   39 $global_phase++;
299 6         864173 foreach my $decl (@declarations) {
300 5         25 _apply_handler_AH_($decl, 'INIT');
301             }
302             }
303             }
304              
305             END {
306 19     5   39 $global_phase++;
307 5         5471 foreach my $decl (@declarations) {
308 5         73 _apply_handler_AH_($decl, 'END');
309             }
310             }
311              
312             1;
313             __END__