Skip to content

Commit 9fa15f1

Browse files
committed
pp.c - forbid localizing and aliasing readonly hash keys in restricted hashes
local $hash{key} and \$hash{key} = \$var are both conceptually modify operations which are forbidden when the hash is restricted and the value is readonly. Unfortunately prior to this commit they were still allowed operations. This patch corrects that oversight. Adds a bunch of tests to t/op/lvref.t to ensure that it is illegal to localize or ref-alias a readonly value in a restricted hash.
1 parent 741a9d2 commit 9fa15f1

File tree

8 files changed

+140
-23
lines changed

8 files changed

+140
-23
lines changed

embed.fnc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,12 @@ AMbdp |HE * |hv_fetch_ent |NULLOK HV *hv \
14681468
|NN SV *keysv \
14691469
|I32 lval \
14701470
|U32 hash
1471+
Edm |HE * |hv_fetch_ent_for \
1472+
|NULLOK HV *hv \
1473+
|NN SV *keysv \
1474+
|I32 lval \
1475+
|U32 hash \
1476+
|U32 for_flags
14711477
Cdop |STRLEN |hv_fill |NN HV * const hv
14721478
Cp |void |hv_free_ent |NULLOK HV *notused \
14731479
|NULLOK HE *entry
@@ -1518,6 +1524,12 @@ AMbdp |HE * |hv_store_ent |NULLOK HV *hv \
15181524
|NULLOK SV *key \
15191525
|NULLOK SV *val \
15201526
|U32 hash
1527+
Edm |HE * |hv_store_ent_for \
1528+
|NULLOK HV *hv \
1529+
|NULLOK SV *key \
1530+
|NULLOK SV *val \
1531+
|U32 hash \
1532+
|U32 for_flags
15211533
AMbpx |SV ** |hv_store_flags |NULLOK HV *hv \
15221534
|NULLOK const char *key \
15231535
|I32 klen \

hv.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,13 @@ C<hv_store> in preference to C<hv_store_ent>.
396396
See L<perlguts/"Understanding the Magic of Tied Hashes and Arrays"> for more
397397
information on how to use this function on tied hashes.
398398
399+
=for apidoc hv_store_ent_for
400+
401+
Identical to C<hv_store_ent()> but accepting an additional parameter which allows
402+
the caller to signal what the store is for, typically HV_ACTION_ISLOCALIZE or
403+
HV_ACTION_ISALIAS. This additional data is passed into hv_common() in the
404+
C<action> field (B<not> the flags field). Intended for internal use only.
405+
399406
=for apidoc hv_exists
400407
401408
Returns a boolean indicating whether the specified hash key exists. The
@@ -442,6 +449,13 @@ store it somewhere.
442449
See L<perlguts/"Understanding the Magic of Tied Hashes and Arrays"> for more
443450
information on how to use this function on tied hashes.
444451
452+
=for apidoc hv_fetch_ent_for
453+
454+
Identical to C<hv_fetch_ent()> but accepting an additional parameter which allows
455+
the caller to signal what the fetch is for, typically HV_ACTION_ISLOCALIZE or
456+
HV_ACTION_ISALIAS. This additional data is passed into hv_common() in the
457+
C<action> field (B<not> the flags field). Intended for internal use only.
458+
445459
=cut
446460
*/
447461

@@ -878,6 +892,14 @@ Perl_hv_common(pTHX_ HV *hv, SV *keysv, const char *key, STRLEN klen,
878892
}
879893
HeVAL(entry) = val;
880894
} else if (action & HV_FETCH_ISSTORE) {
895+
if (SvREADONLY(hv) && SvREADONLY(HeVAL(entry))) {
896+
hv_notallowed(flags, key, klen,
897+
(action & HV_ACTION_ISLOCALIZE) ? "localize" :
898+
(action & HV_ACTION_ISALIAS) ? "alias" : "modify",
899+
"Attempt to %s readonly key %" SVf_QUOTEDPREFIX " in"
900+
" restricted hash");
901+
}
902+
881903
SvREFCNT_dec(HeVAL(entry));
882904
HeVAL(entry) = val;
883905
}

