Skip to content

Commit 1afa289

Browse files
apsteve-m-hay
authored andcommitted
wip
(cherry picked from commit e85f59b)
1 parent c12c012 commit 1afa289

File tree

5 files changed

+131
-2
lines changed

5 files changed

+131
-2
lines changed

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,6 +3007,7 @@ dist/base/t/fields-5_6_0.t See if fields work
30073007
dist/base/t/fields-5_8_0.t See if fields work
30083008
dist/base/t/fields-base.t See if fields work
30093009
dist/base/t/fields.t See if fields work
3010+
dist/base/t/incdot.t Test how base.pm handles '.' in @INC
30103011
dist/base/t/isa.t See if base's behaviour doesn't change
30113012
dist/base/t/lib/Broken.pm Test module for base.pm
30123013
dist/base/t/lib/Dummy.pm Test module for base.pm

dist/base/lib/base.pm

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use vars qw($VERSION);
66
$VERSION = '2.23';
77
$VERSION =~ tr/_//d;
88

9+
# simplest way to avoid indexing of the package: no package statement
10+
sub base::__inc::unhook { @INC = grep !(ref eq 'CODE' && $_ == $_[0]), @INC }
11+
# instance is blessed array of coderefs to be removed from @INC at scope exit
12+
sub base::__inc::scope_guard::DESTROY { base::__inc::unhook $_ for @{$_[0]} }
13+
914
# constant.pm is slow
1015
sub SUCCESS () { 1 }
1116

