File Coverage

lib/Syntax/Keyword/Dynamically.xs
Criterion Covered Total %
statement 164 169 97.0
branch 55 64 85.9
condition n/a
subroutine n/a
pod n/a
total 219 233 93.9


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