File Coverage

blib/lib/Attribute/Handlers/Clean.pm
Criterion Covered Total %
statement 364 376 96.8
branch 68 108 62.9
condition 27 49 55.1
subroutine 96 97 98.9
pod 1 1 100.0
total 556 631 88.1


line stmt bran cond sub pod time code
1             package Attribute::Handlers::Clean;
2 4     4   211958 use 5.010;
  4         37  
3 4     4   25 use Carp;
  4         8  
  4         285  
4 4     4   26 use warnings;
  4         8  
  4         124  
5 4     4   23 use strict;
  4         7  
  4         90  
6 4     4   2351 use attributes ();
  4         5099  
  4         98  
7 4     4   2336 use Perlmazing ();
  4         843597  
  4         357  
8            
9             our $VERSION = '1.06'; # remember to update version in POD!
10             our $AUTOLOAD;
11            
12             my %symcache;
13             sub findsym {
14 74     74 1 170 my ($pkg, $ref, $type) = @_;
15 74 100       284 return $symcache{$pkg,$ref} if $symcache{$pkg,$ref};
16 73   33     368 $type ||= ref($ref);
17 4     4   37 no strict 'refs';
  4         9  
  4         436  
18 73         103 my $symtab = \%{$pkg."::"};
  73         207  
19 73         2081 for (keys %$symtab) {
20 7569         13137 for my $sym ($$symtab{$_}) {
21 7569 50 66     13646 if (ref $sym && $sym == $ref) {
22 0         0 return $symcache{$pkg,$ref} = \*{"$pkg:\:$_"};
  0         0  
23             }
24 4     4   29 use strict;
  4         8  
  4         1524  
25 7569 100       13690 next unless ref (\$sym) eq 'GLOB';
26             return $symcache{$pkg,$ref} = \$sym
27 7563 100 100     10192 if *{$sym}{$type} && *{$sym}{$type} == $ref;
  7563         23212  
  3674         15505  
28             }
29             }
30             }
31            
32             my %validtype = (
33             VAR => [qw[SCALAR ARRAY HASH]],
34             ANY => [qw[SCALAR ARRAY HASH CODE]],
35             "" => [qw[SCALAR ARRAY HASH CODE]],
36             SCALAR => [qw[SCALAR]],
37             ARRAY => [qw[ARRAY]],
38             HASH => [qw[HASH]],
39             CODE => [qw[CODE]],
40             );
41             my %lastattr;
42             my @declarations;
43             my %raw;
44             my %phase;
45             my %sigil = (SCALAR=>'$', ARRAY=>'@', HASH=>'%');
46             my $global_phase = 0;
47             my %global_phases = (
48             BEGIN => 0,
49             CHECK => 1,
50             INIT => 2,
51             END => 3,
52             );
53             my @global_phases = qw(BEGIN CHECK INIT END);
54            
55             sub _usage_AH_ {
56 0     0   0 croak "Usage: use $_[0] autotie => {AttrName => TieClassName,...}";
57             }
58            
59             my $qual_id = qr/^[_a-z]\w*(::[_a-z]\w*)*$/i;
60            
61             sub import {
62 7     7   1114 my $class = shift @_;
63             {
64 7         12 my @caller_0 = caller 0;
  7         45  
65 7         37 my @caller_1 = caller 1;
66             # Allow just calling "package->import" inside of subclses own import method, to mean their caller should get the same import effect from this module - in the same style of warnings->unimport
67 7 50 33     72 my $caller = (@caller_1 and $caller_1[3] eq "$caller_0[0]::import") ? $caller_1[0] : $caller_0[0];
68 4     4   55 no strict 'refs';
  4         12  
  4         2646  
69 7         98 warnings->unimport('reserved');
70 7 100 66     40 push (@{"${caller}::ISA"}, $class) unless $caller eq $class or grep {$_ eq $class} @{"${caller}::ISA"};
  6         81  
  3         13  
  7         52  
71 7         18 for (@{$validtype{ANY}}) {
  7         20  
72 28 100       47 *{"${caller}::MODIFY_${_}_ATTRIBUTES"} = _gen_handler_AH_() unless defined *{"${caller}::MODIFY_${_}_ATTRIBUTES"};
  20         90  
  28         127  
73             }
74             }
75 7 100       165 return unless $class eq __PACKAGE__;
76 6         586 while (@_) {
77 1         2 my $cmd = shift;
78 1 50       18 if ($cmd =~ /^autotie((?:ref)?)$/) {
79 1 50       7 my $tiedata = ($1 ? '$ref, ' : '') . '@$data';
80 1         3 my $mapping = shift;
81 1 50       4 _usage_AH_ $class unless ref($mapping) eq 'HASH';
82 1         12 while (my($attr, $tieclass) = each %$mapping) {
83 3         37 $tieclass =~ s/^([_a-z]\w*(::[_a-z]\w*)*)(.*)/$1/is;
84 3   50     16 my $args = $3||'()';
85 3 50 33 1   238 _usage_AH_ $class unless $attr =~ $qual_id && $tieclass =~ $qual_id && eval "use base q\0$tieclass\0; 1";
  1   33 1   7  
  1     1   3  
  1         31  
  1         42  
  1         7  
  1         2  
  1         2  
  1         50  
  1         7  
86 3 50       26 if ($tieclass->isa('Exporter')) {
87 0         0 local $Exporter::ExportLevel = 2;
88 0         0 $tieclass->import(eval $args);
89             }
90 3         7 $attr =~ s/__CALLER__/caller(1)/e;
  0         0  
91 3 100       15 $attr = caller()."::".$attr unless $attr =~ /::/;
92 3 50       526 eval '# line '.__LINE__.' "'.__FILE__.qq{"
93 1         8 sub $attr : ATTR(VAR) {
  1         41  
  1         2  
94 1         3 my (\$ref, \$data) = \@_[2,4];
  1         6  
  1         51  
95 1 50       42 my \$was_arrayref = ref \$data eq 'ARRAY';
  1 50       4  
  1 50       7  
96 1   33     6 \$data = [ \$data ] unless \$was_arrayref;
  1   33     31  
  1   33     2  
97 1 0       2 my \$type = ref(\$ref)||"value (".(\$ref||"").")";
  1 50       5  
  1 50       31  
    0          
    0          
    50          
    50          
    50          
    50          
98             (\$type eq 'SCALAR') ?
99             tie \$\$ref,'$tieclass',$tiedata :
100             (\$type eq 'ARRAY') ?
101             tie \@\$ref,'$tieclass',$tiedata :
102             (\$type eq 'HASH') ?
103             tie \%\$ref,'$tieclass',$tiedata :
104             die "Can't autotie a \$type\n";
105 1     1   5 } 1
  1     1   2  
  1     1   52  
  1         44  
  1         7  
  1         2  
  1         2  
  1         32  
  1         6  
106             } or die "Internal error: $@";
107             }
108             } else {
109 0         0 croak "Can't understand $_";
110             }
111             }
112             }
113            
114             # On older perls, code attribute handlers run before the sub gets placed
115             # in its package. Since the :ATTR handlers need to know the name of the
116             # sub they're applied to, the name lookup (via findsym) needs to be
117             # delayed: we do it immediately before we might need to find attribute
118             # handlers from their name. However, on newer perls (which fix some
119             # problems relating to attribute application), a sub gets placed in its
120             # package before its attributes are processed. In this case, the
121             # delayed name lookup might be too late, because the sub we're looking
122             # for might have already been replaced. So we need to detect which way
123             # round this perl does things, and time the name lookup accordingly.
124             BEGIN {
125 4     4   1121 my $delayed;
126             sub Attribute::Handlers::Clean::_TEST_::MODIFY_CODE_ATTRIBUTES {
127             $delayed = \&Attribute::Handlers::Clean::_TEST_::t != $_[1];
128             return ();
129             }
130 4     4   39 sub Attribute::Handlers::Clean::_TEST_::t :T { }
  4         10  
  4         37  
131 4     100   44 *_delayed_name_resolution = sub() { $delayed };
  99         327  
132 4         33 undef &Attribute::Handlers::Clean::_TEST_::MODIFY_CODE_ATTRIBUTES;
133 4         548 undef &Attribute::Handlers::Clean::_TEST_::t;
134             }
135            
136             sub _resolve_lastattr {
137 21 50   22   80 return unless $lastattr{ref};
138 21 50       93 my $sym = findsym @lastattr{'pkg','ref'} or die "Internal error: $lastattr{pkg} symbol went missing";
139 21         110 my $name = *{$sym}{NAME};
  21         53  
140 21 50 66     87 warn "Declaration of $name attribute in package $lastattr{pkg} may clash with future reserved word\n" if $^W and $name !~ /[A-Z]/;
141 21         37 foreach ( @{$validtype{$lastattr{type}}} ) {
  21         64  
142 4     4   41 no strict 'refs';
  4         8  
  4         1866  
143 47         78 *{"$lastattr{pkg}::_ATTR_${_}_${name}"} = $lastattr{ref};
  47         243  
144             }
145 21         79 %lastattr = ();
146             }
147            
148             sub AUTOLOAD {
149 0 0   1   0 return if $AUTOLOAD =~ /::DESTROY$/;
150 0         0 my ($class) = $AUTOLOAD =~ m/(.*)::/g;
151 0 0       0 $AUTOLOAD =~ m/_ATTR_(.*?)_(.*)/ or croak "Can't locate class method '$AUTOLOAD' via package '$class'";
152 0         0 croak "Attribute handler '$2' doesn't handle $1 attributes";
153             }
154            
155             my $builtin = qr/lvalue|locked|unique|shared/; # Method left out on purpose.
156            
157            
158             sub _gen_handler_AH_() {
159             sub {
160 74 50   74   17845 _resolve_lastattr if _delayed_name_resolution;
161 74         211 my ($pkg, $ref, @attrs) = @_;
162 74         244 push @attrs, attributes::get($ref);
163 74         1716 Perlmazing::remove_duplicates(@attrs);
164 74         26086 my (undef, $filename, $linenum) = caller 2;
165 74         194 foreach (@attrs) {
166 75 50       661 my ($attr, $data) = /^([a-z_]\w*)(?:[(](.*)[)])?$/is or next;
167 75 100       224 if ($attr eq 'ATTR') {
168 4     4   47 no strict 'refs';
  4         10  
  4         3241  
169 21   100     53 $data ||= "ANY";
170 21         78 $raw{$ref} = $data =~ s/\s*,?\s*RAWDATA\s*,?\s*//;
171 21 100       65 $phase{$ref}{BEGIN} = 1 if $data =~ s/\s*,?\s*(BEGIN)\s*,?\s*//;
172 21 100       59 $phase{$ref}{INIT} = 1 if $data =~ s/\s*,?\s*(INIT)\s*,?\s*//;
173 21 100       57 $phase{$ref}{END} = 1 if $data =~ s/\s*,?\s*(END)\s*,?\s*//;
174 21 100 100     64 $phase{$ref}{CHECK} = 1 if $data =~ s/\s*,?\s*(CHECK)\s*,?\s*// || ! keys %{$phase{$ref}};
  20         144  
175             # Added for cleanup to not pollute next call.
176 21 50       80 (%lastattr = ()), croak "Can't have two ATTR specifiers on one subroutine" if keys %lastattr;
177 21 50       60 croak "Bad attribute type: ATTR($data)" unless $validtype{$data};
178 21         74 %lastattr=(pkg=>$pkg,ref=>$ref,type=>$data);
179 21 50       47 _resolve_lastattr unless _delayed_name_resolution;
180             } else {
181 54         118 my $type = ref $ref;
182 54         364 my $handler = $pkg->can("_ATTR_${type}_${attr}");
183 54 50       147 next unless $handler;
184 54         225 my $decl = [$pkg, $ref, $attr, $data, $raw{$handler}, $phase{$handler}, $filename, $linenum];
185 54         117 foreach my $gphase (@global_phases) {
186             _apply_handler_AH_($decl,$gphase)
187 216 100       621 if $global_phases{$gphase} <= $global_phase;
188             }
189 54 100       119 if ($global_phase != 0) {
190             # if _gen_handler_AH_ is being called after
191             # CHECK it's for a lexical, so make sure
192             # it didn't want to run anything later
193 35         63 local $Carp::CarpLevel = 2;
194 35 100       370 carp "Won't be able to apply END handler" if $phase{$handler}{END};
195             } else {
196 19         45 push @declarations, $decl
197             }
198             }
199 74         176 $_ = undef;
200             }
201 73 50       137 return grep {defined && !/$builtin/} @attrs;
  74         366  
202             }
203 20     20   100 }
204            
205             sub _apply_handler_AH_ {
206 181     181   349 my ($declaration, $phase) = @_;
207 181         459 my ($pkg, $ref, $attr, $data, $raw, $handlerphase, $filename, $linenum) = @$declaration;
208 181 100       1487 return unless $handlerphase->{$phase};
209             # print STDERR "Handling $attr on $ref in $phase with [$data]\n";
210 53         100 my $type = ref $ref;
211 53         115 my $handler = "_ATTR_${type}_${attr}";
212 53         122 my $sym = findsym($pkg, $ref);
213 53 50 66     477 $sym ||= $type eq 'CODE' ? 'ANON' : 'LEXICAL';
214 4     4   35 no warnings;
  4         17  
  4         653  
215 53 100 66     185 if (!$raw && defined($data)) {
216 45 50       96 if ($data ne '') {
217 45     4   3784 my $evaled = eval(
  4     4   28  
  4     4   24  
  4     4   577  
  4     3   25  
  4     3   27  
  4     2   183  
  4     2   26  
  4     2   10  
  4     2   170  
  4     2   24  
  4     2   14  
  4     2   216  
  3     2   16  
  3     2   341  
  3     2   92  
  3     1   16  
  3     1   8  
  3     1   104  
  3     1   17  
  3     1   9  
  3     1   67  
  3     1   17  
  3     1   12  
  3     1   193  
  3     1   333  
  3     1   11  
  3     1   63  
  3     1   17  
  3     1   8  
  3     1   110  
  3     1   18  
  3     1   8  
  3     1   91  
  2     1   13  
  2     1   22  
  2     1   99  
  2     1   16  
  2     1   5  
  2     1   88  
  2     1   13  
  2     1   5  
  2     1   114  
  2     1   15  
  2     1   4  
  2     1   63  
  2     1   11  
  2     1   3  
  2     1   112  
  1     1   6  
  1     1   2  
  1     1   40  
  1     1   6  
  1     1   2  
  1     1   43  
  1     1   6  
  1     1   3  
  1     1   40  
  1     1   8  
  1     1   3  
  1     1   31  
  1     1   5  
  1     1   2  
  1     1   53  
  1         7  
  1         3  
  1         29  
  1         6  
  1         2  
  1         50  
  1         7  
  1         2  
  1         30  
  1         5  
  1         3  
  1         51  
  1         7  
  1         3  
  1         32  
  1         5  
  1         2  
  1         50  
  1         6  
  1         2  
  1         30  
  1         5  
  1         3  
  1         49  
  1         7  
  1         2  
  1         46  
  1         6  
  1         2  
  1         61  
  1         7  
  1         3  
  1         43  
  1         7  
  1         2  
  1         40  
  1         7  
  1         2  
  1         32  
  1         5  
  1         2  
  1         39  
  1         6  
  1         2  
  1         30  
  1         5  
  1         3  
  1         38  
  1         6  
  1         3  
  1         40  
  1         6  
  1         3  
  1         51  
  1         7  
  1         2  
  1         44  
  1         6  
  1         2  
  1         70  
  1         7  
  1         3  
  1         30  
  1         5  
  1         3  
  1         57  
  1         7  
  1         2  
  1         42  
  1         6  
  1         2  
  1         40  
  1         57  
  1         5  
  1         35  
  1         6  
  1         2  
  1         76  
  1         6  
  1         3  
  1         67  
  1         10  
  1         2  
  1         46  
  1         7  
  1         3  
  1         46  
  1         6  
  1         3  
  1         51  
  1         6  
  1         2  
  1         40  
  1         7  
  1         2  
  1         43  
  1         6  
  1         2  
  1         32  
  1         5  
  1         3  
  1         51  
  1         7  
  1         3  
  1         33  
  1         5  
  1         2  
  1         40  
  1         7  
  1         2  
  1         32  
  1         5  
  1         2  
  1         54  
218             "
219             package $pkg;
220             no warnings;
221             no strict;
222             local \$SIG{__WARN__}=sub{die};
223             [$data]
224             "
225             );
226 45 100       197 $data = $evaled unless $@;
227             } else {
228 0         0 $data = undef;
229             }
230             }
231             $pkg->$handler(
232             $sym,
233 53 100 33     287 (ref $sym eq 'GLOB' ? *{$sym}{ref $ref} || $ref : $ref),
234             $attr,
235             $data,
236             $phase,
237             $filename,
238             $linenum,
239             );
240 53         16911 return 1;
241             }
242            
243             {
244 4     4   31 no warnings 'void';
  4         9  
  4         883  
245             CHECK {
246 4     4   4786 $global_phase++;
247 4 50       16 _resolve_lastattr if _delayed_name_resolution;
248 4         819 foreach my $decl (@declarations) {
249 19         44 _apply_handler_AH_($decl, 'CHECK');
250             }
251             }
252             INIT {
253 4     4   254 $global_phase++;
254 4         19 foreach my $decl (@declarations) {
255 19         61 _apply_handler_AH_($decl, 'INIT');
256             }
257             }
258             }
259            
260             END {
261 4     4   2086 $global_phase++;
262 4         36 foreach my $decl (@declarations) {
263 19         36 _apply_handler_AH_($decl, 'END');
264             }
265             }
266            
267             1;
268            
269             __END__