1#!./perl
2#
3#  Copyright (c) 1995-2000, Raphael Manfredi
4#
5#  You may redistribute only under the same terms as Perl 5, as specified
6#  in the README file that comes with the distribution.
7#
8
9BEGIN {
10    # Do this as the very first thing, in order to avoid problems with the
11    # PADTMP flag on pre-5.19.3 threaded Perls.  On those Perls, compiling
12    # code that contains a constant-folded canonical truth value breaks
13    # the ability to take a reference to that canonical truth value later.
14    $::false = 0;
15    %::immortals = (
16	'u' => \undef,
17	'y' => \!$::false,
18	'n' => \!!$::false,
19    );
20}
21
22sub BEGIN {
23    if ($ENV{PERL_CORE}) {
24        chdir 'dist/Storable' if -d 'dist/Storable';
25        @INC = ('../../lib', 't');
26    } else {
27        unshift @INC, 't';
28        unshift @INC, 't/compat' if $] < 5.006002;
29    }
30    require Config; import Config;
31    if ($ENV{PERL_CORE} and $Config{'extensions'} !~ /\bStorable\b/) {
32        print "1..0 # Skip: Storable was not built\n";
33        exit 0;
34    }
35}
36
37use Test::More;
38
39use Storable qw(freeze thaw store retrieve fd_retrieve);
40
41%::weird_refs =
42  (REF            => \(my $aref    = []),
43   VSTRING        => \(my $vstring = v1.2.3),
44   'long VSTRING' => \(my $lvstring = eval "v" . 0 x 300),
45   LVALUE         => \(my $substr  = substr((my $str = "foo"), 0, 3)));
46
47my $test = 13;
48my $tests = $test + 41 + (2 * 6 * keys %::immortals) + (3 * keys %::weird_refs);
49plan(tests => $tests);
50
51package SHORT_NAME;
52
53sub make { bless [], shift }
54
55package SHORT_NAME_WITH_HOOK;
56
57sub make { bless [], shift }
58
59sub STORABLE_freeze {
60	my $self = shift;
61	return ("", $self);
62}
63
64sub STORABLE_thaw {
65	my $self = shift;
66	my $cloning = shift;
67	my ($x, $obj) = @_;
68	die "STORABLE_thaw" unless $obj eq $self;
69}
70
71package main;
72
73# Still less than 256 bytes, so long classname logic not fully exercised
74#   Identifier too long - 5.004
75#   parser.h: char	tokenbuf[256]: cperl5.24 => 1024
76my $m = ($Config{usecperl} and $] >= 5.024) ? 56 : 14;
77my $longname = "LONG_NAME_" . ('xxxxxxxxxxxxx::' x $m) . "final";
78
79eval <<EOC;
80package $longname;
81
82\@ISA = ("SHORT_NAME");
83EOC
84is($@, '');
85
86eval <<EOC;
87package ${longname}_WITH_HOOK;
88
89\@ISA = ("SHORT_NAME_WITH_HOOK");
90EOC
91is($@, '');
92
93# Construct a pool of objects
94my @pool;
95for (my $i = 0; $i < 10; $i++) {
96    push(@pool, SHORT_NAME->make);
97    push(@pool, SHORT_NAME_WITH_HOOK->make);
98    push(@pool, $longname->make);
99    push(@pool, "${longname}_WITH_HOOK"->make);
100}
101
102my $x = freeze \@pool;
103pass("Freeze didn't crash");
104
105my $y = thaw $x;
106is(ref $y, 'ARRAY');
107is(scalar @{$y}, @pool);
108
109is(ref $y->[0], 'SHORT_NAME');
110is(ref $y->[1], 'SHORT_NAME_WITH_HOOK');
111is(ref $y->[2], $longname);
112is(ref $y->[3], "${longname}_WITH_HOOK");
113
114my $good = 1;
115for (my $i = 0; $i < 10; $i++) {
116    do { $good = 0; last } unless ref $y->[4*$i]   eq 'SHORT_NAME';
117    do { $good = 0; last } unless ref $y->[4*$i+1] eq 'SHORT_NAME_WITH_HOOK';
118    do { $good = 0; last } unless ref $y->[4*$i+2] eq $longname;
119    do { $good = 0; last } unless ref $y->[4*$i+3] eq "${longname}_WITH_HOOK";
120}
121is($good, 1);
122
123{
124    my $blessed_ref = bless \\[1,2,3], 'Foobar';
125    my $x = freeze $blessed_ref;
126    my $y = thaw $x;
127    is(ref $y, 'Foobar');
128    is($$$y->[0], 1);
129}
130
131package RETURNS_IMMORTALS;
132
133sub make { my $self = shift; bless [@_], $self }
134
135sub STORABLE_freeze {
136    # Some reference some number of times.
137    my $self = shift;
138    my ($what, $times) = @$self;
139    return ("$what$times", ($::immortals{$what}) x $times);
140}
141
142sub STORABLE_thaw {
143    my $self = shift;
144    my $cloning = shift;
145    my ($x, @refs) = @_;
146    my ($what, $times) = $x =~ /(.)(\d+)/;
147    die "'$x' didn't match" unless defined $times;
148    main::is(scalar @refs, $times);
149    my $expect = $::immortals{$what};
150    die "'$x' did not give a reference" unless ref $expect;
151    my $fail;
152    foreach (@refs) {
153        $fail++ if $_ != $expect;
154    }
155    main::is($fail, undef);
156}
157
158package main;
159
160# XXX Failed tests:  15, 27, 39 with 5.12 and 5.10 threaded.
161# 15: 1 fail (y x 1), 27: 2 fail (y x 2), 39: 3 fail (y x 3)
162# $Storable::DEBUGME = 1;
163my $count;
164foreach $count (1..3) {
165  my $immortal;
166  foreach $immortal (keys %::immortals) {
167    print "# $immortal x $count\n";
168    my $i =  RETURNS_IMMORTALS->make ($immortal, $count);
169
170    my $f = freeze ($i);
171  TODO: {
172      # ref sv_true is not always sv_true, at least in older threaded perls.
173      local $TODO = "Some 5.10/12 do not preserve ref identity with freeze \\(1 == 1)"
174        if !defined($f) and $] < 5.013 and $] > 5.009 and $immortal eq 'y';
175      isnt($f, undef);
176    }
177    my $t = thaw $f;
178    pass("thaw didn't crash");
179  }
180}
181
182# Test automatic require of packages to find thaw hook.
183
184package HAS_HOOK;
185
186$loaded_count = 0;
187$thawed_count = 0;
188
189sub make {
190  bless [];
191}
192
193sub STORABLE_freeze {
194  my $self = shift;
195  return '';
196}
197
198package main;
199
200my $f = freeze (HAS_HOOK->make);
201
202is($HAS_HOOK::loaded_count, 0);
203is($HAS_HOOK::thawed_count, 0);
204
205my $t = thaw $f;
206is($HAS_HOOK::loaded_count, 1);
207is($HAS_HOOK::thawed_count, 1);
208isnt($t, undef);
209is(ref $t, 'HAS_HOOK');
210
211delete $INC{"HAS_HOOK.pm"};
212delete $HAS_HOOK::{STORABLE_thaw};
213
214$t = thaw $f;
215is($HAS_HOOK::loaded_count, 2);
216is($HAS_HOOK::thawed_count, 2);
217isnt($t, undef);
218is(ref $t, 'HAS_HOOK');
219
220{
221    package STRESS_THE_STACK;
222
223    my $stress;
224    sub make {
225	bless [];
226    }
227
228    sub no_op {
229	0;
230    }
231
232    sub STORABLE_freeze {
233	my $self = shift;
234	++$freeze_count;
235	return no_op(1..(++$stress * 2000)) ? die "can't happen" : '';
236    }
237
238    sub STORABLE_thaw {
239	my $self = shift;
240	++$thaw_count;
241	no_op(1..(++$stress * 2000)) && die "can't happen";
242	return;
243    }
244}
245
246$STRESS_THE_STACK::freeze_count = 0;
247$STRESS_THE_STACK::thaw_count = 0;
248
249$f = freeze (STRESS_THE_STACK->make);
250
251is($STRESS_THE_STACK::freeze_count, 1);
252is($STRESS_THE_STACK::thaw_count, 0);
253
254$t = thaw $f;
255is($STRESS_THE_STACK::freeze_count, 1);
256is($STRESS_THE_STACK::thaw_count, 1);
257isnt($t, undef);
258is(ref $t, 'STRESS_THE_STACK');
259
260my $file = "storable-testfile.$$";
261die "Temporary file '$file' already exists" if -e $file;
262
263END { while (-f $file) {unlink $file or die "Can't unlink '$file': $!" }}
264
265$STRESS_THE_STACK::freeze_count = 0;
266$STRESS_THE_STACK::thaw_count = 0;
267
268store (STRESS_THE_STACK->make, $file);
269
270is($STRESS_THE_STACK::freeze_count, 1);
271is($STRESS_THE_STACK::thaw_count, 0);
272
273$t = retrieve ($file);
274is($STRESS_THE_STACK::freeze_count, 1);
275is($STRESS_THE_STACK::thaw_count, 1);
276isnt($t, undef);
277is(ref $t, 'STRESS_THE_STACK');
278
279{
280    package ModifyARG112358;
281    sub STORABLE_freeze { $_[0] = "foo"; }
282    my $o= {str=>bless {}};
283    my $f= ::freeze($o);
284    ::is ref $o->{str}, __PACKAGE__,
285	'assignment to $_[0] in STORABLE_freeze does not corrupt things';
286}
287
288# [perl #113880]
289{
290    {
291        package WeirdRefHook;
292        sub STORABLE_freeze { () }
293        $INC{'WeirdRefHook.pm'} = __FILE__;
294    }
295
296    for my $weird (keys %weird_refs) {
297        my $obj = $weird_refs{$weird};
298        bless $obj, 'WeirdRefHook';
299        my $frozen;
300        my $success = eval { $frozen = freeze($obj); 1 };
301        ok($success, "can freeze $weird objects")
302            || diag("freezing failed: $@");
303        my $thawn = thaw($frozen);
304        # is_deeply ignores blessings
305        is ref $thawn, ref $obj, "get the right blessing back for $weird";
306        if ($weird =~ 'VSTRING') {
307            # It is not just Storable that did not support vstrings. :-)
308            # See https://rt.cpan.org/Ticket/Display.html?id=78678
309            my $newver = "version"->can("new")
310                           ? sub { "version"->new(shift) }
311                           : sub { "" };
312            if (!ok
313                  $$thawn eq $$obj && &$newver($$thawn) eq &$newver($$obj),
314                 "get the right value back"
315            ) {
316                diag "$$thawn vs $$obj";
317                diag &$newver($$thawn) eq &$newver($$obj) if &$newver(1);
318             }
319        }
320        else {
321            is_deeply($thawn, $obj, "get the right value back");
322        }
323    }
324}
325
326{
327    # [perl #118551]
328    {
329        package RT118551;
330
331        sub new {
332            my $class = shift;
333            my $string = shift;
334            die 'Bad data' unless defined $string;
335            my $self = { string => $string };
336            return bless $self, $class;
337        }
338
339        sub STORABLE_freeze {
340            my $self = shift;
341            my $cloning = shift;
342            return if $cloning;
343            return ($self->{string});
344        }
345
346        sub STORABLE_attach {
347            my $class = shift;
348            my $cloning = shift;
349            my $string = shift;
350            return $class->new($string);
351        }
352    }
353
354    my $x = [ RT118551->new('a'), RT118551->new('') ];
355
356    $y = freeze($x);
357
358    ok(eval {thaw($y)}, "empty serialized") or diag $@; # <-- dies here with "Bad data"
359}
360
361{
362    {
363        package FreezeHookDies;
364        sub STORABLE_freeze {
365            die ${$_[0]}
366        }
367
368	package ThawHookDies;
369	sub STORABLE_freeze {
370	    my ($self, $cloning) = @_;
371	    my $tmp = $$self;
372	    return "a", \$tmp;
373	}
374	sub STORABLE_thaw {
375	    my ($self, $cloning, $str, $obj) = @_;
376	    die $$obj;
377	}
378    }
379    my $x = bless \(my $tmpx = "Foo"), "FreezeHookDies";
380    my $y = bless \(my $tmpy = []), "FreezeHookDies";
381
382    ok(!eval { store($x, "store$$"); 1 }, "store of hook which throws no NL died");
383    ok(!eval { store($y, "store$$"); 1 }, "store of hook which throws ref died");
384
385    ok(!eval { freeze($x); 1 }, "freeze of hook which throws no NL died");
386    ok(!eval { freeze($y); 1 }, "freeze of hook which throws ref died");
387
388    ok(!eval { dclone($x); 1 }, "dclone of hook which throws no NL died");
389    ok(!eval { dclone($y); 1 }, "dclone of hook which throws ref died");
390
391    my $ostr = bless \(my $tmpstr = "Foo"), "ThawHookDies";
392    my $oref = bless \(my $tmpref = []), "ThawHookDies";
393    ok(store($ostr, "store$$"), "save throw Foo on thaw");
394    ok(!eval { retrieve("store$$"); 1 }, "retrieve of throw Foo on thaw died");
395    open FH, "<", "store$$" or die;
396    binmode FH;
397    ok(!eval { fd_retrieve(*FH); 1 }, "fd_retrieve of throw Foo on thaw died");
398    ok(!ref $@, "right thing thrown");
399    close FH;
400    ok(store($oref, "store$$"), "save throw ref on thaw");
401    ok(!eval { retrieve("store$$"); 1 }, "retrieve of throw ref on thaw died");
402    open FH, "<", "store$$" or die;
403    binmode FH;
404    ok(!eval { fd_retrieve(*FH); 1 }, "fd_retrieve of throw [] on thaw died");
405    ok(ref $@, "right thing thrown");
406    close FH;
407
408    my $strdata = freeze($ostr);
409    ok(!eval { thaw($strdata); 1 }, "thaw of throw Foo on thaw died");
410    ok(!ref $@, "and a string thrown");
411    my $refdata = freeze($oref);
412    ok(!eval { thaw($refdata); 1 }, "thaw of throw [] on thaw died");
413    ok(ref $@, "and a ref thrown");
414
415    unlink("store$$");
416}
417