Skip Menu |

This queue is for tickets about the Module-Runtime CPAN distribution.

Report information
The Basics
Id: 74789
Status: resolved
Priority: 0/
Queue: Module-Runtime

People
Owner: Nobody in particular
Requestors: ribasushi [...] leporine.io
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



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
Subject: Re: [rt.cpan.org #74789] require_module needs to work around the perl 5.8 %INC pollution bug
Date: Thu, 9 Feb 2012 12:30:39 +0000
To: Peter Rabbitson via RT <bug-Module-Runtime [...] rt.cpan.org>
From: Zefram <zefram [...] fysh.org>
I'm willing to include some workaround for this bug in M:R. However, I note that this isn't a complete solution to the problem. Even if every module applies this workaround to every explicit require, or every module uses M:R (incorporating workaround) instead of the core require, the bug still affects the implicit require in "use" statements. Also, although the workaround avoids polluting %INC with bad entries that mislead future requires, it can still itself be confused by bad %INC entries created by previous requires that lack the workaround. I don't see any way to distinguish duff entries outside the process that created them. It is not practical to get every require and use statement to explicitly incorporate some workaround. If you're interested in really fixing this, there'll have to be a more implicit approach. Lexical::SealRequireHints is a possible model for this sort of intervention, but it hasn't been without difficulty. Speaking of L:SRH, I think if I'm incorporating a workaround for the %INC bug into M:R, I should probably incorporate a workaround for the hint leakage bug too. This won't be as tricky as the L:SRH code, because it doesn't need to mess with packages: anything required via M:R sees the M:R package rather than package of M:R's caller. I'm not satisfied with the code you propose to work around the %INC bug. I don't want an eval frame there at all, and especially if it's just for cleanup (as it is). I have a better way to do it. -zefram
Subject: Re: [rt.cpan.org #74789] require_module needs to work around the perl 5.8 %INC pollution bug
Date: Thu, 09 Feb 2012 13:40:54 +0100
To: bug-Module-Runtime [...] rt.cpan.org
From: Peter Rabbitson <ribasushi [...] cpan.org>
Zefram via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=74789 > > > I'm willing to include some workaround for this bug in M:R. However, > I note that this isn't a complete solution to the problem. Even if every > module applies this workaround to every explicit require, or every module > uses M:R (incorporating workaround) instead of the core require, the bug > still affects the implicit require in "use" statements. Also, although > the workaround avoids polluting %INC with bad entries that mislead future > requires, it can still itself be confused by bad %INC entries created > by previous requires that lack the workaround. I don't see any way to > distinguish duff entries outside the process that created them. > > It is not practical to get every require and use statement to explicitly > incorporate some workaround. If you're interested in really fixing this, > there'll have to be a more implicit approach. Lexical::SealRequireHints > is a possible model for this sort of intervention, but it hasn't been > without difficulty.
This is a valid point. In fact I had to do this here specifically for this reason: https://github.com/dbsrgits/dbix-class/blob/master/t/55namespaces_cleaned.t#L1 In fact the comment pretty much mirrors your observation. Show quoted text
> Speaking of L:SRH, I think if I'm incorporating a workaround for the %INC > bug into M:R, I should probably incorporate a workaround for the hint > leakage bug too. This won't be as tricky as the L:SRH code, because it > doesn't need to mess with packages: anything required via M:R sees the > M:R package rather than package of M:R's caller. > > I'm not satisfied with the code you propose to work around the %INC bug. > I don't want an eval frame there at all, and especially if it's just > for cleanup (as it is). I have a better way to do it. >
I would be interested to hear how you can avoid the eval frame. In any case - do you propose a better solution? Currently the only recourse when needing to repeatedly require a module (and failing) is to do the cleanup oneself. This is far from ideal (and many get it wrong anyway).
Done in Module-Runtime-0.012.