File Coverage

blib/lib/Data/Reuse.pm
Criterion Covered Total %
statement 114 118 96.6
branch 51 62 82.2
condition 5 12 41.6
subroutine 15 16 93.7
pod 3 4 75.0
total 188 212 88.6


line stmt bran cond sub pod time code
1             package Data::Reuse;
2              
3             # runtime settings we need
4 4     4   165709 use 5.008001;
  4         15  
  4         10136  
5 4     4   32 use strict;
  4         9  
  4         132  
6 4     4   110 use warnings;
  4         11  
  4         1242  
7              
8             # set up version info
9             our $VERSION= '0.10';
10              
11             # we need this otherwise nothing works
12 4     4   4678 use Data::Alias qw( alias copy );
  4         16868  
  4         1552  
13              
14             # other modules we need
15 4     4   38 use Carp qw( croak );
  4         8  
  4         251  
16 4     4   22 use Digest::MD5 qw( md5 );
  4         6  
  4         9839  
17 4     4   14668 use Encode qw( encode_utf8 );
  4         104060  
  4         433  
18 4     4   37 use Scalar::Util qw( reftype );
  4         11  
  4         19583  
19              
20             =for Explanation:
21             Since Data::Alias uses Exporter, we might as well do that also. Otherwise
22             we'd probably hack an import method ourselves
23              
24             =cut
25              
26 4     4   38 use base 'Exporter';
  4         12  
  4         16505  
