File Coverage

src/parse_subsignature_ex.c
Criterion Covered Total %
statement 205 251 81.6
branch 109 190 57.3
condition n/a
subroutine n/a
pod n/a
total 314 441 71.2


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