Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a custom error handling system that generates a hash containing information about an error, and then uses a reference to that hash as the argument to die().

For the following code:

eval { require('some_code.pl'); }; if ($@) { print($@); }

The output, when a error occurs, is:
HASH(0x27ff548)Compilation failed in require...

I would like to recover the hash ref from $@ (possibly by stripping off the trailing text?) so that I can look at the data inside it.

Can this be done? If so, how?

Replies are listed 'Best First'.
Re: Extracting a hash reference from a string
by kschwab (Vicar) on Mar 23, 2003 at 02:16 UTC

      This is exactly the pointer (pun intended :) I was looking for.

      The following code accomplishes my task:

      #!/usr/bin/perl -T use strict; use warnings; use Devel::Pointer; eval { require('some_code.pl'); }; if ($@) { my $start = index($@, 'HASH(0x') + 7; my $end = index($@, ')', $start); my $hex = substr($@, $start, $end-$start); my $addr = hex($hex); my $href = unsmash_hv(0+$addr); print("$$href{'Error data'}\n"); } # EOF

      Since the hash is guaranteed (in my case) to have a non-zero reference count, this code is safe for my purposes.

        my $start = index($@, 'HASH(0x') + 7; my $end = index($@, ')', $start); my $hex = substr($@, $start, $end-$start);
        Why the handwork? Why aren't you using a regex?
        my($hex) = $@ =~ /HASH\(0x(\w+)\)/;
