File Coverage

lib/Syntax/Keyword/Dynamically.xs
Criterion Covered Total %
statement 186 217 85.7
branch 55 72 76.3
condition n/a
subroutine n/a
pod n/a
total 241 289 83.3


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