Subject: | require_module needs to work around the perl 5.8 %INC pollution bug |
This is just a placeholder ticket to reference by tickets filed against
dependencies. A patch fixing this has been submitted to author and is
also attached to this ticket.
Subject: | 0001-Make-require_module-aware-of-INC-pollution-bug-prese.patch |
From 4ce1feef9ba07a3bb482e10100c6e076bb0d3e58 Mon Sep 17 00:00:00 2001
From: Peter Rabbitson <ribasushi@cpan.org>
Date: Wed, 8 Feb 2012 11:58:26 +0100
Subject: [PATCH] Make require_module() aware of %INC pollution bug present on
perl <= 5.8
---
Changes | 3 ++
lib/Module/Runtime.pm | 32 ++++++++++++++++++++++-
t/ModExc.pm | 11 ++++++++
t/ModFalse.pm | 9 ++++++
t/rm.t | 66 +++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 117 insertions(+), 4 deletions(-)
create mode 100644 t/ModExc.pm
create mode 100644 t/ModFalse.pm
diff --git a/Changes b/Changes
index 54849fe..ce4fb1e 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,7 @@
* convert .cvsignore to .gitignore
+ * bugfix: require_module() now maintains correct %INC on perl
+ versions older than 5.10. See t/rm.t for extensive discussion
+ of the problem
version 0.011; 2011-10-24
diff --git a/lib/Module/Runtime.pm b/lib/Module/Runtime.pm
index b41889e..2570987 100644
--- a/lib/Module/Runtime.pm
+++ b/lib/Module/Runtime.pm
@@ -203,8 +203,36 @@ sub require_module($) {
# statically, doesn't have this problem, because the op flags
# are forced to scalar.
return scalar(require(&module_notional_filename));
+ use constant _OUR_REQUIRE_CALL_LINE => __LINE__ - 1;
}
+# Wrap a safety blanket to guard against the perl < 5.10 %INC bug.
+# This simply restores the semantics of require() wrt %INC state
+# as described in perlop (see t/rm.t for details):
+#
+# Done with this weird redefinition to avoid incurring eval-wrap
+# penalties on newer perl versions
+#
+# FIXME: this does not take in consideration 5.9, neither do tests
+# I do not know when the bug is fixed exactly, not sure it matters
+# either.
+#
+BEGIN { if ($] < '5.010') {
+ my $old_req_mod = \&require_module;
+ local ($@, $SIG{__DIE__});
+ no warnings 'redefine';
+ eval <<'EOC';
+ sub require_module($) {
+ my $ret = eval { &$old_req_mod };
+ if ($@ ne '') {
+ delete $INC{&module_notional_filename};
+ die $@;
+ }
+ return $ret;
+ }
+EOC
+}}
+
=back
=head2 Structured module use
@@ -267,10 +295,10 @@ function work just like L</use_module>.
sub use_package_optimistically($;$) {
my($name, $version) = @_;
check_module_name($name);
- eval { local $SIG{__DIE__}; require(module_notional_filename($name)); };
+ eval { local $SIG{__DIE__}; require_module($name) };
die $@ if $@ ne "" && $@ !~ /\A
Can't\ locate\ .+\ at
- \ \Q@{[__FILE__]}\E\ line\ \Q@{[__LINE__-1]}\E
+ \ \Q@{[__FILE__]}\E\ line\ \Q@{[_OUR_REQUIRE_CALL_LINE]}\E
/xs;
$name->VERSION($version) if defined $version;
return $name;
diff --git a/t/ModExc.pm b/t/ModExc.pm
new file mode 100644
index 0000000..3e39761
--- /dev/null
+++ b/t/ModExc.pm
@@ -0,0 +1,11 @@
+package t::ModExc;
+
+{ use 5.006; }
+use warnings;
+use strict;
+
+our $VERSION = 1;
+
+die "NO PASARAN!";
+
+1;
diff --git a/t/ModFalse.pm b/t/ModFalse.pm
new file mode 100644
index 0000000..cfa0920
--- /dev/null
+++ b/t/ModFalse.pm
@@ -0,0 +1,9 @@
+package t::ModFalse;
+
+{ use 5.006; }
+use warnings;
+use strict;
+
+our $VERSION = 1;
+
+0; # by design
diff --git a/t/rm.t b/t/rm.t
index b203fd1..2577bbf 100644
--- a/t/rm.t
+++ b/t/rm.t
@@ -1,11 +1,25 @@
use warnings;
use strict;
-use Test::More tests => 9;
+# All perls before 5.010 (not sure which 5.009xxx fixes the actual problem)
+# have a bug when requiring a module which throws an exception (syntax error
+# or otherwise) in the process. While the exception is raised and properly
+# propagated the %INC{filename} entry is left incorrectly set, so a future
+# require of the same module will appear to have succeeded, since require
+# never retries to compile a module which has a proper %INC entry. To work
+# around this a require() shim is compiled on perls with _HAS_INC_BUG, which
+# simply deletes the %INC entry. The deletion is necessary, because the buggy
+# behavior checks for existance of the %INC key, without checking the value.
+# In other words `$INC{"foo.pm"} = undef; require foo;` appears to work and
+# does not throw an exception unlike it does on perl 5.10+
+#
+use constant _HAS_INC_BUG => $] < '5.009999';
+
+use Test::More tests => _HAS_INC_BUG ? 33 : 34;
BEGIN { use_ok "Module::Runtime", qw(require_module); }
-my($result, $err);
+my($result, $err, $inc);
sub test_require_module($) {
my($name) = @_;
@@ -14,23 +28,71 @@ sub test_require_module($) {
}
# a module that doesn't exist
+ok(!exists $INC{'t/NotExist.pm'});
test_require_module("t::NotExist");
like($err, qr/^Can't locate /);
+is($result, undef);
+ok(!exists $INC{'t/NotExist.pm'}, '%INC unchanged after missing module');
# a module that's already loaded
+ok($inc = $INC{'Test/More.pm'});
test_require_module("Test::More");
is($err, "");
is($result, 1);
+is($INC{'Test/More.pm'}, $inc, '%INC unchanged after already loaded module');
# a module that we'll load now
+ok(!exists $INC{'t/Mod0.pm'});
test_require_module("t::Mod0");
is($err, "");
is($result, "t::Mod0 return");
+like($INC{'t/Mod0.pm'}, qr/Mod0.pm$/);
# re-requiring the module that we just loaded
+ok($inc = $INC{'t/Mod0.pm'});
test_require_module("t::Mod0");
is($err, "");
is($result, 1);
+is($INC{'t/Mod0.pm'}, $inc, '%INC unchanged after already loaded module');
+
+# a module that returns false
+ok(!exists $INC{'t/ModFalse.pm'});
+test_require_module("t::ModFalse");
+like($err, qr/\QModFalse.pm did not return a true value/);
+is($result, undef);
+ok(!exists $INC{'t/ModFalse.pm'}, '%INC unchanged after false-returning module load');
+
+# re-requiring a module that returns false
+ok(!exists $INC{'t/ModFalse.pm'});
+test_require_module("t::ModFalse");
+like($err, qr/\QModFalse.pm did not return a true value/);
+is($result, undef);
+ok(!exists $INC{'t/ModFalse.pm'}, '%INC unchanged after false-returning module reload');
+
+# a module that throws a exception
+ok(!exists $INC{'t/ModExc.pm'});
+test_require_module("t::ModExc");
+like($err, qr/^NO PASARAN.+^Compilation failed in require/ms);
+is($result, undef);
+ok(exists $INC{'t/ModExc.pm'}, '%INC entry is actually set to undef on throwing module')
+ unless _HAS_INC_BUG;
+is($INC{'t/ModExc.pm'}, undef, '%INC properly evaluates to undef on throwing module');
+
+# re-require a module that throws a exception on 5.10+
+if (_HAS_INC_BUG) {
+ test_require_module("t::ModExc");
+ like($err, qr/^NO PASARAN.+^Compilation failed in require/ms);
+ is($result, undef);
+ ok(!defined $INC{'t/ModExc.pm'}, '%INC still unset on re-require of throwing module');
+} else {
+ test_require_module("t::ModExc");
+ like($err, qr/^Attempt to reload .+ aborted.+^Compilation failed in require/ms);
+ is($result, undef);
+ ok(
+ (exists $INC{'t/ModExc.pm'} and ! defined $INC{'t/ModExc.pm'}),
+ '%INC still undef on re-require of throwing module'
+ );
+}
# module file scope sees scalar context regardless of calling context
eval { require_module("t::Mod1"); 1 };
--
1.7.9