27             our @EXPORT= qw();
28             our @EXPORT_OK= qw( alias fixate forget reuse spread );
29             our %EXPORT_TAGS= ( all => \@EXPORT_OK );
30              
31             # ID prefixes
32             my $U= "\1U\1";
33             my $S= "\1S\1";
34             my $A= "\1A\1";
35             my $H= "\1H\1";
36 53         49  
37             # set up data store with predefined ro undef value
38             my %reuse;
39             forget();
40              
41             # mark constants as read only
42             Internals::SvREADONLY $_, 1 foreach ( $U, $S, $A, $H );
43              
44             # recursion level
45             my $level= 0;
46              
47             # references being handled
48             my %handling;
49              
50             # satisfy -require-
51             1;
52              
53             #---------------------------------------------------------------------------
54             # reuse
55             #
56             # Make given values, store them in the constant hash and return them
57             #
58             # IN: 1..N values to be folded into constants
59             # OUT: 1..N same as input, but read-only and folded
60              
61             sub reuse (@); # needed because of recursion
62             sub reuse (@) {
63              
64 6         19 # being called with return values
  2         26  
65 96     96 1 36886 my $not_void= defined wantarray;
66              
67             # we're one level deeper
68 96         251 $level++;
69              
70             # there are values specified that haven't been folded yet or are undef
71 96 100 100     299 if ( alias my @special= grep { !defined or !exists $reuse{$_} } @_ ) {
  143         902  
72              
73             # values specified that haven't been folded yet
74 71 100       105 if ( alias my @new= grep { defined() } @special ) {
  85         2453  
75 67         130 foreach (@new) { # natural aliasing
76              
77             # reference being handled, make sure it is known
78 81 100       421 if ( exists $handling{$_} ) {
    100          
    100          
79 3   0     43 alias $reuse{$_} ||= $_;
80             }
81              
82             # handle references
83             elsif ( my $reftype= reftype $_ ) {
84 53         136 $handling{$_}= $level;
85 53         55 my $id;
86              
87             # aliasing everything in here
88             alias {
89              
90             # all elements of list
91 53 100       383 if ( $reftype eq 'ARRAY' ) {
    100          
    50          
92 35         71 $id= _list_id( $_ );
93              
94             # not seen, recurse
95 35 100       104 if ( !exists $reuse{$id} ) {
96 25         27 my @list= @{$_};
  25         178  
97 25         68 (@list)= reuse @list;
98              
99             =for Explanation:
100             We need to use copy semantics, because aliasing semantics segfaults.
101             Or as Matthijs put it:
102             =
103             Hah, het is een combinatie van:
104             1. bug in perl m.b.t. \-prototypes (\[$%@] in dit geval)
105             2. de refgen operator van Data::Alias (de impliciete \)
106             3. het in void context zijn van dit blok
107             4. totaal gebrek aan argument-checking in Internals::SvREADONLY
108             het prototype maakt van:
109             =
110             Internals::SvREADONLY my @array, 1
111             =
112             zoiets als:
113             =
114             &Internals::SvREADONLY(\my @array, 1);
115             =
116             Echter hij markeert de \ zonder enige context. Dit hoort normaal alleen
117             de gebeuren voor de laatste expressie in een block, en betekent "evalueer
118             dit in de context van deze block". In dit geval is het omliggende block
119             de top-level code, en die is altijd in void context. Perl evalueert dus
120             de \ in void context.
121             =
122             Hier ben je echter in perl zelf geen last van, omdat perl's ingebouwde
123             refgen op (\) alleen maar test op list-context, en gaat er van uit dat
124             het anders scalar context is. D::A's refgen onderscheid alle drie de
125             contexts, en produceert dus niets omdat het in void context is.
126             =
127             Hierdoor wordt de call dus:
128             =
129             &Internals::SvREADONLY(1);
130             =
131             En zoals ik al zei, Internals::SvREADONLY heeft geen argument validatie
132             en probeert dus fijn op adres 0x00000001 te lezen.. SEGV
133              
134             =cut
135              
136             # mark readonly, see above
137 25         79 copy Internals::SvREADONLY @list, 1;
138              
139             # recursive structures may be replaced
140 25         54 $id= _list_id( $_ );
141             }
142             }
143              
144             # all values of hash
145             elsif ( $reftype eq 'HASH' ) {
146 12         28 $id= _hash_id( $_ );
147              
148             # not seen, recurse, set result if first
149 12 100       129 if ( !$reuse{$id} ) {
150 6         10 my %hash= %{$_};
  6         10  
151 6         32 ( @hash{ keys %hash } )= reuse values %hash;
152              
153             # mark readonly, see above
154 6         21 copy Internals::SvREADONLY %hash, 1;
155              
156             # recursive structures may be replaced
157 6         13 $id= _hash_id( $_ );
158             }
159             }
160              
161             # the value of a scalar ref
162             elsif ( $reftype eq 'SCALAR' ) {
163 6         6 my $scalar= ${$_};
  6         20  
164              
165             # may be reused
166 6 100       33 if ( defined $scalar ) {
167 5         32 $id= md5( $S . $scalar );
168              
169             # not seen, recurse, set result if first
170 5 100       21 if ( !$reuse{$id} ) {
171 2         17 ($scalar)= reuse $scalar;
172 2         7 copy Internals::SvREADONLY $scalar, 1;
173              
174             # recursive structures may be replaced
175 2         10 $id= md5( $S . $scalar );
176             }
177             }
178              
179             # always reuse the default undef value
180             else {
181 1         4 $id= $U;
182             }
183             }
184              
185             # huh?
186             else {
187 0         0 croak "Cannot reuse '$reftype' references";
188             }
189              
190             =for Explanation:
191             When called in void context, perl may actually have used a memory location
192             for a temporary data structure that may return later with a different
193             content. As we don't want to equate those two different structures, we
194             are not going to save this reference if called in void context. And we
195             are also not going to overwrite anything that's there already.
196              
197             =cut
198              
199 53 100 0     410 $reuse{$id} ||= $_ if $not_void;
200              
201             # store in data store
202 53   66     234 $reuse{$_}= $reuse{$id} || $_;
203             }; #alias
204              
205             # done handling this ref
206 53         200 delete $handling{$_};
207             }
208              
209             # not a ref, but now already in store
210             elsif ( exists $reuse{$_} ) {
211             }
212              
213             # not a ref, and not in store either
214             else {
215              
216             # not readonly already, make a read only copy
217 23 100       185 $_= $_, Internals::SvREADONLY $_, 1
218             if !Internals::SvREADONLY $_;
219              
220             # store in data store
221 23         75 alias $reuse{$_}= $_;
222             }
223             }
224             }
225             }
226              
227             # done on this level
228 96         189 $level--;
229              
230             # return aliases of the specified values if needed
231 96 100       443 alias return @reuse{ map { defined() ? $_ : $U } @_ } if $not_void;
  127 100       572  
232             } #reuse
233              
234             #---------------------------------------------------------------------------
235             # fixate
236             #
237             # Fixate the values of the given hash / array ref
238             #
239             # IN: 1 hash / array ref
240             # 2..N values for fixation
241              
242             sub fixate (\[@%]@) {
243              
244             # fetch structure
245 9     9 1 34300 alias my $struct= shift;
246 9 100       249 croak "Must specify a hash or array as first parameter to fixate"
247             unless my $reftype= reftype $struct;
248              
249             # just fixate existing structure
250 8 100       26 reuse($struct), return if !@_;
251              
252             # alias semantices from here on
253             alias {
254              
255             # it's a hash
256 6 100       25 if ( $reftype eq 'HASH' ) {
    50          
257 1         2 my %hash= %{$struct};
  1         5  
258 1 50       6 croak "Can only fixate specific values on an empty hash"
259             if keys %hash;
260              
261             # fill the hash and make sure only its values are reused
262 1         10 (%hash)= @_;
263 1         5 reuse \%hash; # also makes hash ro
264             }
265              
266             # it's is an array
267             elsif ( $reftype eq 'ARRAY' ) {
268 5         7 my @array= @{$struct};
  5         15  
269 5 50       19 croak "Can only fixate specific values on an empty array"
270             if @array;
271              
272             # fill the array and make sure its values are reused
273 5         17 (@array)= reuse @_;
274 5         16 copy Internals::SvREADONLY @array, 1; # must copy, see above
275             }
276              
277             # huh?
278             else {
279 0         0 croak "Don't know how to fixate '$reftype' references";
280             }
281             };
282              
283 6         13 return;
284             } #fixate
285              
286             #---------------------------------------------------------------------------
287             # spread
288             #
289             # Spread a shared constant value in a data structure
290             #
291             # IN: 1 data structure (hash / array ref)
292             # 2 value to be set (default: undef )
293             # 3..N keys / indexes to set
294              
295             sub spread (\[@%]@) {
296              
297             # find out where to spread
298 3     3 1 13074 alias my $struct= shift;
299 3 50       21 croak "Must specify a hash or array as first parameter to spread"
300             unless my $reftype= reftype $struct;
301              
302             # huh? no value?
303 3 100       824 croak "Must specify a value as second parameter to spread"
304             if !@_;
305              
306             # fetch proper constant alias
307 2         7 alias my $value= reuse shift;
308              
309             # nothing to be done
310 2 50       7 return if !@_;
311              
312             # alias semantics from here on
313             alias {
314              
315             # it's a hash, but can we do it?
316 2 100       8 if ( $reftype eq 'HASH' ) {
    50          
317 1         2 my %hash= %{$struct};
  1         2  
318 1 50       6 croak "Cannot spread values in a restricted hash"
319             if Internals::SvREADONLY %hash;
320              
321             # spread the values in the hash
322 1         8 $hash{$_}= $value foreach @_;
323             }
324              
325             # it's an array, but can we do it?
326             elsif ( $reftype eq 'ARRAY' ) {
327 1         2 my @array= @{$struct};
  1         2  
328 1 50       12 croak "Cannot spread values in a restricted array"
329             if Internals::SvREADONLY @array;
330              
331             # spread the values in the list
332 1         7 $array[$_]= $value foreach @_;
333             }
334              
335             # huh?
336             else {
337 0         0 croak "Don't know how to spread values in '$reftype' references";
338             }
339             };
340              
341 2         5 return;
342             } #spread
343              
344             #---------------------------------------------------------------------------
345             # forget
346             #
347             # Forget about the values that have been reused so far, or since the last
348             # time "forget" was called.
349              
350             sub forget {
351              
352             # copy a fresh undef value (shouldn't alias the system undef!)
353 5     5 0 1877 %reuse= ( $U => undef );
354              
355             # make sure this undef can't be changed
356 5         25 Internals::SvREADONLY $reuse{$U}, 1;
357              
358 5         15 return;
359             } #forget
360              
361             #---------------------------------------------------------------------------
362             #
363             # Internal methods
364             #
365             #---------------------------------------------------------------------------
366             # _hash_id
367             #
368             # Return the ID for a hash ref
369             #
370             # IN: 1 hash ref
371             # OUT: 1 id
372              
373             sub _hash_id {
374 18     18   24 alias my %hash= %{ $_[0] };
  18         30  
375              
376 35 50       173 return md5( encode_utf8( $H . join $;, map {
377 18         78 $_ => ( defined $hash{$_} ? $hash{$_} : $U )
378             } sort keys %hash ) );
379             } #_hash_id
380              
381             #---------------------------------------------------------------------------
382             # _list_id
383             #
384             # Return the ID for a list ref
385             #
386             # IN: 1 list ref
387             # OUT: 1 id
388              
389             sub _list_id {
390 60     60   67 alias my @list= @{ $_[0] };
  60         104  
391              
392 60 50       89 return md5( $A . join $;, map { defined() ? $_ : $U } @list );
  120         684  
393             } #_list_id
394              
395             #---------------------------------------------------------------------------
396             #
397             # Debug methods
398             #
399             #---------------------------------------------------------------------------
400             # _constants
401             #
402             # Return hash ref of hash containing the constant values
403             #
404             # OUT: 1 hash ref
405              
406 0     0     sub _constants { return \%reuse } #_constants
407              
408             #---------------------------------------------------------------------------
409              
410             __END__