summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTony Cook <tony@develop-help.com>2022-08-25 16:02:25 +1000
committerTony Cook <tony@develop-help.com>2022-10-18 09:53:06 +1100
commitb0bc598140d48d19b21d667a581c0dfa28a3749a (patch)
tree83af02259c23331399a9d98f0e6d5743bc342a1a
parent8d7fc27c6d9f61892eb3be8104a7d98690a46dd3 (diff)
downloadperl-b0bc598140d48d19b21d667a581c0dfa28a3749a.tar.gz
handle intermediate pads not including the name in find_lexical_cv()
In the following each line is the pad for a scope, starting from the outermost scope. In non-eval cases a use of a lexical symbol in an inner pad also adds that name to the pad in the intermediate scope. So for code like: my $bar; sub foo { ... } sub f { sub g { foo($bar) } } we might get pads like: [&foo] [$bar] # defining pad [&foo] [$bar] # intermediate pad (for f) [&foo] [$bar] # using pad (for g) and each inner pad has an index of the same name in the parent scope. find_lexical_cv() followed that chain of indexes to find the definition, or at least the prototype, of the lexical sub. Unfortunately names can only be added to the pad at compile-time, so for an eval the intermediate scope is closed to further modifications, and the pad for the intermediate scope doesn't get an entry for the name, so code like: my $bar; sub foo { ... } sub f { eval 'foo($bar)' } we get pads like: [&foo] [$bar] # defining pad # intermediate pad (f) doesn't have the names [&foo] [$bar] # eval 'foo($bar)' pad but find_lexical_cv() assumed that the names would always exist in the intermediate pads, and failed an assertion. find_lexical_cv() now falls back to searching for the cv by name up the chain of nested pads. This doesn't fix a slightly related problem: my sub foo { } BEGIN { foo(); # Undefined subroutine &foo } The fix for that may make find_lexical_cv() unnecessary, but is a more complex fix.
-rw-r--r--op.c33
-rw-r--r--t/op/lexsub.t13
2 files changed, 42 insertions, 4 deletions
diff --git a/op.c b/op.c
index e6b5842e7f..f67d38cf68 100644
--- a/op.c
+++ b/op.c
@@ -13545,13 +13545,40 @@ subroutine.
CV *
Perl_find_lexical_cv(pTHX_ PADOFFSET off)
{
- PADNAME *name = PAD_COMPNAME(off);
+ const PADNAME *name = PAD_COMPNAME(off);
CV *compcv = PL_compcv;
while (PadnameOUTER(name)) {
- assert(PARENT_PAD_INDEX(name));
compcv = CvOUTSIDE(compcv);
- name = PadlistNAMESARRAY(CvPADLIST(compcv))
+ if (LIKELY(PARENT_PAD_INDEX(name))) {
+ name = PadlistNAMESARRAY(CvPADLIST(compcv))
[off = PARENT_PAD_INDEX(name)];
+ }
+ else {
+ /* In an eval() in an inner scope like a function, the
+ intermediate pad in the sub might not be populated with the
+ sub. So search harder.
+
+ It is possible we won't find the name in this
+ particular scope, but that's fine, if we don't we'll
+ find it in some outer scope. Finding it here will let us
+ go back to following the PARENT_PAD_INDEX() chain.
+ */
+ const PADNAMELIST * const names = PadlistNAMES(CvPADLIST(compcv));
+ PADNAME * const * const name_p = PadnamelistARRAY(names);
+ int offset;
+ for (offset = PadnamelistMAXNAMED(names); offset > 0; offset--) {
+ const PADNAME * const thisname = name_p[offset];
+ /* The pv is copied from the outer PADNAME to the
+ inner PADNAMEs so we don't need to compare the
+ string contents
+ */
+ if (thisname && PadnameLEN(thisname) == PadnameLEN(name)
+ && PadnamePV(thisname) == PadnamePV(name)) {
+ name = thisname;
+ break;
+ }
+ }
+ }
}
assert(!PadnameIsOUR(name));
if (!PadnameIsSTATE(name) && PadnamePROTOCV(name)) {
diff --git a/t/op/lexsub.t b/t/op/lexsub.t
index f085cd97e8..639fd29860 100644
--- a/t/op/lexsub.t
+++ b/t/op/lexsub.t
@@ -7,7 +7,7 @@ BEGIN {
*bar::is = *is;
*bar::like = *like;
}
-plan 150;
+plan 151;
# -------------------- our -------------------- #
@@ -960,3 +960,14 @@ like runperl(
is join("-", qw(aa bb), do { my sub lleexx; 123 }, qw(cc dd)),
"aa-bb-123-cc-dd", 'do { my sub...} in a list [perl #132442]';
+
+{
+ # this would crash because find_lexical_cv() couldn't handle an
+ # intermediate scope which didn't include the sub
+ no warnings 'experimental::builtin';
+ use builtin 'ceil';
+ sub nested {
+ ok(eval 'ceil(1.5)', "no assertion failure calling a lexical sub from nested eval");
+ }
+ nested();
+}