hv.h

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -509,15 +509,24 @@ whether it is valid to call C<HvAUX()>.
509509
->shared_he_he.he_valu.hent_refcount), \
510510
hek)
511511

512+
#define hv_store_ent_for(hv, keysv, val, hash, for_flags) \
513+
((HE *) hv_common((hv), (keysv), NULL, 0, 0, \
514+
(HV_FETCH_ISSTORE | (for_flags)), (val), (hash)))
515+
512516
#define hv_store_ent(hv, keysv, val, hash) \
513-
((HE *) hv_common((hv), (keysv), NULL, 0, 0, HV_FETCH_ISSTORE, \
514-
(val), (hash)))
517+
hv_store_ent_for(hv, keysv, val, hash, 0)
515518

516519
#define hv_exists_ent(hv, keysv, hash) \
517520
cBOOL(hv_common((hv), (keysv), NULL, 0, 0, HV_FETCH_ISEXISTS, 0, (hash)))
518-
#define hv_fetch_ent(hv, keysv, lval, hash) \
521+
522+
#define hv_fetch_ent_for(hv, keysv, lval, hash, for_flags) \
519523
((HE *) hv_common((hv), (keysv), NULL, 0, 0, \
520-
((lval) ? HV_FETCH_LVALUE : 0), NULL, (hash)))
524+
(((lval) ? HV_FETCH_LVALUE : 0)|(for_flags)), \
525+
NULL, (hash)))
526+
527+
#define hv_fetch_ent(hv, keysv, lval, hash) \
528+
hv_fetch_ent_for(hv, keysv, lval, hash, 0)
529+
521530
#define hv_delete_ent(hv, key, flags, hash) \
522531
(MUTABLE_SV(hv_common((hv), (key), NULL, 0, 0, (flags) | HV_DELETE, \
523532
NULL, (hash))))
@@ -681,18 +690,20 @@ instead of a string/length pair, and no precomputed hash.
681690
/* Hash actions
682691
* Passed in PERL_MAGIC_uvar calls
683692
*/
684-
#define HV_DISABLE_UVAR_XKEY0x01
693+
#define HV_DISABLE_UVAR_XKEY 0x001
685694
/* We need to ensure that these don't clash with G_DISCARD, which is 2, as it
686695
is documented as being passed to hv_delete(). */
687-
#define HV_FETCH_ISSTORE0x04
688-
#define HV_FETCH_ISEXISTS0x08
689-
#define HV_FETCH_LVALUE0x10
690-
#define HV_FETCH_JUST_SV0x20
691-
#define HV_DELETE0x40
692-
#define HV_FETCH_EMPTY_HE0x80 /* Leave HeVAL null. */
696+
#define HV_FETCH_ISSTORE 0x004
697+
#define HV_FETCH_ISEXISTS 0x008
698+
#define HV_FETCH_LVALUE 0x010
699+
#define HV_FETCH_JUST_SV 0x020
700+
#define HV_DELETE 0x040
701+
#define HV_FETCH_EMPTY_HE 0x080 /* Leave HeVAL null. */
702+
#define HV_ACTION_ISLOCALIZE 0x100
703+
#define HV_ACTION_ISALIAS 0x200
693704

694705
/* Must not conflict with HVhek_UTF8 */
695-
#define HV_NAME_SETALL0x02
706+
#define HV_NAME_SETALL 0x002
696707

