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, 2018 -- leonerd@leonerd.org.uk
5 */
6 #include "EXTERN.h"
7 #include "perl.h"
8 #include "XSUB.h"
9
10 #include "AsyncAwait.h"
11
12 #include "XSParseKeyword.h"
13
14 #ifdef HAVE_DMD_HELPER
15 # include "DMD_helper.h"
16 #endif
17
18 #define HAVE_PERL_VERSION(R, V, S) \
19 (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
20
21 #include "perl-additions.c.inc"
22
23 static bool is_async = FALSE;
24
25 #ifdef MULTIPLICITY
26 # define dynamicstack \
27 *((AV **)hv_fetchs(PL_modglobal, "Syntax::Keyword::Dynamically/dynamicstack", GV_ADD))
28 #else
29 /* without MULTIPLICITY there's only one, so we might as well just store it
30 * in a static
31 */
32 static AV *dynamicstack;
33 #endif
34
35 typedef struct {
36 SV *var; /* is HV * if keysv is set; indicates an HELEM */
37 SV *keysv;
38 SV *oldval; /* is NULL for HELEMs if we should delete at pop time */
39 int saveix;
40 } DynamicVar;
41
42 #define newSVdynamicvar() S_newSVdynamicvar(aTHX)
S_newSVdynamicvar(pTHX)43 static SV *S_newSVdynamicvar(pTHX)
44 {
45 SV *ret = newSV(sizeof(DynamicVar));
46
47 #ifdef HAVE_DMD_HELPER
48 if(DMD_IS_ACTIVE()) {
49 SV *tmpRV = newRV_inc(ret);
50 sv_bless(tmpRV, get_hv("Syntax::Keyword::Dynamically::_DynamicVar::", GV_ADD));
51 SvREFCNT_dec(tmpRV);
52 }
53 #endif
54
55 return ret;
56 }
57
58 #ifdef HAVE_DMD_HELPER
dmd_help_dynamicvar(pTHX_ const SV * sv)59 static int dmd_help_dynamicvar(pTHX_ const SV *sv)
60 {
61 int ret = 0;
62
63 DynamicVar *dyn = (void *)SvPVX((SV *)sv);
64
65 if(dyn->keysv) {
66 ret += DMD_ANNOTATE_SV(sv, dyn->var, "the helem HV");
67 ret += DMD_ANNOTATE_SV(sv, dyn->keysv, "the helem key");
68 }
69 else
70 ret += DMD_ANNOTATE_SV(sv, dyn->var, "the variable slot");
71
72 if(dyn->oldval)
73 ret += DMD_ANNOTATE_SV(sv, dyn->oldval, "the old value slot");
74
75 return ret;
76 }
77 #endif
78
79 typedef struct {
80 SV *var; /* is HV * if keysv is set; indicates an HELEM */
81 SV *keysv;
82 SV *curval; /* is NULL for HELEMs if we should delete at resume time */
83 bool is_outer;
84 } SuspendedDynamicVar;
85
86 #define newSVsuspendeddynamicvar() S_newSVsuspendeddynamicvar(aTHX)
S_newSVsuspendeddynamicvar(pTHX)87 static SV *S_newSVsuspendeddynamicvar(pTHX)
88 {
89 SV *ret = newSV(sizeof(SuspendedDynamicVar));
90
91 #ifdef HAVE_DMD_HELPER
92 if(DMD_IS_ACTIVE()) {
93 SV *tmpRV = newRV_inc(ret);
94 sv_bless(tmpRV, get_hv("Syntax::Keyword::Dynamically::_SuspendedDynamicVar::", GV_ADD));
95 SvREFCNT_dec(tmpRV);
96 }
97 #endif
98
99 return ret;
100 }
101
102 #ifdef HAVE_DMD_HELPER
dmd_help_suspendeddynamicvar(pTHX_ const SV * sv)103 static int dmd_help_suspendeddynamicvar(pTHX_ const SV *sv)
104 {
105 int ret = 0;
106
107 SuspendedDynamicVar *suspdyn = (void *)SvPVX((SV *)sv);
108
109 if(suspdyn->keysv) {
110 ret += DMD_ANNOTATE_SV(sv, suspdyn->var, "the helem HV");
111 ret += DMD_ANNOTATE_SV(sv, suspdyn->keysv, "the helem key");
112 }
113 else
114 ret += DMD_ANNOTATE_SV(sv, suspdyn->var, "the variable slot");
115
116 if(suspdyn->curval)
117 ret += DMD_ANNOTATE_SV(sv, suspdyn->curval, "the current value slot");
118
119 return ret;
120 }
121 #endif
122
123 #ifndef av_top_index
124 # define av_top_index(av) AvFILL(av)
125 #endif
126
av_top(AV * av)127 static SV *av_top(AV *av)
128 {
129 return AvARRAY(av)[av_top_index(av)];
130 }
131
av_push_r(AV * av,SV * sv)132 static SV *av_push_r(AV *av, SV *sv)
133 {
134 av_push(av, sv);
135 return sv;
136 }
137
138 #ifndef hv_deletes
139 # define hv_deletes(hv, key, flags) \
140 hv_delete((hv), ("" key ""), (sizeof(key)-1), (flags))
141 #endif
142
143 #define hv_setsv_or_delete(hv, key, val) S_hv_setsv_or_delete(aTHX_ hv, key, val)
S_hv_setsv_or_delete(pTHX_ HV * hv,SV * key,SV * val)144 static void S_hv_setsv_or_delete(pTHX_ HV *hv, SV *key, SV *val)
145 {
146 if(!val) {
147 hv_delete_ent(hv, key, G_DISCARD, 0);
148 }
149 else
150 sv_setsv(HeVAL(hv_fetch_ent(hv, key, 1, 0)), val);
151 }
152
153 #define ENSURE_HV(sv) S_ensure_hv(aTHX_ sv)
S_ensure_hv(pTHX_ SV * sv)154 static HV *S_ensure_hv(pTHX_ SV *sv)
155 {
156 if(SvTYPE(sv) == SVt_PVHV)
157 return (HV *)sv;
158
159 croak("Expected HV, got SvTYPE(sv)=%d", SvTYPE(sv));
160 }
161
162 #define pushdyn(var) S_pushdyn(aTHX_ var)
S_pushdyn(pTHX_ SV * var)163 static void S_pushdyn(pTHX_ SV *var)
164 {
165 DynamicVar *dyn = (void *)SvPVX(
166 av_push_r(dynamicstack, newSVdynamicvar())
167 );
168
169 dyn->var = var;
170 dyn->keysv = NULL;
171 dyn->oldval = newSVsv(var);
172 dyn->saveix = PL_savestack_ix;
173 }
174
175 #define pushdynhelem(hv,keysv,curval) S_pushdynhelem(aTHX_ hv,keysv,curval)
S_pushdynhelem(pTHX_ HV * hv,SV * keysv,SV * curval)176 static void S_pushdynhelem(pTHX_ HV *hv, SV *keysv, SV *curval)
177 {
178 DynamicVar *dyn = (void *)SvPVX(
179 av_push_r(dynamicstack, newSVdynamicvar())
180 );
181
182 dyn->var = (SV *)hv;
183 dyn->keysv = keysv;
184 dyn->oldval = newSVsv(curval);
185 dyn->saveix = PL_savestack_ix;
186 }
187
S_popdyn(pTHX_ void * _data)188 static void S_popdyn(pTHX_ void *_data)
189 {
190 DynamicVar *dyn = (void *)SvPVX(av_top(dynamicstack));
191 if(dyn->var != (SV *)_data)
192 croak("ARGH: dynamicstack top mismatch");
193
194 SV *sv = av_pop(dynamicstack);
195
196 if(dyn->keysv) {
197 HV *hv = ENSURE_HV(dyn->var);
198
199 hv_setsv_or_delete(hv, dyn->keysv, dyn->oldval);
200
201 SvREFCNT_dec(dyn->keysv);
202 }
203 else {
204 sv_setsv_mg(dyn->var, dyn->oldval);
205 }
206
207 SvREFCNT_dec(dyn->var);
208 SvREFCNT_dec(dyn->oldval);
209
210 SvREFCNT_dec(sv);
211 }
212
hook_postsuspend(pTHX_ HV * modhookdata)213 static void hook_postsuspend(pTHX_ HV *modhookdata)
214 {
215 IV i, max = av_top_index(dynamicstack);
216 SV **avp = AvARRAY(dynamicstack);
217 int height = PL_savestack_ix;
218 AV *suspendedvars = NULL;
219
220 for(i = max; i >= 0; i--) {
221 DynamicVar *dyn = (void *)SvPVX(avp[i]);
222
223 if(dyn->saveix < height)
224 break;
225
226 /* An inner dynamic variable - capture and restore */
227
228 if(!suspendedvars) {
229 suspendedvars = newAV();
230 hv_stores(modhookdata, "Syntax::Keyword::Dynamically/suspendedvars", (SV *)suspendedvars);
231 }
232
233 SuspendedDynamicVar *suspdyn = (void *)SvPVX(
234 av_push_r(suspendedvars, newSVsuspendeddynamicvar())
235 );
236
237 suspdyn->var = dyn->var; /* steal */
238 suspdyn->keysv = dyn->keysv; /* steal */
239 suspdyn->is_outer = FALSE;
240
241 if(dyn->keysv) {
242 HV *hv = ENSURE_HV(dyn->var);
243 HE *he = hv_fetch_ent(hv, dyn->keysv, 0, 0);
244 suspdyn->curval = he ? newSVsv(HeVAL(he)) : NULL;
245
246 hv_setsv_or_delete(hv, dyn->keysv, dyn->oldval);
247 }
248 else {
249 suspdyn->curval = newSVsv(dyn->var);
250
251 sv_setsv_mg(dyn->var, dyn->oldval);
252 }
253 SvREFCNT_dec(dyn->oldval);
254 }
255
256 if(i < max)
257 /* truncate */
258 av_fill(dynamicstack, i);
259
260 for( ; i >= 0; i--) {
261 DynamicVar *dyn = (void *)SvPVX(avp[i]);
262 /* An outer dynamic variable - capture but do not restore */
263
264 if(!suspendedvars) {
265 suspendedvars = newAV();
266 hv_stores(modhookdata, "Syntax::Keyword::Dynamically/suspendedvars", (SV *)suspendedvars);
267 }
268
269 SuspendedDynamicVar *suspdyn = (void *)SvPVX(
270 av_push_r(suspendedvars, newSVsuspendeddynamicvar())
271 );
272
273 suspdyn->var = SvREFCNT_inc(dyn->var);
274 suspdyn->is_outer = TRUE;
275
276 if(dyn->keysv) {
277 HV *hv = ENSURE_HV(dyn->var);
278 HE *he = hv_fetch_ent(hv, dyn->keysv, 0, 0);
279 suspdyn->keysv = SvREFCNT_inc(dyn->keysv);
280 suspdyn->curval = he ? newSVsv(HeVAL(he)) : NULL;
281 }
282 else {
283 suspdyn->keysv = NULL;
284 suspdyn->curval = newSVsv(dyn->var);
285 }
286 }
287 }
288
hook_preresume(pTHX_ HV * modhookdata)289 static void hook_preresume(pTHX_ HV *modhookdata)
290 {
291 AV *suspendedvars = (AV *)hv_deletes(modhookdata, "Syntax::Keyword::Dynamically/suspendedvars", 0);
292 if(!suspendedvars)
293 return;
294
295 SV **avp = AvARRAY(suspendedvars);
296 IV i, max = av_top_index(suspendedvars);
297
298 for(i = max; i >= 0; i--) {
299 SuspendedDynamicVar *suspdyn = (void *)SvPVX(avp[i]);
300
301 if(suspdyn->keysv) {
302 HV *hv = ENSURE_HV(suspdyn->var);
303 HE *he = hv_fetch_ent(hv, suspdyn->keysv, 0, 0);
304 pushdynhelem(hv, suspdyn->keysv, he ? HeVAL(he) : NULL);
305
306 hv_setsv_or_delete(hv, suspdyn->keysv, suspdyn->curval);
307 }
308 else {
309 SV *var = suspdyn->var;
310 pushdyn(var);
311
312 sv_setsv_mg(var, suspdyn->curval);
313 }
314 SvREFCNT_dec(suspdyn->curval);
315
316 if(suspdyn->is_outer) {
317 SAVEDESTRUCTOR_X(&S_popdyn, suspdyn->var);
318 }
319 else {
320 /* Don't SAVEDESTRUCTOR_X a second time because F-AA restored it */
321 }
322 }
323 }
324
325 static SuspendHookFunc *nexthook;
326
S_suspendhook(pTHX_ U8 phase,CV * cv,HV * modhookdata)327 static void S_suspendhook(pTHX_ U8 phase, CV *cv, HV *modhookdata)
328 {
329 switch(phase) {
330 case FAA_PHASE_POSTSUSPEND:
331 (*nexthook)(aTHX_ phase, cv, modhookdata);
332
333 hook_postsuspend(aTHX_ modhookdata);
334 break;
335
336 case FAA_PHASE_PRERESUME:
337 hook_preresume(aTHX_ modhookdata);
338
339 (*nexthook)(aTHX_ phase, cv, modhookdata);
340 break;
341
342 default:
343 (*nexthook)(aTHX_ phase, cv, modhookdata);
344 break;
345 }
346 }
347
348 /* STARTDYN is the primary op that makes this work. It is used in two ways:
349 * With OPf_STACKED it takes an optree, which pushes an SV to the stack.
350 * Without OPf_STACKED it uses op->op_targ to select a lexical
351 * Either way, it saves the current value of the SV and arranges for that
352 * value to be assigned back in on scope exit
353 *
354 * This op is _not_ used for dynamic assignments to hash elements; for that
355 * see HELEMDYN
356 */
357
358 static XOP xop_startdyn;
359
pp_startdyn(pTHX)360 static OP *pp_startdyn(pTHX)
361 {
362 dSP;
363 SV *var = (PL_op->op_flags & OPf_STACKED) ? TOPs : PAD_SV(PL_op->op_targ);
364
365 if(is_async) {
366 pushdyn(SvREFCNT_inc(var));
367 SAVEDESTRUCTOR_X(&S_popdyn, var);
368 }
369 else {
370 save_freesv(SvREFCNT_inc(var));
371 /* When save_item() is restored it won't reset the SvPADMY flag properly.
372 * This upsets -DDEBUGGING perls, so we'll have to save the flags too */
373 if(SvFLAGS(var) & SVs_PADMY)
374 save_set_svflags(var, SvFLAGS(var), SvFLAGS(var));
375 save_item(var);
376 }
377
378 return cUNOP->op_next;
379 }
380
381 /* HELEMDYN is a variant of core's HELEM op which arranges for the existing
382 * value (or absence of) the key in the hash to be restored again on scope
383 * exit. It copes with missing keys by deleting them again to "restore".
384 */
385
S_restore(pTHX_ void * _data)386 static void S_restore(pTHX_ void *_data)
387 {
388 DynamicVar *dyn = _data;
389
390 if(dyn->keysv) {
391 hv_setsv_or_delete(ENSURE_HV(dyn->var), dyn->keysv, dyn->oldval);
392
393 SvREFCNT_dec(dyn->var);
394 SvREFCNT_dec(dyn->keysv);
395 SvREFCNT_dec(dyn->oldval);
396 }
397 else
398 croak("ARGH: Expected a keysv");
399
400 Safefree(dyn);
401 }
402
403 static XOP xop_helemdyn;
404
pp_helemdyn(pTHX)405 static OP *pp_helemdyn(pTHX)
406 {
407 /* Contents inspired by core's pp_helem */
408 dSP;
409 SV * keysv = POPs;
410 HV * const hv = MUTABLE_HV(POPs);
411 bool preexisting;
412 HE *he;
413 SV **svp;
414
415 /* Take a long-lived copy of keysv */
416 keysv = newSVsv(keysv);
417
418 preexisting = hv_exists_ent(hv, keysv, 0);
419 he = hv_fetch_ent(hv, keysv, 1, 0);
420 svp = &HeVAL(he);
421
422 if(is_async) {
423 SvREFCNT_inc((SV *)hv);
424
425 if(preexisting)
426 pushdynhelem(hv, keysv, *svp);
427 else
428 pushdynhelem(hv, keysv, NULL);
429 SAVEDESTRUCTOR_X(&S_popdyn, (SV *)hv);
430 }
431 else {
432 DynamicVar *dyn;
433 Newx(dyn, 1, DynamicVar);
434
435 dyn->var = SvREFCNT_inc(hv);
436 dyn->keysv = SvREFCNT_inc(keysv);
437 dyn->oldval = preexisting ? newSVsv(*svp) : NULL;
438 SAVEDESTRUCTOR_X(&S_restore, dyn);
439 }
440
441 PUSHs(*svp);
442
443 RETURN;
444 }
445
build_dynamically(pTHX_ OP ** out,XSParseKeywordPiece * arg0,void * hookdata)446 static int build_dynamically(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
447 {
448 OP *aop = arg0->op;
449 OP *lvalop = NULL, *rvalop = NULL;
450
451 /* While most scalar assignments become OP_SASSIGN, some cases of assignment
452 * from a binary operator into a pad lexical instead set OPpTARGET_MY and use
453 * op->op_targ instead.
454 */
455 if((PL_opargs[aop->op_type] & OA_TARGLEX) && (aop->op_private & OPpTARGET_MY)) {
456 /* dynamically LEXVAR = EXPR */
457
458 /* Since LEXVAR is a pad lexical we can generate a non-stacked STARTDYN
459 * and set the same targ on it, then perform that just before the
460 * otherwise-unmodified op
461 */
462 OP *dynop = newUNOP_CUSTOM(&pp_startdyn, 0, newOP(OP_NULL, 0));
463 dynop->op_targ = aop->op_targ;
464
465 *out = op_prepend_elem(OP_LINESEQ,
466 dynop, aop);
467
468 return KEYWORD_PLUGIN_EXPR;
469 }
470
471 if(aop->op_type != OP_SASSIGN)
472 croak("Expected scalar assignment for 'dynamically'");
473
474 rvalop = cBINOPx(aop)->op_first;
475 lvalop = cBINOPx(aop)->op_last;
476
477 if(lvalop->op_type == OP_HELEM) {
478 /* dynamically $h{key} = EXPR */
479
480 /* In order to handle with the added complexities around delete $h{key}
481 * we need to use our special version of OP_HELEM here instead of simply
482 * calling STARTDYN on the fetched SV
483 */
484
485 /* Change the OP_HELEM into our custom one.
486 * To ensure the peephole optimiser doesn't turn this into multideref we
487 * have to change the op_type too */
488 lvalop->op_type = OP_CUSTOM;
489 lvalop->op_ppaddr = &pp_helemdyn;
490 *out = aop;
491 }
492 else {
493 /* dynamimcally LEXPR = EXPR */
494
495 /* Rather than splicing in STARTDYN op, we'll just make a new optree */
496 *out = newBINOP(aop->op_type, aop->op_flags,
497 rvalop,
498 newUNOP_CUSTOM(&pp_startdyn, aop->op_flags & OPf_STACKED, lvalop));
499
500 /* op_free will destroy the entire optree so replace the child ops first */
501 cBINOPx(aop)->op_first = NULL;
502 cBINOPx(aop)->op_last = NULL;
503 op_free(aop);
504 }
505
506 return KEYWORD_PLUGIN_EXPR;
507 }
508
509 static const struct XSParseKeywordHooks hooks_dynamically = {
510 .permit_hintkey = "Syntax::Keyword::Dynamically/dynamically",
511 .piece1 = XPK_TERMEXPR,
512 .build1 = &build_dynamically,
513 };
514
enable_async_mode(pTHX_ void * _unused)515 static void enable_async_mode(pTHX_ void *_unused)
516 {
517 if(is_async)
518 return;
519
520 is_async = TRUE;
521 dynamicstack = newAV();
522 av_extend(dynamicstack, 50);
523
524 future_asyncawait_wrap_suspendhook(&S_suspendhook, &nexthook);
525 }
526
527 MODULE = Syntax::Keyword::Dynamically PACKAGE = Syntax::Keyword::Dynamically
528
529 void
530 _enable_async_mode()
531 CODE:
532 enable_async_mode(aTHX_ NULL);
533
534 BOOT:
535 XopENTRY_set(&xop_startdyn, xop_name, "startdyn");
536 XopENTRY_set(&xop_startdyn, xop_desc,
537 "starts a dynamic variable scope");
538 XopENTRY_set(&xop_startdyn, xop_class, OA_UNOP);
539 Perl_custom_op_register(aTHX_ &pp_startdyn, &xop_startdyn);
540
541 boot_xs_parse_keyword(0.13);
542
543 register_xs_parse_keyword("dynamically", &hooks_dynamically, NULL);
544 #ifdef HAVE_DMD_HELPER
545 DMD_SET_PACKAGE_HELPER("Syntax::Keyword::Dynamically::_DynamicVar", &dmd_help_dynamicvar);
546 DMD_SET_PACKAGE_HELPER("Syntax::Keyword::Dynamically::_SuspendedDynamicVar", &dmd_help_suspendeddynamicvar);
547 #endif
548
549 future_asyncawait_on_activate(&enable_async_mode, NULL);
550