File Coverage

lib/Object/Pad/FieldAttr/Checked.xs
Criterion Covered Total %
statement 65 72 90.2
branch 12 28 42.8
condition n/a
subroutine n/a
pod n/a
total 77 100 77.0


line stmt bran cond sub pod time code
1             /* You may distribute under the terms of either the GNU General Public License
2             * or the Artistic License (the same terms as Perl itself)
3             *
4             * (C) Paul Evans, 2023-2024 -- leonerd@leonerd.org.uk
5             */
6             #define PERL_NO_GET_CONTEXT
7              
8             #include "EXTERN.h"
9             #include "perl.h"
10             #include "XSUB.h"
11              
12             #include "object_pad.h"
13              
14             #define HAVE_PERL_VERSION(R, V, S) \
15             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
16              
17             #include "compilerun_sv.c.inc"
18             #include "optree-additions.c.inc"
19              
20             #include "DataChecks.h"
21              
22 3           static int checkmagic_get(pTHX_ SV *sv, MAGIC *mg)
23             {
24 3           SV *fieldsv = mg->mg_obj;
25 3           sv_setsv_nomg(sv, fieldsv);
26 3           return 1;
27             }
28              
29 2           static int checkmagic_set(pTHX_ SV *sv, MAGIC *mg)
30             {
31 2           struct DataChecks_Checker *checker = (struct DataChecks_Checker *)mg->mg_ptr;
32 2           assert_value(checker, sv);
33              
34 1           SV *fieldsv = mg->mg_obj;
35 1           sv_setsv_nomg(fieldsv, sv);
36 1           return 1;
37             }
38              
39             static const MGVTBL vtbl_checkmagic = {
40             .svt_get = &checkmagic_get,
41             .svt_set = &checkmagic_set,
42             };
43              
44 5           static OP *pp_wrap_checkmagic(pTHX)
45             {
46 5           dSP;
47 5           SV *sv = TOPs;
48 5           SV *ret = sv_newmortal();
49              
50 5           struct DataChecks_Checker *checker = (struct DataChecks_Checker *)cUNOP_AUX->op_aux;
51              
52 5           sv_magicext(ret, sv, PERL_MAGIC_ext, &vtbl_checkmagic, (char *)checker, 0);
53              
54 5           SETs(ret);
55 5           RETURN;
56             }
57              
58 15           static SV *checked_parse(pTHX_ FieldMeta *fieldmeta, SV *valuesrc, void *_funcdata)
59             {
60 15 50         if(mop_field_get_sigil(fieldmeta) != '$')
61 0           croak("Can only apply the :Checked attribute to scalar fields");
62              
63             dSP;
64              
65 15           ENTER;
66 15           SAVETMPS;
67              
68             /* eval_sv() et.al. will forgets what package we're actually running in
69             * because during compiletime, CopSTASH(PL_curcop == &PL_compiling) isn't
70             * accurate. We need to help it along
71             */
72              
73 15           SAVECOPSTASH_FREE(PL_curcop);
74 15           CopSTASH_set(PL_curcop, PL_curstash);
75              
76 15           compilerun_sv(valuesrc, G_SCALAR);
77              
78 13           SPAGAIN;
79              
80 13 50         SV *ret = SvREFCNT_inc(POPs);
81              
82 13 50         FREETMPS;
83 13           LEAVE;
84              
85 13           return ret;
86             }
87              
88 13           static bool checked_apply(pTHX_ FieldMeta *fieldmeta, SV *value, SV **attrdata_ptr, void *_funcdata)
89             {
90 13 50         if(mop_field_get_sigil(fieldmeta) != '$')
91 0           croak("Can only apply the :Checked attribute to scalar fields");
92              
93 13           struct DataChecks_Checker *checker = make_checkdata(value);
94 11           SvREFCNT_dec(value);
95              
96 11           gen_assertmess(checker,
97             sv_2mortal(newSVpvf("Field %" SVf, SVfARG(mop_field_get_name(fieldmeta)))),
98             NULL);
99              
100 11           *attrdata_ptr = (SV *)checker;
101              
102 11           return TRUE;
103             }
104              
105 18           static void checked_gen_accessor_ops(pTHX_ FieldMeta *fieldmeta, SV *attrdata, void *_funcdata,
106             enum AccessorType type, struct AccessorGenerationCtx *ctx)
107             {
108             struct DataChecks_Checker *checker = (struct DataChecks_Checker *)attrdata;
109              
110 18           switch(type) {
111             case ACCESSOR_READER:
112             return;
113              
114 7           case ACCESSOR_WRITER:
115 7           ctx->bodyop = op_append_elem(OP_LINESEQ,
116             make_assertop(checker, newSLUGOP(0)),
117             ctx->bodyop);
118 7           return;
119              
120 1           case ACCESSOR_LVALUE_MUTATOR:
121             {
122 1           OP *o = ctx->retop;
123 1 50         if(o->op_type != OP_RETURN)
124 0           croak("Expected ctx->retop to be OP_RETURN");
125 1 50         OP *kid = o->op_flags & OPf_KIDS ? cLISTOPo->op_first : NULL, *prevkid = NULL;
126 1 50         if(kid && kid->op_type == OP_PUSHMARK)
    50          
127 1 50         prevkid = kid, kid = OpSIBLING(kid);
128             // TODO: maybe kid is always OP_PADSV, or maybe not.. Should we assert on it?
129 1           OP *newkid = newUNOP_AUX(OP_CUSTOM, 0, kid, (UNOP_AUX_item *)attrdata);
130 1           newkid->op_ppaddr = &pp_wrap_checkmagic;
131 1 50         if(prevkid)
132 1           OpMORESIB_set(prevkid, newkid);
133             else
134 0           croak("TODO: Need to set newkid as kid of listop?!");
135              
136 1 50         if(OpSIBLING(kid))
    0          
137 0 0         OpMORESIB_set(newkid, OpSIBLING(kid));
138             else
139 1           OpLASTSIB_set(newkid, o);
140              
141 1 50         if(cLISTOPo->op_last == kid)
142 1           cLISTOPo->op_last = newkid;
143              
144 1           OpLASTSIB_set(kid, newkid);
145 1           return;
146             }
147              
148 1           case ACCESSOR_COMBINED:
149 1           ctx->bodyop = op_append_elem(OP_LINESEQ,
150             newLOGOP(OP_AND, 0,
151             /* scalar @_ */
152             op_contextualize(newUNOP(OP_RV2AV, 0, newGVOP(OP_GV, 0, PL_defgv)), G_SCALAR),
153             make_assertop(checker, newSLUGOP(0))),
154             ctx->bodyop);
155 1           return;
156              
157 0           default:
158 0           croak("TODO: Unsure what to do with accessor type %d and :Checked", type);
159             }
160             }
161              
162 11           static OP *checked_gen_valueassert_op(pTHX_ FieldMeta *fieldmeta, SV *attrdata, void *_funcdata,
163             OP *valueop)
164             {
165             struct DataChecks_Checker *checker = (struct DataChecks_Checker *)attrdata;
166              
167 11           return make_assertop(checker, valueop);
168             }
169              
170             static const struct FieldHookFuncs checked_hooks = {
171             .ver = OBJECTPAD_ABIVERSION,
172             .flags = OBJECTPAD_FLAG_ATTR_MUST_VALUE,
173             .permit_hintkey = "Object::Pad::FieldAttr::Checked/Checked",
174              
175             .parse = &checked_parse,
176             .apply = &checked_apply,
177             .gen_accessor_ops = &checked_gen_accessor_ops,
178             .gen_valueassert_op = &checked_gen_valueassert_op,
179             };
180              
181             MODULE = Object::Pad::FieldAttr::Checked PACKAGE = Object::Pad::FieldAttr::Checked
182              
183             BOOT:
184 6           boot_data_checks(0.09);
185              
186 6           register_field_attribute("Checked", &checked_hooks, NULL);