File Coverage

lib/Data/Checks.xs
Criterion Covered Total %
statement 97 105 92.3
branch 52 68 76.4
condition n/a
subroutine n/a
pod n/a
total 149 173 86.1


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, 2024 -- leonerd@leonerd.org.uk
5             */
6              
7             #define PERL_NO_GET_CONTEXT
8              
9             #include "EXTERN.h"
10             #include "perl.h"
11             #include "XSUB.h"
12              
13             #define HAVE_DATA_CHECKS_IMPL
14             #include "DataChecks.h"
15              
16             struct DataChecks_Checker
17             {
18             CV *cv;
19             struct Constraint *constraint;
20             SV *arg0;
21             SV *assertmess;
22             };
23              
24             #include "perl-backcompat.c.inc"
25              
26             #include "newOP_CUSTOM.c.inc"
27             #include "optree-additions.c.inc"
28              
29             #include "constraints.h"
30              
31             #define warn_deprecated(...) Perl_ck_warner(aTHX_ packWARN(WARN_DEPRECATED), __VA_ARGS__)
32              
33 33           static struct DataChecks_Checker *S_DataChecks_make_checkdata(pTHX_ SV *checkspec)
34             {
35             HV *stash = NULL;
36             CV *checkcv = NULL;
37             struct Constraint *constraint = NULL;
38              
39 33 100         if(SvROK(checkspec) && SvOBJECT(SvRV(checkspec)))
    100          
40 31           stash = SvSTASH(SvRV(checkspec));
41 2 100         else if(SvPOK(checkspec) && (stash = gv_stashsv(checkspec, GV_NOADD_NOINIT)))
    50          
42             ; /* checkspec is package name */
43 1 50         else if(SvROK(checkspec) && !SvOBJECT(SvRV(checkspec)) && SvTYPE(SvRV(checkspec)) == SVt_PVCV) {
    50          
44             /* checkspec is a code reference */
45 1           warn_deprecated("Using a CODE reference as a constraint checker is deprecated");
46 1 50         checkcv = (CV *)SvREFCNT_inc(SvRV(checkspec));
47             checkspec = NULL;
48             }
49             else
50 0           croak("Expected the checker expression to yield an object or code reference or package name; got %" SVf " instead",
51             SVfARG(checkspec));
52              
53 33 100         if(stash && sv_isa(checkspec, "Data::Checks::Constraint")) {
    100          
54 29           constraint = (struct Constraint *)SvPVX(SvRV(checkspec));
55             /* arg0 will store checkspec pointer, thus ensuring this SV is retained */
56             }
57 4 100         else if(!checkcv) {
58             GV *methgv;
59 3 50         if(!(methgv = gv_fetchmeth_pv(stash, "check", -1, 0)))
60 0           croak("Expected that the checker expression can ->check");
61 3 50         if(!GvCV(methgv))
62 0           croak("Expected that methgv has a GvCV");
63             checkcv = (CV *)SvREFCNT_inc(GvCV(methgv));
64             }
65              
66             struct DataChecks_Checker *checker;
67 33           Newx(checker, 1, struct DataChecks_Checker);
68              
69 33           *checker = (struct DataChecks_Checker){
70             .cv = checkcv,
71             .constraint = constraint,
72             .arg0 = SvREFCNT_inc(checkspec),
73             };
74              
75 33           return checker;
76             }
77              
78 5           static void S_DataChecks_free_checkdata(pTHX_ struct DataChecks_Checker *checker)
79             {
80 5 50         if(checker->assertmess)
81 5           SvREFCNT_dec(checker->assertmess);
82              
83 5           SvREFCNT_dec(checker->cv);
84              
85 5 100         if(checker->arg0)
86 4           SvREFCNT_dec(checker->arg0);
87              
88 5           Safefree(checker);
89 5           }
90              
91 33           static void S_DataChecks_gen_assertmess(pTHX_ struct DataChecks_Checker *checker, SV *name, SV *constraint)
92             {
93 33 100         if(!constraint || !SvOK(constraint)) {
    100          
94 30 100         if(checker->constraint)
95 29           constraint = stringify_constraint(checker->constraint);
96 1 50         else if(checker->arg0) {
97 1           constraint = sv_newmortal();
98 1           sv_copypv(constraint, checker->arg0);
99             }
100             else
101 0           croak("gen_assertmess requires a constraint name if the constraint is a CODE reference");
102             }
103 33           checker->assertmess = newSVpvf("%" SVf " requires a value satisfying %" SVf,
104             SVfARG(name), SVfARG(constraint));
105 33           }
106              
107             static XOP xop_invoke_checkfunc;
108 200007           static OP *pp_invoke_checkfunc(pTHX)
109             {
110 200007           dSP;
111 200007           struct Constraint *constraint = (struct Constraint *)cUNOP_AUX->op_aux;
112 200007           SV *value = POPs;
113              
114 200007 100         PUSHs(boolSV((*constraint->func)(aTHX_ constraint, value)));
115              
116 200007           RETURN;
117             }
118              
119             #define make_checkop(checker, argop) S_DataChecks_make_checkop(aTHX_ checker, argop)
120 9           static OP *S_DataChecks_make_checkop(pTHX_ struct DataChecks_Checker *checker, OP *argop)
121             {
122 9 100         if(checker->constraint) {
123 5           return newUNOP_AUX_CUSTOM(&pp_invoke_checkfunc, OPf_WANT_SCALAR,
124             argop,
125             (UNOP_AUX_item *)checker->constraint);
126             }
127              
128 4 50         if(checker->cv && checker->arg0)
    100          
129             /* checkcv($checker, ARGOP) ... */
130 6 50         return newLISTOPn(OP_ENTERSUB, OPf_WANT_SCALAR|OPf_STACKED,
131             newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->arg0)),
132             argop,
133             newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->cv)),
134             NULL);
135              
136 1 50         if(checker->cv)
137             /* checkcv(ARGOP) ... */
138 1           return newLISTOPn(OP_ENTERSUB, OPf_WANT_SCALAR|OPf_STACKED,
139             argop,
140             newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->cv)),
141             NULL);
142              
143 0           croak("ARGH unsure how to make checkop");
144             }
145              
146 9           static OP *S_DataChecks_make_assertop(pTHX_ struct DataChecks_Checker *checker, U32 flags, OP *argop)
147             {
148 9           U32 want = flags & OPf_WANT; flags &= ~OPf_WANT;
149             bool want_void = (want == OPf_WANT_VOID);
150              
151 9 50         if(flags)
152 0           croak("TODO: make_assertop with flags 0x%x", flags);
153              
154 18 50         OP *o = newLOGOP(OP_OR, 0,
155             make_checkop(checker, argop),
156             /* ... or die MESSAGE */
157             newLISTOPn(OP_DIE, 0,
158             newSVOP(OP_CONST, 0, SvREFCNT_inc(checker->assertmess)),
159             NULL));
160              
161 9 100         if(want_void) {
162             /* Wrap it in a full enter/leave pair so it unstacks correctly */
163 1           o->op_flags |= OPf_PARENS;
164 1           o = op_contextualize(op_scope(o), OPf_WANT_VOID);
165             }
166              
167 9           return o;
168             }
169              
170 0           static OP *S_DataChecks_make_assertop_v0(pTHX_ struct DataChecks_Checker *checker, OP *argop)
171             {
172 0           return S_DataChecks_make_assertop(aTHX_ checker, 0, argop);
173             }
174              
175 172           static bool S_DataChecks_check_value(pTHX_ struct DataChecks_Checker *checker, SV *value)
176             {
177 172 100         if(checker->constraint) {
178 160           return (*checker->constraint->func)(aTHX_ checker->constraint, value);
179             }
180              
181 12           dSP;
182              
183 12           ENTER;
184 12           SAVETMPS;
185              
186 12 50         EXTEND(SP, 2);
187 12 50         PUSHMARK(SP);
188 12 100         if(checker->arg0)
189 8           PUSHs(sv_mortalcopy(checker->arg0));
190 12           PUSHs(value); /* Yes we're pushing the SV itself */
191 12           PUTBACK;
192              
193 12           call_sv((SV *)checker->cv, G_SCALAR);
194              
195 12           SPAGAIN;
196              
197 12           bool ok = SvTRUEx(POPs);
198              
199 12           PUTBACK;
200              
201 12 50         FREETMPS;
202 12           LEAVE;
203              
204 12           return ok;
205             }
206              
207 8           static void S_DataChecks_assert_value(pTHX_ struct DataChecks_Checker *checker, SV *value)
208             {
209 8 100         if(check_value(checker, value))
210 4           return;
211              
212 4           croak_sv(checker->assertmess);
213             }
214              
215             MODULE = Data::Checks PACKAGE = Data::Checks::Debug
216              
217             void stringify_constraint(SV *sv)
218             PPCODE:
219             /* Prevent XSUB from double-mortalising it */
220 46           PUSHs(stringify_constraint_sv(extract_constraint(sv)));
221 46           XSRETURN(1);
222              
223             MODULE = Data::Checks PACKAGE = Data::Checks::Constraint
224              
225             void DESTROY(SV *self)
226             CODE:
227             {
228 196           struct Constraint *c = (struct Constraint *)SvPVX(SvRV(self));
229 353 100         for(int i = c->n - 1; i >= 0; i--)
230 157           SvREFCNT_dec(c->args[i]);
231             }
232              
233             bool check(SV *self, SV *value)
234             CODE:
235 2           struct Constraint *c = (struct Constraint *)SvPVX(SvRV(self));
236 2           RETVAL = (c->func)(aTHX_ c, value);
237             OUTPUT:
238             RETVAL
239              
240             MODULE = Data::Checks PACKAGE = Data::Checks
241              
242             BOOT:
243 14           sv_setiv(*hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MIN", GV_ADD), 0);
244 14           sv_setiv(*hv_fetchs(PL_modglobal, "Data::Checks/ABIVERSION_MAX", GV_ADD), DATACHECKS_ABI_VERSION);
245              
246 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_checkdata()@0", GV_ADD),
247             PTR2UV(&S_DataChecks_make_checkdata));
248 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/free_checkdata()@0", GV_ADD),
249             PTR2UV(&S_DataChecks_free_checkdata));
250 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/gen_assertmess()@0", GV_ADD),
251             PTR2UV(&S_DataChecks_gen_assertmess));
252 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_assertop()@0", GV_ADD),
253             PTR2UV(&S_DataChecks_make_assertop_v0));
254 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/make_assertop()@1", GV_ADD),
255             PTR2UV(&S_DataChecks_make_assertop));
256 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/check_value()@0", GV_ADD),
257             PTR2UV(&S_DataChecks_check_value));
258 14           sv_setuv(*hv_fetchs(PL_modglobal, "Data::Checks/assert_value()@0", GV_ADD),
259             PTR2UV(&S_DataChecks_assert_value));
260              
261 14           boot_Data_Checks__constraints(aTHX);
262              
263 14           XopENTRY_set(&xop_invoke_checkfunc, xop_name, "invoke_checkfunc");
264 14           XopENTRY_set(&xop_invoke_checkfunc, xop_desc, "invoke checkfunc");
265 14           XopENTRY_set(&xop_invoke_checkfunc, xop_class, OA_UNOP_AUX);
266 14           Perl_custom_op_register(aTHX_ &pp_invoke_checkfunc, &xop_invoke_checkfunc);