In perl.git, the branch blead has been updated <https://perl5.git.perl.org/perl.git/commitdiff/a0da1e165c011c775b9f39a37ab6d3dd6a1c0969?hp=f88ca576baabd4517ec5766efa11b1e1fc8109af>
- Log ----------------------------------------------------------------- commit a0da1e165c011c775b9f39a37ab6d3dd6a1c0969 Author: Father Chrysostomos <spr...@cpan.org> Date: Mon Feb 26 09:23:28 2018 -0800 warnings.pm: _at_level functions and chunky handles The _at_level functions, which have to bypass Carp, were not reporting non-line-based filehandles correctly. The perl core does: ..., <fh> chunk 7. if $/ is not "\n". warnings.pm should do the same. It was using ‘line’. commit 5c8d1071aaf72214e66b1a224890384ab6ca5153 Author: Father Chrysostomos <spr...@cpan.org> Date: Mon Feb 26 01:23:53 2018 -0800 Carp: Avoid run-time mods; StrVal workarounds Carp needs to avoid loading modules while reporting errors, because it may be invoked via $SIG{__DIE__} after a syntax error, when BEGIN blocks are forbidden. Before this commit (as of v5.27.8-360-gc99363a) it was doing just that for reference arguments within stack traces. That means we either need to load overload.pm at start-up so that overload::StrVal is already available, or avoid overload::StrVal altogether. It turns out that various versions of overload::StrVal have their own problems that prevent Carp from using them (out- lined in the comments added to Carp.pm and also described at <https://rt.perl.org/Ticket/Display.html?id=132902#txn-1535564>). So we now follow two approaches: If overloading.pm is available, use that; otherwise, use a hideous workaround inspired by ancient imple- entations of overload::StrVal and Scalar::Util::blessed, while avoid- ing the bugs in those old versions. ----------------------------------------------------------------------- Summary of changes: MANIFEST | 1 + dist/Carp/lib/Carp.pm | 91 ++++++++++++++++++++++++++++++++----------- dist/Carp/t/stack_after_err.t | 73 ++++++++++++++++++++++++++++++++++ dist/Carp/t/vivify_stash.t | 12 +++--- lib/warnings.pm | 5 ++- regen/warnings.pl | 5 ++- t/lib/warnings/9enabled | 19 +++++++++ 7 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 dist/Carp/t/stack_after_err.t diff --git a/MANIFEST b/MANIFEST index acc1bcba7f..b054110b65 100644 --- a/MANIFEST +++ b/MANIFEST @@ -2975,6 +2975,7 @@ dist/Carp/t/errno.t See if Carp preserves $! and $^E dist/Carp/t/heavy.t See if Carp::Heavy works dist/Carp/t/heavy_mismatch.t See if Carp::Heavy catches version mismatch dist/Carp/t/rt52610_crash.t Test that we can gracefully handle serializing the stack with stack-refcounting bugs +dist/Carp/t/stack_after_err.t Test stack traces after syntax errors dist/Carp/t/stash_deletion.t See if Carp handles stash deletion dist/Carp/t/swash.t See if Carp avoids breaking swash loading dist/Carp/t/vivify_gv.t See if Carp leaves utf8:: stuff alone diff --git a/dist/Carp/lib/Carp.pm b/dist/Carp/lib/Carp.pm index d5443ba676..10509d4339 100644 --- a/dist/Carp/lib/Carp.pm +++ b/dist/Carp/lib/Carp.pm @@ -130,12 +130,71 @@ sub _univ_mod_loaded { } } -# _mycan is either UNIVERSAL::can, or, in the presence of an override, -# overload::mycan. +# We need an overload::StrVal or equivalent function, but we must avoid +# loading any modules on demand, as Carp is used from __DIE__ handlers and +# may be invoked after a syntax error. +# We can copy recent implementations of overload::StrVal and use +# overloading.pm, which is the fastest implementation, so long as +# overloading is available. If it is not available, we use our own pure- +# Perl StrVal. We never actually use overload::StrVal, for various rea- +# sons described below. +# overload versions are as follows: +# undef-1.00 (up to perl 5.8.0) uses bless (avoid!) +# 1.01-1.17 (perl 5.8.1 to 5.14) uses Scalar::Util +# 1.18+ (perl 5.16+) uses overloading +# The ancient 'bless' implementation (that inspires our pure-Perl version) +# blesses unblessed references and must be avoided. Those using +# Scalar::Util use refaddr, possibly the pure-Perl implementation, which +# has the same blessing bug, and must be avoided. Also, Scalar::Util is +# loaded on demand. Since we avoid the Scalar::Util implementations, we +# end up having to implement our own overloading.pm-based version for perl +# 5.10.1 to 5.14. Since it also works just as well in more recent ver- +# sions, we use it there, too. BEGIN { - *_mycan = _univ_mod_loaded('can') - ? do { require "overload.pm"; _fetch_sub overload => 'mycan' } - : \&UNIVERSAL::can + if (eval { require "overloading.pm" }) { + *_StrVal = eval 'sub { no overloading; "$_[0]" }' + } + else { + # Work around the UNIVERSAL::can/isa modules to avoid recursion. + + # _mycan is either UNIVERSAL::can, or, in the presence of an + # override, overload::mycan. + *_mycan = _univ_mod_loaded('can') + ? do { require "overload.pm"; _fetch_sub overload => 'mycan' } + : \&UNIVERSAL::can; + + # _blessed is either UNIVERAL::isa(...), or, in the presence of an + # override, a hideous, but fairly reliable, workaround. + *_blessed = _univ_mod_loaded('isa') + ? sub { + my $probe = "UNIVERSAL::Carp_probe_" . rand; + no strict 'refs'; + local *$probe = sub { "unlikely string" }; + local $@; + local $SIG{__DIE__} = sub{}; + (eval { $_[0]->$probe } || '') eq 'unlikely string' + } + : do { + my $isa = _fetch_sub(qw/UNIVERSAL isa/); + sub { &$isa($_[0], "UNIVERSAL") } + }; + + *_StrVal = sub { + my $pack = ref $_[0]; + # Perl's overload mechanism uses the presence of a special + # "method" named "((" or "()" to signal it is in effect. + # This test seeks to see if it has been set up. "((" post- + # dates overloading.pm, so we can skip it. + return "$_[0]" unless _mycan($pack, "()"); + # Even at this point, the invocant may not be blessed, so + # check for that. + return "$_[0]" if not _blessed($_[0]); + bless $_[0], "Carp"; + my $str = "$_[0]"; + bless $_[0], $pack; + $pack . substr $str, index $str, "="; + } + } } @@ -358,23 +417,11 @@ sub format_arg { } else { - # overload uses the presence of a special - # "method" named "((" or "()" to signal - # it is in effect. This test seeks to see if it has been set up. - if (_mycan($pack, "((") || _mycan($pack, "()")) { - # Argument is blessed into a class with overloading, and - # so might have an overloaded stringification. We don't - # want to risk getting the overloaded stringification, - # so we need to use overload::StrVal() below. But it's - # possible that the overload module hasn't been loaded: - # overload methods can be installed without it. So load - # the module here. The bareword form of require is here - # eschewed to avoid this compile-time effect of vivifying - # the target module's stash. - require "overload.pm"; - } - my $sub = _fetch_sub(overload => 'StrVal'); - return $sub ? &$sub($arg) : "$arg"; + # Argument may be blessed into a class with overloading, and so + # might have an overloaded stringification. We don't want to + # risk getting the overloaded stringification, so we need to + # use _StrVal, our overload::StrVal()-equivalent. + return _StrVal $arg; } } return "undef" if !defined($arg); diff --git a/dist/Carp/t/stack_after_err.t b/dist/Carp/t/stack_after_err.t new file mode 100644 index 0000000000..8bf5be965a --- /dev/null +++ b/dist/Carp/t/stack_after_err.t @@ -0,0 +1,73 @@ +use Config; +use IPC::Open3 1.0103 qw(open3); +use Test::More tests => 4; + +sub runperl { + my(%args) = @_; + my($w, $r); + + local $ENV{PERL5LIB} = join ($Config::Config{path_sep}, @INC); + + my $pid = open3($w, $r, undef, $^X, "-e", $args{prog}); + close $w; + my $output = ""; + while(<$r>) { $output .= $_; } + waitpid($pid, 0); + return $output; +} + + +# Make sure we don’t try to load modules on demand in the presence of over- +# loaded args. If there has been a syntax error, they won’t load. +like( + runperl( + prog => q< + use Carp; + sub foom { + Carp::confess("Looks lark we got a error: $_[0]") + } + BEGIN { + *{"o::()"} = sub {}; + *{'o::(""'} = sub {"hay"}; + $o::OVERLOAD{dummy}++; # perls before 5.18 need this + *{"CODE::()"} = sub {}; + $SIG{__DIE__} = sub { foom (@_, bless([], o), sub {}) } + } + $a + + >, + ), + qr 'Looks lark.*o=ARRAY.* CODE's, + 'Carp does not try to load modules on demand for overloaded args', +); + +# Run the test also in the presence of +# a) A UNIVERSAL::can module +# b) A UNIVERSAL::isa module +# c) Both +# since they follow slightly different code paths on old pre-5.10.1 perls. +my $prog = q< + use Carp; + sub foom { + Carp::confess("Looks lark we got a error: $_[0]") + } + BEGIN { + *{"o::()"} = sub {}; + *{'o::(""'} = sub {"hay"}; + $o::OVERLOAD{dummy}++; # perls before 5.18 need this + *{"CODE::()"} = sub {}; + $SIG{__DIE__} = sub { foom (@_, bless([], o), sub{}) } + } + $a + +>; +for ( + ["UNIVERSAL::isa", 'BEGIN { $UNIVERSAL::isa::VERSION = 1 }'], + ["UNIVERSAL::can", 'BEGIN { $UNIVERSAL::can::VERSION = 1 }'], + ["UNIVERSAL::can/isa", 'BEGIN { $UNIVERSAL::can::VERSION = + $UNIVERSAL::isa::VERSION = 1 }'], +) { + my ($tn, $preamble) = @$_; + like(runperl( prog => "$preamble$prog" ), + qr 'Looks lark.*o=ARRAY.* CODE's, + "StrVal fallback in the presence of $tn", + ) +} diff --git a/dist/Carp/t/vivify_stash.t b/dist/Carp/t/vivify_stash.t index 46e0b637e9..744d0d2584 100644 --- a/dist/Carp/t/vivify_stash.t +++ b/dist/Carp/t/vivify_stash.t @@ -1,7 +1,6 @@ -BEGIN { print "1..6\n"; } +BEGIN { print "1..5\n"; } our $has_utf8; BEGIN { $has_utf8 = exists($::{"utf8::"}); } -our $has_overload; BEGIN { $has_overload = exists($::{"overload::"}); } our $has_B; BEGIN { $has_B = exists($::{"B::"}); } our $has_UNIVERSAL_isa; BEGIN { $has_UNIVERSAL_isa = exists($UNIVERSAL::{"isa::"}); } @@ -9,19 +8,18 @@ use Carp; sub { sub { Carp::longmess("x") }->() }->(\1, "\x{2603}", qr/\x{2603}/); print !(exists($::{"utf8::"}) xor $has_utf8) ? "" : "not ", "ok 1 # used utf8\n"; -print !(exists($::{"overload::"}) xor $has_overload) ? "" : "not ", "ok 2 # used overload\n"; -print !(exists($::{"B::"}) xor $has_B) ? "" : "not ", "ok 3 # used B\n"; -print !(exists($UNIVERSAL::{"isa::"}) xor $has_UNIVERSAL_isa) ? "" : "not ", "ok 4 # used UNIVERSAL::isa\n"; +print !(exists($::{"B::"}) xor $has_B) ? "" : "not ", "ok 2 # used B\n"; +print !(exists($UNIVERSAL::{"isa::"}) xor $has_UNIVERSAL_isa) ? "" : "not ", "ok 3 # used UNIVERSAL::isa\n"; # Autovivify $::{"overload::"} () = \$::{"overload::"}; () = \$::{"utf8::"}; eval { sub { Carp::longmess() }->(\1) }; -print $@ eq '' ? "ok 5 # longmess check1\n" : "not ok 5 # longmess check1\n# $@"; +print $@ eq '' ? "ok 4 # longmess check1\n" : "not ok 4 # longmess check1\n# $@"; # overload:: glob without hash undef *{"overload::"}; eval { sub { Carp::longmess() }->(\1) }; -print $@ eq '' ? "ok 6 # longmess check2\n" : "not ok 6 # longmess check2\n# $@"; +print $@ eq '' ? "ok 5 # longmess check2\n" : "not ok 5 # longmess check2\n# $@"; 1; diff --git a/lib/warnings.pm b/lib/warnings.pm index f0e2a7fcdd..af23f909b7 100644 --- a/lib/warnings.pm +++ b/lib/warnings.pm @@ -450,8 +450,11 @@ sub __chk # If we have an explicit level, bypass Carp. if ($has_level and @callers_bitmask) { + # logic copied from util.c:mess_sv my $stuff = " at " . join " line ", (caller $i)[1,2]; - $stuff .= ", <" . *${^LAST_FH}{NAME} . "> line $." if $. && ${^LAST_FH}; + $stuff .= ", <" . *${^LAST_FH}{NAME} . "> " + . ($/ eq "\n" ? "line" : "chunk") . " $." + if $. && ${^LAST_FH}; die "$message$stuff.\n" if $results[0]; return warn "$message$stuff.\n"; } diff --git a/regen/warnings.pl b/regen/warnings.pl index a9bd467269..abc10d2949 100644 --- a/regen/warnings.pl +++ b/regen/warnings.pl @@ -766,8 +766,11 @@ sub __chk # If we have an explicit level, bypass Carp. if ($has_level and @callers_bitmask) { + # logic copied from util.c:mess_sv my $stuff = " at " . join " line ", (caller $i)[1,2]; - $stuff .= ", <" . *${^LAST_FH}{NAME} . "> line $." if $. && ${^LAST_FH}; + $stuff .= ", <" . *${^LAST_FH}{NAME} . "> " + . ($/ eq "\n" ? "line" : "chunk") . " $." + if $. && ${^LAST_FH}; die "$message$stuff.\n" if $results[0]; return warn "$message$stuff.\n"; } diff --git a/t/lib/warnings/9enabled b/t/lib/warnings/9enabled index 7a9acd4bb8..3e0bcba7e4 100644 --- a/t/lib/warnings/9enabled +++ b/t/lib/warnings/9enabled @@ -1454,3 +1454,22 @@ bimp; EXPECT Foo warning at - line 13, <FH> line 2. Bar warning at - line 13. +######## +# NAME _at_level with chunky filehandle +use warnings; +# Create temp file for testing handles. +open oUt, ">tmp" or die $!; +print oUt "foo7bar7"; +close oUt; +sub bimp { + open FH, "tmp"; + $/ = 7; + <FH>; <FH>; + warnings::warn_at_level("syntax", 0, "Foo warning"); + close FH; + warnings::warn_at_level("syntax", 0, "Bar warning"); +}; +bimp; +EXPECT +Foo warning at - line 14, <FH> chunk 2. +Bar warning at - line 14. -- Perl5 Master Repository