This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Can't do require Foo if $tainted; because taint tunnels.
[perl5.git] / lib / Tie / RefHash.pm
index d4111d9..2f09628 100644 (file)
@@ -1,5 +1,9 @@
 package Tie::RefHash;
 
+use vars qw/$VERSION/;
+
+$VERSION = "1.34_01";
+
 =head1 NAME
 
 Tie::RefHash - use references as hash keys
@@ -19,7 +23,7 @@ This module provides the ability to use references as hash keys if you
 first C<tie> the hash variable to this module.  Normally, only the
 keys of the tied hash itself are preserved as references; to use
 references as keys in hashes-of-hashes, use Tie::RefHash::Nestable,
-included as part of Tie::Hash.
+included as part of Tie::RefHash.
 
 It is implemented using the standard perl TIEHASH interface.  Please
 see the C<tie> entry in perlfunc(1) and perltie(1) for more information.
@@ -51,13 +55,29 @@ store a reference to one of your own hashes in the tied hash.
        print ref($_), "\n";
     }
 
+=head1 THREAD SUPPORT
+
+L<Tie::RefHash> fully supports threading using the C<CLONE> method.
+
+=head1 STORABLE SUPPORT
+
+L<Storable> hooks are provided for semantically correct serialization and
+cloning of tied refhashes.
+
+=head1 RELIC SUPPORT
+
+This version of Tie::RefHash seems to no longer work with 5.004. This has not
+been throughly investigated. Patches welcome ;-)
+
+=head1 MAINTAINER
+
+Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
+
 =head1 AUTHOR
 
 Gurusamy Sarathy        gsar@activestate.com
 
-=head1 VERSION
-
-Version 1.21    22 Jun 1999
+'Nestable' by Ed Avis   ed@membled.com
 
 =head1 SEE ALSO
 
@@ -65,10 +85,44 @@ perl(1), perlfunc(1), perltie(1)
 
 =cut
 
-require 5.003_11;
 use Tie::Hash;
+use vars '@ISA';
 @ISA = qw(Tie::Hash);
 use strict;
+use Carp qw/croak/;
+
+BEGIN {
+  # determine whether we need to take care of threads
+  use Config ();
+  my $usethreads = $Config::Config{usethreads}; # && exists $INC{"threads.pm"}
+  *_HAS_THREADS = $usethreads ? sub () { 1 } : sub () { 0 };
+  if ($usethreads) {
+    # The magic of taint tunneling means that we can't do this require in the
+    # same statement as the boolean check on $usethreads, as $usethreads is
+    # tainted.
+    require Scalar::Util;
+  }
+}
+
+BEGIN {
+  # create a refaddr function
+
+  if ( eval { require Scalar::Util; 1 } ) {
+    Scalar::Util->import("refaddr");
+  } else {
+    require overload;
+
+    *refaddr = sub {
+      if ( overload::StrVal($_[0]) =~ /\( 0x ([a-zA-Z0-9]+) \)$/x) {
+          return $1;
+      } else {
+        die "couldn't parse StrVal: " . overload::StrVal($_[0]);
+      }
+    };
+  }
+}
+
+my (@thread_object_registry, $count); # used by the CLONE method to rehash the keys after their refaddr changed
 
 sub TIEHASH {
   my $c = shift;
@@ -77,14 +131,60 @@ sub TIEHASH {
   while (@_) {
     $s->STORE(shift, shift);
   }
+
+  if (_HAS_THREADS) {
+    # remember the object so that we can rekey it on CLONE
+    push @thread_object_registry, $s;
+    # but make this a weak reference, so that there are no leaks
+    Scalar::Util::weaken( $thread_object_registry[-1] );
+
+    if ( ++$count > 1000 ) {
+      # this ensures we don't fill up with a huge array dead weakrefs
+      @thread_object_registry = grep { defined } @thread_object_registry;
+      $count = 0;
+    }
+  }
+
   return $s;
 }
 
+my $storable_format_version = join("/", __PACKAGE__, "0.01");
+
+sub STORABLE_freeze {
+  my ( $self, $is_cloning ) = @_;
+  my ( $refs, $reg ) = @$self;
+  return ( $storable_format_version, [ values %$refs ], $reg );
+}
+
+sub STORABLE_thaw {
+  my ( $self, $is_cloning, $version, $refs, $reg ) = @_;
+  croak "incompatible versions of Tie::RefHash between freeze and thaw"
+    unless $version eq $storable_format_version;
+
+  @$self = ( {}, $reg );
+  $self->_reindex_keys( $refs );
+}
+
+sub CLONE {
+  my $pkg = shift;
+  # when the thread has been cloned all the objects need to be updated.
+  # dead weakrefs are undefined, so we filter them out
+  @thread_object_registry = grep { defined && do { $_->_reindex_keys; 1 } } @thread_object_registry;
+  $count = 0; # we just cleaned up
+}
+
+sub _reindex_keys {
+  my ( $self, $extra_keys ) = @_;
+  # rehash all the ref keys based on their new StrVal
+  %{ $self->[0] } = map { refaddr($_->[0]) => $_ } (values(%{ $self->[0] }), @{ $extra_keys || [] });
+}
+
 sub FETCH {
   my($s, $k) = @_;
   if (ref $k) {
-      if (defined $s->[0]{"$k"}) {
-        $s->[0]{"$k"}[1];
+      my $kstr = refaddr($k);
+      if (defined $s->[0]{$kstr}) {
+        $s->[0]{$kstr}[1];
       }
       else {
         undef;
@@ -98,7 +198,7 @@ sub FETCH {
 sub STORE {
   my($s, $k, $v) = @_;
   if (ref $k) {
-    $s->[0]{"$k"} = [$k, $v];
+    $s->[0]{refaddr($k)} = [$k, $v];
   }
   else {
     $s->[1]{$k} = $v;
@@ -108,19 +208,21 @@ sub STORE {
 
 sub DELETE {
   my($s, $k) = @_;
-  (ref $k) ? delete($s->[0]{"$k"}) : delete($s->[1]{$k});
+  (ref $k)
+    ? (delete($s->[0]{refaddr($k)}) || [])->[1]
+    : delete($s->[1]{$k});
 }
 
 sub EXISTS {
   my($s, $k) = @_;
-  (ref $k) ? exists($s->[0]{"$k"}) : exists($s->[1]{$k});
+  (ref $k) ? exists($s->[0]{refaddr($k)}) : exists($s->[1]{$k});
 }
 
 sub FIRSTKEY {
   my $s = shift;
-  keys %{$s->[0]};     # reset iterator
-  keys %{$s->[1]};     # reset iterator
-  $s->[2] = 0;
+  keys %{$s->[0]};  # reset iterator
+  keys %{$s->[1]};  # reset iterator
+  $s->[2] = 0;      # flag for iteration, see NEXTKEY
   $s->NEXTKEY;
 }
 
@@ -129,7 +231,7 @@ sub NEXTKEY {
   my ($k, $v);
   if (!$s->[2]) {
     if (($k, $v) = each %{$s->[0]}) {
-      return $s->[0]{"$k"}[0];
+      return $v->[0];
     }
     else {
       $s->[2] = 1;
@@ -146,7 +248,8 @@ sub CLEAR {
 }
 
 package Tie::RefHash::Nestable;
-use vars '@ISA'; @ISA = qw(Tie::RefHash);
+use vars '@ISA';
+@ISA = 'Tie::RefHash';
 
 sub STORE {
   my($s, $k, $v) = @_;