File Coverage

lib/Class/Plain.xs
Criterion Covered Total %
statement 157 207 75.8
branch 68 130 52.3
condition n/a
subroutine n/a
pod n/a
total 225 337 66.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_comp_class(pTHX) {
75 203           SV** svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/comp_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_comp_class(pTHX) {
82 106           SV** svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/comp_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_comp_class_set(pTHX_ ClassMeta *class) {
93 40           SV* sv = *hv_fetchs(GvHV(PL_hintgv), "Class::Plain/comp_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 40           static int build_classlike(pTHX_ OP* *out, XSParseKeywordPiece* args[], size_t nargs, void* hookdata) {
152             int argi = 0;
153            
154              
155 40           SV* packagename = args[argi++]->sv;
156             /* Grrr; XPK bug */
157 40 50         if(!packagename)
158 0           croak("Expected a class name after 'class'");
159              
160 40           IV type = (IV)(intptr_t)hookdata;
161            
162 40           ClassMeta* class = ClassPlain_create_class(aTHX_ type, packagename);
163            
164 40 100         if (type == 1) {
165 1           class->is_role = 1;
166             }
167              
168 40           int nattrs = args[argi++]->i;
169 40 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 40           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 40 50         if(lex_consume_unichar('{')) {
193             exists_class_block = true;
194 40           ENTER;
195             }
196 0 0         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 40           SAVEGENERICSV(PL_curstash);
205 40           save_item(PL_curstname);
206              
207 40           PL_curstash = (HV *)SvREFCNT_inc(gv_stashsv(class->name, GV_ADD));
208 40           sv_setsv(PL_curstname, packagename);
209              
210 40           PL_hints |= HINT_BLOCK_SCOPE;
211 40           PL_parser->copline = NOLINE;
212             }
213              
214 40 50         if (exists_class_block) {
215 40           I32 save_ix = block_start(TRUE);
216 40           S_comp_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            
247 40           LEAVE;
248              
249             /* CARGOCULT from perl/perly.y:PACKAGE BAREWORD BAREWORD '{' */
250             /* a block is a loop that happens once */
251 40           *out = op_append_elem(OP_LINESEQ,
252             newWHILEOP(0, 1, NULL, NULL, body, NULL, 0),
253             newSVOP(OP_CONST, 0, &PL_sv_yes));
254 40           return KEYWORD_PLUGIN_STMT;
255             }
256             else {
257 0           SAVEHINTS();
258 0           S_comp_class_set(aTHX_ class);
259              
260 0           *out = newSVOP(OP_CONST, 0, &PL_sv_yes);
261 0           return KEYWORD_PLUGIN_STMT;
262             }
263             }
264              
265             static const struct XSParseKeywordPieceType pieces_classlike[] = {
266             XPK_PACKAGENAME,
267             /* This should really a repeated (tagged?) choice of a number of things, but
268             * right now there's only one thing permitted here anyway
269             */
270             XPK_ATTRIBUTES,
271             {0}
272             };
273              
274             static const struct XSParseKeywordHooks kwhooks_class = {
275             .permit_hintkey = "Class::Plain/class",
276             .pieces = pieces_classlike,
277             .build = &build_classlike,
278             };
279              
280 40           static void check_field(pTHX_ void* hookdata) {
281             char *kwname = hookdata;
282            
283 40 100         if(!S_have_comp_class(aTHX))
284 2           croak("Cannot '%s' outside of 'class'", kwname);
285              
286 38 50         if(!sv_eq(PL_curstname, S_comp_class(aTHX)->name))
287 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
288 0           PL_curstname, S_comp_class(aTHX)->name);
289 38           }
290              
291 38           static int build_field(pTHX_ OP* *out, XSParseKeywordPiece* args[], size_t nargs, void* hookdata) {
292             int argi = 0;
293              
294 38           SV* name = args[argi++]->sv;
295              
296 38           FieldMeta *field_class = ClassPlain_class_add_field(aTHX_ S_comp_class(aTHX), name);
297             SvREFCNT_dec(name);
298              
299 38           int nattrs = args[argi++]->i;
300 38 100         if(nattrs) {
301 50 100         while(argi < (nattrs+2)) {
302 26           SV* attrname = args[argi]->attr.name;
303 26           SV* attrval = args[argi]->attr.value;
304              
305 26           inplace_trim_whitespace(attrval);
306              
307 26           ClassPlain_field_apply_attribute(aTHX_ field_class, SvPVX(attrname), attrval);
308              
309 26 50         if(attrval)
310             SvREFCNT_dec(attrval);
311              
312 26           argi++;
313             }
314             }
315              
316 38           return KEYWORD_PLUGIN_STMT;
317             }
318              
319             static const struct XSParseKeywordHooks kwhooks_field = {
320             .flags = XPK_FLAG_STMT,
321              
322             .check = &check_field,
323              
324             .permit_hintkey = "Class::Plain/field",
325             .pieces = (const struct XSParseKeywordPieceType []){
326             XPK_IDENT,
327             XPK_ATTRIBUTES,
328             {0}
329             },
330             .build = &build_field,
331             };
332 66           static bool parse_method_permit(pTHX_ void* hookdata)
333             {
334 66 100         if(!S_have_comp_class(aTHX))
335 1           croak("Cannot 'method' outside of 'class'");
336              
337 65 50         if(!sv_eq(PL_curstname, S_comp_class(aTHX)->name))
338 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
339 0           PL_curstname, S_comp_class(aTHX)->name);
340              
341 65           return true;
342             }
343              
344 65           static void parse_method_pre_subparse(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
345             /* While creating the new scope CV we need to ENTER a block so as not to
346             * break any interpvars
347             */
348 65           ENTER;
349 65           SAVESPTR(PL_comppad);
350 65           SAVESPTR(PL_comppad_name);
351 65           SAVESPTR(PL_curpad);
352              
353 65           intro_my();
354              
355             MethodMeta* comp_method;
356 65           Newxz(comp_method, 1, MethodMeta);
357              
358 130           comp_method->name = SvREFCNT_inc(ctx->name);
359            
360 65           hv_stores(ctx->moddata, "Class::Plain/comp_method", newSVuv(PTR2UV(comp_method)));
361            
362 65           LEAVE;
363 65           }
364              
365 18           static bool parse_method_filter_attr(pTHX_ struct XSParseSublikeContext* ctx, SV* attr, SV* val, void* hookdata) {
366 18 50         MethodMeta* comp_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/comp_method", 0)));
367              
368             struct MethodAttributeDefinition *def;
369 18 50         for(def = method_attributes; def->attrname; def++) {
370 18 50         if(!strEQ(SvPVX(attr), def->attrname))
371 0           continue;
372              
373             /* TODO: We might want to wrap the CV in some sort of MethodMeta struct
374             * but for now we'll just pass the XSParseSublikeContext context */
375 18 50         (*def->apply)(aTHX_ comp_method, SvPOK(val) ? SvPVX(val) : NULL, def->applydata);
376              
377 18           return true;
378             }
379              
380             /* No error, just let it fall back to usual attribute handling */
381             return false;
382             }
383              
384 65           static void parse_method_post_blockstart(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
385              
386 65 50         MethodMeta* comp_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/comp_method", 0)));
387 65 100         if(comp_method->is_common) {
388 18           IV var_index = pad_add_name_pvs("$class", 0, NULL, NULL);
389 18 50         if (!(var_index == 1)) {
390 0           croak("[Unexpected]Invalid index of the $class variable:%d", (int)var_index);
391             }
392             }
393             else {
394 47           IV var_index = pad_add_name_pvs("$self", 0, NULL, NULL);
395 47 50         if(var_index != 1) {
396 0           croak("[Unexpected]Invalid index of the $self variable:%d", (int)var_index);
397             }
398             }
399              
400 65           intro_my();
401 65           }
402              
403 65           static void parse_method_pre_blockend(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
404              
405 65 50         MethodMeta* comp_method = NUM2PTR(MethodMeta* , SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/comp_method", 0)));
406              
407             /* If we have no ctx->body that means this was a bodyless method
408             * declaration; a required method
409             */
410 65 100         if (ctx->body) {
411 61 100         if(comp_method->is_common) {
412 18           ctx->body = op_append_list(OP_LINESEQ,
413             ClassPlain_newCOMMONMETHSTARTOP(aTHX_ 0 |
414             (0)),
415             ctx->body);
416             }
417             else {
418             OP* fieldops = NULL, *methstartop;
419 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
420             newSTATEOP(0, NULL, NULL));
421 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
422             (methstartop = ClassPlain_newMETHSTARTOP(aTHX_ 0 |
423             (0) |
424             (0))));
425              
426 43           ctx->body = op_append_list(OP_LINESEQ, fieldops, ctx->body);
427             }
428             }
429 65           }
430              
431 65           static void parse_method_post_newcv(pTHX_ struct XSParseSublikeContext* ctx, void* hookdata) {
432             MethodMeta* comp_method;
433             {
434 65           SV* tmpsv = *hv_fetchs(ctx->moddata, "Class::Plain/comp_method", 0);
435 65 50         comp_method = NUM2PTR(MethodMeta* , SvUV(tmpsv));
436 65           sv_setuv(tmpsv, 0);
437             }
438              
439 65 100         if(ctx->cv) {
440 61           CvMETHOD_on(ctx->cv);
441             }
442            
443 65 100         if(ctx->name && (ctx->actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL)) {
    50          
444 62           MethodMeta* method = ClassPlain_class_add_method(aTHX_ S_comp_class(aTHX), ctx->name);
445 62           method->is_common = comp_method->is_common;
446            
447             // "sub foo;" means requred method in roles.
448 62 50         if (!ctx->body) {
449 62           method->is_required = 1;
450              
451 62 50         if (method->class->is_role) {
452             if (method->is_required) {
453             // The source code of Role::Tiny->import
454 0           SV* sv_source_code = sv_2mortal(newSVpv("", 0));
455 0           sv_catpv(sv_source_code, "{\n");
456 0           sv_catpv(sv_source_code, " package ");
457 0 0         sv_catpv(sv_source_code, SvPV_nolen(method->class->name));
458 0           sv_catpv(sv_source_code, ";\n");
459 0           sv_catpv(sv_source_code, " requires('");
460 0 0         sv_catpv(sv_source_code, SvPV_nolen(method->name));
461 0           sv_catpv(sv_source_code, "');\n");
462 0           sv_catpv(sv_source_code, "}\n");
463            
464             // Role::Tiny->import
465 0 0         Perl_eval_pv(aTHX_ SvPV_nolen(sv_source_code), 1);
466             }
467             }
468             }
469             }
470              
471 65           SvREFCNT_dec(comp_method->name);
472 65           Safefree(comp_method);
473 65           }
474              
475             static struct XSParseSublikeHooks parse_method_hooks = {
476             .flags = XS_PARSE_SUBLIKE_FLAG_FILTERATTRS |
477             XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS |
478             XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL,
479             .permit_hintkey = "Class::Plain/method",
480             .permit = parse_method_permit,
481             .pre_subparse = parse_method_pre_subparse,
482             .filter_attr = parse_method_filter_attr,
483             .post_blockstart = parse_method_post_blockstart,
484             .pre_blockend = parse_method_pre_blockend,
485             .post_newcv = parse_method_post_newcv,
486             };
487              
488             /* internal function shared by various *.c files */
489 0           void ClassPlain_need_PLparser(pTHX)
490             {
491 0 0         if(!PL_parser) {
492             /* We need to generate just enough of a PL_parser to keep newSTATEOP()
493             * happy, otherwise it will SIGSEGV (RT133258)
494             */
495 0           SAVEVPTR(PL_parser);
496 0           Newxz(PL_parser, 1, yy_parser);
497 0           SAVEFREEPV(PL_parser);
498              
499 0           PL_parser->copline = NOLINE;
500             }
501 0           }
502              
503             MODULE = Class::Plain PACKAGE = Class::Plain::MetaFunctions
504              
505             BOOT:
506 7           XopENTRY_set(&xop_methstart, xop_name, "methstart");
507 7           XopENTRY_set(&xop_methstart, xop_desc, "enter method");
508 7           XopENTRY_set(&xop_methstart, xop_class, OA_BASEOP);
509 7           Perl_custom_op_register(aTHX_ &pp_methstart, &xop_methstart);
510              
511 7           boot_xs_parse_keyword(0.22); /* XPK_AUTOSEMI */
512            
513             register_xs_parse_keyword("class", &kwhooks_class, (void*)0);
514             register_xs_parse_keyword("role", &kwhooks_class, (void*)METATYPE_ROLE);
515              
516             register_xs_parse_keyword("field", &kwhooks_field, "field");
517            
518 7           boot_xs_parse_sublike(0.15); /* dynamic actions */
519              
520             register_xs_parse_sublike("method", &parse_method_hooks, (void*)0);