CV_TRACING
=> !DBICTest::RunMode->is_plain && DBIx::Class::Optional::Dependencies->req_ok_for (
'test_leaks_heavy'
),
SKIP_SCALAR_REFS
=> (
"$]"
< 5.008004 ),
};
our
@EXPORT_OK
=
qw(populate_weakregistry assert_empty_weakregistry visit_refs)
;
my
$refs_traced
= 0;
my
$leaks_found
= 0;
my
%reg_of_regs
;
sub
populate_weakregistry {
my
(
$weak_registry
,
$target
,
$note
) =
@_
;
croak
'Expecting a registry hashref'
unless
ref
$weak_registry
eq
'HASH'
;
croak
'Target is not a reference'
unless
length
ref
$target
;
my
$refaddr
= hrefaddr
$target
;
return
$target
if
$reg_of_regs
{
$refaddr
};
return
$target
if
SKIP_SCALAR_REFS and reftype(
$target
) eq
'SCALAR'
;
weaken(
$reg_of_regs
{ hrefaddr(
$weak_registry
) } =
$weak_registry
)
unless
(
$reg_of_regs
{ hrefaddr(
$weak_registry
) } );
for
my
$reg
(
values
%reg_of_regs
) {
(
defined
$reg
->{
$_
}{weakref}) or
delete
$reg
->{
$_
}
for
keys
%$reg
;
}
if
(!
defined
$weak_registry
->{
$refaddr
}{weakref}) {
$weak_registry
->{
$refaddr
} = {
stacktrace
=> stacktrace(1),
weakref
=>
$target
,
};
weaken(
$weak_registry
->{
$refaddr
}{weakref} );
$refs_traced
++;
}
my
$desc
= refdesc
$target
;
$weak_registry
->{
$refaddr
}{slot_names}{
$desc
} = 1;
if
(
$note
) {
$note
=~ s/\s*\Q
$desc
\E\s*//g;
$weak_registry
->{
$refaddr
}{slot_names}{
$note
} = 1;
}
$target
;
}
sub
CLONE {
my
@individual_regs
=
grep
{
scalar
keys
%{
$_
||{}} }
values
%reg_of_regs
;
%reg_of_regs
= ();
for
my
$reg
(
@individual_regs
) {
my
@live_slots
=
grep
{
defined
$_
->{weakref} }
values
%$reg
or
next
;
$reg
= {};
weaken(
$reg_of_regs
{hrefaddr(
$reg
)} =
$reg
);
for
my
$slot_info
(
@live_slots
) {
my
$new_addr
= hrefaddr
$slot_info
->{weakref};
$slot_info
->{slot_names} = {
map
{
my
$name
=
$_
;
$name
=~ s/\(0x[0-9A-F]+\)/
sprintf
(
'(%s)'
,
$new_addr
)/ieg;
(
$name
=> 1);
}
keys
%{
$slot_info
->{slot_names}} };
$reg
->{
$new_addr
} =
$slot_info
;
}
}
}
sub
visit_refs {
my
$args
= { (
ref
$_
[0]) ? %{
$_
[0]} :
@_
};
$args
->{seen_refs} ||= {};
my
$visited_cnt
=
'0E0'
;
for
my
$i
(0 .. $
next
unless
length
ref
$args
->{refs}[
$i
];
my
$addr
= hrefaddr
$args
->{refs}[
$i
];
next
if
$reg_of_regs
{
$addr
};
next
if
$args
->{seen_refs}{
$addr
}++;
$visited_cnt
++;
my
$r
=
$args
->{refs}[
$i
];
$args
->{action}->(
$r
) or
next
;
my
$type
= reftype
$r
;
local
$@;
eval
{
if
(
$type
eq
'HASH'
) {
$visited_cnt
+= visit_refs({
%$args
,
refs
=> [
map
{
( !isweak(
$r
->{
$_
}) ) ?
$r
->{
$_
} : ()
}
keys
%$r
] });
}
elsif
(
$type
eq
'ARRAY'
) {
$visited_cnt
+= visit_refs({
%$args
,
refs
=> [
map
{
( !isweak(
$r
->[
$_
]) ) ?
$r
->[
$_
] : ()
} 0..
$#$r
] });
}
elsif
(
$type
eq
'REF'
and !isweak(
$$r
)) {
$visited_cnt
+= visit_refs({
%$args
,
refs
=> [
$$r
] });
}
elsif
(CV_TRACING and
$type
eq
'CODE'
) {
$visited_cnt
+= visit_refs({
%$args
,
refs
=> [
map
{
( !isweak(
$_
) ) ?
$_
: ()
}
values
%{
scalar
PadWalker::closed_over(
$r
) } ] });
}
1;
} or
warn
"Could not descend into @{[ refdesc $r ]}: $@\n"
;
}
$visited_cnt
;
}
sub
symtable_referenced_addresses {
my
$refs_per_pkg
;
my
$seen_refs
= {};
visit_namespaces(
action
=>
sub
{
no
strict
'refs'
;
my
$pkg
=
shift
;
$refs_per_pkg
->{
$pkg
} += visit_refs (
seen_refs
=>
$seen_refs
,
action
=>
sub
{ 1 },
refs
=> [
map
{
my
$sym
=
$_
;
( CV_TRACING ? Class::MethodCache::get_cv(
"${pkg}::$sym"
) : () ),
(
defined
*{
"${pkg}::$sym"
}{SCALAR} and
length
ref
${
"${pkg}::$sym"
} and ! isweak( ${
"${pkg}::$sym"
} ) )
? ${
"${pkg}::$sym"
} : ()
,
(
map
{
(
defined
*{
"${pkg}::$sym"
}{
$_
} and ! isweak(
defined
*{
"${pkg}::$sym"
}{
$_
}) )
? *{
"${pkg}::$sym"
}{
$_
}
: ()
}
qw(HASH ARRAY IO GLOB)
),
}
keys
%{
"${pkg}::"
} ],
)
unless
$pkg
=~ /^ (?:
DB |
next
| B | .+? ::::ISA (?: ::CACHE ) | Class::C3 | B::Hooks::EndOfScope::PP::HintHash::.+
) $/x;
}
);
$seen_refs
;
}
sub
assert_empty_weakregistry {
my
(
$weak_registry
,
$quiet
) =
@_
;
local
*CORE::GLOBAL::bless
;
*CORE::GLOBAL::bless
=
sub
{ CORE::
bless
(
$_
[0], (
@_
> 1) ?
$_
[1] :
caller
() ) };
croak
'Expecting a registry hashref'
unless
ref
$weak_registry
eq
'HASH'
;
defined
$weak_registry
->{
$_
}{weakref} or
delete
$weak_registry
->{
$_
}
for
keys
%$weak_registry
;
return
unless
keys
%$weak_registry
;
my
$tb
=
eval
{ Test::Builder->new }
or croak
"Calling assert_empty_weakregistry in $0 without a loaded Test::Builder makes no sense"
;
for
my
$addr
(
keys
%$weak_registry
) {
$weak_registry
->{
$addr
}{display_name} =
join
' | '
, (
sort
{
length
$a
<=>
length
$b
or
$a
cmp
$b
}
keys
%{
$weak_registry
->{
$addr
}{slot_names}}
);
$tb
->BAILOUT(
"!!!! WEAK REGISTRY SLOT $weak_registry->{$addr}{display_name} IS NOT A WEAKREF !!!!"
)
if
defined
$weak_registry
->{
$addr
}{weakref} and ! isweak(
$weak_registry
->{
$addr
}{weakref} );
}
delete
$weak_registry
->{
$_
}
for
$quiet
?
do
{
my
$refs
= {};
visit_refs (
refs
=> [
grep
{
length
ref
$_
} (
(
map
{
values
%{
$_
->[2]} }
grep
{
ref
$_
eq
'ARRAY'
}
values
%Sub::Quote::QUOTED
),
(
map
{
values
%{
$_
->{captures}} }
grep
{
ref
$_
eq
'HASH'
}
values
%Sub::Quote::QUOTED
),
)],
seen_refs
=>
$refs
,
action
=>
sub
{ 1 },
);
keys
%$refs
;
}
: (
keys
%{ symtable_referenced_addresses() }
)
;
for
my
$addr
(
sort
{
$weak_registry
->{
$a
}{display_name} cmp
$weak_registry
->{
$b
}{display_name} }
keys
%$weak_registry
) {
next
if
!
defined
$weak_registry
->{
$addr
}{weakref};
$leaks_found
++
unless
$tb
->in_todo;
$tb
->ok (0,
"Expected garbage collection of $weak_registry->{$addr}{display_name}"
);
my
$diag
=
do
{
local
$Data::Dumper::Maxdepth
= 1;
sprintf
"\n%s (refcnt %d) => %s\n"
,
$weak_registry
->{
$addr
}{display_name},
refcount(
$weak_registry
->{
$addr
}{weakref}),
(
ref
(
$weak_registry
->{
$addr
}{weakref}) eq
'CODE'
and
B::svref_2object(
$weak_registry
->{
$addr
}{weakref})->XSUB
) ?
'__XSUB__'
: Dumper(
$weak_registry
->{
$addr
}{weakref} )
;
};
$diag
.= Devel::FindRef::track (
$weak_registry
->{
$addr
}{weakref}, 50) .
"\n"
$diag
=~ s/^/ /mg;
if
(
my
$stack
=
$weak_registry
->{
$addr
}{stacktrace}) {
$diag
.=
" Reference first seen$stack"
;
}
$tb
->diag(
$diag
);
}
if
(!
$quiet
and !
$leaks_found
and !
$tb
->in_todo) {
$tb
->ok(1,
sprintf
"No leaks found at %s line %d"
, (
caller
())[1,2] );
}
}
END {
if
(
$INC
{
'Test/Builder.pm'
}
and
my
$tb
=
do
{
local
$@;
my
$t
=
eval
{ Test::Builder->new }
or
warn
"Test::Builder->new failed:\n$@\n"
;
$t
;
}
) {
if
(
$leaks_found
and !
$tb
->is_passing) {
$tb
->diag(
sprintf
"\n\n%s\n%s\n\nInstall Devel::FindRef and re-run the test with set "
.
'$ENV{TEST_VERBOSE} (prove -v) to see a more detailed leak-report'
.
"\n\n%s\n%s\n\n"
, (
'#'
x 16) x 4
)
if
( !
$ENV
{TEST_VERBOSE} or !
$INC
{
'Devel/FindRef.pm'
} );
}
else
{
$tb
->note(
"Auto checked $refs_traced references for leaks - none detected"
);
}
unless
(
$ENV
{MOO_FATAL_WARNINGS}
or
$INC
{
'SQL/Translator.pm'
}
or
DBICTest::RunMode->is_plain
) {
for
my
$mod
(
qw(indirect multidimensional bareword::filehandles)
) {
(
my
$fn
=
"$mod.pm"
) =~ s|::|/|g;
$tb
->ok(0,
"Load of '$mod' should not have been attempted!!!"
)
if
exists
$INC
{
$fn
};
}
}
}
}
1;