File Coverage

src/parse_subsignature_ex.c
Criterion Covered Total %
statement 203 249 81.5
branch 109 190 57.3
condition n/a
subroutine n/a
pod n/a
total 312 439 71.0


line stmt bran cond sub pod time code
1             /* vi: set ft=c : */
2              
3             #include "EXTERN.h"
4             #include "perl.h"
5             #include "XSUB.h"
6              
7             #define HAVE_PERL_VERSION(R, V, S) \
8             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
9              
10             #include "XSParseSublike.h"
11              
12             /* Skip this entire file on perls older than OP_ARGCHECK */
13             #if HAVE_PERL_VERSION(5, 26, 0)
14              
15             /* We need to be able to see FEATURE_*_IS_ENABLED */
16             #define PERL_EXT
17             #include "feature.h"
18              
19             #include "make_argcheck_aux.c.inc"
20              
21             #include "LOGOP_ANY.c.inc"
22              
23             #include "parse_subsignature_ex.h"
24              
25             #include "lexer-additions.c.inc"
26              
27             #include "newSV_with_free.c.inc"
28              
29             #define newSVpvx(ptr) S_newSVpvx(aTHX_ ptr)
30             static SV *S_newSVpvx(pTHX_ void *ptr)
31             {
32 9           SV *sv = newSV(0);
33 9           sv_upgrade(sv, SVt_PV);
34 9           SvPVX(sv) = ptr;
35             return sv;
36             }
37              
38             /*
39             * Need to grab some things that aren't quite core perl API
40             */
41              
42             /* yyerror() is a long function and hard to emulate or copy-paste for our
43             * purposes; we'll reïmplement a smaller version of it
44             */
45              
46             #define LEX_IGNORE_UTF8_HINTS 0x00000002
47              
48             #define PL_linestr (PL_parser->linestr)
49              
50             #ifdef USE_UTF8_SCRIPTS
51             # define UTF cBOOL(!IN_BYTES)
52             #else
53             # define UTF cBOOL((PL_linestr && DO_UTF8(PL_linestr)) || ( !(PL_parser->lex_flags & LEX_IGNORE_UTF8_HINTS) && (PL_hints & HINT_UTF8)))
54             #endif
55              
56             #define yyerror(s) S_yyerror(aTHX_ s)
57 0           void S_yyerror(pTHX_ const char *s)
58             {
59 0           SV *message = sv_2mortal(newSVpvs_flags("", 0));
60              
61 0           char *context = PL_parser->oldbufptr;
62 0           STRLEN contlen = PL_parser->bufptr - PL_parser->oldbufptr;
63              
64 0 0         sv_catpvf(message, "%s at %s line %" IVdf,
65 0           s, OutCopFILE(PL_curcop), (IV)CopLINE(PL_curcop));
66              
67 0 0         if(context)
68 0 0         sv_catpvf(message, ", near \"%" UTF8f "\"",
    0          
69 0 0         UTF8fARG(UTF, contlen, context));
    0          
    0          
70              
71 0           sv_catpvf(message, "\n");
72              
73 0           PL_parser->error_count++;
74 0           warn_sv(message);
75 0           }
76              
77             /* Stolen from op.c */
78             #ifndef OpTYPE_set
79             # define OpTYPE_set(op, type) \
80             STMT_START { \
81             op->op_type = (OPCODE)type; \
82             op->op_ppaddr = PL_ppaddr[type]; \
83             } STMT_END
84             #endif
85              
86             #define alloc_LOGOP(a,b,c) S_alloc_LOGOP(aTHX_ a,b,c)
87 1           static LOGOP *S_alloc_LOGOP(pTHX_ I32 type, OP *first, OP* other)
88             {
89             dVAR;
90             LOGOP *logop;
91             OP *kid = first;
92 1           NewOp(1101, logop, 1, LOGOP);
93 1           OpTYPE_set(logop, type);
94 1           logop->op_first = first;
95 1           logop->op_other = other;
96 1 50         if (first)
97 1           logop->op_flags = OPf_KIDS;
98 1 50         while (kid && OpHAS_SIBLING(kid))
    50          
99 1 0         kid = OpSIBLING(kid);
100 1 50         if (kid)
101 1           OpLASTSIB_set(kid, (OP*)logop);
102 1           return logop;
103             }
104              
105             /* copypaste from core's pp.c */
106             static SV *
107 2           S_find_runcv_name(pTHX)
108             {
109             CV *cv;
110             GV *gv;
111             SV *sv;
112              
113 2           cv = find_runcv(0);
114 2 50         if (!cv)
115             return &PL_sv_no;
116              
117             gv = CvGV(cv);
118 2 50         if (!gv)
119             return &PL_sv_no;
120              
121 2           sv = sv_newmortal();
122 2           gv_fullname4(sv, gv, NULL, TRUE);
123 2           return sv;
124             }
125              
126 13           static OP *pp_namedargdefelem(pTHX)
127             {
128 13           dSP;
129 13           ANY *op_any = cLOGOP_ANY->op_any;
130 13           SV *keysv = op_any[0].any_sv;
131 13           HV *slurpy_hv = (HV *)PAD_SVl(op_any[1].any_iv);
132              
133             assert(slurpy_hv && SvTYPE(slurpy_hv) == SVt_PVHV);
134              
135             /* TODO: we could precompute the hash and store it in the ANY vector */
136 13           SV *value = hv_delete_ent(slurpy_hv, keysv, 0, 0);
137              
138 13 100         if(value) {
139 11 50         EXTEND(SP, 1);
140 11           PUSHs(value);
141 11           RETURN;
142             }
143              
144 2 100         if(cLOGOP->op_other)
145             return cLOGOP->op_other;
146              
147 1           croak("Missing argument '%" SVf "' for subroutine %" SVf,
148 1           SVfARG(keysv), SVfARG(S_find_runcv_name(aTHX)));
149             }
150              
151 5           static OP *pp_checknomorenamed(pTHX)
152             {
153 5           HV *slurpy_hv = (HV *)PAD_SVl(PL_op->op_targ);
154              
155 5 100         if(!hv_iterinit(slurpy_hv))
156 4           return NORMAL;
157              
158             /* There are remaining named arguments; concat their names into a message */
159              
160 1           HE *he = hv_iternext(slurpy_hv);
161              
162 1           SV *keynames = newSVpvn("", 0);
163 1           SAVEFREESV(keynames);
164              
165 1 50         sv_catpvf(keynames, "'%" SVf "'", SVfARG(HeSVKEY_force(he)));
    50          
    50          
166              
167             IV nkeys = 1;
168              
169 1 50         while((he = hv_iternext(slurpy_hv)))
170 0 0         sv_catpvf(keynames, ", '%" SVf "'", SVfARG(HeSVKEY_force(he))), nkeys++;
    0          
    0          
171              
172 1 50         croak("Unrecognised %s %" SVf " for subroutine %" SVf,
173             nkeys > 1 ? "arguments" : "argument",
174 1           SVfARG(keynames), SVfARG(S_find_runcv_name(aTHX)));
175             }
176              
177             #define OP_IS_NAMED_PARAM(o) (o->op_type == OP_ARGELEM && cUNOPx(o)->op_first && \
178             cUNOPx(o)->op_first->op_type == OP_CUSTOM && \
179             cUNOPx(o)->op_first->op_ppaddr == &pp_namedargdefelem)
180              
181             /* Parameter attribute extensions */
182             typedef struct SignatureAttributeRegistration SignatureAttributeRegistration;
183              
184             struct SignatureAttributeRegistration {
185             SignatureAttributeRegistration *next;
186              
187             const char *name;
188             STRLEN permit_hintkeylen;
189              
190             const struct XPSSignatureAttributeFuncs *funcs;
191             void *funcdata;
192             };
193              
194             static SignatureAttributeRegistration *sigattrs = NULL;
195              
196             #define find_registered_attribute(name) S_find_registered_attribute(aTHX_ name)
197 4           static SignatureAttributeRegistration *S_find_registered_attribute(pTHX_ const char *name)
198             {
199 4           HV *hints = GvHV(PL_hintgv);
200              
201             SignatureAttributeRegistration *reg;
202 4 50         for(reg = sigattrs; reg; reg = reg->next) {
203 4 50         if(!strEQ(name, reg->name))
204 0           continue;
205              
206 4 50         if(reg->funcs->permit_hintkey &&
    50          
207 4 50         (!hints || !hv_fetch(hints, reg->funcs->permit_hintkey, reg->permit_hintkeylen, 0)))
208 0           continue;
209              
210 4           return reg;
211             }
212              
213 0           croak("Unrecognised signature parameter attribute :%s", name);
214             }
215              
216             struct PendingSignatureFunc {
217             const struct XPSSignatureAttributeFuncs *funcs;
218             void *funcdata;
219             void *attrdata;
220             };
221              
222             #define PENDING_FROM_SV(sv) ((struct PendingSignatureFunc *)SvPVX(sv))
223              
224 0           static void pending_free(pTHX_ SV *sv)
225             {
226 0           struct PendingSignatureFunc *p = PENDING_FROM_SV(sv);
227              
228 0 0         if(p->funcs->free)
229 0           (*p->funcs->free)(aTHX_ p->attrdata, p->funcdata);
230 0           }
231              
232             #define NEW_SV_PENDING() newSV_with_free(sizeof(struct PendingSignatureFunc), &pending_free)
233              
234             struct SignatureParsingContext {
235             AV *named_varops; /* SV ptrs to the varop of every named parameter */
236              
237             OP *last_varop; /* the most recently-constructed varop */
238             };
239              
240 19           static void free_parsing_ctx(pTHX_ void *_ctx)
241             {
242             struct SignatureParsingContext *ctx = _ctx;
243 19 100         if(ctx->named_varops)
244             SvREFCNT_dec((SV *)ctx->named_varops);
245 19           }
246              
247             #define parse_sigelem(ctx, flags) S_parse_sigelem(aTHX_ ctx, flags)
248 27           static OP *S_parse_sigelem(pTHX_ struct SignatureParsingContext *ctx, U32 flags)
249             {
250 27           bool permit_attributes = flags & PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES;
251              
252 27           yy_parser *parser = PL_parser;
253              
254 27           int c = lex_peek_unichar(0);
255             int private;
256 27           struct XPSSignatureParamContext paramctx = {};
257              
258             AV *pending = NULL;
259              
260 27 100         if((flags & PARSE_SUBSIGNATURE_NAMED_PARAMS) && c == ':') {
    100          
261 9           lex_read_unichar(0);
262 9           lex_read_space(0);
263              
264 9           paramctx.is_named = true;
265 9           c = lex_peek_unichar(0);
266             }
267              
268 27           switch(c) {
269             case '$': private = OPpARGELEM_SV; break;
270 2           case '@': private = OPpARGELEM_AV; break;
271 2           case '%': private = OPpARGELEM_HV; break;
272             default:
273 0           croak("Expected a signature element at <%s>\n", parser->bufptr);
274             }
275              
276 27           char *lexname = parser->bufptr;
277              
278             /* Consume sigil */
279 27           lex_read_unichar(0);
280              
281             char *lexname_end;
282              
283 27 50         if(isIDFIRST_uni(lex_peek_unichar(0))) {
    50          
    100          
    100          
284 26           lex_read_unichar(0);
285 42 50         while(isALNUM_uni(lex_peek_unichar(0)))
    50          
    100          
    100          
286 16           lex_read_unichar(0);
287              
288 26           paramctx.varop = newUNOP_AUX(OP_ARGELEM, 0, NULL, INT2PTR(UNOP_AUX_item *, (parser->sig_elems)));
289 26           paramctx.varop->op_private |= private;
290              
291 26 100         if(paramctx.is_named) {
292 9 100         if(!ctx->named_varops)
293 6           ctx->named_varops = newAV();
294              
295 9           av_push(ctx->named_varops, newSVpvx(paramctx.varop));
296             }
297              
298 26           ctx->last_varop = paramctx.varop;
299              
300 26           lexname_end = PL_parser->bufptr;
301 26           paramctx.padix = paramctx.varop->op_targ =
302 26           pad_add_name_pvn(lexname, lexname_end - lexname, 0, NULL, NULL);
303              
304 26           lex_read_space(0);
305             }
306              
307 27 100         if(permit_attributes && lex_peek_unichar(0) == ':') {
    100          
308 4           lex_read_unichar(0);
309 4           lex_read_space(0);
310              
311 4           SV *attrname = sv_newmortal(), *attrval = sv_newmortal();
312              
313 8 100         while(lex_scan_attrval_into(attrname, attrval)) {
314 4           lex_read_space(0);
315              
316 4 50         SignatureAttributeRegistration *reg = find_registered_attribute(SvPV_nolen(attrname));
317              
318 4           void *attrdata = NULL;
319 4 50         if(reg->funcs->apply)
320 4           (*reg->funcs->apply)(aTHX_ ¶mctx, attrval, &attrdata, reg->funcdata);
321              
322 4 50         if(attrdata || reg->funcs->post_defop) {
    50          
323 0 0         if(!pending) {
324 0           pending = newAV();
325 0           SAVEFREESV(pending);
326             }
327              
328             SV *psv;
329 0           av_push(pending, psv = NEW_SV_PENDING());
330              
331 0           PENDING_FROM_SV(psv)->funcs = reg->funcs;
332 0           PENDING_FROM_SV(psv)->funcdata = reg->funcdata;
333 0           PENDING_FROM_SV(psv)->attrdata = attrdata;
334             }
335              
336 4 50         if(lex_peek_unichar(0) == ':') {
337 0           lex_read_unichar(0);
338 4           lex_read_space(0);
339             }
340             }
341             }
342              
343 27 100         if(c == '$') {
344             SV *argname;
345              
346 23 100         if(paramctx.is_named) {
347 9           parser->sig_slurpy = '+';
348 9           argname = newSVpvn(lexname + 1, lexname_end - lexname - 1);
349             }
350             else {
351 14 50         if(parser->sig_slurpy)
352 0           yyerror("Slurpy parameters not last");
353              
354 14           parser->sig_elems++;
355             }
356              
357 23 100         if(lex_peek_unichar(0) == '=') {
358 2           lex_read_unichar(0);
359 2           lex_read_space(0);
360              
361 2 100         if(!paramctx.is_named)
362 1           parser->sig_optelems++;
363              
364 2           OP *defexpr = parse_termexpr(0);
365              
366 2 100         if(paramctx.is_named) {
367 1 50         paramctx.defop = (OP *)alloc_LOGOP_ANY(OP_CUSTOM, defexpr, LINKLIST(defexpr));
368 1           paramctx.defop->op_ppaddr = &pp_namedargdefelem;
369             }
370             else {
371 1 50         paramctx.defop = (OP *)alloc_LOGOP(OP_ARGDEFELEM, defexpr, LINKLIST(defexpr));
372 1           paramctx.defop->op_targ = (PADOFFSET)(parser->sig_elems - 1);
373             }
374              
375 2           paramctx.varop->op_flags |= OPf_STACKED;
376 2           op_sibling_splice(paramctx.varop, NULL, 0, paramctx.defop);
377 2           paramctx.defop = op_contextualize(paramctx.defop, G_SCALAR);
378              
379 2 50         LINKLIST(paramctx.varop);
380              
381 2           paramctx.varop->op_next = paramctx.defop;
382 2           defexpr->op_next = paramctx.varop;
383             }
384             else {
385 21 50         if(parser->sig_optelems)
386 0           yyerror("Mandatory parameter follows optional parameter");
387             }
388              
389 23 100         if(paramctx.is_named) {
390 9           OP *defop = paramctx.defop;
391 9 100         if(!defop) {
392 8           defop = (OP *)alloc_LOGOP_ANY(OP_CUSTOM, NULL, NULL);
393 8           defop->op_ppaddr = &pp_namedargdefelem;
394              
395 8           paramctx.varop->op_flags |= OPf_STACKED;
396 8           op_sibling_splice(paramctx.varop, NULL, 0, defop);
397              
398 8 50         LINKLIST(paramctx.varop);
399              
400 8           paramctx.varop->op_next = defop;
401             }
402              
403             ANY *op_any;
404 9           Newx(op_any, 2, ANY);
405              
406 9           op_any[0].any_sv = argname;
407             /* [1] is filled in later */
408              
409 9           cLOGOP_ANYx(defop)->op_any = op_any;
410             }
411             }
412             else {
413 4 50         if(paramctx.is_named)
414 0           yyerror("Slurpy parameters may not be named");
415 4 50         if(parser->sig_slurpy && parser->sig_slurpy != '+')
416 0           yyerror("Multiple slurpy parameters not allowed");
417              
418 4           parser->sig_slurpy = c;
419              
420 4 50         if(lex_peek_unichar(0) == '=')
421 0           yyerror("A slurpy parameter may not have a default value");
422             }
423              
424 27           paramctx.op = paramctx.varop;
425              
426 27 50         if(pending) {
427 0 0         for(int i = 0; i <= AvFILL(pending); i++) {
    0          
428 0           struct PendingSignatureFunc *p = PENDING_FROM_SV(AvARRAY(pending)[i]);
429              
430 0 0         if(p->funcs->post_defop)
431 0           (*p->funcs->post_defop)(aTHX_ ¶mctx, p->attrdata, p->funcdata);
432             }
433             }
434              
435 27 100         return paramctx.op ? newSTATEOP(0, NULL, paramctx.op) : NULL;
436             }
437              
438 19           OP *XPS_parse_subsignature_ex(pTHX_ int flags)
439             {
440             /* Mostly reconstructed logic from perl 5.28.0's toke.c and perly.y
441             */
442 19           yy_parser *parser = PL_parser;
443 19           struct SignatureParsingContext ctx = {};
444              
445             bool permit_named_params = flags & PARSE_SUBSIGNATURE_NAMED_PARAMS;
446              
447             assert((flags & ~(PARSE_SUBSIGNATURE_NAMED_PARAMS|PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES)) == 0);
448              
449 19           ENTER;
450 19           SAVEDESTRUCTOR_X(&free_parsing_ctx, &ctx);
451              
452 19           SAVEIV(parser->sig_elems);
453 19           SAVEIV(parser->sig_optelems);
454 19           SAVEI8(parser->sig_slurpy);
455              
456 19           parser->sig_elems = 0;
457 19           parser->sig_optelems = 0;
458 19           parser->sig_slurpy = 0;
459              
460             OP *elems = NULL;
461             OP *namedelems = NULL;
462             OP *final_elem = NULL;
463              
464 30 100         while(lex_peek_unichar(0) != ')') {
465 27           lex_read_space(0);
466 27           OP *elem = parse_sigelem(&ctx, flags);
467              
468             /* placeholder anonymous elems are NULL */
469 27 100         if(elem) {
470             /* elem should be an OP_LINESEQ[ OP_NEXTSTATE. actual elem ] */
471             assert(elem->op_type == OP_LINESEQ);
472             assert(cLISTOPx(elem)->op_first);
473             assert(OpSIBLING(cLISTOPx(elem)->op_first));
474              
475 26 50         final_elem = OpSIBLING(cLISTOPx(elem)->op_first);
476              
477 26 50         if(OP_IS_NAMED_PARAM(ctx.last_varop))
    100          
    100          
    50          
478 9           namedelems = op_append_list(OP_LIST, namedelems, elem);
479             else
480 17           elems = op_append_list(OP_LINESEQ, elems, elem);
481             }
482              
483 27 50         if(PL_parser->error_count) {
484 0           LEAVE;
485 0           return NULL;
486             }
487              
488 27           lex_read_space(0);
489 27           switch(lex_peek_unichar(0)) {
490             case ')': goto endofelems;
491             case ',': break;
492             default:
493 0           fprintf(stderr, "ARGH unsure how to proceed parse_subsignature at <%s>\n",
494             parser->bufptr);
495 0           croak("ARGH");
496             break;
497             }
498              
499 11           lex_read_unichar(0);
500 11           lex_read_space(0);
501             }
502             endofelems:
503              
504 19 50         if (!FEATURE_SIGNATURES_IS_ENABLED)
    50          
    0          
    50          
    50          
    50          
505 0           croak("Experimental subroutine signatures not enabled");
506              
507             #if !HAVE_PERL_VERSION(5, 37, 0)
508 19           Perl_ck_warner_d(aTHX_ packWARN(WARN_EXPERIMENTAL__SIGNATURES),
509             "The signatures feature is experimental");
510             #endif
511              
512             bool allow_extras_after_named = true;
513 19 100         if(ctx.named_varops) {
514 6           switch(PL_parser->sig_slurpy) {
515             case 0:
516             case '@':
517 0           NOT_REACHED;
518             case '+':
519             {
520             /* Pretend we have a new, unnamed slurpy hash */
521 4           OP *varop = newUNOP_AUX(OP_ARGELEM, 0, NULL, NULL);
522 4           varop->op_private |= OPpARGELEM_HV;
523 4           varop->op_targ = pad_add_name_pvs("%(params)", 0, NULL, NULL);
524              
525             final_elem = varop;
526              
527 4           OP *elem = newSTATEOP(0, NULL, varop);
528 4           elems = op_append_list(OP_LINESEQ, elems, elem);
529              
530 4           PL_parser->sig_slurpy = '%';
531             allow_extras_after_named = false;
532             }
533 4           break;
534             case '%':
535             break;
536             }
537             }
538              
539 19           UNOP_AUX_item *aux = make_argcheck_aux(
540             parser->sig_elems, parser->sig_optelems, parser->sig_slurpy);
541              
542 19           OP *checkop = newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux);
543              
544 19           OP *ops = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
545             op_prepend_elem(OP_LINESEQ, checkop, elems));
546              
547 19 100         if(ctx.named_varops) {
548             assert(final_elem->op_type == OP_ARGELEM);
549             assert(final_elem->op_private == OPpARGELEM_HV);
550              
551 6           PADOFFSET slurpy_padix = final_elem->op_targ;
552              
553             /* Tell all the pp_namedargdefelem()s where to find the slurpy hash */
554 15 50         for(int i = 0; i <= AvFILL(ctx.named_varops); i++) {
    100          
555 9           OP *elemop = (OP *)(SvPVX(AvARRAY(ctx.named_varops)[i]));
556             assert(elemop);
557             assert(OP_IS_NAMED_PARAM(elemop));
558              
559 9           OP *defelemop = cUNOPx(elemop)->op_first;
560             assert(defelemop);
561             assert(defelemop->op_type == OP_CUSTOM &&
562             defelemop->op_ppaddr == &pp_namedargdefelem);
563 9           ANY *op_any = cLOGOP_ANYx(defelemop)->op_any;
564 9           op_any[1].any_iv = slurpy_padix;
565             }
566              
567 6           ops = op_append_list(OP_LINESEQ, ops,
568             namedelems);
569              
570 6 100         if(!allow_extras_after_named) {
571 4           ops = op_append_list(OP_LINESEQ, ops,
572             newSTATEOP(0, NULL, checkop = newOP(OP_CUSTOM, 0)));
573 4           checkop->op_ppaddr = &pp_checknomorenamed;
574 4           checkop->op_targ = slurpy_padix;
575             }
576             }
577              
578             /* a nextstate at the end handles context correctly for an empty
579             * sub body */
580 19           ops = op_append_elem(OP_LINESEQ, ops, newSTATEOP(0, NULL, NULL));
581              
582 19           LEAVE;
583              
584 19           return ops;
585             }
586              
587 6           void XPS_register_subsignature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
588             {
589             SignatureAttributeRegistration *reg;
590 6           Newx(reg, 1, struct SignatureAttributeRegistration);
591              
592 6           *reg = (struct SignatureAttributeRegistration){
593             .name = name,
594             .funcs = funcs,
595             .funcdata = funcdata,
596             };
597              
598 6 50         if(funcs->permit_hintkey)
599 6           reg->permit_hintkeylen = strlen(funcs->permit_hintkey);
600              
601 6           reg->next = sigattrs;
602 6           sigattrs = reg;
603 6           }
604              
605             #else /* !HAVE_PERL_VERSION(5, 26, 0) */
606              
607             void XPS_register_subsignature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
608             {
609             croak("Custom subroutine signature attributes are not supported on this verison of Perl");
610             }
611              
612             #endif