11

Is there any way to get Perl to convert the stringified version e.g (ARRAY(0x8152c28)) of an array reference to the actual array reference?

For example

perl -e 'use Data::Dumper; $a = [1,2,3];$b = $a; $a = $a.""; warn Dumper (Then some magic happens);'

would yield

$VAR1 = [
      1,
      2,
      3
    ];
brian d foy
  • 129,424
  • 31
  • 207
  • 592
Tim
  • 2,369
  • 18
  • 16
  • Don't see how that requires any magic beyond `Dumper( $b )`. Obviously you want to `warn`. When do you want to `warn`? In most places where I can stringify `$a`, I also have `$a` to dump. – Axeman Nov 04 '09 at 08:12
  • It would be easier if you overloaded stringification to return the number. – MkV Mar 28 '11 at 19:10
  • Sounds like you just want `eval $thing`. Of course this depends on the way it was stringified. If you used Data::Dumper or Data::Dump, just `eval`ing it will recreate your objects. – simbabque Mar 03 '14 at 11:48
  • Also, you cannot stringify a reference by putting it in quotes. `"$ref"` will give you `"ARRAY(0x1234...)"`, and **not** `"[]"`. – simbabque Mar 03 '14 at 11:50
  • @simbabque: I'm aware, and I really did mean the `"ARRAY(0x1234...)"` version. :) – Moh Mar 04 '14 at 13:29
  • 1
    @mpapec: Looks like you're right, this question is pretty much the same as that one, though the answer to this one is novel and seems safer. – Moh Mar 04 '14 at 13:35

6 Answers6

18

Yes, you can do this (even without Inline C). An example:

use strict;
use warnings;

# make a stringified reference
my $array_ref = [ qw/foo bar baz/ ];
my $stringified_ref = "$array_ref";

use B; # core module providing introspection facilities
# extract the hex address
my ($addr) = $stringified_ref =~ /.*(0x\w+)/;
# fake up a B object of the correct class for this type of reference
# and convert it back to a real reference
my $real_ref = bless(\(0+hex $addr), "B::AV")->object_2svref;

print join(",", @$real_ref), "\n";

but don't do that. If your actual object is freed or reused, you may very well end up getting segfaults.

Whatever you are actually trying to achieve, there is certainly a better way. A comment to another answer reveals that the stringification is due to using a reference as a hash key. As responded to there, the better way to do that is the well-battle-tested Tie::RefHash.

ysth
  • 96,171
  • 6
  • 121
  • 214
  • There are a couple of modules to do this for you, one XS and one similar to the above. But don't use those, either. :) – ysth Nov 04 '09 at 03:40
  • Well, if you're willing to increment the reference count, it *might* be okay--assuming that it still exists when you get the AV. – Axeman Nov 04 '09 at 05:16
6

The first question is: do you really want to do this?

Where is that string coming from?

If it's coming from outside your Perl program, the pointer value (the hex digits) are going to be meaningless, and there's no way to do it.

If it's coming from inside your program, then there's no need to stringify it in the first place.

ScottJ
  • 1,080
  • 12
  • 20
  • 1
    Additionally, in some circumstances the array will get GCed, at which point the stringified version of the reference can no longer be converted back to a valid array. – outis Nov 04 '09 at 02:14
  • Presume I actually wanted to do this. Also the data would still be referenced, I updated the code in the question to reflect this. So garbage collection not an issue. – Tim Nov 04 '09 at 02:21
  • 1
    @Tim - In an effort to help make your life easier, I would be very interested to know _why_ you want to do this. There may well be a valid reason for doing this, and if so, knock yourself out with the excellent answers provided so far. However, I somehow suspect that there may be a better way to accomplish what you're trying to do, and I guarantee you it will be much more stable and easier to maintain if you find that better way of doing it. – Chris Lutz Nov 04 '09 at 03:20
  • 2
    @Tim: presuming you actually wanted to do this, "If it's coming from inside your program, then there's no need to stringify it in the first place." – ysth Nov 04 '09 at 03:41
  • I wanted to use an array ref as a hash key. I eventually just created a hash with the ref_string->reference. Also it was more of a curiosity for me then an "I can't get this program to work without this functionality" thing. That's why I said "Presume that I want to do it". @Chris thanks for trying to make my life easier - I tracked down a post of yours (on a subjective subject) and gave you an upvote. – Tim Nov 04 '09 at 04:52
  • 5
    Use Tie::RefHash if you want to use a ref as a hash key. Stringifying is not safe. Neither is "refaddr $ref". If you don't know why, just stick to Tie::RefHash. The corner-cases are properly handled there. – jrockway Nov 04 '09 at 06:03
5

