File Coverage

lib/Syntax/Keyword/Try.xs
Criterion Covered Total %
statement 205 216 94.9
branch 131 230 56.9
condition n/a
subroutine n/a
pod n/a
total 336 446 75.3


line stmt bran cond sub pod time code
1             /* You may distribute under the terms of either the GNU General Public License
2             * or the Artistic License (the same terms as Perl itself)
3             *
4             * (C) Paul Evans, 2016-2021 -- leonerd@leonerd.org.uk
5             */
6             #include "EXTERN.h"
7             #include "perl.h"
8             #include "XSUB.h"
9              
10             #include "XSParseKeyword.h"
11              
12             #include "perl-backcompat.c.inc"
13              
14             #if HAVE_PERL_VERSION(5,32,0)
15             # define HAVE_OP_ISA
16             #endif
17              
18             #if HAVE_PERL_VERSION(5,26,0)
19             # define HAVE_OP_SIBPARENT
20             #endif
21              
22             #if HAVE_PERL_VERSION(5,19,4)
23             typedef SSize_t array_ix_t;
24             #else /* <5.19.4 */
25             typedef I32 array_ix_t;
26             #endif /* <5.19.4 */
27              
28             #include "perl-additions.c.inc"
29             #include "optree-additions.c.inc"
30             #include "op_sibling_splice.c.inc"
31             #include "newOP_CUSTOM.c.inc"
32              
33             static OP *pp_entertrycatch(pTHX);
34             static OP *pp_catch(pTHX);
35              
36             /*
37             * A modified version of pp_return for returning from inside a try block.
38             * To do this, we unwind the context stack to just past the CXt_EVAL and then
39             * chain to the regular OP_RETURN func
40             */
41 7           static OP *pp_returnintry(pTHX)
42             {
43             I32 cxix;
44              
45 23 50         for (cxix = cxstack_ix; cxix; cxix--) {
46 23 100         if(CxTYPE(&cxstack[cxix]) == CXt_SUB)
47             break;
48              
49 17 100         if(CxTYPE(&cxstack[cxix]) == CXt_EVAL && CxTRYBLOCK(&cxstack[cxix])) {
50             /* If this CXt_EVAL frame came from our own ENTERTRYCATCH, then the
51             * retop should point at an OP_CUSTOM and its first grand-child will be
52             * our custom modified ENTERTRY. We can skip over it and continue in
53             * this case.
54             */
55 9           OP *retop = cxstack[cxix].blk_eval.retop;
56             OP *leave, *enter;
57 9 100         if(retop->op_type == OP_CUSTOM && retop->op_ppaddr == &pp_catch &&
    50          
    50          
58 8 50         (leave = cLOGOPx(retop)->op_first) && leave->op_type == OP_LEAVETRY &&
    50          
59 8 50         (enter = cLOGOPx(leave)->op_first) && enter->op_type == OP_ENTERTRY &&
    50          
60 8           enter->op_ppaddr == &pp_entertrycatch) {
61 8           continue;
62             }
63             /* We have to stop at any other kind of CXt_EVAL */
64             break;
65             }
66             }
67 7 50         if(!cxix)
68 0           croak("Unable to find an CXt_SUB to pop back to");
69              
70 7           I32 gimme = cxstack[cxix].blk_gimme;
71             SV *retval;
72              
73             /* chunks of this code inspired by
74             * ZEFRAM/Scope-Escape-0.005/lib/Scope/Escape.xs
75             */
76 7           switch(gimme) {
77             case G_VOID:
78             (void)POPMARK;
79             break;
80              
81             case G_SCALAR: {
82 5           dSP;
83 10           dMARK;
84 5 50         retval = (MARK == SP) ? &PL_sv_undef : TOPs;
85             SvREFCNT_inc(retval);
86 5           sv_2mortal(retval);
87 5           break;
88             }
89              
90             case G_LIST: {
91 1           dSP;
92 2           dMARK;
93 1           SV **retvals = MARK+1;
94 1           array_ix_t retcount = SP-MARK;
95             array_ix_t i;
96 1           AV *retav = newAV();
97             retval = (SV *)retav;
98 1           sv_2mortal(retval);
99 1           av_fill(retav, retcount-1);
100 1 50         Copy(retvals, AvARRAY(retav), retcount, SV *);
101 4 100         for(i = 0; i < retcount; i++)
102 3           SvREFCNT_inc(retvals[i]);
103             break;
104             }
105             }
106              
107 7           dounwind(cxix);
108              
109             /* Now put the value back */
110 7           switch(gimme) {
111             case G_VOID: {
112 1           dSP;
113 1 50         PUSHMARK(SP);
114 1           break;
115             }
116              
117             case G_SCALAR: {
118 5           dSP;
119 5 50         PUSHMARK(SP);
120 5 50         XPUSHs(retval);
121 5           PUTBACK;
122 5           break;
123             }
124              
125             case G_LIST: {
126 1           dSP;
127 1 50         PUSHMARK(SP);
128             AV *retav = (AV *)retval;
129 1           array_ix_t retcount = av_len(retav) + 1; /* because av_len means top index */
130 1 50         EXTEND(SP, retcount);
    50          
131 1 50         Copy(AvARRAY(retav), SP+1, retcount, SV *);
132 1           SP += retcount;
133 1           PUTBACK;
134 1           break;
135             }
136             }
137              
138 7           return PL_ppaddr[OP_RETURN](aTHX);
139             }
140              
141             /*
142             * A custom SVOP that takes a CV and arranges for it to be invoked on scope
143             * leave
144             */
145             static XOP xop_pushfinally;
146              
147 8           static void invoke_finally(pTHX_ void *arg)
148             {
149             CV *finally = arg;
150 8           dSP;
151              
152 8 50         PUSHMARK(SP);
153 8           call_sv((SV *)finally, G_DISCARD|G_EVAL|G_KEEPERR);
154              
155             SvREFCNT_dec(finally);
156 8           }
157              
158 8           static OP *pp_pushfinally(pTHX)
159             {
160 8           CV *finally = (CV *)cSVOP->op_sv;
161              
162             /* finally is a closure protosub; we have to clone it into a real sub.
163             * If we do this now then captured lexicals still work even around
164             * Future::AsyncAwait (see RT122796)
165             * */
166 8           SAVEDESTRUCTOR_X(&invoke_finally, (SV *)cv_clone(finally));
167 8           return PL_op->op_next;
168             }
169              
170             #define newLOCALISEOP(gv) MY_newLOCALISEOP(aTHX_ gv)
171             static OP *MY_newLOCALISEOP(pTHX_ GV *gv)
172             {
173             OP *op = newGVOP(OP_GVSV, 0, gv);
174             op->op_private |= OPpLVAL_INTRO;
175             return op;
176             }
177              
178             #define newSTATEOP_nowarnings() MY_newSTATEOP_nowarnings(aTHX)
179 2           static OP *MY_newSTATEOP_nowarnings(pTHX)
180             {
181 2           OP *op = newSTATEOP(0, NULL, NULL);
182 2           STRLEN *warnings = ((COP *)op)->cop_warnings;
183             char *warning_bits;
184              
185 2 50         if(warnings == pWARN_NONE)
186             return op;
187              
188 2 50         if(warnings == pWARN_STD)
189             /* TODO: understand what STD vs ALL means */
190             warning_bits = WARN_ALLstring;
191 2 50         else if(warnings == pWARN_ALL)
192             warning_bits = WARN_ALLstring;
193             else
194 0           warning_bits = (char *)(warnings + 1);
195              
196 2           warnings = Perl_new_warnings_bitfield(aTHX_ warnings, warning_bits, WARNsize);
197 2           ((COP *)op)->cop_warnings = warnings;
198              
199             warning_bits = (char *)(warnings + 1);
200 2           warning_bits[(2*WARN_EXITING) / 8] &= ~(1 << (2*WARN_EXITING % 8));
201              
202 2           return op;
203             }
204              
205 14           static void rethread_op(OP *op, OP *old, OP *new)
206             {
207 14 100         if(op->op_next == old)
208 1           op->op_next = new;
209              
210 14 50         switch(OP_CLASS(op)) {
211             case OA_LOGOP:
212 1 50         if(cLOGOPx(op)->op_other == old)
213 1           cLOGOPx(op)->op_other = new;
214             break;
215              
216             case OA_LISTOP:
217 2 50         if(cLISTOPx(op)->op_last == old)
218 0           cLISTOPx(op)->op_last = new;
219             break;
220             }
221              
222 14 100         if(op->op_flags & OPf_KIDS) {
223             OP *kid;
224 18 100         for(kid = cUNOPx(op)->op_first; kid; kid = OpSIBLING(kid))
    100          
225 12           rethread_op(kid, old, new);
226             }
227 14           }
228              
229             #define walk_optree_try_in_eval(op_ptr, root) MY_walk_optree_try_in_eval(aTHX_ op_ptr, root)
230             static void MY_walk_optree_try_in_eval(pTHX_ OP **op_ptr, OP *root);
231 260           static void MY_walk_optree_try_in_eval(pTHX_ OP **op_ptr, OP *root)
232             {
233 260           OP *op = *op_ptr;
234              
235 260           switch(op->op_type) {
236             /* Fix 'return' to unwind the CXt_EVAL block that implements try{} first */
237             case OP_RETURN:
238 7           op->op_ppaddr = &pp_returnintry;
239 7           break;
240              
241             /* wrap no warnings 'exiting' around loop controls */
242             case OP_NEXT:
243             case OP_LAST:
244             case OP_REDO:
245             {
246             #ifdef HAVE_OP_SIBPARENT
247 2 100         OP *parent = OpHAS_SIBLING(op) ? NULL : op->op_sibparent;
248             #endif
249              
250 2           OP *stateop = newSTATEOP_nowarnings();
251              
252 2           OP *scope = newLISTOP(OP_SCOPE, 0,
253             stateop, op);
254             #ifdef HAVE_OP_SIBPARENT
255 2 100         if(parent)
256 1           OpLASTSIB_set(scope, parent);
257             else
258 1           OpLASTSIB_set(scope, NULL);
259             #else
260             op->op_sibling = NULL;
261             #endif
262              
263             /* Rethread */
264 2           scope->op_next = stateop;
265 2           stateop->op_next = op;
266              
267 2           *op_ptr = scope;
268             }
269 2           break;
270              
271             /* Don't enter inside nested eval{} blocks */
272             case OP_LEAVETRY:
273             return;
274             }
275              
276 258 100         if(op->op_flags & OPf_KIDS) {
277             OP *kid, *next, *prev = NULL;
278 334 100         for(kid = cUNOPx(op)->op_first; kid; kid = next) {
279 222           OP *newkid = kid;
280 222 100         next = OpSIBLING(kid);
281              
282 222           walk_optree_try_in_eval(&newkid, root);
283              
284 222 100         if(newkid != kid) {
285 2           rethread_op(root, kid, newkid);
286              
287 2 50         if(prev) {
288 2           OpMORESIB_set(prev, newkid);
289             }
290             else
291 0           cUNOPx(op)->op_first = newkid;
292              
293 2 100         if(next)
294 222           OpMORESIB_set(newkid, next);
295             }
296              
297             prev = kid;
298             }
299             }
300             }
301              
302 43           static OP *pp_entertrycatch(pTHX)
303             {
304             /* Localise the errgv */
305 43           save_scalar(PL_errgv);
306              
307 43           return PL_ppaddr[OP_ENTERTRY](aTHX);
308             }
309              
310             static XOP xop_catch;
311              
312 33           static OP *pp_catch(pTHX)
313             {
314             /* If an error didn't happen, then ERRSV will be both not true and not a
315             * reference. If it's a reference, then an error definitely happened
316             */
317 33 50         if(SvROK(ERRSV) || SvTRUE(ERRSV))
    100          
    50          
    50          
    50          
    50          
    0          
    0          
    50          
    50          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    0          
    0          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
318 22           return cLOGOP->op_other;
319             else
320 11           return cLOGOP->op_next;
321             }
322              
323             #define newENTERTRYCATCHOP(flags, try, catch) MY_newENTERTRYCATCHOP(aTHX_ flags, try, catch)
324 76           static OP *MY_newENTERTRYCATCHOP(pTHX_ U32 flags, OP *try, OP *catch)
325             {
326             OP *enter, *entertry, *ret;
327              
328             /* Walk the block for OP_RETURN ops, so we can apply a hack to them to
329             * make
330             * try { return }
331             * return from the containing sub, not just the eval block
332             */
333 38           walk_optree_try_in_eval(&try, try);
334              
335 38           enter = newUNOP(OP_ENTERTRY, 0, try);
336             /* despite calling newUNOP(OP_ENTERTRY,...) the returned root node is the
337             * OP_LEAVETRY, whose first child is the ENTERTRY we wanted
338             */
339 38           entertry = ((UNOP *)enter)->op_first;
340 38           entertry->op_ppaddr = &pp_entertrycatch;
341              
342             /* If we call newLOGOP_CUSTOM it will op_contextualize the enter block into
343             * G_SCALAR. This is not what we want
344             */
345             {
346             LOGOP *logop;
347              
348 38           OP *first = enter, *other = newLISTOP(OP_SCOPE, 0, catch, NULL);
349              
350 38           NewOp(1101, logop, 1, LOGOP);
351              
352 38           logop->op_type = OP_CUSTOM;
353 38           logop->op_ppaddr = &pp_catch;
354 38           logop->op_first = first;
355 38           logop->op_flags = OPf_KIDS;
356 38 50         logop->op_other = LINKLIST(other);
357              
358 38 50         logop->op_next = LINKLIST(first);
359 38           enter->op_next = (OP *)logop;
360             #if HAVE_PERL_VERSION(5, 22, 0)
361 38           op_sibling_splice((OP *)logop, first, 0, other);
362             #else
363             first->op_sibling = other;
364             #endif
365              
366 38           ret = newUNOP(OP_NULL, 0, (OP *)logop);
367 38           other->op_next = ret;
368             }
369              
370 38           return ret;
371             }
372              
373             #ifndef HAVE_OP_ISA
374             static XOP xop_isa;
375              
376             /* Totally stolen from perl 5.32.0's pp.c */
377             #define sv_isa_sv(sv, namesv) S_sv_isa_sv(aTHX_ sv, namesv)
378 5           static bool S_sv_isa_sv(pTHX_ SV *sv, SV *namesv)
379             {
380 5 100         if(!SvROK(sv) || !SvOBJECT(SvRV(sv)))
    50          
381             return FALSE;
382              
383             /* TODO: ->isa invocation */
384              
385             #if HAVE_PERL_VERSION(5,16,0)
386 2           return sv_derived_from_sv(sv, namesv, 0);
387             #else
388             return sv_derived_from(sv, SvPV_nolen(namesv));
389             #endif
390             }
391              
392 5           static OP *pp_isa(pTHX)
393             {
394 5           dSP;
395              
396             SV *left, *right;
397              
398 5           right = POPs;
399 5           left = TOPs;
400              
401 5 100         SETs(boolSV(sv_isa_sv(left, right)));
402 5           RETURN;
403             }
404             #endif
405              
406 47           static int build_try(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
407             {
408             U32 argi = 0;
409              
410 47           OP *try = args[argi++]->op;
411              
412             OP *ret = NULL;
413 47           HV *hints = GvHV(PL_hintgv);
414              
415 47 50         bool require_catch = hints && hv_fetchs(hints, "Syntax::Keyword::Try/require_catch", 0);
    50          
416 47 50         bool require_var = hints && hv_fetchs(hints, "Syntax::Keyword::Try/require_var", 0);
    100          
417              
418 47           U32 ncatches = args[argi++]->i;
419              
420             AV *condcatch = NULL;
421             OP *catch = NULL;
422 87 100         while(ncatches--) {
423 41           bool has_catchvar = args[argi++]->i;
424 41 100         PADOFFSET catchvar = has_catchvar ? args[argi++]->padix : 0;
425 41 100         int catchtype = has_catchvar ? args[argi++]->i : -1;
426              
427             bool warned = FALSE;
428              
429             OP *condop = NULL;
430              
431 41           switch(catchtype) {
432             case -1: /* no type */
433             break;
434              
435             case 0: /* isa */
436             {
437 2           OP *type = args[argi++]->op;
438             #ifdef HAVE_OP_ISA
439             condop = newBINOP(OP_ISA, 0,
440             newPADxVOP(OP_PADSV, catchvar, 0, 0), type);
441             #else
442             /* Allow a bareword on RHS of `isa` */
443 2 50         if(type->op_type == OP_CONST)
444 2           type->op_private &= ~(OPpCONST_BARE|OPpCONST_STRICT);
445              
446             condop = newBINOP_CUSTOM(&pp_isa, 0,
447             newPADxVOP(OP_PADSV, catchvar, 0, 0), type);
448             #endif
449 2           break;
450             }
451              
452             case 1: /* =~ */
453             {
454 1           OP *regexp = args[argi++]->op;
455              
456 1 50         if(regexp->op_type != OP_MATCH || cPMOPx(regexp)->op_first)
    50          
457 0           croak("Expected a regexp match");
458             #if HAVE_PERL_VERSION(5,22,0)
459             /* Perl 5.22+ uses op_targ on OP_MATCH directly */
460 1           regexp->op_targ = catchvar;
461             #else
462             /* Older perls need a stacked OP_PADSV op */
463             cPMOPx(regexp)->op_first = newPADxVOP(OP_PADSV, catchvar, 0, 0);
464             regexp->op_flags |= OPf_KIDS|OPf_STACKED;
465             #endif
466             condop = regexp;
467 1           break;
468             }
469              
470             default:
471 0           croak("TODO\n");
472             }
473              
474             #ifdef WARN_EXPERIMENTAL
475 41 100         if(condop && !warned &&
    50          
476 3 50         (!hints || !hv_fetchs(hints, "Syntax::Keyword::Try/experimental(typed)", 0))) {
477             warned = true;
478 0           Perl_ck_warner(aTHX_ packWARN(WARN_EXPERIMENTAL),
479             "typed catch syntax is experimental and may be changed or removed without notice");
480             }
481             #endif
482              
483 41           OP *body = args[argi++]->op;
484              
485 41 100         if(require_var && !has_catchvar)
486 1           croak("Expected (VAR) for catch");
487              
488 40 50         if(catch)
489 0           croak("Already have a default catch {} block");
490              
491             OP *assignop = NULL;
492 40 100         if(catchvar) {
493             /* my $var = $@ */
494 37           assignop = newBINOP(OP_SASSIGN, 0,
495             newGVOP(OP_GVSV, 0, PL_errgv), newPADxVOP(OP_PADSV, catchvar, OPf_MOD, OPpLVAL_INTRO));
496             }
497              
498 40 100         if(condop) {
499 3 100         if(!condcatch)
500 2           condcatch = newAV();
501              
502 3           av_push(condcatch, (SV *)op_append_elem(OP_LINESEQ, assignop, condop));
503 3           av_push(condcatch, (SV *)body);
504             /* catch remains NULL for now */
505             }
506 37 100         else if(assignop) {
507 40           catch = op_prepend_elem(OP_LINESEQ,
508             assignop, body);
509             }
510             else
511             catch = body;
512             }
513              
514 46 100         if(condcatch) {
515             I32 i;
516              
517 2 100         if(!catch)
518             /* A default fallthrough */
519             /* die $@ */
520 1           catch = newLISTOP(OP_DIE, 0,
521             newOP(OP_PUSHMARK, 0), newGVOP(OP_GVSV, 0, PL_errgv));
522              
523 5 50         for(i = AvFILL(condcatch)-1; i >= 0; i -= 2) {
    100          
524 3           OP *body = (OP *)av_pop(condcatch),
525 3           *condop = (OP *)av_pop(condcatch);
526              
527 3           catch = newCONDOP(0, condop, op_scope(body), catch);
528             }
529              
530             SvREFCNT_dec(condcatch);
531             }
532              
533 46 50         if(require_catch && !catch)
534 0           croak("Expected a catch {} block");
535              
536 46 50         bool no_finally = hints && hv_fetchs(hints, "Syntax::Keyword::Try/no_finally", 0);
    100          
537              
538 46           U32 has_finally = args[argi++]->i;
539 46 100         CV *finally = has_finally ? args[argi++]->cv : NULL;
540              
541 46 100         if(no_finally && finally)
542 1           croak("finally {} is not permitted here");
543              
544 45 50         if(!catch && !finally) {
545 0           op_free(try);
546 0 0         croak(no_finally
547             ? "Expected try {} to be followed by catch {}"
548             : "Expected try {} to be followed by either catch {} or finally {}");
549             }
550              
551             ret = try;
552              
553 45 100         if(catch) {
554 38           ret = newENTERTRYCATCHOP(0, try, catch);
555             }
556              
557             /* If there's a finally, make
558             * $RET = OP_PUSHFINALLY($FINALLY); $RET
559             */
560 45 100         if(finally) {
561 10           ret = op_prepend_elem(OP_LINESEQ,
562             newSVOP_CUSTOM(&pp_pushfinally, 0, (SV *)finally),
563             ret);
564             }
565              
566 45           ret = op_append_list(OP_LEAVE,
567             newOP(OP_ENTER, 0),
568             ret);
569              
570 45           *out = ret;
571 45           return KEYWORD_PLUGIN_STMT;
572             }
573              
574             static struct XSParseKeywordHooks hooks_try = {
575             .permit_hintkey = "Syntax::Keyword::Try/try",
576              
577             .pieces = (const struct XSParseKeywordPieceType []){
578             XPK_BLOCK,
579             XPK_REPEATED(
580             XPK_LITERAL("catch"),
581             XPK_PREFIXED_BLOCK(
582             /* optionally ($var), ($var isa Type) or ($var =~ m/.../) */
583             XPK_PARENSCOPE_OPT(
584             XPK_LEXVAR_MY(XPK_LEXVAR_SCALAR),
585             XPK_CHOICE(
586             XPK_SEQUENCE(XPK_LITERAL("isa"), XPK_TERMEXPR),
587             XPK_SEQUENCE(XPK_LITERAL("=~"), XPK_TERMEXPR)
588             )
589             )
590             )
591             ),
592             XPK_OPTIONAL(
593             XPK_LITERAL("finally"), XPK_ANONSUB
594             ),
595             {0},
596             },
597             .build = &build_try,
598             };
599              
600             MODULE = Syntax::Keyword::Try PACKAGE = Syntax::Keyword::Try
601              
602             BOOT:
603 15           XopENTRY_set(&xop_catch, xop_name, "catch");
604 15           XopENTRY_set(&xop_catch, xop_desc,
605             "optionally invoke the catch block if required");
606 15           XopENTRY_set(&xop_catch, xop_class, OA_LOGOP);
607 15           Perl_custom_op_register(aTHX_ &pp_catch, &xop_catch);
608              
609 15           XopENTRY_set(&xop_pushfinally, xop_name, "pushfinally");
610 15           XopENTRY_set(&xop_pushfinally, xop_desc,
611             "arrange for a CV to be invoked at scope exit");
612 15           XopENTRY_set(&xop_pushfinally, xop_class, OA_SVOP);
613 15           Perl_custom_op_register(aTHX_ &pp_pushfinally, &xop_pushfinally);
614             #ifndef HAVE_OP_ISA
615 15           XopENTRY_set(&xop_isa, xop_name, "isa");
616 15           XopENTRY_set(&xop_isa, xop_desc,
617             "check if a value is an object of the given class");
618 15           XopENTRY_set(&xop_isa, xop_class, OA_BINOP);
619 15           Perl_custom_op_register(aTHX_ &pp_isa, &xop_isa);
620             #endif
621              
622 15           boot_xs_parse_keyword(0.06);
623              
624             register_xs_parse_keyword("try", &hooks_try, NULL);