8 use Scalar::Util qw(weaken refaddr);
9 use namespace::clean -except => 'meta';
11 extends 'Data::Visitor';
13 has '_traced_refs' => (is => 'ro', lazy => 1, default => sub { {} });
14 has '_traced_names' => (is => 'ro', lazy => 1, default => sub { {} });
15 has '_current_trace_name' => (is => 'ro');
16 has 'root_name' => (is => 'ro');
18 before visit_ref => sub {
19 my ($self, $data) = @_;
21 # can't just rely on refaddr because it may get re-used if the data goes
22 # out of scope (we could play clever games with free magic on the wizard
23 # or whatever but KISS) - but we -can- keep a weak reference which will
24 # turn to undef if the variable disappears
26 weaken($self->_traced_refs->{refaddr $data} = $data);
28 $self->_traced_names->{refaddr $data} = $self->_current_trace_name;
32 my ($orig, $self) = (shift, shift);
33 local $self->{_current_trace_name}
34 = ($self->{_current_trace_name}||$self->root_name);
35 return $self->$orig(@_);
38 around visit_hash_entry => sub {
39 my ($orig, $self) = (shift, shift);
40 my $key = $_[0]; # $key, $value
41 local $self->{_current_trace_name}
42 = $self->{_current_trace_name}.'->{'.(perlstring $key).'}';
43 return $self->$orig(@_);
46 around visit_array_entry => sub {
47 my ($orig, $self) = (shift, shift);
48 my $index = $_[1]; # $value, $index
49 local $self->{_current_trace_name}
50 = $self->{_current_trace_name}.'->['.$index.']';
51 return $self->$orig(@_);
54 around visit_scalar => sub {
55 my ($orig, $self) = (shift, shift);
56 local $self->{_current_trace_name} = '${'.$self->{_current_trace_name}.'}';
57 return $self->$orig(@_);
62 my $refs = $self->_traced_refs;
63 my $names = $self->_traced_names;
65 # nuke keys where the traced refs entry is undef since they indicate
66 # "went out of scope" so the name is no longer valid. however if we
67 # do still have a refs entry we know the name is valid because if it
68 # didn't go out of scope that refaddr can't have been re-used.
69 # (NB: I don't care if this works under ithreads)
71 delete @{$names}{grep !defined($refs->{$_}), keys %$names};
75 # force recursion into objects (Data::Visitor doesn't by default)
77 sub visit_object { shift->visit_ref(@_) }
79 #package Ref::Replacer;
82 #use Variable::Magic qw(getdata);
86 use Test::More qw(no_plan);
87 use Scalar::Util qw(refaddr);
90 bar => { baz => [ 'quux', { fleem => 1 } ] },
91 skald => \[ { hot => 'story' } ],
94 my @expect = split "\n", <<'EOEXPECT';
99 ${$foo->{"skald"}}->[0]
102 my $tracer = Ref::Tracer->new({ root_name => '$foo' });
104 $tracer->visit($foo);
106 delete $foo->{bar}{baz};
108 my $result = $tracer->traced_ref_map;
112 [ sort { length($a) <=> length($b) } values %$result ],
113 "Expected results present"
116 my %map = reverse %$result;
118 foreach my $e (@expect) {
122 die "Error $@ evaluating $e" if $@;
125 is($map{$e},refaddr($value), "Result for ${e} ok");
130 use Data::Dumper; $Data::Dumper::Indent = 1;
132 warn Dumper($tracer->_traced_refs);
133 warn Dumper($tracer->_traced_names);
138 scalar(@expect), '==', scalar(@{$tracer->_traced_refs}),
139 'Right number of traced refs',
142 my $reflist = $tracer->_traced_refs;
144 foreach my $i (0 .. $#expect) {
145 my $tail = "at list pos $i";
146 is($e[$i], $reflist[$i], "Correct ref $e[$i] at $tail");
147 my $data = getdata $reflist[$i], Trace::Constants::TRACE_WIZ;
148 is($data->[0], 'Blue', "tag ok at $tail");
149 cmp_ok($data->[1], '==', $i, "Index ok at $i");