Skip to content

Commit

Permalink
pp_ctl.c - add support for hooking require.
Browse files Browse the repository at this point in the history
This defines a new magic hash C<%{^HOOK}> which is intended to be used for
hooking keywords. It is similar to %SIG in that the values it contains
are validated on set, and it is not allowed to store something in
C<%{^HOOK}> that isn't supposed to be there. Hooks are expected to be
coderefs (people can use currying if they really want to put an object
in there, the API is deliberately simple.)

The C<%{^HOOK}> hash is documented to have keys of the form
"${keyword}__${phase}" where $phase is either "before" or "after"
and in this initial release two hooks are supported,
"require__before" and "require__after":

The C<require__before> hook is called before require is executed,
including any @inc hooks that might be fired. It is called with the path
of the file being required, just as would be stored in %INC. The hook
may alter the filename by writing to $_[0] and it may return a coderef
to be executed *after* the require has completed, otherwise the return
is ignored.  This coderef is also called with the path of the file which
was required, and it will be called regardless as to whether the require
(or its dependencies) die during execution.  This mechanism makes it
trivial and safe to share state between the initial hook and the coderef
it returns.

The C<require__after> hook is similar to the C<require__before> hook
however except that it is called after the require completes
(successfully or not), and its return is ignored always.
  • Loading branch information
