File Coverage

lib/Class/Plain.xs
Criterion Covered Total %
statement 159 204 77.9
branch 72 132 54.5
condition n/a
subroutine n/a
pod n/a
total 231 336 68.7


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2              
3             #include "EXTERN.h"
4             #include "perl.h"
5             #include "XSUB.h"
6              
7             #include "XSParseKeyword.h"
8              
9             #include "XSParseSublike.h"
10              
11             #include "perl-backcompat.c.inc"
12             #include "sv_setrv.c.inc"
13              
14             #include "perl-additions.c.inc"
15             #include "lexer-additions.c.inc"
16             #include "forbid_outofblock_ops.c.inc"
17             #include "force_list_keeping_pushmark.c.inc"
18             #include "optree-additions.c.inc"
19             #include "newOP_CUSTOM.c.inc"
20              
21             #include "class_plain_parser.h"
22             #include "class_plain_class.h"
23             #include "class_plain_field.h"
24             #include "class_plain_method.h"
25              
26             /**********************************
27             * Class and Field Implementation *
28             **********************************/
29              
30             enum {
31             METATYPE_ROLE = 1,
32             };
33              
34             static XOP xop_methstart;
35 47           static OP* pp_methstart(pTHX) {
36 47           SV* self = av_shift(GvAV(PL_defgv));
37              
38 47 100         if(!SvROK(self) || !SvOBJECT(SvRV(self)))
    50          
39 1           croak("Cannot invoke method on a non-instance");
40              
41 46           save_clearsv(&PAD_SVl(1));
42 46           sv_setsv(PAD_SVl(1), self);
43              
44 46           return PL_op->op_next;
45             }
46              
47 43           OP* ClassPlain_newMETHSTARTOP(pTHX_ U32 flags)
48             {
49             OP* op = newOP_CUSTOM(&pp_methstart, flags);
50 43           op->op_private = (U8)(flags >> 8);
51 43           return op;
52             }
53              
54 21           static OP* pp_common_methstart(pTHX) {
55 21           SV* self = av_shift(GvAV(PL_defgv));
56              
57 21 50         if(SvROK(self))
58             /* TODO: Should handle this somehow */
59 0           croak("Cannot invoke common method on an instance");
60              
61 21           save_clearsv(&PAD_SVl(1));
62 21           sv_setsv(PAD_SVl(1), self);
63              
64 21           return PL_op->op_next;
65             }
66              
67 18           OP* ClassPlain_newCOMMONMETHSTARTOP(pTHX_ U32 flags) {
68             OP* op = newOP_CUSTOM(&pp_common_methstart, flags);
69 18           op->op_private = (U8)(flags >> 8);
70 18           return op;
71             }
72              
73             /* The classdata on the currently-compiling class */
74 203           static ClassMeta *S_current_parsed_class(pTHX) {
75 203           SV** svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/current_parsed_class", 0);
76 203 50         if(!svp || !*svp || !SvOK(*svp))
    50          
    50          
    0          
    0          
77             return NULL;
78 203 50         return (ClassMeta *)(intptr_t)SvIV(*svp);
79             }
80              
81 106           static bool S_have_current_parsed_class(pTHX) {
82 106           SV** svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/current_parsed_class", 0);
83 106 100         if(!svp || !*svp)
    50          
84             return false;
85              
86 103 50         if(SvOK(*svp) && SvIV(*svp))
    0          
    0          
    50          
    50          
87             return true;
88              
89             return false;
90             }
91              
92 40           static void S_current_parsed_class_set(pTHX_ ClassMeta *class) {
93 40           SV* sv = *hv_fetchs(GvHV(PL_hintgv), "Class::Plain/current_parsed_class", GV_ADD);
94 40           sv_setiv(sv, (IV)(intptr_t)class);
95 40           }
96              
97             static bool S_is_valid_ident_utf8(pTHX_ const U8* s) {
98             const U8* e = s + strlen((char *)s);
99              
100             if(!isIDFIRST_utf8_safe(s, e))
101             return false;
102              
103             s += UTF8SKIP(s);
104             while(*s) {
105             if(!isIDCONT_utf8_safe(s, e))
106             return false;
107             s += UTF8SKIP(s);
108             }
109              
110             return true;
111             }
112              
113 37           static void inplace_trim_whitespace(SV* sv)
114             {
115 37 100         if(!SvPOK(sv) || !SvCUR(sv))
    100          
116             return;
117              
118 12           char *dst = SvPVX(sv);
119             char *src = dst;
120              
121 12 50         while(*src && isSPACE(*src))
    50          
122 0           src++;
123              
124 12 50         if(src > dst) {
125 0           size_t offset = src - dst;
126 0           Move(src, dst, SvCUR(sv) - offset, char);
127 0           SvCUR(sv) -= offset;
128             }
129              
130 12           src = dst + SvCUR(sv) - 1;
131 12 50         while(src > dst && isSPACE(*src))
    50          
132 0           src--;
133              
134 12           SvCUR(sv) = src - dst + 1;
135 12           dst[SvCUR(sv)] = 0;
136             }
137              
138 18           static void S_apply_method_common(pTHX_ MethodMeta* class, const char *val, void* _data) {
139 18           class->is_common = true;
140 18           }
141              
142             static struct MethodAttributeDefinition method_attributes[] = {
143             { "common", &S_apply_method_common, 0 },
144             { 0 }
145             };
146              
147             /*******************
148             * Custom Keywords *
149             *******************/
150              
151 41           static int build_classlike(pTHX_ OP* *out, XSParseKeywordPiece* args[], size_t nargs, void* hookdata) {
152             int argi = 0;
153            
154              
155 41           SV* package_name = args[argi++]->sv;
156             /* Grrr; XPK bug */
157 41 50         if(!package_name)
158 0           croak("Expected a class name after 'class'");
159              
160 41           IV type = (IV)(intptr_t)hookdata;
161            
162 41           ClassMeta* class = ClassPlain_create_class(aTHX_ type, package_name);
163            
164 41 100         if (type == 1) {
165 1           class->is_role = 1;
166             }
167              
168 41           int nattrs = args[argi++]->i;
169 41 100         if(nattrs) {
170             int i;
171 21 100         for(i = 0; i < nattrs; i++) {
172 11           SV* attrname = args[argi]->attr.name;
173 11           SV* attrval = args[argi]->attr.value;
174              
175 11           inplace_trim_whitespace(attrval);
176              
177 11           ClassPlain_class_apply_attribute(aTHX_ class, SvPVX(attrname), attrval);
178              
179 11           argi++;
180             }
181             }
182              
183 41           ClassPlain_begin_class_block(aTHX_ class);
184              
185             /* At this point XS::Parse::Keyword has parsed all it can. From here we will
186             * take over to perform the odd "block or statement" behaviour of `class`
187             * keywords
188             */
189              
190             bool exists_class_block;
191              
192 41 100         if(lex_consume_unichar('{')) {
193             exists_class_block = true;
194 40           ENTER;
195             }
196 1 50         else if(lex_consume_unichar(';')) {
197             exists_class_block = false;
198             }
199             else
200 0           croak("Expected a block or ';'");
201              
202             /* CARGOCULT from perl/op.c:Perl_package() */
203             {
204 41           SAVEGENERICSV(PL_curstash);
205 41           save_item(PL_curstname);
206              
207 41           PL_curstash = (HV *)SvREFCNT_inc(gv_stashsv(class->name, GV_ADD));
208 41           sv_setsv(PL_curstname, package_name);
209              
210 41           PL_hints |= HINT_BLOCK_SCOPE;
211 41           PL_parser->copline = NOLINE;
212             }
213              
214 41 100         if (exists_class_block) {
215 40           I32 save_ix = block_start(TRUE);
216 40           S_current_parsed_class_set(aTHX_ class);
217              
218 40           OP* body = parse_stmtseq(0);
219 40           body = block_end(save_ix, body);
220              
221 40 50         if(!lex_consume_unichar('}'))
222 0           croak("Expected }");
223            
224             // the end of the class block
225              
226 40           AV* role_names = class->role_names;
227 40 50         for (int32_t i = 0; i < av_count(role_names); i++) {
    50          
228 0           SV* role_name = AvARRAY(role_names)[i];
229 0 0         if (role_name) {
230             // The source code of Role::Tiny->import
231 0           SV* sv_source_code = sv_2mortal(newSVpv("", 0));
232 0           sv_catpv(sv_source_code, "{\n");
233 0           sv_catpv(sv_source_code, " package ");
234 0 0         sv_catpv(sv_source_code, SvPV_nolen(class->name));
235 0           sv_catpv(sv_source_code, ";\n");
236 0           sv_catpv(sv_source_code, " Role::Tiny::With::with(");
237 0 0         sv_catpv(sv_source_code, SvPV_nolen(role_name));
238 0           sv_catpv(sv_source_code, ");\n");
239 0           sv_catpv(sv_source_code, "}\n");
240            
241             // Role::Tiny->import
242 0 0         Perl_eval_pv(aTHX_ SvPV_nolen(sv_source_code), 1);
243             }
244             }
245            
246 40           LEAVE;
247              
248             /* CARGOCULT from perl/perly.y:PACKAGE BAREWORD BAREWORD '{' */
249             /* a block is a loop that happens once */
250 40           *out = op_append_elem(OP_LINESEQ,
251             newWHILEOP(0, 1, NULL, NULL, body, NULL, 0),
252             newSVOP(OP_CONST, 0, &PL_sv_yes));
253 40           return KEYWORD_PLUGIN_STMT;
254             }
255             else {
256 1 50         croak("The %s class must have its block", SvPV_nolen(class->name));
257             }
258             }
259              
260             static const struct XSParseKeywordPieceType pieces_classlike[] = {
261             XPK_PACKAGENAME,
262             /* This should really a repeated (tagged?) choice of a number of things, but
263             * right now there's only one thing permitted here anyway
264             */
265             XPK_ATTRIBUTES,
266             {0}
267             };
268              
269             static const struct XSParseKeywordHooks kwhooks_class = {
270             .permit_hintkey = "Class::Plain/class",
271             .pieces = pieces_classlike,
272             .build = &build_classlike,
273             };
274              
275             static const struct XSParseKeywordHooks kwhooks_role = {
276             .permit_hintkey = "Class::Plain/role",
277             .pieces = pieces_classlike,
278             .build = &build_classlike,
279             };
280              
281 40           static void check_field(pTHX_ void* hookdata) {
282             char *kwname = hookdata;
283            
284 40 100         if(!S_have_current_parsed_class(aTHX))
285 2           croak("Cannot '%s' outside of 'class'", kwname);
286              
287 38 50         if(!sv_eq(PL_curstname, S_current_parsed_class(aTHX)->name))
288 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
289 0           PL_curstname, S_current_parsed_class(aTHX)->name);
290 38           }
291              
292 38           static int build_field(pTHX_ OP* *out, XSParseKeywordPiece* args[], size_t nargs, void* hookdata) {
293             int argi = 0;
294              
295 38           SV* name = args[argi++]->sv;
296              
297 38           FieldMeta *field_class = ClassPlain_class_add_field(aTHX_ S_current_parsed_class(aTHX), name);
298             SvREFCNT_dec(name);
299              
300 38           int nattrs = args[argi++]->i;
301 38 100         if(nattrs) {
302 50 100         while(argi < (nattrs+2)) {
303 26           SV* attrname = args[argi]->attr.name;
304 26           SV* attrval = args[argi]->attr.value;
305              
306 26           inplace_trim_whitespace(attrval);
307              
308 26           ClassPlain_field_apply_attribute(aTHX_ field_class, SvPVX(attrname), attrval);
309              
310 26 50         if(attrval)
311             SvREFCNT_dec(attrval);
312              
313 26           argi++;
314             }
315             }
316              
317 38           return KEYWORD_PLUGIN_STMT;
318             }
319              
320             static const struct XSParseKeywordHooks kwhooks_field = {
321             .flags = XPK_FLAG_STMT,
322              
323             .check = &check_field,
324              
325             .permit_hintkey = "Class::Plain/field",
326             .pieces = (const struct XSParseKeywordPieceType []){
327             XPK_IDENT,
328             XPK_ATTRIBUTES,
329             {0}
330             },
331             .build = &build_field,
332             };
333 66           static bool parse_method_permit(pTHX_ void* hookdata)
334             {
335 66 100         if(!S_have_current_parsed_class(aTHX))
336 1           croak("Cannot 'method' outside of 'class'");
337              
338 65 50         if(!sv_eq(PL_curstname, S_current_parsed_class(aTHX)->name))
339 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
340 0           PL_curstname, S_current_parsed_class(aTHX)->name);
341              
342 65           return true;
343             }
344              
345 65           static void parse_method_pre_subparse(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
346             /* While creating the new scope CV we need to ENTER a block so as not to
347             * break any interpvars
348             */
349 65           ENTER;
350 65           SAVESPTR(PL_comppad);
351 65           SAVESPTR(PL_comppad_name);
352 65           SAVESPTR(PL_curpad);
353              
354 65           intro_my();
355              
356             MethodMeta* current_parsed_method;
357 65           Newxz(current_parsed_method, 1, MethodMeta);
358              
359 130           current_parsed_method->name = SvREFCNT_inc(ctx->name);
360            
361 65           hv_stores(ctx->moddata, "Class::Plain/current_parsed_method", newSVuv(PTR2UV(current_parsed_method)));
362            
363 65           LEAVE;
364 65           }
365              
366 18           static bool parse_method_filter_attr(pTHX_ struct XSParseSublikeContext* ctx, SV* attr, SV* val, void* hookdata) {
367 18 50         MethodMeta* current_parsed_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/current_parsed_method", 0)));
368              
369             struct MethodAttributeDefinition *def;
370 18 50         for(def = method_attributes; def->attrname; def++) {
371 18 50         if(!strEQ(SvPVX(attr), def->attrname))
372 0           continue;
373              
374             /* TODO: We might want to wrap the CV in some sort of MethodMeta struct
375             * but for now we'll just pass the XSParseSublikeContext context */
376 18 50         (*def->apply)(aTHX_ current_parsed_method, SvPOK(val) ? SvPVX(val) : NULL, def->applydata);
377              
378 18           return true;
379             }
380              
381             /* No error, just let it fall back to usual attribute handling */
382             return false;
383             }
384              
385 65           static void parse_method_post_blockstart(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
386              
387 65 50         MethodMeta* current_parsed_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/current_parsed_method", 0)));
388 65 100         if(current_parsed_method->is_common) {
389 18           IV var_index = pad_add_name_pvs("$class", 0, NULL, NULL);
390 18 50         if (!(var_index == 1)) {
391 0           croak("[Unexpected]Invalid index of the $class variable:%d", (int)var_index);
392             }
393             }
394             else {
395 47           IV var_index = pad_add_name_pvs("$self", 0, NULL, NULL);
396 47 50         if(var_index != 1) {
397 0           croak("[Unexpected]Invalid index of the $self variable:%d", (int)var_index);
398             }
399             }
400              
401 65           intro_my();
402 65           }
403              
404 65           static void parse_method_pre_blockend(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
405              
406 65 50         MethodMeta* current_parsed_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/current_parsed_method", 0)));
407              
408             /* If we have no ctx->body that means this was a bodyless method
409             * declaration; a required method
410             */
411 65 100         if (ctx->body) {
412 61 100         if(current_parsed_method->is_common) {
413 18           ctx->body = op_append_list(OP_LINESEQ,
414             ClassPlain_newCOMMONMETHSTARTOP(aTHX_ 0 |
415             (0)),
416             ctx->body);
417             }
418             else {
419             OP* fieldops = NULL, *methstartop;
420 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
421             newSTATEOP(0, NULL, NULL));
422 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
423             (methstartop = ClassPlain_newMETHSTARTOP(aTHX_ 0 |
424             (0) |
425             (0))));
426              
427 43           ctx->body = op_append_list(OP_LINESEQ, fieldops, ctx->body);
428             }
429             }
430 65           }
431              
432 65           static void parse_method_post_newcv(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
433             MethodMeta* current_parsed_method;
434             {
435 65           SV* tmpsv = *hv_fetchs(ctx->moddata, "Class::Plain/current_parsed_method", 0);
436 65 50         current_parsed_method = NUM2PTR(MethodMeta* , SvUV(tmpsv));
437 65           sv_setuv(tmpsv, 0);
438             }
439              
440 65 100         if(ctx->cv) {
441 61           CvMETHOD_on(ctx->cv);
442             }
443            
444 65 100         if(ctx->name && (ctx->actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL)) {
    50          
445 62           MethodMeta* method = ClassPlain_class_add_method(aTHX_ S_current_parsed_class(aTHX), ctx->name);
446 62           method->is_common = current_parsed_method->is_common;
447            
448             // "sub foo;" means requred method in roles.
449 62 50         if (!ctx->body) {
450 62           method->is_required = 1;
451              
452 62 50         if (method->class->is_role) {
453             if (method->is_required) {
454             // The source code of Role::Tiny->import
455 0           SV* sv_source_code = sv_2mortal(newSVpv("", 0));
456 0           sv_catpv(sv_source_code, "{\n");
457 0           sv_catpv(sv_source_code, " package ");
458 0 0         sv_catpv(sv_source_code, SvPV_nolen(method->class->name));
459 0           sv_catpv(sv_source_code, ";\n");
460 0           sv_catpv(sv_source_code, " requires('");
461 0 0         sv_catpv(sv_source_code, SvPV_nolen(method->name));
462 0           sv_catpv(sv_source_code, "');\n");
463 0           sv_catpv(sv_source_code, "}\n");
464            
465             // Role::Tiny->import
466 0 0         Perl_eval_pv(aTHX_ SvPV_nolen(sv_source_code), 1);
467             }
468             }
469             }
470             }
471              
472 65           SvREFCNT_dec(current_parsed_method->name);
473 65           Safefree(current_parsed_method);
474 65           }
475              
476             static struct XSParseSublikeHooks parse_method_hooks = {
477             .flags = XS_PARSE_SUBLIKE_FLAG_FILTERATTRS |
478             XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS |
479             XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL,
480             .permit_hintkey = "Class::Plain/method",
481             .permit = parse_method_permit,
482             .pre_subparse = parse_method_pre_subparse,
483             .filter_attr = parse_method_filter_attr,
484             .post_blockstart = parse_method_post_blockstart,
485             .pre_blockend = parse_method_pre_blockend,
486             .post_newcv = parse_method_post_newcv,
487             };
488              
489             /* internal function shared by various *.c files */
490 0           void ClassPlain_need_PLparser(pTHX)
491             {
492 0 0         if(!PL_parser) {
493             /* We need to generate just enough of a PL_parser to keep newSTATEOP()
494             * happy, otherwise it will SIGSEGV (RT133258)
495             */
496 0           SAVEVPTR(PL_parser);
497 0           Newxz(PL_parser, 1, yy_parser);
498 0           SAVEFREEPV(PL_parser);
499              
500 0           PL_parser->copline = NOLINE;
501             }
502 0           }
503              
504             MODULE = Class::Plain PACKAGE = Class::Plain::MetaFunctions
505              
506             BOOT:
507 7           XopENTRY_set(&xop_methstart, xop_name, "methstart");
508 7           XopENTRY_set(&xop_methstart, xop_desc, "enter method");
509 7           XopENTRY_set(&xop_methstart, xop_class, OA_BASEOP);
510 7           Perl_custom_op_register(aTHX_ &pp_methstart, &xop_methstart);
511              
512 7           boot_xs_parse_keyword(0.22); /* XPK_AUTOSEMI */
513            
514             register_xs_parse_keyword("class", &kwhooks_class, (void*)0);
515            
516             // Note: Using kwhooks_role is maybe correct, but it doesn't work well
517             register_xs_parse_keyword("role", &kwhooks_class, (void*)METATYPE_ROLE);
518              
519             register_xs_parse_keyword("field", &kwhooks_field, "field");
520            
521 7           boot_xs_parse_sublike(0.15); /* dynamic actions */
522              
523             register_xs_parse_sublike("method", &parse_method_hooks, (void*)0);