Re: Extracting a hash reference from a string
by DaveH (Monk) on Mar 23, 2003 at 03:29 UTC

    Hi.

    As it stands, what you are doing is unlikely to work. The hash in the require block is likely to be lexically scoped to that block (or file). Taking a hash reference to something declared in the other file is only going to work if you explicity force the variable into the current scope before dying, e.g. in 'some_code.pl', you say something like:

    %main::err = ( "errno" => 1, "message" => "I'm dead I am", );

    Now, assuming package 'main' was the current scope, saying for example $err{errno} would give access to the error hash. But since you have just died out of that file, using data from there might not be the best idea... not to mention the crime of forcing your variables onto another packages scope.

    If you really want to use die, then you need to die with something useful. Dying with a normal string representation of a hash is unlikely to prove very useful. Better would be to use Data::Dumper to die with a data structure.

    some_file.pl

    #!/usr/bin/perl use Data::Dumper; die Data::Dumper->Dump([ { # die with a hash value "errno" => 1, "message" => "I'm dead I am", } ], [ '*err' ]) . "\n"; 1;

    Your main program:

    # This is our error container my %err; # Rather than "require", we use "do". This stops # the compilation warning from appearing. eval { do "some_code.pl"; die $@ if $@; }; if ($@) { eval $@; # evaluate our "error" string print $err{errno}; # print our errno print $err{message}; # print our message }

    I don't really like this method of passing data structures around, but it does work. I suppose that if you have full control over the contents of "some_file.pl" then it is *just* acceptable, but there are far too many evals in this solution. I don't like the thought of accidentally eval'ing a string contained in $@ which I wasn't expecting...

    I would strongly suggest using Taint (-T) if this is going to end up in production code. However, there are loads of CPAN modules which encapsulate this sort of throw/catch/try type structure you are creating. Searching for Exception is likely to return one or two which would more than likely fit your purposes.

    Hope that helps.

    Cheers,

    -- Dave :-)


    $q=[split+qr,,,q,~swmi,.$,],+s.$.Em~w^,,.,s,.,$&&$$q[pos],eg,print

      While Devel::Pointers technically accomplished what I was looking to do, the use of do() instead of require() proved to be a much better solution. Further, it addresses the concerns expressed by 'pg'.

      my $rc = do($mycode); if ($@ || !defined($rc)) { if ($@) { if (ref($@) eq 'HASH') { print("Error from $mycode: $$@{'Error'}\n"); } else { print("Compilation errors in $mycode: $@"); } } else { print("Couldn't access $mycode: $!"; } }
Re: Extracting a hash reference from a string (not)
by Aristotle (Chancellor) on Mar 23, 2003 at 12:20 UTC
    You don't want require. You want do. Quoting perlfunc:
    do 'stat.pl'; is just like scalar eval `cat stat.pl`; except that it's more efficient and concise, keeps track of the current filename for error messages, searches the @INC libraries, and updates %INC if the file is found.
    Assuming some_code.pl is
    die +{ foo => 1 };
    you can easily test that it does what you want:
    do 'some_code.pl'; print ref $@ if $@; =output HASH
    You should not fiddle with pointers. What if a future revision somewhere in a different place, likely made by a different maintainer who is not aware of your trickery, undoes the guarantee for the hash's refcount? There's a reason that module is in the Devel:: namespace. I'd be wary of any production code that uses it.

    Makeshifts last the longest.

Re: Extracting a hash reference from a string
by jand (Friar) on Mar 23, 2003 at 02:17 UTC
    Well, if you really died like this:
    my %exception = (err => 42, str => 'My fault'); die \%exception;
    then $@ would contain the hash reference. Since it seems to also include the text, I assume that you are throwing the error in a different way, stringifying the hash reference as you go along.

    A better way to do this (if you want the error object to still work as a normal error message when not used as a hash ref) is to return a proper blessed object. That way you can use the overload.pm module to provide custom stringification, and also have access to your exception record.

      The text 'Compilation failed...' is added by the require() function. It appears that require() adds this text to whatever is returned by the compilation process - in this case the hash ref from die().
        Yes, you are right. The require opcode always appends the string to $@, regardless if it is an object or not. Note that this will stringify your reference and then decrement the reference count to your hash. So don't try to access it through the pointer provided by the stringification (which would be a bad idea anyways).

        I guess this means you will have to use a different error mechanism if your required modules can fail in a variety of ways. You could adopt some convention of package variables, like this:

        eval { require MyModule } if (defined $MyModule::Error) { print "Couldn't load MyModule: ", $MyModule::Error->{message}, + "\n"; # ... }
        and in your MyModule.pm file:
        package MyModule; our $Error; # ... if ($some_error) { $Error = {err = 42, message => 'Sorry, my fault!'}; die; # or maybe just 'return 0;' }
Re: Extracting a hash reference from a string
by Zaxo (Archbishop) on Mar 23, 2003 at 02:22 UTC

    You are likely to find that the underlying hash has been destroyed before you get to the $@ postmortem. Stringified, the "reference" is not represented in the reference count.

    After Compline,
    Zaxo

      This is a good point, but in this case it is not a problem. The underlying hash guaranteed not to have had it reference count go to zero.
Re: Extracting a hash reference from a string
by data64 (Chaplain) on Mar 23, 2003 at 02:33 UTC

    You might want to look at Exception::Class, since that is what you seem to be trying to do.


    Just a tongue-tied, twisted, earth-bound misfit. -- Pink Floyd

      I did look at the expection modules available, but they looked to limited and cumbersome to use.

      There is also the question of whether or not the mechanisms those modules employ are subject to the same problem that I am encountering here - namely trying to 'throw' a complex data structure through a require() call

Re: Extracting a hash reference from a string
by pg (Canon) on Mar 23, 2003 at 02:41 UTC
    I strongly discourage the idea to fiddle with c pointers thru Perl.

    Perl is a high level language, so use it as a high level language, and leave all the low level details to Perl.

    To deal with addresses and pointers directly, would most likely cause more problems.

      This non-answer is not helpful. While I don't disagree with you in principle, I need to know what I can do; not what I shouldn't do.
Re: Extracting a hash reference from a string
by Anonymous Monk on Mar 23, 2003 at 11:19 UTC
    Usually, the syntax is %{$hashref} to dereference the reference. Try %$@.