File Coverage

lib/Syntax/Keyword/Dynamically.xs
Criterion Covered Total %
statement 184 189 97.3
branch 53 60 88.3
condition n/a
subroutine n/a
pod n/a
total 237 249 95.1


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