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