demerphq committed Mar 17, 2023
1 parent 9961aac commit d0b1ad1
Show file tree
Hide file tree
Showing 25 changed files with 603 additions and 13 deletions.
12 changes: 8 additions & 4 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -5787,10 +5787,13 @@ t/io/tell.t See if file seeking works
t/io/through.t See if pipe passes data intact
t/io/utf8.t See if file seeking works
t/japh/abigail.t Obscure tests
t/lib/caller/Apack.pm test Module for caller.t
t/lib/caller/Bpack.pm test Module for caller.t
t/lib/caller/Cpack.pm test Module for caller.t
t/lib/caller/Foo.pm test Module for caller.t
t/lib/caller/Apack.pm test Module for caller.t and t/op/hook/require.t
t/lib/caller/Bicycle.pm test Module for t/op/hook/require.t (cyclic)
t/lib/caller/Bpack.pm test Module for caller.t and t/op/hook/require.t
t/lib/caller/Cpack.pm test Module for caller.t and t/op/hook/require.t
t/lib/caller/Cycle.pm test Module for t/op/hook/require.t (cyclic)
t/lib/caller/Foo.pm test Module for caller.t and t/op/hook/require.t
t/lib/caller/Tricycle.pm test Module for t/op/hook/require.t (cyclic)
t/lib/CannotParse.pm For test case in op/require_errors.t
t/lib/charnames/alias Tests of "use charnames" with aliases.
t/lib/Cname.pm Test charnames in regexes (op/pat.t)
Expand Down Expand Up @@ -6045,6 +6048,7 @@ t/op/hashassign.t See if hash assignments work
t/op/hashwarn.t See if warnings for bad hash assignments work
t/op/heredoc.t See if heredoc edge and corner cases work
t/op/hexfp.t See if hexadecimal float literals work
t/op/hook/require.t See if require hooks work properly.
t/op/inc.t See if inc/dec of integers near 32 bit limit work
t/op/inccode.t See if coderefs work in @INC
t/op/inccode-tie.t See if tie to @INC works
Expand Down
10 changes: 10 additions & 0 deletions embed.fnc
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,11 @@ dp |int |magic_clearhint|NN SV *sv \
dp |int |magic_clearhints \
|NN SV *sv \
|NN MAGIC *mg
p |int |magic_clearhook|NULLOK SV *sv \
|NN MAGIC *mg
p |int |magic_clearhookall \
|NULLOK SV *sv \
|NN MAGIC *mg
p |int |magic_clearisa |NULLOK SV *sv \
|NN MAGIC *mg
p |int |magic_clearpack|NN SV *sv \
Expand Down Expand Up @@ -1889,6 +1894,11 @@ p |int |magic_setenv |NN SV *sv \
|NN MAGIC *mg
dp |int |magic_sethint |NN SV *sv \
|NN MAGIC *mg
p |int |magic_sethook |NULLOK SV *sv \
|NN MAGIC *mg
p |int |magic_sethookall \
|NN SV *sv \
|NN MAGIC *mg
p |int |magic_setisa |NN SV *sv \
|NN MAGIC *mg
p |int |magic_setlvref |NN SV *sv \
Expand Down
4 changes: 4 additions & 0 deletions embed.h
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,8 @@
# define magic_clearenv(a,b) Perl_magic_clearenv(aTHX_ a,b)
# define magic_clearhint(a,b) Perl_magic_clearhint(aTHX_ a,b)
# define magic_clearhints(a,b) Perl_magic_clearhints(aTHX_ a,b)
# define magic_clearhook(a,b) Perl_magic_clearhook(aTHX_ a,b)
# define magic_clearhookall(a,b) Perl_magic_clearhookall(aTHX_ a,b)
# define magic_clearisa(a,b) Perl_magic_clearisa(aTHX_ a,b)
# define magic_clearpack(a,b) Perl_magic_clearpack(aTHX_ a,b)
# define magic_clearsig(a,b) Perl_magic_clearsig(aTHX_ a,b)
Expand Down Expand Up @@ -992,6 +994,8 @@
# define magic_setdefelem(a,b) Perl_magic_setdefelem(aTHX_ a,b)
# define magic_setenv(a,b) Perl_magic_setenv(aTHX_ a,b)
# define magic_sethint(a,b) Perl_magic_sethint(aTHX_ a,b)
# define magic_sethook(a,b) Perl_magic_sethook(aTHX_ a,b)
# define magic_sethookall(a,b) Perl_magic_sethookall(aTHX_ a,b)
# define magic_setisa(a,b) Perl_magic_setisa(aTHX_ a,b)
# define magic_setlvref(a,b) Perl_magic_setlvref(aTHX_ a,b)
# define magic_setmglob(a,b) Perl_magic_setmglob(aTHX_ a,b)
Expand Down
2 changes: 2 additions & 0 deletions embedvar.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions gv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,13 @@ S_gv_magicalize(pTHX_ GV *gv, HV *stash, const char *name, STRLEN len,
if (memEQs(name, len, "\007LOBAL_PHASE"))
goto ro_magicalize;
break;
case '\010': /* %{^HOOK} */
if (memEQs(name, len, "\010OOK")) {
GvMULTI_on(gv);
HV *hv = GvHVn(gv);
hv_magic(hv, NULL, PERL_MAGIC_hook);
}
break;
case '\014':
if ( memEQs(name, len, "\014AST_FH") || /* ${^LAST_FH} */
memEQs(name, len, "\014AST_SUCCESSFUL_PATTERN")) /* ${^LAST_SUCCESSFUL_PATTERN} */
Expand Down
6 changes: 5 additions & 1 deletion intrpvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ thread's copy.
=cut
*/

PERLVAR(I, localizing, U8) /* are we processing a local() list? */
PERLVAR(I, localizing, U8) /* are we processing a local() list?
0 = no, 1 = localizing, 2 = delocalizing */
PERLVAR(I, in_eval, U8) /* trap "fatal" errors? */
PERLVAR(I, defgv, GV *) /* the *_ glob */

Expand Down Expand Up @@ -495,6 +496,9 @@ PERLVAR(I, origfilename, char *)
PERLVARI(I, xsubfilename, const char *, NULL)
PERLVAR(I, diehook, SV *)
PERLVAR(I, warnhook, SV *)
/* keyword hooks*/
PERLVARI(I, hook__require__before, SV *,NULL)
PERLVARI(I, hook__require__after, SV *,NULL)

/* switches */
PERLVAR(I, patchlevel, SV *)
Expand Down
92 changes: 90 additions & 2 deletions mg.c
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,8 @@ Perl_magic_setsig(pTHX_ SV *sv, MAGIC *mg)
For magic_clearsig, we don't change the warnings handler if it's
set to the &PL_warnhook. */
svp = &PL_warnhook;
} else if (sv) {
}
else if (sv) {
SV *tmp = sv_newmortal();
Perl_croak(aTHX_ "No such hook: %s",
pv_pretty(tmp, s, len, 0, NULL, NULL, 0));
Expand Down Expand Up @@ -1820,8 +1821,9 @@ Perl_magic_setsig(pTHX_ SV *sv, MAGIC *mg)
if (i) {
(void)rsignal(i, PL_csighandlerp);
}
else
else {
*svp = SvREFCNT_inc_simple_NN(sv);
}
} else {
if (sv && SvOK(sv)) {
s = SvPV_force(sv, len);
Expand Down Expand Up @@ -1891,6 +1893,92 @@ Perl_magic_setsigall(pTHX_ SV* sv, MAGIC* mg)
return 0;
}

int
Perl_magic_clearhook(pTHX_ SV *sv, MAGIC *mg)
{
PERL_ARGS_ASSERT_MAGIC_CLEARHOOK;

magic_sethook(NULL, mg);
return sv_unmagic(sv, mg->mg_type);
}

/* sv of NULL signifies that we're acting as magic_clearhook. */
int
Perl_magic_sethook(pTHX_ SV *sv, MAGIC *mg)
{
SV** svp = NULL;
STRLEN len;
const char *s = MgPV_const(mg,len);

PERL_ARGS_ASSERT_MAGIC_SETHOOK;

if (memEQs(s, len, "require__before")) {
svp = &PL_hook__require__before;
}
else if (memEQs(s, len, "require__after")) {
svp = &PL_hook__require__after;
}
else {
SV *tmp = sv_newmortal();
Perl_croak(aTHX_ "Attempt to set unknown hook '%s' in %%{^HOOK}",
pv_pretty(tmp, s, len, 0, NULL, NULL, 0));
}
if (sv && SvOK(sv) && (!SvROK(sv) || SvTYPE(SvRV(sv))!= SVt_PVCV))
croak("${^HOOK}{%.*s} may only be a CODE reference or undef", (int)len, s);

if (svp) {
if (*svp)
SvREFCNT_dec(*svp);

if (sv)
*svp = SvREFCNT_inc_simple_NN(sv);
else
*svp = NULL;
}

return 0;
}

int
Perl_magic_sethookall(pTHX_ SV* sv, MAGIC* mg)
{
PERL_ARGS_ASSERT_MAGIC_SETHOOKALL;
PERL_UNUSED_ARG(mg);

if (PL_localizing == 1) {
SAVEGENERICSV(PL_hook__require__before);
PL_hook__require__before = NULL;
SAVEGENERICSV(PL_hook__require__after);
PL_hook__require__after = NULL;
}
else
if (PL_localizing == 2) {
HV* hv = (HV*)sv;
HE* current;
hv_iterinit(hv);
while ((current = hv_iternext(hv))) {
SV* hookelem = hv_iterval(hv, current);
mg_set(hookelem);
}
}
return 0;
}

int
Perl_magic_clearhookall(pTHX_ SV* sv, MAGIC* mg)
{
PERL_ARGS_ASSERT_MAGIC_CLEARHOOKALL;
PERL_UNUSED_ARG(mg);
PERL_UNUSED_ARG(sv);

SvREFCNT_dec_set_NULL(PL_hook__require__before);

SvREFCNT_dec_set_NULL(PL_hook__require__after);

return 0;
}


int
Perl_magic_setisa(pTHX_ SV *sv, MAGIC *mg)
{
Expand Down
2 changes: 2 additions & 0 deletions mg_names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
{ PERL_MAGIC_substr, "substr(x)" },
{ PERL_MAGIC_nonelem, "nonelem(Y)" },
{ PERL_MAGIC_defelem, "defelem(y)" },
{ PERL_MAGIC_hook, "hook(Z)" },
{ PERL_MAGIC_hookelem, "hookelem(z)" },
{ PERL_MAGIC_lvref, "lvref(\\)" },
{ PERL_MAGIC_checkcall, "checkcall(])" },
{ PERL_MAGIC_extvalue, "extvalue(^)" },
Expand Down
4 changes: 4 additions & 0 deletions mg_raw.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
"/* nonelem 'Y' Array element that does not exist */" },
{ 'y', "want_vtbl_defelem | PERL_MAGIC_VALUE_MAGIC",
"/* defelem 'y' Shadow \"foreach\" iterator variable / smart parameter vivification */" },
{ 'Z', "want_vtbl_hook",
"/* hook 'Z' %{^HOOK} hash */" },
{ 'z', "want_vtbl_hookelem",
"/* hookelem 'z' %{^HOOK} hash element */" },
{ '\\', "want_vtbl_lvref",
"/* lvref '\\' Lvalue reference constructor */" },
{ ']', "want_vtbl_checkcall | PERL_MAGIC_VALUE_MAGIC",
Expand Down
10 changes: 10 additions & 0 deletions mg_vtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
#define PERL_MAGIC_nonelem 'Y' /* Array element that does not exist */
#define PERL_MAGIC_defelem 'y' /* Shadow "foreach" iterator variable /
smart parameter vivification */
#define PERL_MAGIC_hook 'Z' /* %{^HOOK} hash */
#define PERL_MAGIC_hookelem 'z' /* %{^HOOK} hash element */
#define PERL_MAGIC_lvref '\\' /* Lvalue reference constructor */
#define PERL_MAGIC_checkcall ']' /* Inlining/mutation of call to this CV */
#define PERL_MAGIC_extvalue '^' /* Value magic available for use by extensions */
Expand All @@ -75,6 +77,8 @@ enum { /* pass one of these to get_vtbl */
want_vtbl_envelem,
want_vtbl_hints,
want_vtbl_hintselem,
want_vtbl_hook,
want_vtbl_hookelem,
want_vtbl_isa,
want_vtbl_isaelem,
want_vtbl_lvref,
Expand Down Expand Up @@ -114,6 +118,8 @@ EXTCONST char * const PL_magic_vtable_names[magic_vtable_max] = {
"envelem",
"hints",
"hintselem",
"hook",
"hookelem",
"isa",
"isaelem",
"lvref",
Expand Down Expand Up @@ -176,6 +182,8 @@ EXT_MGVTBL PL_magic_vtables[magic_vtable_max] = {
{ 0, Perl_magic_setenv, 0, Perl_magic_clearenv, 0, 0, 0, 0 },
{ 0, 0, 0, Perl_magic_clearhints, 0, 0, 0, 0 },
{ 0, Perl_magic_sethint, 0, Perl_magic_clearhint, 0, 0, 0, 0 },
{ 0, Perl_magic_sethookall, 0, Perl_magic_clearhookall, 0, 0, 0, 0 },
{ 0, Perl_magic_sethook, 0, Perl_magic_clearhook, 0, 0, 0, 0 },
{ 0, Perl_magic_setisa, 0, Perl_magic_clearisa, 0, 0, 0, 0 },
{ 0, Perl_magic_setisa, 0, 0, 0, 0, 0, 0 },
{ 0, Perl_magic_setlvref, 0, 0, 0, 0, 0, 0 },
Expand Down Expand Up @@ -224,6 +232,8 @@ EXT_MGVTBL PL_magic_vtables[magic_vtable_max];
#define PL_vtbl_fm PL_magic_vtables[want_vtbl_fm]
#define PL_vtbl_hints PL_magic_vtables[want_vtbl_hints]
#define PL_vtbl_hintselem PL_magic_vtables[want_vtbl_hintselem]
#define PL_vtbl_hook PL_magic_vtables[want_vtbl_hook]
#define PL_vtbl_hookelem PL_magic_vtables[want_vtbl_hookelem]
#define PL_vtbl_isa PL_magic_vtables[want_vtbl_isa]
#define PL_vtbl_isaelem PL_magic_vtables[want_vtbl_isaelem]
#define PL_vtbl_lvref PL_magic_vtables[want_vtbl_lvref]
Expand Down
4 changes: 4 additions & 0 deletions perl.c
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ perl_destruct(pTHXx)
PL_warnhook = NULL;
SvREFCNT_dec(PL_diehook);
PL_diehook = NULL;
SvREFCNT_dec(PL_hook__require__before);
PL_hook__require__before = NULL;
SvREFCNT_dec(PL_hook__require__after);
PL_hook__require__after = NULL;

/* call exit list functions */
while (PL_exitlistlen-- > 0)
Expand Down
24 changes: 21 additions & 3 deletions pod/perldiag.pod
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,18 @@ the C<encoding> pragma, is no longer supported as of Perl 5.26.0.
Setting it to anything other than C<undef> is a fatal error as of Perl
5.28.

=item ${^HOOK}{%s} may only be a CODE reference or undef

(F) You attempted to assign something other than undef or a CODE ref to
C<%{^HOOK}>. Hooks may only be CODE refs. See L<perlvar/%{^HOOK}> for
details.

=item Attempt to set unknown hook '%s' in %{^HOOK}

(F) You attempted to assign something other than undef or a CODE ref to
C<%{^HOOK}>. Hooks may only be CODE refs. See L<perlvar/%{^HOOK}> for
details.

=item entering effective %s failed

(F) While under the C<use filetest> pragma, switching the real and
Expand Down Expand Up @@ -3961,11 +3973,17 @@ can vary from one line to the next.

=item Missing or undefined argument to %s

(F) You tried to call require or do with no argument or with an undefined
value as an argument. Require expects either a package name or a
file-specification as an argument; do expects a filename. See
(F) You tried to call C<require> or C<do> with no argument or with an
undefined value as an argument. Require expects either a package name or
a file-specification as an argument; do expects a filename. See
L<perlfunc/require EXPR> and L<perlfunc/do EXPR>.

=item Missing or undefined argument to %s via %{^HOOK}{require__before}

(F) A C<%{^HOOK}{require__before}> hook rewrote the name of the file being
compiled with C<require> or C<do> with an empty string an undefined value
which is forbidden. See L<perlvar/%{^HOOK}> and L<perlfunc/require EXPR>.

=item Missing right brace on \%c{} in regex; marked by S<<-- HERE> in m/%s/

(F) Missing right brace in C<\x{...}>, C<\p{...}>, C<\P{...}>, or C<\N{...}>.
Expand Down
Loading

0 comments on commit d0b1ad1

Please sign in to comment.