@@ -91,13 +96,59 @@ sub import {
9196

9297
next if grep $_->isa($base), ($inheritor, @bases);
9398

94-
# Following blocks help isolate $SIG{__DIE__} changes
99+
# Following blocks help isolate $SIG{__DIE__} and @INC changes
95100
{
96101
my $sigdie;
97102
{
98103
local $SIG{__DIE__};
99104
my $fn = _module_to_filename($base);
100-
eval { require $fn };
105+
my $dot_hidden;
106+
eval {
107+
my $guard;
108+
if ($INC[-1] eq '.' && %{"$base\::"}) {
109+
# So: the package already exists => this an optional load
110+
# And: there is a dot at the end of @INC => we want to hide it
111+
# However: we only want to hide it during our *own* require()
112+
# (i.e. without affecting nested require()s).
113+
# So we add a hook to @INC whose job is to hide the dot, but which
114+
# first checks checks the callstack depth, because within nested
115+
# require()s the callstack is deeper.
116+
# Since CORE::GLOBAL::require makes it unknowable in advance what
117+
# the exact relevant callstack depth will be, we have to record it
118+
# inside a hook. So we put another hook just for that at the front
119+
# of @INC, where it's guaranteed to run -- immediately.
120+
# The dot-hiding hook does its job by sitting directly in front of
121+
# the dot and removing itself from @INC when reached. This causes
122+
# the dot to move up one index in @INC, causing the loop inside
123+
# pp_require() to skip it.
124+
# Loaded coded may disturb this precise arrangement, but that's OK
125+
# because the hook is inert by that time. It is only active during
126+
# the top-level require(), when @INC is in our control. The only
127+
# possible gotcha is if other hooks already in @INC modify @INC in
128+
# some way during that initial require().
129+
# Note that this jiggery hookery works just fine recursively: if
130+
# a module loaded via base.pm uses base.pm itself, there will be
131+
# one pair of hooks in @INC per base::import call frame, but the
132+
# pairs from different nestings do not interfere with each other.
133+
my $lvl;
134+
unshift @INC, sub { return if defined $lvl; 1 while defined caller ++$lvl; () };
135+
splice @INC, -1, 0, sub { return if defined caller $lvl; ++$dot_hidden, &base::__inc::unhook; () };
136+
$guard = bless [ @INC[0,-2] ], 'base::__inc::scope_guard';
137+
}
138+
require $fn
139+
};
140+
if ($dot_hidden && (my @fn = grep -e && !( -d _ || -b _ ), $fn.'c', $fn)) {
141+
require Carp;
142+
Carp::croak(<<ERROR);
143+
Base class package "$base" is not empty but "$fn[0]" exists in the current directory.
144+
To help avoid security issues, base.pm now refuses to load optional modules
145+
from the current working directory when it is the last entry in \@INC.
146+
If your software worked on previous versions of Perl, the best solution
147+
is to use FindBin to detect the path properly and to add that path to
148+
\@INC. As a last resort, you can re-enable looking in the current working
149+
directory by adding "use lib '.'" to your code.
150+
ERROR
151+
}
101152
# Only ignore "Can't locate" errors from our eval require.
102153
# Other fatal errors (syntax etc) must be reported.
103154
#

dist/base/t/incdot.t

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/perl -w
2+
3+
use strict;
4+
5+
#######################################################################
6+
7+
sub array_diff {
8+
my ( $got, $expected ) = @_;
9+
push @$got, ( '(missing)' ) x ( @$expected - @$got ) if @$got < @$expected;
10+
push @$expected, ( '(should not exist)' ) x ( @$got - @$expected ) if @$got > @$expected;
11+
join "\n ", ' All differences:', (
12+
map +( "got [$_] " . $got->[$_], 'expected'.(' ' x length).$expected->[$_] ),
13+
grep $got->[$_] ne $expected->[$_],
14+
0 .. $#$got
15+
);
16+
}
17+
18+
#######################################################################
19+
20+
use Test::More tests => 8; # some extra tests in t/lib/BaseInc*
21+
22+
use lib 't/lib', sub {()};
23+
24+
# make it look like an older perl
25+
BEGIN { push @INC, '.' if $INC[-1] ne '.' }
26+
27+
BEGIN {
28+
my $x = sub { CORE::require $_[0] };
29+
my $y = sub { &$x };
30+
my $z = sub { &$y };
31+
*CORE::GLOBAL::require = $z;
32+
}
33+
34+
my @expected; BEGIN { @expected = @INC }
35+
36+
use base 'BaseIncMandatory';
37+
38+
BEGIN {
39+
@t::lib::Dummy::ISA = (); # make it look like an optional load
40+
my $success = eval q{use base 't::lib::Dummy'}, my $err = $@;
41+
ok !$success, 'loading optional modules from . using base.pm fails';
42+
is_deeply \@INC, \@expected, '... without changes to @INC'
43+
or diag array_diff [@INC], [@expected];
44+
like $err, qr!Base class package "t::lib::Dummy" is not empty but "t/lib/Dummy\.pm" exists in the current directory\.!,
45+
'... and the proper error message';
46+
}
47+
48+
BEGIN { @BaseIncOptional::ISA = () } # make it look like an optional load
49+
use base 'BaseIncOptional';
50+
51+
BEGIN {
52+
@expected = ( 't/lib/on-head', @expected, 't/lib/on-tail' );
53+
is_deeply \@INC, \@expected, 'modules loaded by base can extend @INC at both ends'
54+
or diag array_diff [@INC], [@expected];
55+
}

dist/base/t/lib/BaseIncMandatory.pm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package BaseIncMandatory;
2+
3+
BEGIN { package main;
4+
is $INC[-1], '.', 'trailing dot remains in @INC during mandatory module load from base';
5+
ok eval('require t::lib::Dummy'), '... and modules load fine from .' or diag "$@";
6+
delete $INC{'t/lib/Dummy.pm'};
7+
}
8+
9+
1;

dist/base/t/lib/BaseIncOptional.pm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package BaseIncOptional;
2+
3+
BEGIN { package main;
4+
is $INC[-1], '.', 'trailing dot remains in @INC during optional module load from base';
5+
ok eval('require t::lib::Dummy'), '... and modules load fine from .' or diag "$@";
6+
delete $INC{'t/lib/Dummy.pm'};
7+
}
8+
9+
use lib 't/lib/on-head';
10+
11+
push @INC, 't/lib/on-tail';
12+
13+
1;

0 commit comments

Comments
 (0)