Skip to content

Commit

Permalink
Undefined subroutine &%s called, close to label '%s'
Browse files Browse the repository at this point in the history
#17839 requested a compile-time
warning for the situation whereby a single colon is accdentally typed
as a package separator when calling a function. For example:
```
package BOOP;
sub beep;
package main;
BOOP:beep();  # Meant to be BOOP::beep();
```
However, because of both Perl's syntax and the potential to populate the
stash at runtime, this falls somewhere between very difficult and
impossible. As an alternative, some enhanced fatal error wording was
requested and these commits attempt to provide that.

The above example would previously die with the message:
```
Undefined subroutine &main::beep called at ... line 4.
```
Now it dies with the message:
```
Undefined subroutine &main::beep called, close to label 'BOOP' at ... line 4.
```

For some of the same reasons mentioned, distinguishing this typo from
other errors at runtime - such as the target subroutine not being
present at all - is also nigh on impossible. The hope is that the
error message will give some additional clue when the error is the
result of a typo, without distracting the user in all other cases.

As part of these commits, some common `DIE()` calls in `pp_entersub` were
extracted into a new helper function, `S_croak_undefined_subroutine`, to
avoid adding (and slightly reduce) cold code bloating in `pp_entersub`.
  • Loading branch information
richardleach committed Dec 14, 2024
1 parent df4d5e8 commit 73a4618
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 9 deletions.
8 changes: 8 additions & 0 deletions pod/perldelta.pod
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ and L</New Warnings>

=item *

L<Undefined subroutine &%s called, close to label '%s'|perldiag/"Undefined subroutine &%s called, close to label '%s'">

(F) The subroutine indicated hasn't been defined, or if it was, it has
since been undefined. This could also indicate a mistyped package
separator, when a single colon was typed instead of two colons.

=item *

XXX L<message|perldiag/"message">

=back
Expand Down
6 changes: 6 additions & 0 deletions pod/perldiag.pod
Original file line number Diff line number Diff line change
Expand Up @@ -6820,6 +6820,12 @@ Perhaps it's in a different package? See L<perlfunc/sort>.
(F) The subroutine indicated hasn't been defined, or if it was, it has
since been undefined.

=item Undefined subroutine &%s called, close to label '%s'

(F) The subroutine indicated hasn't been defined, or if it was, it has
since been undefined. This could also indicate a mistyped package
separator, when a single colon was typed instead of two colons.

=item Undefined subroutine called

(F) The anonymous subroutine you're trying to call hasn't been defined,
Expand Down
40 changes: 31 additions & 9 deletions pp_hot.c
Original file line number Diff line number Diff line change
Expand Up @@ -6212,6 +6212,33 @@ Perl_clear_defarray(pTHX_ AV* av, bool abandon)
}
}

/* S_croak_undefined_subroutine is a helper function for pp_entersub.
* It takes assorted DIE() logic out of that hot function.
*/
static void
S_croak_undefined_subroutine(pTHX_ CV const *cv, GV const *gv)
{
if (cv) {
if (CvLEXICAL(cv) && CvHASGV(cv))
croak("Undefined subroutine &%" SVf " called",
SVfARG(cv_name((CV*)cv, NULL, 0)));
else /* pp_entersub triggers when (CvANON(cv) || !CvHASGV(cv)) */
croak("Undefined subroutine called");
} else { /* pp_entersub triggers when (!cv) after `try_autoload` */
SV *sub_name = newSV_type_mortal(SVt_PV);
gv_efullname3(sub_name, gv, NULL);

/* Heuristic to spot BOOP:boop() typo, when the intention was
* to call BOOP::boop(). */
const char * label = CopLABEL(PL_curcop);
if (label) {
croak("Undefined subroutine &%" SVf " called, close to label '%s'",
SVfARG(sub_name), label);
}
croak("Undefined subroutine &%" SVf " called", SVfARG(sub_name));
}
NOT_REACHED; /* NOTREACHED */
}

PP(pp_entersub)
{
Expand Down Expand Up @@ -6306,14 +6333,12 @@ PP(pp_entersub)
assert((void*)&CvROOT(cv) == (void*)&CvXSUB(cv));
while (UNLIKELY(!CvROOT(cv))) {
GV* autogv;
SV* sub_name;

/* anonymous or undef'd function leaves us no recourse */
if (CvLEXICAL(cv) && CvHASGV(cv))
DIE(aTHX_ "Undefined subroutine &%" SVf " called",
SVfARG(cv_name(cv, NULL, 0)));
S_croak_undefined_subroutine(aTHX_ cv, NULL);
if (CvANON(cv) || !CvHASGV(cv)) {
DIE(aTHX_ "Undefined subroutine called");
S_croak_undefined_subroutine(aTHX_ cv, NULL);
}

/* autoloaded stub? */
Expand All @@ -6330,11 +6355,8 @@ PP(pp_entersub)
: 0));
cv = autogv ? GvCV(autogv) : NULL;
}
if (!cv) {
sub_name = sv_newmortal();
gv_efullname3(sub_name, gv, NULL);
DIE(aTHX_ "Undefined subroutine &%" SVf " called", SVfARG(sub_name));
}
if (!cv)
S_croak_undefined_subroutine(aTHX_ NULL, gv);
}

/* unrolled "CvCLONE(cv) && ! CvCLONED(cv)" */
Expand Down
8 changes: 8 additions & 0 deletions t/lib/croak/pp_hot
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ Undefined subroutine &main::foo called at - line 3.
EXPECT
Undefined subroutine &main::foo called at - line 2.
########
# NAME package separator typo, creating a label by accident
package BEEP;
sub boop;
package main;
BEEP:boop();
EXPECT
Undefined subroutine &main::boop called, close to label 'BEEP' at - line 4.
########
# NAME calling undef scalar
&{+undef};
EXPECT
Expand Down

0 comments on commit 73a4618

Please sign in to comment.