Yes, it's possible: use Devel::FindRef.

use strict;
use warnings;
use Data::Dumper;
use Devel::FindRef;

sub ref_again {
   my $str = @_ ? shift : $_;
   my ($addr) = map hex, ($str =~ /\((.+?)\)/);
   Devel::FindRef::ptr2ref $addr;
}

my $ref = [1, 2, 3];
my $str = "$ref";
my $ref_again = ref_again($str);

print Dumper($ref_again);
tobyink
  • 13,478
  • 1
  • 23
  • 35
  • Thanks! This works very well, even though I'm getting messages like `Hexadecimal number > 0xffffffff non-portable at ./findref.pl line 8`. I wonder why Perl's builtin `hex` can't handle values as high as the address of a reference. – Moh Mar 04 '14 at 13:23
  • thanks, this is probably an horrible idea in production code, but it comes in very handy when you're debugging code and trying to figure out what's in an accidentaly stringified reference that was generated in a completely different corner of the software. – mirod May 09 '19 at 11:00
4

The stringified version contains the memory address of the array object, so yes, you can recover it. This code works for me, anyway (Cygwin, perl 5.8):

use Inline C;
@a = (1,2,3,8,12,17);
$a = \@a . "";
print "Stringified array ref is $a\n";
($addr) = $a =~ /0x(\w+)/;
$addr = hex($addr);
$c = recover_arrayref($addr);
@c = @$c;
print join ":", @c;
__END__
__C__
AV* recover_arrayref(int av_address) { return (AV*) av_address; }

.

$ perl ref-to-av.pl
Stringified array ref is ARRAY(0x67ead8)
1:2:3:8:12:17
mob
  • 117,087
  • 18
  • 149
  • 283
2

I'm not sure why you want to do this, but if you really need it, ignore the answers that use the tricks to look into memory. They'll only cause you problems.

Why do you want to do this? There's probably a better design. Where are you getting that stringified reference from.

Let's say you need to do it for whatever reason. First, create a registry of objects where the hash key is the stringified form, and the value is a weakened reference:

 use Scalar::Util qw(weaken);

 my $array = [ ... ];

 $registry{ $array } = $array;

 weaken( $registry{ $array } ); # doesn't count toward ref count

Now, when you have the stringified form, you just look it up in the hash, checking to see that it's still a reference:

 if( ref $registry{$string} ) { ... }

You could also try Tie::RefHash and let it handle all of the details of this.

There is a longer example of this in Intermediate Perl.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • This is what I actually did. Although someone suggested using Tie::RefHash which would have accomplished what I wanted as well. I commented elsewhere that the question was more of a curiosity for me than something I actually needed to do. Thanks for the concern. – Tim Nov 04 '09 at 16:24
  • Rather than use Tie::RefHash, use Hash::Util::FieldHash (Hash::Util::FieldHash::Compat before 5.10) with its register, id and id_2obj functions. In addition, as keys, the ids survive threads and are automatically garbage collected. – MkV Mar 28 '11 at 19:22
0

In case someone finds this useful, I'm extending tobyink's answer by adding support for detecting segmentation faults. There are two approaches I discovered. The first way locally replaces $SIG{SEGV} and $SIG{BUS} before dereferencing. The second way masks the child signal and checks if a forked child can dereference successfully. The first way is significantly faster than the second.

Anyone is welcome to improve this answer.

First Approach

sub unstringify_ref($) {
  use bigint qw(hex);
  use Devel::FindRef;

  my $str = @_ ? shift : $_;
  if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
    my $addr = (hex $1)->bstr;

    local $@;
    return eval {
      local $SIG{SEGV} = sub { die };
      local $SIG{BUS} = sub { die };
      return Devel::FindRef::ptr2ref $addr;
    };
  }
  return undef;
}

I'm not sure if any other signals can occur in an attempt to access illegal memory.

Second Approach

sub unstringify_ref($) {
  use bigint qw(hex);
  use Devel::FindRef;
  use Signal::Mask;

  my $str = @_ ? shift : $_;
  if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
    my $addr = (hex $1)->bstr;

    local $!;
    local $?;
    local $Signal::Mask{CHLD} = 1;
    if (defined(my $kid = fork)) {
      # Child -- This might seg fault on invalid address.
      exit(not Devel::FindRef::ptr2ref $addr) unless $kid;
      # Parent
      waitpid $kid, 0;
      return Devel::FindRef::ptr2ref $addr if $? == 0;
    } else {
      warn 'Unable to fork: $!';
    }
  }
  return undef;
}

I'm not sure if the return value of waitpid needs to be checked.

Community
  • 1
  • 1
Moh
  • 304
  • 2
  • 8