-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathTryCatch.xs
292 lines (240 loc) · 6.86 KB
/
TryCatch.xs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define NEED_PL_parser_GLOBAL
#define NEED_newRV_noinc_GLOBAL
#define NEED_sv_2pv_flags_GLOBAL
#include "ppport.h"
#include "hook_op_check.h"
#include "hook_op_ppaddr.h"
#ifndef CvISXSUB
# define CvISXSUB(cv) CvXSUB(cv)
#endif
static int trycatch_debug = 0;
STATIC I32
dump_cxstack()
{
I32 i;
for (i = cxstack_ix; i >= 0; i--) {
register const PERL_CONTEXT * const cx = cxstack+i;
switch (CxTYPE(cx)) {
default:
continue;
case CXt_EVAL:
printf("***\n* eval stack %d: WA: %d\n", (int)i, cx->blk_gimme);
/* sv_dump((SV*)cx->blk_eval.cv); */
break;
case CXt_SUB:
printf("***\n* cx stack %d: WA: %d\n", (int)i, cx->blk_gimme);
sv_dump((SV*)cx->blk_sub.cv);
break;
}
}
return i;
}
/* Return the (array)context of the first subroutine context up the Cx stack */
int get_sub_context()
{
I32 i;
for (i = cxstack_ix; i >= 0; i--) {
register const PERL_CONTEXT * const cx = cxstack+i;
switch (CxTYPE(cx)) {
default:
continue;
case CXt_SUB:
return cx->blk_gimme;
}
}
return G_VOID;
}
/* the implementation of 'return' op inside try blocks. */
STATIC OP*
try_return (pTHX_ OP *op, void *user_data) {
dSP;
SV* ctx;
CV *unwind;
PERL_UNUSED_VAR(op);
PERL_UNUSED_VAR(user_data);
ctx = get_sv("TryCatch::CTX", 0);
if (ctx) {
XPUSHs( ctx );
PUTBACK;
if (trycatch_debug & 2) {
printf("have a $CTX of %d\n", SvIV(ctx));
}
} else {
PUSHMARK(SP);
PUTBACK;
call_pv("Scope::Upper::SUB", G_SCALAR);
if (trycatch_debug & 2) {
printf("No ctx, making it up\n");
}
SPAGAIN;
}
if (trycatch_debug & 2) {
printf("unwinding to %d\n", (int)SvIV(*sp));
}
/* Can't use call_sv et al. since it resets PL_op. */
/* call_pv("Scope::Upper::unwind", G_VOID); */
unwind = get_cv("Scope::Upper::unwind", 0);
XPUSHs( (SV*)unwind);
PUTBACK;
/* pp_entersub gets the XSUB arguments from @_ if there are any.
* Bypass this as we pushed the arguments directly on the stack. */
if (CvISXSUB(unwind))
AvFILLp(GvAV(PL_defgv)) = -1;
return CALL_FPTR(PL_ppaddr[OP_ENTERSUB])(aTHX);
}
/* The implementation of wantarray op/keyword inside try blocks. */
STATIC OP*
try_wantarray( pTHX_ OP *op, void *user_data ) {
dVAR;
dSP;
EXTEND(SP, 1);
PERL_UNUSED_VAR(op);
PERL_UNUSED_VAR(user_data);
/* We want the context from the closest subroutine, not from the closest
* block
*/
switch ( get_sub_context() ) {
case G_ARRAY:
RETPUSHYES;
case G_SCALAR:
RETPUSHNO;
default:
RETPUSHUNDEF;
}
}
/* After the scope has been created, fix up the context of the C<eval {}> block */
STATIC OP*
try_after_entertry(pTHX_ OP *op, void *user_data) {
PERL_CONTEXT * cx = cxstack+cxstack_ix;
cx->blk_gimme = get_sub_context();
return op;
}
STATIC OP*
hook_if_correct_file( pTHX_ OP *op, void* user_data ) {
SV* eval_is_try;
const char* wanted_file = SvPV_nolen( (SV*)user_data );
const char* cur_file = CopFILE( &PL_compiling );
if ( strcmp(wanted_file, cur_file) ) {
if ( trycatch_debug & 4 )
Perl_warn( aTHX_ "Not hooking OP %s since its not in '%s'", PL_op_name[op->op_type], wanted_file );
return op;
}
if (trycatch_debug & 4) {
Perl_warn(aTHX_ "hooking OP %s", PL_op_name[op->op_type]);
}
switch (op->op_type) {
case OP_WANTARRAY:
hook_op_ppaddr(op, try_wantarray, NULL);
break;
case OP_RETURN:
hook_op_ppaddr(op, try_return, NULL);
break;
#if (PERL_BCDVERSION < 0x5011000)
case OP_ENTEREVAL:
/* Do nothing if its still an entereval */
break;
#endif
case OP_LEAVETRY:
/* eval {} starts off as an OP_ENTEREVAL, and then the PL_check[OP_ENTEREVAL]
returns a newly created ENTERTRY (and LEAVETRY) ops without calling the
PL_check for these new ops into OP_ENTERTRY. How ever versions prior to perl
5.10.1 didn't call the PL_check for these new ops */
hook_if_correct_file( aTHX_ ((LISTOP*)op)->op_first, user_data );
break;
case OP_ENTERTRY:
eval_is_try = get_sv("TryCatch::NEXT_EVAL_IS_TRY", 0);
if ( eval_is_try && SvOK( eval_is_try ) && SvTRUE( eval_is_try ) ) {
/* We've hooked a try block, so reset the flag */
SvIV_set( eval_is_try, 0 );
hook_op_ppaddr_around( op, NULL, try_after_entertry, NULL );
}
break;
default:
fprintf(stderr, "Try Catch Internal Error: Unknown op %d: %s\n", op->op_type, PL_op_name[op->op_type]);
abort();
}
return op;
}
/* Hook all the *_check functions we need. Return an arrayref of:
*
* [ current_file_name, op_id, hook_id, op_id, hook_id, ... ]
*/
SV*
xs_install_op_checks() {
SV *sv_curfile = newSV( 0 );
AV* av = newAV();
/* Get the filename we install check op hooks into. Need this so that we
don't hook ops if a require Other::Module happens in a try block. */
char* file = CopFILE(&PL_compiling);
STRLEN len = strlen(file);
(void)SvUPGRADE(sv_curfile,SVt_PVNV);
sv_setpvn(sv_curfile,file,len);
av_push(av, sv_curfile);
#define do_hook(op) \
av_push(av, newSVuv( (op) ) ); \
av_push(av, newSVuv( hook_op_check( op, hook_if_correct_file, sv_curfile ) ) ); \
/* This replace return with an unwird */
do_hook( OP_RETURN );
/* This fixes 'wantarray' keyword */
do_hook( OP_WANTARRAY );
/* And this gives the right context to C<return foo()> in a try block */
do_hook( OP_ENTERTRY );
#if (PERL_BCDVERSION < 0x5011000)
/* Prior to 5.10.1(?) the ENTERTRY starts out as an ENTEREVAL and doesn't get
* PL_checked, so we need to hook ENTEREVAL (string eval) too and see if the
* type got changed. */
do_hook( OP_ENTEREVAL );
#endif
#undef do_hook
/* Get an array ref form the array, return that. This keeps the sv_curfile alive */
return newRV_noinc( (SV*) av );
}
MODULE = TryCatch PACKAGE = TryCatch::XS
PROTOTYPES: DISABLE
void
install_op_checks()
CODE:
ST(0) = xs_install_op_checks();
XSRETURN(1);
void
uninstall_op_checks( aref )
SV* aref;
PREINIT:
AV* av;
SV *op, *id;
CODE:
if ( !SvROK(aref) && SvTYPE(SvRV(aref)) != SVt_PVAV ) {
Perl_croak(aTHX_ "ArrayRef expected");
}
av = (AV*)(SvRV(aref));
/* throw away cur_file */
av_shift(av);
while (av_len(av) != -1) {
op = av_shift(av);
id = av_shift(av);
hook_op_check_remove( SvUV(op), SvUV(id) );
}
OUTPUT:
void dump_stack()
CODE:
dump_cxstack();
OUTPUT:
void set_linestr_offset(int offset)
CODE:
char* linestr = SvPVX(PL_linestr);
PL_bufptr = linestr + offset;
BOOT:
{
char *debug = getenv ("TRYCATCH_DEBUG");
/* Debug meanings:
1 - line string changes (from the .pm)
2 - Debug unwid contexts
4 - debug op hooking
*/
if (debug && (trycatch_debug = atoi(debug)) ) {
fprintf(stderr, "TryCatch XS debug enabled: %d\n", trycatch_debug);
}
}