697708
/*
698709
=for apidoc newHV

pod/perldiag.pod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ corrupted.
398398

399399
=item Attempt to %s readonly key "%s" in restricted hash
400400

401-
(F) The failing code has attempted to modify or
401+
(F) The failing code has attempted to localize, alias, modify or
402402
delete a key with a readonly value in a restricted hash.
403403

404404
=item Attempt to %s disallowed key "%s" in restricted hash

pp.c

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6915,13 +6915,19 @@ S_localise_aelem_lval(pTHX_ AV * const av, SV * const keysv,
69156915

69166916
static void
69176917
S_localise_helem_lval(pTHX_ HV * const hv, SV * const keysv,
6918-
const bool can_preserve)
6918+
const bool can_preserve, U32 for_flags)
69196919
{
69206920
if (can_preserve ? hv_exists_ent(hv, keysv, 0) : TRUE) {
6921-
HE * const he = hv_fetch_ent(hv, keysv, 1, 0);
6921+
HE * const he = hv_fetch_ent_for(hv, keysv, 1, 0, for_flags);
69226922
SV ** const svp = he ? &HeVAL(he) : NULL;
69236923
if (!svp || !*svp)
69246924
Perl_croak(aTHX_ PL_no_helem_sv, SVfARG(keysv));
6925+
if (SvREADONLY(hv) && SvREADONLY(*svp)) {
6926+
croak("Attempt to %s readonly key %" SVf_QUOTEDPREFIX
6927+
" in restricted hash",
6928+
(for_flags & HV_ACTION_ISLOCALIZE) ? "localize" : "alias",
6929+
keysv);
6930+
}
69256931
save_helem_flags(hv, keysv, svp, 0);
69266932
}
69276933
else
@@ -7006,12 +7012,16 @@ PP(pp_refassign)
70067012
av_store((AV *)left, SvIV(key), SvREFCNT_inc_simple_NN(SvRV(sv)));
70077013
break;
70087014
case SVt_PVHV:
7009-
if (UNLIKELY(PL_op->op_private & OPpLVAL_INTRO)) {
7010-
assert(key);
7011-
S_localise_helem_lval(aTHX_ (HV *)left, key,
7012-
SvCANEXISTDELETE(left));
7015+
{
7016+
HV *hv = (HV *)left;
7017+
if (UNLIKELY(PL_op->op_private & OPpLVAL_INTRO)) {
7018+
assert(key);
7019+
S_localise_helem_lval(aTHX_ hv, key,
7020+
SvCANEXISTDELETE(left), HV_ACTION_ISALIAS);
7021+
}
7022+
(void)hv_store_ent_for(hv, key,
7023+
SvREFCNT_inc_simple_NN(SvRV(sv)), 0, HV_ACTION_ISALIAS);
70137024
}
7014-
(void)hv_store_ent((HV *)left, key, SvREFCNT_inc_simple_NN(SvRV(sv)), 0);
70157025
}
70167026
if (PL_op->op_flags & OPf_MOD)
70177027
SETs(sv_2mortal(newSVsv(sv)));
@@ -7043,7 +7053,7 @@ PP(pp_lvref)
70437053
if (SvTYPE(arg) == SVt_PVAV)
70447054
S_localise_aelem_lval(aTHX_ (AV *)arg, elem, can_preserve);
70457055
else
7046-
S_localise_helem_lval(aTHX_ (HV *)arg, elem, can_preserve);
7056+
S_localise_helem_lval(aTHX_ (HV *)arg, elem, can_preserve, 0);
70477057
}
70487058
}
70497059
else if (arg) {
@@ -7090,7 +7100,7 @@ PP(pp_lvrefslice)
70907100
if (SvTYPE(av) == SVt_PVAV)
70917101
S_localise_aelem_lval(aTHX_ av, elemsv, can_preserve);
70927102
else
7093-
S_localise_helem_lval(aTHX_ (HV *)av, elemsv, can_preserve);
7103+
S_localise_helem_lval(aTHX_ (HV *)av, elemsv, can_preserve, 0);
70947104
}
70957105
*MARK = newSV_type_mortal(SVt_PVMG);
70967106
sv_magic(*MARK,(SV *)av,PERL_MAGIC_lvref,(char *)elemsv,HEf_SVKEY);

pp_hot.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4011,6 +4011,10 @@ PP(pp_multideref)
40114011
}
40124012
else {
40134013
if (localizing) {
4014+
if (SvREADONLY(hv) && SvREADONLY(*svp))
4015+
croak("Attempt to %s readonly key %"
4016+
SVf_QUOTEDPREFIX " in restricted hash",
4017+
"localize", keysv);
40144018
if (HvNAME_get(hv) && isGV_or_RVCV(sv))
40154019
save_gp(MUTABLE_GV(sv),
40164020
!(PL_op->op_flags & OPf_SPECIAL));

proto.h

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

t/op/lvref.t

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ BEGIN {
55
set_up_inc("../lib");
66
}
77

8-
plan 167;
8+
plan 188;
99

1010
eval '\$x = \$y';
1111
like $@, qr/^Experimental aliasing via reference not enabled/,
@@ -651,3 +651,55 @@ pass("RT #123821");
651651
is("$h{one} $h{two} $h{three}", "102 200 301", "rt #133538 %h still aliased");
652652

653653
}
654+
{
655+
use feature 'refaliasing', 'declared_refs';
656+
no warnings 'experimental::declared_refs';
657+
my %hash = ( locked => "k1", normal => "k2" );
658+
Internals::SvREADONLY(%hash, 1);
659+
Internals::SvREADONLY($hash{locked}, 1);
660+
my $href = \%hash;
661+
my $val = "val";
662+
ok(!eval { \$hash{locked} = \$val; 1},
663+
"Eval alias of readonly hash key in restricted hash died");
664+
like($@,qr/Attempt to alias readonly key "locked" in restricted hash at/,
665+
"Got the error we expected");
666+
ok(!eval { local \$hash{locked} = \$val; 1},
667+
"Eval local alias of readonly hash key in restricted hash died");
668+
like($@,qr/Attempt to alias readonly key "locked" in restricted hash at/,
669+
"Got the error we expected");
670+
ok(!eval { local $hash{locked} = $val; 1},
671+
"Eval localization of readonly hash key in restricted hash died");
672+
like($@,qr/Attempt to localize readonly key "locked" in restricted hash at/,
673+
"Got the error we expected");
674+
ok(!eval { \$href->{locked} = \$val; 1},
675+
"Eval alias of readonly hashref key in restricted hash died");
676+
like($@,qr/Attempt to alias readonly key "locked" in restricted hash at/,
677+
"Got the error we expected");
678+
ok(!eval { local \$href->{locked} = \$val; 1},
679+
"Eval local alias of readonly hashref key in restricted hash died");
680+
like($@,qr/Attempt to alias readonly key "locked" in restricted hash at/,
681+
"Got the error we expected");
682+
ok(!eval { local $href->{locked} = $val; 1},
683+
"Eval localization of readonly hashref key in restricted hash died");
684+
like($@,qr/Attempt to localize readonly key "locked" in restricted hash at/,
685+
"Got the error we expected");
686+
ok(eval { { local \$hash{normal} = \1; } 1},
687+
"Eval unlocalization of aliased readonly hash key should not die");
688+
ok(eval { { local $hash{normal} = 1; Internals::SvREADONLY($hash{normal},1); } 1},
689+
"Eval unlocalization of readonly hash key should not die");
690+
ok(eval { { local $hash{normal} = 1; Internals::SvREADONLY($hash{normal},1); } 1},
691+
"Eval unlocalization of readonly hash key should not die");
692+
ok(eval { { local \$hash->{normal} = \1; } 1},
693+
"Eval unlocalization of aliased readonly hashref key should not die");
694+
ok(eval { { local $hash->{normal} = 1; Internals::SvREADONLY($hash{normal},1); } 1},
695+
"Eval unlocalization of readonly hashref key should not die");
696+
ok(!eval { { local \$hash{normal} = \1; local \$hash{normal} = \2; } 1},
697+
"Eval aliased localization of localized aliased readonly hash key should die");
698+
like($@,qr/Attempt to alias readonly key \"normal\" in restricted hash/,
699+
"Got the error we expected");
700+
ok(!eval { { local $hash{normal} = 1; Internals::SvREADONLY($hash{normal},1);
701+
local $hash{normal} = 2; } 1},
702+
"Eval localization of localized readonly hash key should die");
703+
like($@,qr/Attempt to localize readonly key \"normal\" in restricted hash/,
704+
"Got the error we expected");
705+
}

0 commit comments

Comments
 (0)