Devel::Canary
package Devel::Canary;
use 5.018;
use strict;
use warnings;
use autodie;
use Carp qw(confess);
use Scalar::Util qw(refaddr reftype);
use PadWalker qw(peek_my);
$Carp::CarpInternal{'Devel::Canary'}++;
$Carp::CarpInternal{'Devel::Canary::Hash'}++;
sub new_hashref {
my $ref = {};
tie %$ref, 'Devel::Canary::Hash', $ref;
return $ref;
}
sub throw_error {
my($self, $format) = @_;
my $var_name = '$devel_canary';
my $pad = peek_my(2);
foreach my $key (keys %$pad) {
my $var_ref = $pad->{$key};
next unless reftype($var_ref) eq 'REF';
if(my $addr = refaddr($$var_ref)) {
if($addr == $$self) {
$var_name = $key;
last;
}
}
}
confess sprintf("Fatal attempt to $format", $var_name);
}
package Devel::Canary::Hash;
use parent 'Devel::Canary';
sub TIEHASH {
my($class, $ref) = @_;
my $addr = Scalar::Util::refaddr($ref);
return bless \$addr, $class;
}
sub FETCH { shift->throw_error('access %s->{' . shift . '}'); }
sub STORE { shift->throw_error('assign to %s->{' . shift . '}'); }
sub EXISTS { shift->throw_error('call exists %s->{' . shift . '}'); }
sub DELETE { shift->throw_error('delete %s->{' . shift . '}'); }
sub CLEAR { shift->throw_error('clear hashref %s'); }
sub FIRSTKEY { shift->throw_error('iterate hashref %s'); }
1;