File Coverage

lib/Syntax/Keyword/Defer.xs
Criterion Covered Total %
statement 39 42 92.8
branch 20 80 25.0
condition n/a
subroutine n/a
pod n/a
total 59 122 48.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, 2021-2022 -- 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             #ifndef cx_pushblock
15             # include "cx_pushblock.c.inc"
16             #endif
17             #ifndef cx_pusheval
18             # include "cx_pusheval.c.inc"
19             #endif
20              
21             // Need to hide core perl's one for now so we get our label-capable version
22             #undef forbid_outofblock_ops
23              
24             #include "perl-additions.c.inc"
25             #include "forbid_outofblock_ops.c.inc"
26             #include "newOP_CUSTOM.c.inc"
27              
28             static XOP xop_pushdefer;
29              
30             // TODO: This test is not very reliable. Eventually perl might gain a
31             // PL_throwing which would be better
32             // https://github.com/Perl/perl5/pull/20407
33             #define PERL_IS_THROWING SvTRUE(ERRSV)
34              
35 31           static void invoke_defer(pTHX_ void *arg)
36             {
37             OP *start = (OP *)arg;
38 30           I32 was_cxstack_ix = cxstack_ix;
39              
40 30           cx_pushblock(CXt_BLOCK, G_VOID, PL_stack_sp, PL_savestack_ix);
41 30           ENTER;
42 30           SAVETMPS;
43              
44 30           SAVEOP();
45 30           PL_op = start;
46              
47 32 50         if(PERL_IS_THROWING) {
    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          
48             /* defer while throwing needs to catch inner exceptions to turn them
49             * into warnings so as not to disturb the outer, original exception
50             * See https://rt.cpan.org/Ticket/Display.html?id=144761
51             */
52             int ret;
53             dJMPENV;
54              
55 3           JMPENV_PUSH(ret);
56              
57             /* Pretend an eval {} happened */
58             /* On perls 5.20 and 5.22 we need to SAVETMPS a second time. I've no
59             idea why but if we don't, we'll forget the temps floor and destroy
60             far too many and break an outer die.
61             */
62 3           SAVETMPS;
63 3           PERL_CONTEXT *cx = cx_pushblock(CXt_EVAL|CXp_EVALBLOCK, G_VOID, PL_stack_sp, PL_savestack_ix);
64             cx_pusheval(cx, NULL, NULL);
65 3           PL_in_eval = EVAL_INEVAL|EVAL_KEEPERR;
66              
67 3           switch (ret) {
68             case 0:
69 2           CALLRUNOPS(aTHX);
70             /* defer block didn't throw */
71             break;
72             case 3:
73             /* defer block did throw; its message was printed as a warning
74             * because of EVAL_KEEPERR so we have nothing extra to do */
75             break;
76             default:
77 0           JMPENV_POP;
78 0 0         JMPENV_JUMP(ret);
    0          
79             NOT_REACHED;
80             }
81 2           JMPENV_POP;
82              
83 2           dounwind(was_cxstack_ix + 1);
84             }
85             else {
86 28           CALLRUNOPS(aTHX);
87             }
88              
89 29 100         FREETMPS;
90 29           LEAVE;
91              
92             /* It's too late to stop this forbidden condition, but at least we can print
93             * why it happened and panic about it in a more controlled way than just
94             * causing a segfault.
95             */
96 29 50         if(cxstack_ix != was_cxstack_ix + 1) {
97 0           croak("panic: A non-local control flow operation exited a defer block");
98             }
99              
100             {
101 29           PERL_CONTEXT *cx = CX_CUR();
102              
103             /* restore stack height */
104 29           PL_stack_sp = PL_stack_base + cx->blk_oldsp;
105             }
106              
107 29           dounwind(was_cxstack_ix);
108 29           }
109              
110 30           static OP *pp_pushdefer(pTHX)
111             {
112 30           OP *defer = cLOGOP->op_other;
113              
114 30           SAVEDESTRUCTOR_X(&invoke_defer, defer);
115              
116 30           return PL_op->op_next;
117             }
118              
119 33           static int build_defer(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
120             {
121 33           OP *body = arg0->op;
122              
123 33           forbid_outofblock_ops(body, "a defer block");
124              
125 29           *out = newLOGOP_CUSTOM(&pp_pushdefer, 0,
126             newOP(OP_NULL, 0), body);
127              
128             /* unlink the terminating condition of 'body' */
129 29           body->op_next = NULL;
130              
131 29           return KEYWORD_PLUGIN_STMT;
132             }
133              
134             static const struct XSParseKeywordHooks hooks_defer = {
135             .permit_hintkey = "Syntax::Keyword::Defer/defer",
136             .piece1 = XPK_BLOCK,
137             .build1 = &build_defer,
138             };
139              
140             MODULE = Syntax::Keyword::Defer PACKAGE = Syntax::Keyword::Defer
141              
142             BOOT:
143 6           XopENTRY_set(&xop_pushdefer, xop_name, "pushdefer");
144 6           XopENTRY_set(&xop_pushdefer, xop_desc,
145             "arrange for a CV to be invoked at scope exit");
146 6           XopENTRY_set(&xop_pushdefer, xop_class, OA_LOGOP);
147 6           Perl_custom_op_register(aTHX_ &pp_pushdefer, &xop_pushdefer);
148              
149 6           boot_xs_parse_keyword(0.13);
150              
151             register_xs_parse_keyword("defer", &hooks_defer, NULL);