summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Mitchell <davem@iabyn.com>2022-05-12 14:04:27 +0100
committerDavid Mitchell <davem@iabyn.com>2022-06-20 11:16:46 +0100
commita6ceaeb95aed574844bcfa9b9779650ed7fb5c63 (patch)
tree546071fe9b4a18c3d2647c9e69bb637d29673658
parent5fd637ce8c4db075c0668d9f9c806f6712cd3c7f (diff)
downloadperl-a6ceaeb95aed574844bcfa9b9779650ed7fb5c63.tar.gz
pp_ctl.c: revamp S_docatch() and docs
Document fully what S_docatch() does, and remove the one macro which calls it, replacing all the call-sites with a direct call to docatch(). docatch() is a core part of perl's internal exception handling, and as such is conceptually complex. It was made even more complex by v5.27.0-75-gd7e3f70f30, which causes functions like pp_entertry() to recursively call themselves sometimes. But this detail was hidden by the RUN_PP_CATCHABLY() macro, which included a hidden 'return'. By unwrapping this trivial macro, it makes the flow of control much easier to understand. I've also added an extra assert(!CATCH_GET), to emphasise that this setting is reset following the JMPENV_PUSH() call. Apart from that, there should be no functional differences.
-rw-r--r--pp_ctl.c117
1 files changed, 100 insertions, 17 deletions
diff --git a/pp_ctl.c b/pp_ctl.c
index 956e2c4751..d34b8d2393 100644
--- a/pp_ctl.c
+++ b/pp_ctl.c
@@ -35,9 +35,6 @@
#include "perl.h"
#include "feature.h"
-#define RUN_PP_CATCHABLY(thispp) \
- STMT_START { if (CATCH_GET) return docatch(thispp); } STMT_END
-
#define dopopto_cursub() \
(PL_curstackinfo->si_cxsubix >= 0 \
? PL_curstackinfo->si_cxsubix \
@@ -3324,17 +3321,65 @@ S_save_lines(pTHX_ AV *array, SV *sv)
/*
=for apidoc docatch
-Check for the cases 0 or 3 of cur_env.je_ret, only used inside an eval context.
+Interpose, for the current op and RUNOPS loop,
+
+ - a new JMPENV stack catch frame, and
+ - an inner RUNOPS loop to run all the remaining ops following the
+ current PL_op.
+
+Then handle any exceptions raised while in that loop.
+For a caught eval at this level, re-enter the loop with the specified
+restart op (i.e. the op following the OP_LEAVETRY etc); otherwise re-throw
+the exception.
-0 is used as continue inside eval,
+docatch() is intended to be used like this:
-3 is used for a die caught by an inner eval - continue inner loop
+ PP(pp_entertry)
+ {
+ if (CATCH_GET)
+ return docatch(Perl_pp_entertry);
-See F<cop.h>: je_mustcatch, when set at any runlevel to TRUE, means eval ops must
-establish a local jmpenv to handle exception traps.
+ ... rest of function ...
+ return PL_op->op_next;
+ }
+
+If a new catch frame isn't needed, the op behaves normally. Otherwise it
+calls docatch(), which recursively calls pp_entertry(), this time with
+CATCH_GET() false, so the rest of the body of the entertry is run. Then
+docatch() calls CALLRUNOPS() which executes all the ops following the
+entertry. When the loop finally finishes, control returns to docatch(),
+which pops the JMPENV and returns to the parent pp_entertry(), which
+itself immediately returns. Note that *all* subsequent ops are run within
+the inner RUNOPS loop, not just the body of the eval. For example, in
+
+ sub TIEARRAY { eval {1}; my $x }
+ tie @a, "main";
+
+at the point the 'my' is executed, the C stack will look something like:
+
+ #10 main()
+ #9 perl_run() # JMPENV_PUSH level 1 here
+ #8 S_run_body()
+ #7 Perl_runops_standard() # main RUNOPS loop
+ #6 Perl_pp_tie()
+ #5 Perl_call_sv()
+ #4 Perl_runops_standard() # unguarded RUNOPS loop: no new JMPENV
+ #3 Perl_pp_entertry()
+ #2 S_docatch() # JMPENV_PUSH level 2 here
+ #1 Perl_runops_standard() # docatch()'s RUNOPs loop
+ #0 Perl_pp_padsv()
+
+Basically, any section of the perl core which starts a RUNOPS loop may
+make a promise that it will catch any exceptions and restart the loop if
+necessary. If it's not prepared to do that (like call_sv() isn't), then
+it sets CATCH_GET() to true, so that any later eval-like code knows to
+set up a new handler and loop (via docatch()).
+
+See L<perlinterp/"Exception handing"> for further details.
=cut
*/
+
STATIC OP *
S_docatch(pTHX_ Perl_ppaddr_t firstpp)
{
@@ -3342,16 +3387,21 @@ S_docatch(pTHX_ Perl_ppaddr_t firstpp)
OP * const oldop = PL_op;
dJMPENV;
- assert(CATCH_GET == TRUE);
-
+ assert(CATCH_GET);
JMPENV_PUSH(ret);
+ assert(!CATCH_GET);
+
switch (ret) {
- case 0:
+ case 0: /* normal flow-of-control return from JMPENV_PUSH */
+
+ /* re-run the current op, this time executing the full body of the
+ * pp function */
PL_op = firstpp(aTHX);
redo_body:
CALLRUNOPS(aTHX);
break;
- case 3:
+
+ case 3: /* an exception raised within an eval */
if (PL_restartjmpenv == PL_top_env) {
/* die caught by an inner eval - continue inner loop */
@@ -3363,10 +3413,11 @@ S_docatch(pTHX_ Perl_ppaddr_t firstpp)
goto redo_body;
}
/* FALLTHROUGH */
+
default:
JMPENV_POP;
PL_op = oldop;
- JMPENV_JUMP(ret);
+ JMPENV_JUMP(ret); /* re-throw the exception */
NOT_REACHED; /* NOTREACHED */
}
JMPENV_POP;
@@ -4441,7 +4492,15 @@ S_require_file(pTHX_ SV *sv)
PP(pp_require)
{
- RUN_PP_CATCHABLY(Perl_pp_require);
+ /* If a suitable JMPENV catch frame isn't present, call do_catch(),
+ * which will:
+ * - add such a frame, and
+ * - start a new RUNOPS loop, which will (as the first op to run),
+ * recursively call this pp function again.
+ * The main body of this function is then executed by the inner call.
+ */
+ if (CATCH_GET)
+ return docatch(Perl_pp_require);
{
dSP;
@@ -4484,7 +4543,15 @@ PP(pp_entereval)
bool bytes;
I32 old_savestack_ix;
- RUN_PP_CATCHABLY(Perl_pp_entereval);
+ /* If a suitable JMPENV catch frame isn't present, call do_catch(),
+ * which will:
+ * - add such a frame, and
+ * - start a new RUNOPS loop, which will (as the first op to run),
+ * recursively call this pp function again.
+ * The main body of this function is then executed by the inner call.
+ */
+ if (CATCH_GET)
+ return docatch(Perl_pp_entereval);
gimme = GIMME_V;
was = PL_breakable_sub_gen;
@@ -4679,7 +4746,15 @@ PP(pp_entertrycatch)
PERL_CONTEXT *cx;
const U8 gimme = GIMME_V;
- RUN_PP_CATCHABLY(Perl_pp_entertrycatch);
+ /* If a suitable JMPENV catch frame isn't present, call do_catch(),
+ * which will:
+ * - add such a frame, and
+ * - start a new RUNOPS loop, which will (as the first op to run),
+ * recursively call this pp function again.
+ * The main body of this function is then executed by the inner call.
+ */
+ if (CATCH_GET)
+ return docatch(Perl_pp_entertrycatch);
assert(!CATCH_GET);
@@ -4760,7 +4835,15 @@ PP(pp_entertry)
{
OP *retop = cLOGOP->op_other->op_next;
- RUN_PP_CATCHABLY(Perl_pp_entertry);
+ /* If a suitable JMPENV catch frame isn't present, call do_catch(),
+ * which will:
+ * - add such a frame, and
+ * - start a new RUNOPS loop, which will (as the first op to run),
+ * recursively call this pp function again.
+ * The main body of this function is then executed by the inner call.
+ */
+ if (CATCH_GET)
+ return docatch(Perl_pp_entertry);
assert(!CATCH_GET);