From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#!./perl
#
# test OP_MULTIDEREF.
#
# This optimising op is used when one or more array or hash aggregate
# lookups / derefs are performed, and where each key/index is a simple
# constant or scalar var; e.g.
#
# $r->{foo}[0]{$k}[$i]
BEGIN {
chdir 't';
require './test.pl';
set_up_inc("../lib");
}
use strict;
plan 65;
# check that strict refs hint is handled
{
package strict_refs;
our %foo;
my @a = ('foo');
eval {
$a[0]{k} = 7;
};
::like($@, qr/Can't use string/, "strict refs");
::ok(!exists $foo{k}, "strict refs, not exist");
no strict 'refs';
$a[0]{k} = 13;
::is($foo{k}, 13, "no strict refs, exist");
}
# check the basics of multilevel lookups
{
package basic;
# build up the multi-level structure piecemeal to try and avoid
# relying on what we're testing
my @a;
my $r = \@a;
my $rh = {};
my $ra = [];
my %h = qw(a 1 b 2 c 3 d 4 e 5 f 6);
push @a, 66, 77, 'abc', $rh;
%$rh = (foo => $ra, bar => 'BAR');
push @$ra, 'def', \%h;
our ($i1, $i2, $k1, $k2) = (3, 1, 'foo', 'c');
my ($li1, $li2, $lk1, $lk2) = (3, 1, 'foo', 'c');
my $z = 0;
# fetch
::is($a[3]{foo}[1]{c}, 3, 'fetch: const indices');
::is($a[$i1]{$k1}[$i2]{$k2}, 3, 'fetch: pkg indices');
::is($r->[$i1]{$k1}[$i2]{$k2}, 3, 'fetch: deref pkg indices');
::is($a[$li1]{$lk1}[$li2]{$lk2}, 3, 'fetch: lexical indices');
::is($r->[$li1]{$lk1}[$li2]{$lk2}, 3, 'fetch: deref lexical indices');
::is(($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2}, 3,
'fetch: general expression and index');
# store
::is($a[3]{foo}[1]{c} = 5, 5, 'store: const indices');
::is($a[3]{foo}[1]{c}, 5, 'store: const indices 2');
::is($a[$i1]{$k1}[$i2]{$k2} = 7, 7, 'store: pkg indices');
::is($a[$i1]{$k1}[$i2]{$k2}, 7, 'store: pkg indices 2');
::is($r->[$i1]{$k1}[$i2]{$k2} = 9, 9, 'store: deref pkg indices');
::is($r->[$i1]{$k1}[$i2]{$k2}, 9, 'store: deref pkg indices 2');
::is($a[$li1]{$lk1}[$li2]{$lk2} = 11, 11, 'store: lexical indices');
::is($a[$li1]{$lk1}[$li2]{$lk2}, 11, 'store: lexical indices 2');
::is($r->[$li1]{$lk1}[$li2]{$lk2} = 13, 13, 'store: deref lexical indices');
::is($r->[$li1]{$lk1}[$li2]{$lk2}, 13, 'store: deref lexical indices 2');
::is(($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2} = 15, 15,
'store: general expression and index');
::is(($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2}, 15,
'store: general expression and index 2');
# local
{
::is(local $a[3]{foo}[1]{c} = 19, 19, 'local const indices');
::is($a[3]{foo}[1]{c}, 19, 'local const indices 2');
}
::is($a[3]{foo}[1]{c}, 15, 'local const indices 3');
{
::is(local $a[$i1]{$k1}[$i2]{$k2} = 21, 21, 'local pkg indices');
::is($a[$i1]{$k1}[$i2]{$k2}, 21, 'local pkg indices 2');
}
::is($a[$i1]{$k1}[$i2]{$k2}, 15, 'local pkg indices 3');
{
::is(local $a[$li1]{$lk1}[$li2]{$lk2} = 23, 23, 'local lexical indices');
::is($a[$li1]{$lk1}[$li2]{$lk2}, 23, 'local lexical indices 2');
}
::is($a[$li1]{$lk1}[$li2]{$lk2}, 15, 'local lexical indices 3');
{
::is(local+($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2} = 25, 25,
'local general');
::is(($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2}, 25, 'local general 2');
}
::is(($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2}, 15, 'local general 3');
# exists
::ok(exists $a[3]{foo}[1]{c}, 'exists: const indices');
::ok(exists $a[$i1]{$k1}[$i2]{$k2}, 'exists: pkg indices');
::ok(exists $r->[$i1]{$k1}[$i2]{$k2}, 'exists: deref pkg indices');
::ok(exists $a[$li1]{$lk1}[$li2]{$lk2}, 'exists: lexical indices');
::ok(exists $r->[$li1]{$lk1}[$li2]{$lk2}, 'exists: deref lexical indices');
::ok(exists +($r//0)->[$li1]{$lk1}[$li2+$z]{$lk2}, 'exists: general');
# delete
our $k3 = 'a';
my $lk4 = 'b';
::is(delete $a[3]{foo}[1]{c}, 15, 'delete: const indices');
::is(delete $a[$i1]{$k1}[$i2]{$k3}, 1, 'delete: pkg indices');
::is(delete $r->[$i1]{$k1}[$i2]{d}, 4, 'delete: deref pkg indices');
::is(delete $a[$li1]{$lk1}[$li2]{$lk4}, 2, 'delete: lexical indices');
::is(delete $r->[$li1]{$lk1}[$li2]{e}, 5, 'delete: deref lexical indices');
::is(delete +($r//0)->[$li1]{$lk1}[$li2+$z]{f}, 6, 'delete: general');
# !exists
::ok(!exists $a[3]{foo}[1]{c}, '!exists: const indices');
::ok(!exists $a[$i1]{$k1}[$i2]{$k3}, '!exists: pkg indices');
::ok(!exists $r->[$i1]{$k1}[$i2]{$k3}, '!exists: deref pkg indices');
::ok(!exists $a[$li1]{$lk1}[$li2]{$lk4}, '!exists: lexical indices');
::ok(!exists $r->[$li1]{$lk1}[$li2]{$lk4},'!exists: deref lexical indices');
::ok(!exists +($r//0)->[$li1]{$lk1}[$li2+$z]{$lk4},'!exists: general');
}
# weird "constant" keys
{
use constant my_undef => undef;
use constant my_ref => [];
no warnings 'uninitialized';
my %h1;
$h1{+my_undef} = 1;
is(join(':', keys %h1), '', "+my_undef");
my %h2;
$h2{+my_ref} = 1;
like(join(':', keys %h2), qr/x/, "+my_ref");
}
{
# test that multideref is marked OA_DANGEROUS, i.e. its one of the ops
# that should set the OPpASSIGN_COMMON flag in list assignments
my $x = {};
$x->{a} = [ 1 ];
$x->{b} = [ 2 ];
($x->{a}, $x->{b}) = ($x->{b}, $x->{a});
is($x->{a}[0], 2, "OA_DANGEROUS a");
is($x->{b}[0], 1, "OA_DANGEROUS b");
}
# defer
sub defer {}
{
my %h;
$h{foo} = {};
defer($h{foo}{bar});
ok(!exists $h{foo}{bar}, "defer");
}
# RT #123609
# don't evaluate a const array index unless it's really a const array
# index
{
my $warn = '';
local $SIG{__WARN__} = sub { $warn .= $_[0] };
ok(
eval q{
my @a = (1);
my $arg = 0;
my $x = $a[ 'foo' eq $arg ? 1 : 0 ];
1;
},
"#123609: eval"
)
or diag("eval gave: $@");
is($warn, "", "#123609: warn");
}
# RT #130727
# a [ah]elem op can be both OPpLVAL_INTRO and OPpDEREF. It may not make
# much sense, but it shouldn't fail an assert.
{
my @x;
eval { @{local $x[0][0]} = 1; };
like $@, qr/Can't use an undefined value as an ARRAY reference/,
"RT #130727 error";
ok !defined $x[0][0],"RT #130727 array not autovivified";
eval { @{1, local $x[0][0]} = 1; };
like $@, qr/Can't use an undefined value as an ARRAY reference/,
"RT #130727 part 2: error";
ok !defined $x[0][0],"RT #130727 part 2: array not autovivified";
}
# RT #131627: assertion failure on OPf_PAREN on OP_GV
{
my @x = (10..12);
our $rt131627 = 1;
no strict qw(refs vars);
is $x[qw(rt131627)->$*], 11, 'RT #131627: $a[qw(var)->$*]';
}
# this used to leak - run the code for ASan to spot any problems
{
package Foo;
our %FIELDS = ();
my Foo $f;
eval q{ my $x = $f->{c}; };
::pass("S_maybe_multideref() shouldn't leak on croak");
}
fresh_perl_is('0for%{scalar local$0[0]}', '', {},
"RT #134045 assertion on the OP_SCALAR");