Skip to content

Commit 73dbc82

Browse files
committed
pp_ctl.c - copy hook into %INC not alias it
When an @inc hook is executed and it updates %INC it can result in @inc being updated and the hook being destroyed. This seems to be because the SV fetched from @inc is stored into %INC directly, essentially creating an alias (in perl terms) of the original. When the alias is updated, for instance by setting it to be a string, this is reflected in both @inc and %INC. By copying the sv before we store it we avoid this problem. We can't run the test under miniperl as it uses IO layers. This should fix GH #20577.
1 parent d0a777f commit 73dbc82

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5972,6 +5972,7 @@ t/op/ref.t See if refs and objects work
59725972
t/op/repeat.t See if x operator works
59735973
t/op/require_37033.t See if require always closes rsfp
59745974
t/op/require_errors.t See if errors from require are reported correctly
5975+
t/op/require_gh20577.t Make sure updating %INC from an INC hook doesnt break @INC
59755976
t/op/require_override.t See if require handles no argument properly
59765977
t/op/reset.t See if reset operator works
59775978
t/op/reverse.t See if reverse operator works

pp_ctl.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4605,10 +4605,13 @@ S_require_file(pTHX_ SV *sv)
46054605
(void)hv_store(GvHVn(PL_incgv),
46064606
unixname, unixlen, newSVpv(tryname,0),0);
46074607
} else {
4608+
/* store the hook in the sv, note we have to *copy* hook_sv,
4609+
* we don't want modifications to it to change @INC - see GH #20577
4610+
*/
46084611
SV** const svp = hv_fetch(GvHVn(PL_incgv), unixname, unixlen, 0);
46094612
if (!svp)
46104613
(void)hv_store(GvHVn(PL_incgv),
4611-
unixname, unixlen, SvREFCNT_inc_simple(hook_sv), 0 );
4614+
unixname, unixlen, newSVsv(hook_sv), 0 );
46124615
}
46134616

46144617
/* Now parse the file */

t/op/require_gh20577.t

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!perl -w
2+
3+
# Check that modifying %INC during an @INC hook does not
4+
# clobber the hook by modifying @INC at the same time.
5+
# See GitHub Issue #20577
6+
7+
chdir "t" if -d "t";
8+
require './test.pl';
9+
skip_all_if_miniperl("as PerlIO layer 'scalar' not supported under miniperl");
10+
set_up_inc( '../lib' );
11+
eval <<'EOF' or die $@;
12+
{
13+
my %fatpacked;
14+
15+
$fatpacked{"Test1.pm"} = <<'TEST1';
16+
package Test1;
17+
sub import {
18+
my $filename = 'Test2.pm';
19+
$INC{$filename} = "the_test_file";
20+
}
21+
1;
22+
TEST1
23+
24+
$fatpacked{"Test2.pm"} = <<'TEST2';
25+
package Test2;
26+
use Test1;
27+
1;
28+
TEST2
29+
30+
my $class = 'FatPacked';
31+
no strict 'refs';
32+
33+
*{"${class}::INC"} = sub {
34+
if ( my $fat = $_[0]{ $_[1] } ) {
35+
open my $fh, '<', \$fat
36+
or die;
37+
return $fh;
38+
}
39+
return;
40+
};
41+
42+
unshift @INC, bless \%fatpacked, $class;
43+
}
44+
1
45+
EOF
46+
47+
ok(UNIVERSAL::isa($INC[0],"FatPacked"), '$INC[0] starts FatPacked');
48+
ok(!exists $INC{"Test1.pm"}, 'Test1.pm not in %INC');
49+
ok(!exists $INC{"Test2.pm"}, 'Test2.pm not in %INC');
50+
my $ok= eval "use Test2; 1";
51+
my $err= !$ok ? $@ : undef;
52+
is($err,undef,"No error loading Test2");
53+
is($ok,1,"Loaded Test2 successfully");
54+
ok(UNIVERSAL::isa($INC[0],"FatPacked"), '$INC[0] is still FatPacked');
55+
ok(UNIVERSAL::isa($INC{"Test1.pm"},"FatPacked"), '$INC{"Test1.pm"} is still FatPacked');
56+
is($INC{"Test2.pm"},"the_test_file", '$INC{"Test2.pm"} is as expected');
57+
is($INC[0],$INC{"Test1.pm"},'Same object in @INC and %INC');
58+
done_testing();

0 commit comments

Comments
 (0)