perltie

Strange behavior of a tied hash in perl, when asking for an arrayref


I was trying to tie an hash (or hashref) in order of tracking variable usages.

Everything is working for simple cases, but when I tried to use my module on some real code I had this error:

hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this)

I've replicated the error using the following code:

use Tie::Hash::Usages;
use JSON;

my @arr = (
    {
        key1 => "ac",
        key2 => 12,
        key3 => 12
    },        
);
my %tied_hash;


tie %tied_hash, 'Tie::Hash::Usages';

$tied_hash{key1} = \@arr;

my @val = $tied_hash{key1};
print encode_json(\@val)."\n\n"; #this works

print encode_json($tied_hash{key1}); #this doesn't

The same code works with a plain hash.

I'd need this to work also in the second case, the code base is huge and I don't want to change it or live with the doubt that something somewhere will not work in some particular case.

Usages.pm (simplified)

package Tie::Hash::Usages;
use strict;
use warnings;

use Tie::Hash;

use vars qw(@ISA);

@ISA = qw(Tie::StdHash);

sub TIEHASH {

    my ($class, $tracker, $filename) = @_;
    my %hash;

    bless \%hash, $class;

}

sub STORE {
    my ($self, $key, $val) = @_;
    $self->{$key} = $val;
}

sub DELETE {
    my ($self, $key) = @_;
    delete $self->{$key};

}

sub FETCH {
    my ($self, $key) = @_;
    return $self->{$key};
}

sub DESTROY {
    my $self = shift;
}
1;

perl version: v5.18.2


Solution

  • Minimal demonstration:

    use JSON::XS  qw( encode_json );
    use Tie::Hash qw( );
    
    our @ISA = 'Tie::StdHash';
    
    {
       tie my %tied, __PACKAGE__;
       $tied{data} = { a => 1 };
       encode_json($tied{data});  # Exception: hash- or arrayref expected ...
    }
    

    JSON is a front-end for JSON::PP (default) or JSON::XS (if found). This is a problem with JSON::XS.

    A lot of XS code doesn't handle magical variables (which is what $tied{EXPR} returns), and while JSON::XS has handled magical values since version 1.2, it doesn't for the value directly passed to encode_json.

    This is an existing bug in JSON::XS that can be worked around as follows:

    encode_json(my $non_magical = $tied{data})
    

    Bug reported.