#!/usr/bin/perl -I../lib
use warnings;
use strict;
#
# these are tests that confirm how perl works.
#
use Scalar::Util qw(refaddr reftype blessed weaken);
use Test::More tests => 129;
use B 'svref_2object';
use strict;
use warnings;
our $storeFunny;
#
# two references to the same thing are themselves the same
#
print "# block at ".__LINE__."\n";
{
my $a = [ 'xyz' ];
my $y = \$a->[0];
my $z = \$a->[0];
ok( $y eq $z );
ok( refaddr($y) eq refaddr($z) );
}
#
# When an hash key is deleted
# the refaddr for a reference to the value
# changes.
#
print "# block at ".__LINE__."\n";
{
my (%x) = ( x => 7 );
my $a = refaddr(\$x{x});
delete $x{x};
$x{zyz} = 77;
my $dummy = \$x{xyz};
my $b = refaddr(\$x{x});
$x{x} = 9;
my $c = refaddr(\$x{x});
ok ( $a ne $b );
ok ( $a ne $c );
ok ( $b eq $c )
}
print "# block at ".__LINE__."\n";
#
# Making an alias causes a hash key to exist.
#
{
my (%x) = ( y => 7 );
my $a = refaddr(\$x{x});
ok ( exists $x{x});
}
#
# Deleting a hash key causes it's refaddr to
# change.
#
print "# block at ".__LINE__."\n";
{
my (%x) = ( y => 7 );
my $a = refaddr(\$x{x});
$x{x} = 9;
my $b = refaddr(\$x{x});
delete $x{x};
$x{zyz} = 77;
my $dummy = \$x{xyz};
my $c = refaddr(\$x{x});
ok ( $a eq $b );
ok ( $a ne $c );
ok ( exists $x{x});
}
#
# Orphaned references remain tied together.
#
print "# block at ".__LINE__."\n";
{
my (%x) = ( y => 7 );
my $a = \$x{x};
my $b = \$x{x};
delete $x{x};
$$a = 8;
ok($$b == 8);
}
# ------------- now with Hash1 instead of untied -------------
#
# Make sure that Hash1 works as a hash table.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash1', \%y;
$x{x} = 7;
ok ($x{x} == 7);
ok (! exists $x{y});
delete $x{x};
ok (! exists $x{x});
}
#
# References to hash values that don't exist don't create
# the hash key (unlike untied hashes). However, the reference
# *is* tied to the hash and assigning to it will change the
# underlying value.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash1', \%y;
my $z = \$x{z};
ok (! exists $x{z}); # bug
ok (! defined($x{z}));
$$z = 12;
ok ($x{z} == 12);
}
#
# This behavior doesn't depend on how the tied hash is
# implemented.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash2', \%y;
my $z = \$x{z};
$$z = 12;
ok ($x{z} =~ /^12/);
}
#
# refaddrs to tied hashes are stable when the tied
# key exists.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash1', \%y;
$x{x} = 7;
my $b = refaddr(\$x{x});
$x{x} = 9;
my $c = refaddr(\$x{x});
ok ( $b eq $c )
}
#
# Orphaned references remain tied together.
# Deleting a hash key and then assinging to a
# stale reference will re-create the key.
# This is different than untied behavior.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash1', \%y;
$x{y} = 7;
my $a = \$x{x};
my $b = \$x{x};
delete $x{x};
$$a = 8;
ok($$b == 8);
ok($x{x} == 8);
}
#
# ditto.
# True no matter how the hash is implemented.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
tie %x, 'Hash2', \%y;
$x{y} = 7;
my $a = \$x{x};
my $b = \$x{x};
delete $x{x};
$$a = 8;
ok($$b =~ /^8/);
ok($x{x} =~ /^8/);
ok($$a =~ /\(\d+\)/);
ok($$a eq $$b);
ok($$a eq $x{x});
}
#
# The refaddr for a tied hash value is different from
# the refaddr for the same hash value untied and different
# from a refaddr to an underlying hash.
#
print "# block at ".__LINE__."\n";
{
my %x;
my %y;
$x{y} = 99;
my $c = \$x{y};
my $r0 = refaddr($c);
tie %x, 'Hash1', \%y;
$x{y} = 7;
my $a = \$x{y};
my $b = \$y{y};
my $r1 = refaddr($a);
my $r2 = refaddr($b);
ok($r0 ne $r1);
ok($r0 ne $r2);
ok($r1 ne $r2);
}
#
# References to tied hash values are all unique. They each have
# their own address. References remain even when the hash key
# is deleted.
#
# Sometimes the references can become disconnected from the underlying
# hash. They'll reconnect on assignement.
#
# When a reference reconnects after assignement, any other references
# disconnect.
#
print "# block at ".__LINE__."\n";
{
my %x;
tie %x, 'Hash1', {};
$x{y} = 7;
my $a = \$x{y};
delete $x{y};
$x{y} = 9;
my $b = \$x{y};
my $c = \$x{y};
ok($$a == 9); # bug
ok(refaddr($a) ne refaddr($b));
ok(refaddr($a) ne refaddr($c));
ok(refaddr($b) ne refaddr($c)); # bug
delete $x{y};
$$c = 17;
ok($$b == 17);
ok($x{y} == 17);
ok($$c == 17); # why?
ok($$a == 9); # bad
$$a = 12;
ok($x{y} == 12);
ok($$c == 17); # bug
ok($$b == 17); # bug
$x{y} = 11;
ok($$a == 11);
ok($$b == 17); # bug
ok($$c == 17); # bug
$$b = 12;
ok($x{y} == 12);
ok($$c == 17); # bug
ok($$a == 11); # bug
}
#
# Assignment though one referenc to a tied hash
# can disconnect other references to the tied hash
#
print "# block at ".__LINE__."\n";
{
my %x;
tie %x, 'Hash1', {};
$x{y} = 9;
$x{a} = \$x{y};
$x{b} = \$x{y};
$x{y} = 10;
ok(${$x{a}} == 10);
ok(${$x{b}} == 10);
${$x{a}} = 11;
ok(${$x{b}} == 10); # bug
}
print "# block at ".__LINE__."\n";
{
my %x;
tie %x, 'Hash1', {};
$x{y} = 7;
my $a = \$x{y};
my $b = \$x{y};
$x{y} = 9;
ok($$a == 9);
ok($$b == 9);
$$a = 10;
ok($$b == 9); # bug
}
# ------------- now let's look at references to references
#
# References to the same thing are identical but they are
# not the same object. This means that references to references
# of the same thing are different.
#
print "# block at ".__LINE__."\n";
{
my $x = 'foobar';
my $a = \$x;
my $b = \$a;
my $aa = \$x;
my $bb = \$aa;
my $c = \$a;
my $cc = \$aa;
ok($a eq $aa);
ok($b eq $c);
ok($bb eq $cc);
ok($aa ne $bb);
}
#
# ditto, but for references to hash values.
#
print "# block at ".__LINE__."\n";
{
my %x;
$x{x} = 7;
my $a = \$x{x};
my $b = \$a;
my $aa = \$x{x};
my $bb = \$aa;
my $c = \$a;
my $cc = \$aa;
ok($a eq $aa);
ok($b eq $c);
ok($bb eq $cc);
ok($aa ne $bb);
}
#
# Ditto for a tied hash.
#
print "# block at ".__LINE__."\n";
{
my $x;
tie $x, 'Ref1';
$x = 'Foobar';
my $a = \$x;
my $b = \$a;
my $aa = \$x;
my $bb = \$aa;
my $c = \$a;
my $cc = \$aa;
ok($a eq $aa);
ok($b eq $c);
ok($bb eq $cc);
ok($aa ne $bb);
}
#
# It doesn't seem to matter if the
# scalar is tied -- the reference remains
# the same tied or not and still works
# when tied and untied.
#
print "# block at ".__LINE__."\n";
{
my $x;
my $a = \$x;
my $aa = refaddr($a);
tie $x, 'Ref1';
$x = 'Foobar';
my $b = \$x;
my $ba = refaddr($b);
ok($a eq $b);
ok($aa eq $ba);
$$a = 22;
ok($$b == 22);
untie $x;
$$b = 99;
ok($$a == 99);
}
#
# ditto for sclar tie of an array element
#
print "# block at ".__LINE__."\n";
{
my (@x) = (1, 2, 3);
my $a = \$x[1];
my $aa = refaddr($a);
tie $x[1], 'Ref1';
$x[1] = 'Foobar';
my $b = \$x[1];
my $ba = refaddr($b);
ok($a eq $b);
ok($aa eq $ba);
$$a = 22;
ok($$b == 22);
untie $x[1];
$$b = 99;
ok($$a == 99);
}
#
# Using an array element that doesn't exist will
# create it.
#
print "# block at ".__LINE__."\n";
{
my @a;
$#a = 8;
goody($a[4]);
ok(exists $a[4]);
}
sub goody
{
my $x = shift;
$x = '' unless defined $x;
return "foo$x";
}
#
# Arrays really do track exists or not exists info
#
print "# block at ".__LINE__."\n";
{
my @a;
$#a = 8;
$a[5] = 'five';
ok(! exists $a[4]);
delete $a[5];
ok(! exists $a[5]);
$a[3] = undef;
ok(exists $a[3]);
}
#
# Elements beyond the end don't exist
#
print "# block at ".__LINE__."\n";
{
my (@a) = qw(a b c d e);
$#a = 2;
ok(exists $a[0]);
ok(exists $a[1]);
ok(exists $a[2]);
ok(! exists $a[3]);
ok(! exists $a[4]);
ok(! exists $a[5]);
}
#
# seems like a bug.
#
print "# block at ".__LINE__."\n";
{
my @a;
$a[0] = 'zero';
$a[1] = 'one';
$#a = 3;
shift(@a);
shift(@a);
delete $a[1];
ok($#a == -1); # bug
my @b;
$#b = 1;
delete $b[1];
ok($#b == -1); # bug
my @c;
$#c = 2;
delete $c[2];
ok($#c == -1); # bug
my @d;
$d[0] = 'zero';
$#d = 3;
delete $d[3];
ok($#d == 0); # bug
}
#
# extending an array doesn't cause elements to exist
#
print "# block at ".__LINE__."\n";
{
my (@a) = qw(a b c);
$#a = 5;
ok(exists $a[0]);
ok(exists $a[1]);
ok(exists $a[2]);
ok(! exists $a[3]);
ok(! exists $a[4]);
ok(! exists $a[5]);
ok(! exists $a[5]);
ok(! exists $a[6]);
}
# ------------- now let's play with B
#
# Method for finding what hash & key a reference to
# a tied hash value points to.
#
print "# block at ".__LINE__."\n";
{
tie my %x, 'Hash1', {};
$x{y} = 7;
my $a = \$x{A_KEY_WAS_FOUND};
my $sv = svref_2object($a);
my $svx = $sv->MAGIC;
while (lc($svx->TYPE) ne 'p') {
$svx = $svx->MOREMAGIC;
}
ok(${$svx->OBJ->RV} eq refaddr(tied %x));
ok($svx->PTR->as_string eq 'A_KEY_WAS_FOUND');
}
#
# for references to tied hash keys, this will return
# the refaddr of the tie object and the hash key
#
sub tied_hash_reference
{
my $ref = shift;
return eval {
my $magic = svref_2object($ref)->MAGIC;
$magic = $magic->MOREMAGIC
while lc($magic->TYPE) ne 'p';
return (${$magic->OBJ->RV}, $magic->PTR->as_string);
};
}
print "# block at ".__LINE__."\n";
{
my $t = tie my %x, 'Hash1', {};
$x{KEY_ONE} = 7;
my $a = \$x{KEY_ONE};
my ($h, $k) = tied_hash_reference($a);
ok($k eq 'KEY_ONE');
ok((tied_hash_reference($a))[1] eq 'KEY_ONE');
}
#
# Even when dis-associated, tied_hash_reference() still works.
#
print "# block at ".__LINE__."\n";
{
my %x;
my $t = tie %x, 'Hash1', {};
my $ta = refaddr($t);
$x{y} = 7;
my $a = \$x{y};
my $b = \$x{y};
$x{y} = 9;
ok($$a == 9);
ok($$b == 9);
ok((tied_hash_reference($a))[1] eq 'y');
ok((tied_hash_reference($a))[0] eq $ta);
ok((tied_hash_reference($b))[1] eq 'y');
ok((tied_hash_reference($b))[0] eq $ta);
$$a = 10;
ok($$b == 9); # bug
ok((tied_hash_reference($a))[1] eq 'y');
ok((tied_hash_reference($a))[0] eq $ta);
ok((tied_hash_reference($b))[1] eq 'y');
ok((tied_hash_reference($b))[0] eq $ta);
}
#
# Parameter aliasing can be used to make references
#
print "# block at ".__LINE__."\n";
{
sub makeref
{
return \$_[0];
}
my %x;
my $t = tie %x, 'Hash1', {};
my $ta = refaddr($t);
$x{y} = 7;
my $a = makeref($x{y});
my $b = makeref($x{y});
$x{y} = 9;
ok($$a == 9);
ok($$b == 9);
}
{
sub makeref2
{
return \$_[0];
}
sub makeref3
{
makeref2($_[0]);
}
my %x;
my $t = tie %x, 'Hash1', {};
my $ta = refaddr($t);
$x{y} = 7;
my $a = makeref3($x{y});
my $b = makeref3($x{y});
$x{y} = 9;
ok($$a == 9);
ok($$b == 9);
}
#
# Something very much like this causes a segv. Why doens't this?
#
print "# block at ".__LINE__."\n";
{
tie my %root, 'Hash1', {};
my $root = \%root;
$root->{skey} = 'sval';
$root->{X9} = [ \$root->{skey} ];
$root->{Y9} = [ \$root->{skey} ];
my $x = \$root->{Y9}[0];
weaken($x);
local($storeFunny) = sub { $$x = \$root->{skey} };
${$root->{X9}[0]} = 'FOO9';
ok(${$root->{Y9}[0]} eq 'FOO9');
}
#
# Why does eval catch exceptions sometimes and not catch them
# other times?
#
print "# block at ".__LINE__."\n";
{
sub foo
{
return eval {
die "foobar\n";
}
}
&foo;
ok($@, "foobar\n");
}
#
# Blessing stays with the scalar (and I assume, hash)
# even when there is no reference to the scalar.
#
print "# block at ".__LINE__."\n";
{
my $x = 'foobar';
{
my $y = \$x;
bless $y, 'baz';
undef $y;
}
my $a = \$x;
my $b = ref($a);
ok($b eq 'baz');
}
#
# What is actually passed to HASH->STORE? Answer: the
# actual hash key. However, there is a very strange
# bug here.
#
print "# block at ".__LINE__."\n";
{
my $z = '77';
my $y = \$z;
my $a = '78';
my $b = \$a;
tie my %x, 'Hash3', {};
$x{$y} = 22;
$x{$b} = 23;
ok(ref($x{$y}));
ok(ref($x{$b}));
{
local $TODO = 'Do these still fail? Multiple versions of Util::Scalar?';
ok(refaddr($x{$y})); # bug
ok(refaddr($x{$b})); # bug
}
my $xy = $x{$y};
my $xb = $x{$b};
ok(refaddr($xy) == refaddr($y));
ok(refaddr($xb) == refaddr($b));
#print "x{y}=$x{$y} y=$y\n";
#print "x{b}=$x{$b} b=$b\n";
#printf "ra(x{y})=%d, ra(y)=%d\n", refaddr($x{$y}), refaddr($y);
#printf "ra(x{b})=%d, ra(b)=%d\n", refaddr($x{$b}), refaddr($b);
#printf "x{y} ref()=%s reftype=%s refaddr=%d %s\n", ref($x{$y}), reftype($x{$y}), refaddr($x{$y}), $x{$y};
#printf "x{b} ref()=%s reftype=%s refaddr=%d %s\n", ref($x{$b}), reftype($x{$b}), refaddr($x{$b}), $x{$b};
#printf "x{y} ref()=%s reftype=%s refaddr=%d %s\n", ref($xy), reftype($xy), refaddr($xy), $xy;
#printf "x{b} ref()=%s reftype=%s refaddr=%d %s\n", ref($xb), reftype($xb), refaddr($xb), $xb;
#ok(refaddr($x{$y}) == refaddr($y));
}
#
# Turns out that caller returns the name of the subroutine
# called rather than the name of the calling subroutine.
# Weird.
#
print "# block at ".__LINE__."\n";
{
tie my %tiecaller, 'MT', sub { my $lvls = $_[0]+2; return [ caller($lvls) ] };
sub subcaller { my $lvls = $_[0]+1; return [ caller($lvls) ] };
sub MT::TIEHASH { my $p = shift; return bless shift, $p }
sub MT::FETCH { my $f = shift; return &$f(shift) }
my $cl;
&A();
sub A
{
$cl = __LINE__; &B();
}
sub B
{
my ($package, $filename, $line, $subroutine);
my $l = 0;
($package, $filename, $line, $subroutine) = @{&subcaller($l)};
ok($line == $cl);
ok($subroutine eq 'main::B'); # bug
print "# p:$package, f:$filename, l:$line, s:$subroutine\n";
($package, $filename, $line, $subroutine) = @{$tiecaller{$l}};
ok($line == $cl);
ok($subroutine eq 'main::B'); # bug
print "# p:$package, f:$filename, l:$line, s:$subroutine\n";
($package, $filename, $line, $subroutine) = caller($l);
print "# p:$package, f:$filename, l:$line, s:$subroutine\n";
ok($line == $cl);
ok($subroutine eq 'main::B'); # bug
}
}
#
# What happens when there is a die inside a die?
#
print "# block at ".__LINE__."\n";
{
sub DIEDIE::DESTROY {
die "DIEDIE\n";
}
sub FO23 {
my $x = bless {}, 'DIEDIE';
die "XXX\n";
}
sub FO22 {
eval {
FO23();
};
my $e = $@;
return $e;
}
my $x = eval {
FO22();
};
ok($x =~ /^XXX/);
}
#
# Does eval { return } return from the outer sub?
#
print "# block at ".__LINE__."\n";
{
sub xy7 {
eval { return 3 };
return 4;
}
my $x = xy7();
ok($x == 4);
}
package Hash1;
sub TIEHASH
{
my $pkg = shift;
return bless [ @_ ], $pkg;
}
sub FETCH
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
return $underlying->{$key};
}
sub STORE
{
my $self = shift;
my $key = shift;
my $value = shift;
my ($underlying) = @$self;
&$main::storeFunny($self, $key, $value) if defined $main::storeFunny;
return ($underlying->{$key} = $value);
}
sub DELETE
{
my ($self, $key) = @_;
my ($underlying) = @$self;
return delete($underlying->{$key});
}
sub CLEAR
{
my $self = shift;
my ($underlying) = @$self;
%$underlying = ();
}
sub EXISTS
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
return exists $underlying->{$key};
}
sub FIRSTKEY
{
my $self = shift;
my ($underlying) = @$self;
keys %$underlying;
return each %$underlying;
}
sub NEXTKEY
{
my $self = shift;
my ($underlying) = @$self;
return each %$underlying;
}
package Hash2;
use Scalar::Util qw(refaddr reftype blessed);
sub TIEHASH
{
my $pkg = shift;
return bless [ @_ ], $pkg;
}
sub FETCH
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
my $x = refaddr(\$underlying->{$key});
return "$underlying->{$key}($x)";
}
sub STORE
{
my $self = shift;
my $key = shift;
my $value = shift;
my ($underlying) = @$self;
return ($underlying->{$key} = $value);
}
sub DELETE
{
my ($self, $key) = @_;
my ($underlying) = @$self;
return delete($underlying->{$key});
}
sub CLEAR
{
my $self = shift;
my ($underlying) = @$self;
%$underlying = ();
}
sub EXISTS
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
return exists $underlying->{$key};
}
sub FIRSTKEY
{
my $self = shift;
my ($underlying) = @$self;
keys %$underlying;
return each %$underlying;
}
sub NEXTKEY
{
my $self = shift;
my ($underlying) = @$self;
return each %$underlying;
}
package Ref1;
sub TIESCALAR
{
my $pkg = shift;
return bless { val => undef };
}
sub FETCH
{
my $self = shift;
#print "FETCH $self->{val}\n";
return $self->{val};
}
sub STORE
{
my $self = shift;
my $new = shift;
#print "STORE $new\n";
$self->{val} = $new;
}
package Hash3;
use Scalar::Util qw(refaddr reftype blessed);
sub TIEHASH
{
my $pkg = shift;
return bless [ @_ ], $pkg;
}
sub FETCH
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
return $underlying->{refaddr($key)};
}
sub STORE
{
my $self = shift;
my $key = shift;
my $value = shift;
my ($underlying) = @$self;
return ($underlying->{refaddr($key)} = $key);
}
sub DELETE
{
my ($self, $key) = @_;
my ($underlying) = @$self;
return delete($underlying->{refaddr($key)});
}
sub CLEAR
{
my $self = shift;
my ($underlying) = @$self;
%$underlying = ();
}
sub EXISTS
{
my $self = shift;
my $key = shift;
my ($underlying) = @$self;
return exists $underlying->{$key};
}
sub FIRSTKEY
{
my $self = shift;
my ($underlying) = @$self;
keys %$underlying;
return each %$underlying;
}
sub NEXTKEY
{
my $self = shift;
my ($underlying) = @$self;
return each %$underlying;
}
1;