line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::Memory::Cycle; |
2
|
|
|
|
|
|
|
|
3
|
8
|
|
|
8
|
|
314603
|
use strict; |
|
8
|
|
|
|
|
15
|
|
|
8
|
|
|
|
|
214
|
|
4
|
8
|
|
|
8
|
|
60
|
use warnings; |
|
8
|
|
|
|
|
12
|
|
|
8
|
|
|
|
|
401
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=head1 NAME |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
Test::Memory::Cycle - Check for memory leaks and circular memory references |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 VERSION |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Version 1.06 |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=cut |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '1.06'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Perl's garbage collection has one big problem: Circular references |
21
|
|
|
|
|
|
|
can't get cleaned up. A circular reference can be as simple as two |
22
|
|
|
|
|
|
|
references that refer to each other: |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
my $mom = { |
25
|
|
|
|
|
|
|
name => "Marilyn Lester", |
26
|
|
|
|
|
|
|
}; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $me = { |
29
|
|
|
|
|
|
|
name => "Andy Lester", |
30
|
|
|
|
|
|
|
mother => $mom, |
31
|
|
|
|
|
|
|
}; |
32
|
|
|
|
|
|
|
$mom->{son} = $me; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
C is built on top of C to give |
35
|
|
|
|
|
|
|
you an easy way to check for these circular references. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
use Test::Memory::Cycle; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
my $object = new MyObject; |
40
|
|
|
|
|
|
|
# Do stuff with the object. |
41
|
|
|
|
|
|
|
memory_cycle_ok( $object ); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
You can also use C to make sure that you have a |
44
|
|
|
|
|
|
|
cycle where you expect to have one. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=cut |
47
|
|
|
|
|
|
|
|
48
|
8
|
|
|
8
|
|
3530
|
use Devel::Cycle qw( find_cycle find_weakened_cycle ); |
|
8
|
|
|
|
|
20356
|
|
|
8
|
|
|
|
|
34
|
|
49
|
8
|
|
|
8
|
|
1202
|
use Test::Builder; |
|
8
|
|
|
|
|
14
|
|
|
8
|
|
|
|
|
354
|
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $Test = Test::Builder->new; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub import { |
54
|
7
|
|
|
7
|
|
59
|
my $self = shift; |
55
|
7
|
|
|
|
|
13
|
my $caller = caller; |
56
|
8
|
|
|
8
|
|
31
|
no strict 'refs'; |
|
8
|
|
|
|
|
11
|
|
|
8
|
|
|
|
|
5562
|
|
57
|
7
|
|
|
|
|
8
|
*{$caller.'::memory_cycle_ok'} = \&memory_cycle_ok; |
|
7
|
|
|
|
|
38
|
|
58
|
7
|
|
|
|
|
8
|
*{$caller.'::memory_cycle_exists'} = \&memory_cycle_exists; |
|
7
|
|
|
|
|
26
|
|
59
|
|
|
|
|
|
|
|
60
|
7
|
|
|
|
|
8
|
*{$caller.'::weakened_memory_cycle_ok'} = \&weakened_memory_cycle_ok; |
|
7
|
|
|
|
|
65
|
|
61
|
7
|
|
|
|
|
9
|
*{$caller.'::weakened_memory_cycle_exists'} = \&weakened_memory_cycle_exists; |
|
7
|
|
|
|
|
45
|
|
62
|
7
|
|
|
|
|
8
|
*{$caller.'::memory_cycle_exists'} = \&memory_cycle_exists; |
|
7
|
|
|
|
|
15
|
|
63
|
|
|
|
|
|
|
|
64
|
7
|
|
|
|
|
8
|
*{$caller.'::weakened_memory_cycle_ok'} = \&weakened_memory_cycle_ok; |
|
7
|
|
|
|
|
16
|
|
65
|
7
|
|
|
|
|
8
|
*{$caller.'::weakened_memory_cycle_exists'} = \&weakened_memory_cycle_exists; |
|
7
|
|
|
|
|
12
|
|
66
|
|
|
|
|
|
|
|
67
|
7
|
|
|
|
|
28
|
$Test->exported_to($caller); |
68
|
7
|
|
|
|
|
72
|
$Test->plan(@_); |
69
|
|
|
|
|
|
|
|
70
|
7
|
|
|
|
|
89
|
return; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 FUNCTIONS |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head2 C, I<$msg> )> |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Checks that I<$reference> doesn't have any circular memory references. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub memory_cycle_ok { |
82
|
11
|
|
|
11
|
1
|
386752
|
my $ref = shift; |
83
|
11
|
|
|
|
|
16
|
my $msg = shift; |
84
|
|
|
|
|
|
|
|
85
|
11
|
|
|
|
|
16
|
my $cycle_no = 0; |
86
|
11
|
|
|
|
|
13
|
my @diags; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Callback function that is called once for each memory cycle found. |
89
|
|
|
|
|
|
|
my $callback = sub { |
90
|
12
|
|
|
12
|
|
1297
|
my $path = shift; |
91
|
12
|
|
|
|
|
14
|
$cycle_no++; |
92
|
12
|
|
|
|
|
24
|
push( @diags, "Cycle #$cycle_no" ); |
93
|
12
|
|
|
|
|
21
|
foreach (@$path) { |
94
|
36
|
|
|
|
|
54
|
my ($type,$index,$ref,$value) = @$_; |
95
|
|
|
|
|
|
|
|
96
|
36
|
|
|
|
|
37
|
my $str = 'Unknown! This should never happen!'; |
97
|
36
|
|
|
|
|
50
|
my $refdisp = _ref_shortname( $ref ); |
98
|
36
|
|
|
|
|
38
|
my $valuedisp = _ref_shortname( $value ); |
99
|
|
|
|
|
|
|
|
100
|
36
|
100
|
|
|
|
70
|
$str = sprintf( ' %s => %s', $refdisp, $valuedisp ) if $type eq 'SCALAR'; |
101
|
36
|
100
|
|
|
|
71
|
$str = sprintf( ' %s => %s', "${refdisp}->[$index]", $valuedisp ) if $type eq 'ARRAY'; |
102
|
36
|
100
|
|
|
|
88
|
$str = sprintf( ' %s => %s', "${refdisp}->{$index}", $valuedisp ) if $type eq 'HASH'; |
103
|
36
|
50
|
|
|
|
45
|
$str = sprintf( ' closure %s => %s', "${refdisp}, $index", $valuedisp ) if $type eq 'CODE'; |
104
|
|
|
|
|
|
|
|
105
|
36
|
|
|
|
|
70
|
push( @diags, $str ); |
106
|
|
|
|
|
|
|
} |
107
|
11
|
|
|
|
|
53
|
}; |
108
|
|
|
|
|
|
|
|
109
|
11
|
|
|
|
|
39
|
find_cycle( $ref, $callback ); |
110
|
11
|
|
|
|
|
843
|
my $ok = !$cycle_no; |
111
|
11
|
|
|
|
|
39
|
$Test->ok( $ok, $msg ); |
112
|
11
|
100
|
|
|
|
3687
|
$Test->diag( join( "\n", @diags, '' ) ) unless $ok; |
113
|
|
|
|
|
|
|
|
114
|
11
|
|
|
|
|
479
|
return $ok; |
115
|
|
|
|
|
|
|
} # memory_cycle_ok |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 C, I<$msg> )> |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Checks that I<$reference> B have any circular memory references. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=cut |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub memory_cycle_exists { |
124
|
13
|
|
|
13
|
1
|
79387
|
my $ref = shift; |
125
|
13
|
|
|
|
|
19
|
my $msg = shift; |
126
|
|
|
|
|
|
|
|
127
|
13
|
|
|
|
|
15
|
my $cycle_no = 0; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Callback function that is called once for each memory cycle found. |
130
|
13
|
|
|
11
|
|
42
|
my $callback = sub { $cycle_no++ }; |
|
11
|
|
|
|
|
999
|
|
131
|
|
|
|
|
|
|
|
132
|
13
|
|
|
|
|
40
|
find_cycle( $ref, $callback ); |
133
|
13
|
|
|
|
|
976
|
my $ok = $cycle_no; |
134
|
13
|
|
|
|
|
33
|
$Test->ok( $ok, $msg ); |
135
|
|
|
|
|
|
|
|
136
|
13
|
|
|
|
|
3454
|
return $ok; |
137
|
|
|
|
|
|
|
} # memory_cycle_exists |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=head2 C, I<$msg> )> |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
Checks that I<$reference> doesn't have any circular memory references, but unlike |
142
|
|
|
|
|
|
|
C this will also check for weakened cycles produced with |
143
|
|
|
|
|
|
|
Scalar::Util's C. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=cut |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub weakened_memory_cycle_ok { |
148
|
3
|
|
|
3
|
1
|
1894
|
my $ref = shift; |
149
|
3
|
|
|
|
|
4
|
my $msg = shift; |
150
|
|
|
|
|
|
|
|
151
|
3
|
|
|
|
|
6
|
my $cycle_no = 0; |
152
|
3
|
|
|
|
|
3
|
my @diags; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# Callback function that is called once for each memory cycle found. |
155
|
|
|
|
|
|
|
my $callback = sub { |
156
|
6
|
|
|
6
|
|
408
|
my $path = shift; |
157
|
6
|
|
|
|
|
5
|
$cycle_no++; |
158
|
6
|
|
|
|
|
14
|
push( @diags, "Cycle #$cycle_no" ); |
159
|
6
|
|
|
|
|
64
|
foreach (@$path) { |
160
|
19
|
|
|
|
|
34
|
my ($type,$index,$ref,$value,$is_weakened) = @$_; |
161
|
|
|
|
|
|
|
|
162
|
19
|
|
|
|
|
20
|
my $str = "Unknown! This should never happen!"; |
163
|
19
|
|
|
|
|
22
|
my $refdisp = _ref_shortname( $ref ); |
164
|
19
|
|
|
|
|
22
|
my $valuedisp = _ref_shortname( $value ); |
165
|
19
|
100
|
|
|
|
36
|
my $weak = $is_weakened ? 'w->' : ''; |
166
|
|
|
|
|
|
|
|
167
|
19
|
100
|
|
|
|
34
|
$str = sprintf( ' %s%s => %s', $weak, $refdisp, $valuedisp ) if $type eq 'SCALAR'; |
168
|
19
|
100
|
|
|
|
43
|
$str = sprintf( ' %s%s => %s', $weak, "${refdisp}->[$index]", $valuedisp ) if $type eq 'ARRAY'; |
169
|
19
|
100
|
|
|
|
43
|
$str = sprintf( ' %s%s => %s', $weak, "${refdisp}->{$index}", $valuedisp ) if $type eq 'HASH'; |
170
|
|
|
|
|
|
|
|
171
|
19
|
|
|
|
|
33
|
push( @diags, $str ); |
172
|
|
|
|
|
|
|
} |
173
|
3
|
|
|
|
|
16
|
}; |
174
|
|
|
|
|
|
|
|
175
|
3
|
|
|
|
|
12
|
find_weakened_cycle( $ref, $callback ); |
176
|
3
|
|
|
|
|
117
|
my $ok = !$cycle_no; |
177
|
3
|
|
|
|
|
10
|
$Test->ok( $ok, $msg ); |
178
|
3
|
50
|
|
|
|
1164
|
$Test->diag( join( "\n", @diags, "" ) ) unless $ok; |
179
|
|
|
|
|
|
|
|
180
|
3
|
|
|
|
|
186
|
return $ok; |
181
|
|
|
|
|
|
|
} # weakened_memory_cycle_ok |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head2 C, I<$msg> )> |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Checks that I<$reference> B have any circular memory references, but unlike |
186
|
|
|
|
|
|
|
C this will also check for weakened cycles produced with |
187
|
|
|
|
|
|
|
Scalar::Util's C. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=cut |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub weakened_memory_cycle_exists { |
192
|
3
|
|
|
3
|
1
|
1700
|
my $ref = shift; |
193
|
3
|
|
|
|
|
6
|
my $msg = shift; |
194
|
|
|
|
|
|
|
|
195
|
3
|
|
|
|
|
6
|
my $cycle_no = 0; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Callback function that is called once for each memory cycle found. |
198
|
3
|
|
|
6
|
|
13
|
my $callback = sub { $cycle_no++ }; |
|
6
|
|
|
|
|
519
|
|
199
|
|
|
|
|
|
|
|
200
|
3
|
|
|
|
|
10
|
find_weakened_cycle( $ref, $callback ); |
201
|
3
|
|
|
|
|
172
|
my $ok = $cycle_no; |
202
|
3
|
|
|
|
|
10
|
$Test->ok( $ok, $msg ); |
203
|
|
|
|
|
|
|
|
204
|
3
|
|
|
|
|
589
|
return $ok; |
205
|
|
|
|
|
|
|
} # weakened_memory_cycle_exists |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
my %shortnames; |
209
|
|
|
|
|
|
|
my $new_shortname = "A"; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub _ref_shortname { |
212
|
110
|
|
|
110
|
|
84
|
my $ref = shift; |
213
|
110
|
|
|
|
|
118
|
my $refstr = "$ref"; |
214
|
110
|
|
|
|
|
90
|
my $refdisp = $shortnames{ $refstr }; |
215
|
110
|
100
|
|
|
|
152
|
if ( !$refdisp ) { |
216
|
17
|
|
|
|
|
24
|
my $sigil = ref($ref) . " "; |
217
|
17
|
100
|
|
|
|
31
|
$sigil = '%' if $sigil eq "HASH "; |
218
|
17
|
100
|
|
|
|
39
|
$sigil = '@' if $sigil eq "ARRAY "; |
219
|
17
|
100
|
|
|
|
28
|
$sigil = '$' if $sigil eq "REF "; |
220
|
17
|
50
|
|
|
|
32
|
$sigil = '&' if $sigil eq "CODE "; |
221
|
17
|
|
|
|
|
41
|
$refdisp = $shortnames{ $refstr } = $sigil . $new_shortname++; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
110
|
|
|
|
|
129
|
return $refdisp; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head1 AUTHOR |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Written by Andy Lester, C<< >>. |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=head1 BUGS |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
234
|
|
|
|
|
|
|
C, or through the web interface at |
235
|
|
|
|
|
|
|
L. |
236
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
237
|
|
|
|
|
|
|
your bug as I make changes. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head1 SUPPORT |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
perldoc Test::Memory::Cycle |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
You can also look for information at: |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=over 4 |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
L |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=item * CPAN Ratings |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
L |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
L |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=item * Search CPAN |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
L |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=back |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Thanks to the contributions of Stevan Little, and to Lincoln Stein for writing Devel::Cycle. |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head1 COPYRIGHT |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
Copyright 2003-2016 Andy Lester. |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify |
276
|
|
|
|
|
|
|
it under the terms of the Artistic License v2.0. |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE |
279
|
|
|
|
|
|
|
file that comes with the Test::Memory::Cycle distribution. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=cut |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
1; |