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