| 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); |