diff options
author | Father Chrysostomos <sprout@cpan.org> | 2014-12-08 21:59:22 -0800 |
---|---|---|
committer | Father Chrysostomos <sprout@cpan.org> | 2014-12-09 05:42:47 -0800 |
commit | a70f21d0d169a526a6bafd2465e01e1ca8d16234 (patch) | |
tree | 140c9962225a341472bed59fd5669fee314f8419 /pad.c | |
parent | 071d040c70fbd1bbb40d7589e2a35485a11508b5 (diff) | |
download | perl-a70f21d0d169a526a6bafd2465e01e1ca8d16234.tar.gz |
Fix OUTSIDE for named subs inside predeclared subs
This prints 42 as expected (foo initialises $x and bar reads it
via eval):
use 5.01;
sub foo {
state $x = 42;
sub bar {
eval 'print $x // "u", "\n"';
}
}
foo();
bar();
If you predeclare foo and vivify its glob,
use 5.01;
*foo; # vivifies the glob at compile time
sub foo;
sub foo {
state $x = 42;
sub bar {
eval 'print $x // "u", "\n"';
}
}
foo();
bar();
then the output is ‘u’, because $x is now undefined.
What’s happening is that ‘eval’ follows CvOUTSIDE pointers (each sub
points to its outer sub), searching each pad to find a lexical $x. In
the former case it succeeds. In the latter, bar’s CvOUTSIDE pointer
is pointing to the wrong thing, so the search fails and $x is treated
as global. You can see it’s global with this example, which prints
‘globular’:
use 5.01;
*foo; # vivifies the glob at compile time
sub foo;
sub foo {
state $x = 42;
sub bar {
eval 'print $x // "u", "\n"';
}
}
foo();
$main::x = "globular";
bar();
When a sub is compiled, a new CV is created at the outset and put in
PL_compcv. When the sub finishes compiling, the CV in PL_compcv is
installed in the sub’s typeglob (or as a subref in the stash if pos-
sible). If there is already a stub in a typeglob, since that stub
could be referenced elsewhere, we have to reuse that stub and transfer
the contents of PL_compcv to that stub.
If we have any subs inside it, those will now have CvOUTSIDE point-
ers pointing to the old PL_compcv that has been eviscerated. So
we go through the pad and fix up the outside pointers for any subs
found there.
Named subs don’t get stored in the pad like that, so the CvOUTSIDE
fix-up never happens. Hence the bug above.
The bug does not occur if the glob is not vivified before the sub def-
inition, because a stub declaration will skip creating a real CV if it
can. It can’t if there is a typeglob.
The solution, of course, is to store named subs in the outer sub’s
pad. We can skip this if the outer ‘sub’ is an eval or the main pro-
gram. These two types of CVs obviously don’t reuse existing stubs,
since they never get installed in the symbol table. Since named subs
have strong outside pointers, we have to store weak refs in the pad,
just as we do for formats.
Diffstat (limited to 'pad.c')
-rw-r--r-- | pad.c | 18 |
1 files changed, 18 insertions, 0 deletions
@@ -829,6 +829,24 @@ Perl_pad_add_anon(pTHX_ CV* func, I32 optype) return ix; } +void +Perl_pad_add_weakref(pTHX_ CV* func) +{ + const PADOFFSET ix = pad_alloc(OP_NULL, SVs_PADMY); + PADNAME * const name = newPADNAMEpvn("&", 1); + SV * const rv = newRV_inc((SV *)func); + + PERL_ARGS_ASSERT_PAD_ADD_WEAKREF; + + /* These two aren't used; just make sure they're not equal to + * PERL_PADSEQ_INTRO. They should be 0 by default. */ + assert(COP_SEQ_RANGE_LOW (name) != PERL_PADSEQ_INTRO); + assert(COP_SEQ_RANGE_HIGH(name) != PERL_PADSEQ_INTRO); + padnamelist_store(PL_comppad_name, ix, name); + sv_rvweaken(rv); + av_store(PL_comppad, ix, rv); +} + /* =for apidoc pad_check_dup |