File Coverage

lib/Syntax/Keyword/Assert.xs
Criterion Covered Total %
statement 120 121 99.1
branch 24 26 92.3
condition n/a
subroutine n/a
pod n/a
total 144 147 97.9


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5              
6             #include "XSParseKeyword.h"
7              
8             #define HAVE_PERL_VERSION(R, V, S) \
9             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
10              
11             #include "newUNOP_CUSTOM.c.inc"
12             #include "newBINOP_CUSTOM.c.inc"
13             #include "sv_numeq.c.inc"
14             #include "sv_numcmp.c.inc"
15             #include "sv_streq.c.inc"
16             #include "sv_isa.c.inc"
17              
18             static bool assert_enabled = TRUE;
19              
20             #define sv_catsv_unqq(sv, val) S_sv_catsv_unqq(aTHX_ sv, val)
21 93           static void S_sv_catsv_unqq(pTHX_ SV *sv, SV *val)
22             {
23 93 100         if(!SvOK(val)) {
24 3           sv_catpvs(sv, "undef");
25 3           return;
26             }
27              
28             #ifdef SvIsBOOL
29 90 100         if(SvIsBOOL(val)) {
30 4 100         SvTRUE(val) ? sv_catpvs(sv, "true") : sv_catpvs(sv, "false");
31 4           return;
32             }
33             #endif
34              
35 86 100         if(!SvPOK(val)) {
36 50           sv_catsv(sv, val);
37 50           return;
38             }
39              
40             #ifdef SVf_QUOTEDPREFIX
41 36           sv_catpvf(sv, "%" SVf_QUOTEDPREFIX, SVfARG(val));
42             #else
43             sv_catpvf(sv, "\"%" SVf "\"", SVfARG(val));
44             #endif
45             }
46              
47             static XOP xop_assert;
48 13           static OP *pp_assert(pTHX)
49             {
50 13           dSP;
51 13           SV *val = POPs;
52              
53 13 100         if(SvTRUE(val))
54 6           RETURN;
55              
56 7           SV *msg = sv_2mortal(newSVpvs("Assertion failed ("));
57 7           sv_catsv_unqq(msg, val);
58 7           sv_catpvs(msg, ")");
59 7           croak_sv(msg);
60             }
61              
62             /* Called after msgop is evaluated to croak with the message */
63             static XOP xop_assert_croak;
64 28           static OP *pp_assert_croak(pTHX)
65             {
66 28           dSP;
67 28           SV *custom_msg = POPs;
68 28           croak_sv(custom_msg);
69             }
70              
71             enum BinopType {
72             BINOP_NONE,
73             BINOP_NUM_EQ,
74             BINOP_NUM_NE,
75             BINOP_NUM_LT,
76             BINOP_NUM_GT,
77             BINOP_NUM_LE,
78             BINOP_NUM_GE,
79             BINOP_STR_EQ,
80             BINOP_STR_NE,
81             BINOP_STR_LT,
82             BINOP_STR_GT,
83             BINOP_STR_LE,
84             BINOP_STR_GE,
85             BINOP_ISA,
86             };
87              
88 85           static enum BinopType classify_binop(int type)
89             {
90 85           switch(type) {
91             case OP_EQ: return BINOP_NUM_EQ;
92 2           case OP_NE: return BINOP_NUM_NE;
93 12           case OP_LT: return BINOP_NUM_LT;
94 12           case OP_GT: return BINOP_NUM_GT;
95 3           case OP_LE: return BINOP_NUM_LE;
96 3           case OP_GE: return BINOP_NUM_GE;
97 5           case OP_SEQ: return BINOP_STR_EQ;
98 2           case OP_SNE: return BINOP_STR_NE;
99 6           case OP_SLT: return BINOP_STR_LT;
100 6           case OP_SGT: return BINOP_STR_GT;
101 6           case OP_SLE: return BINOP_STR_LE;
102 6           case OP_SGE: return BINOP_STR_GE;
103 2           case OP_ISA: return BINOP_ISA;
104             }
105 13           return BINOP_NONE;
106             }
107              
108             /* Check if binary assertion passes. Returns true if assertion succeeds. */
109 72           static bool S_assertbin_check(pTHX_ enum BinopType binoptype, SV *lhs, SV *rhs)
110             {
111 72           switch(binoptype) {
112 7           case BINOP_NUM_EQ: return sv_numeq(lhs, rhs);
113 2           case BINOP_NUM_NE: return !sv_numeq(lhs, rhs);
114 12           case BINOP_NUM_LT: return sv_numcmp(lhs, rhs) == -1;
115 12           case BINOP_NUM_GT: return sv_numcmp(lhs, rhs) == 1;
116 3           case BINOP_NUM_LE: return sv_numcmp(lhs, rhs) != 1;
117 3           case BINOP_NUM_GE: return sv_numcmp(lhs, rhs) != -1;
118 5           case BINOP_STR_EQ: return sv_streq(lhs, rhs);
119 2           case BINOP_STR_NE: return !sv_streq(lhs, rhs);
120 6           case BINOP_STR_LT: return sv_cmp(lhs, rhs) == -1;
121 6           case BINOP_STR_GT: return sv_cmp(lhs, rhs) == 1;
122 6           case BINOP_STR_LE: return sv_cmp(lhs, rhs) != 1;
123 6           case BINOP_STR_GE: return sv_cmp(lhs, rhs) != -1;
124 2           case BINOP_ISA: return sv_isa_sv(lhs, rhs);
125             default: return FALSE; /* unreachable */
126             }
127             }
128             #define assertbin_check(binoptype, lhs, rhs) S_assertbin_check(aTHX_ binoptype, lhs, rhs)
129              
130             /* Get operator string for error message */
131 43           static const char *binop_to_str(enum BinopType binoptype)
132             {
133 43           switch(binoptype) {
134             case BINOP_NUM_EQ: return "==";
135 1           case BINOP_NUM_NE: return "!=";
136 8           case BINOP_NUM_LT: return "<";
137 8           case BINOP_NUM_GT: return ">";
138 1           case BINOP_NUM_LE: return "<=";
139 1           case BINOP_NUM_GE: return ">=";
140 4           case BINOP_STR_EQ: return "eq";
141 1           case BINOP_STR_NE: return "ne";
142 4           case BINOP_STR_LT: return "lt";
143 4           case BINOP_STR_GT: return "gt";
144 2           case BINOP_STR_LE: return "le";
145 2           case BINOP_STR_GE: return "ge";
146 1           case BINOP_ISA: return "isa";
147 0           default: return "??"; /* unreachable */
148             }
149             }
150              
151             static XOP xop_assertbin;
152 72           static OP *pp_assertbin(pTHX)
153             {
154 72           dSP;
155 72           SV *rhs = POPs;
156 72           SV *lhs = POPs;
157 72           enum BinopType binoptype = PL_op->op_private;
158              
159 72 100         if(assertbin_check(binoptype, lhs, rhs))
160 29           RETURN;
161              
162 43           SV *msg = sv_2mortal(newSVpvs("Assertion failed ("));
163 43           sv_catsv_unqq(msg, lhs);
164 43           sv_catpvf(msg, " %s ", binop_to_str(binoptype));
165 43           sv_catsv_unqq(msg, rhs);
166 43           sv_catpvs(msg, ")");
167 43           croak_sv(msg);
168             }
169              
170              
171 137           static int build_assert(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
172             {
173             // assert(EXPR, EXPR)
174             //
175             // assert($x == 1)
176             // assert($x == 1, "x is not 1");
177             //
178             // first EXPR is the condition, second is the message.
179             // error message is optional.
180             // if the condition is false, the message is printed and the program dies.
181 137           OP *condop = args[0]->op;
182 137 50         OP *msgop = args[2] ? args[2]->op : NULL;
183              
184 137 100         if (assert_enabled) {
185 131 100         if (msgop) {
186             // With custom message: lazy evaluation using OP_OR
187             // assert(cond, msg) becomes: cond || do { croak(msg) }
188             //
189             // OP_OR: if condop is true, short-circuit; if false, evaluate other
190             // We use op_scope to isolate the other branch's op_next chain
191              
192             OP *croakop = newUNOP_CUSTOM(&pp_assert_croak, 0, msgop);
193 46           OP *scopedblock = op_scope(croakop);
194              
195 46           *out = newLOGOP(OP_OR, 0, condop, scopedblock);
196             }
197             else {
198             // Without custom message: check if binary operator for better error
199 85           enum BinopType binoptype = classify_binop(condop->op_type);
200 85 100         if (binoptype) {
201             // Binary operator: use pp_assertbin for detailed error message
202 72           condop->op_type = OP_CUSTOM;
203 72           condop->op_ppaddr = &pp_assertbin;
204 72           condop->op_private = binoptype;
205              
206 72           *out = condop;
207             }
208             else {
209             // Other expressions: use pp_assert
210 13           *out = newUNOP_CUSTOM(&pp_assert, 0, condop);
211             }
212             }
213             }
214             else {
215             // do nothing.
216 6           op_free(condop);
217 6 100         if (msgop) {
218 3           op_free(msgop);
219             }
220 6           *out = newOP(OP_NULL, 0);
221             }
222              
223 137           return KEYWORD_PLUGIN_EXPR;
224             }
225              
226             static const struct XSParseKeywordHooks hooks_assert = {
227             .permit_hintkey = "Syntax::Keyword::Assert/assert",
228             .pieces = (const struct XSParseKeywordPieceType[]) {
229             XPK_ARGS(
230             XPK_TERMEXPR_SCALARCTX,
231             XPK_OPTIONAL(XPK_COMMA),
232             XPK_TERMEXPR_SCALARCTX_OPT
233             ),
234             {0}
235             },
236             .build = &build_assert,
237             };
238              
239             MODULE = Syntax::Keyword::Assert PACKAGE = Syntax::Keyword::Assert
240              
241             BOOT:
242 11           boot_xs_parse_keyword(0.36);
243              
244 11           XopENTRY_set(&xop_assert, xop_name, "assert");
245 11           XopENTRY_set(&xop_assert, xop_desc, "assert");
246 11           XopENTRY_set(&xop_assert, xop_class, OA_UNOP);
247 11           Perl_custom_op_register(aTHX_ &pp_assert, &xop_assert);
248              
249 11           XopENTRY_set(&xop_assertbin, xop_name, "assertbin");
250 11           XopENTRY_set(&xop_assertbin, xop_desc, "assert(binary)");
251 11           XopENTRY_set(&xop_assertbin, xop_class, OA_BINOP);
252 11           Perl_custom_op_register(aTHX_ &pp_assertbin, &xop_assertbin);
253              
254 11           XopENTRY_set(&xop_assert_croak, xop_name, "assert_croak");
255 11           XopENTRY_set(&xop_assert_croak, xop_desc, "assert croak with message");
256 11           XopENTRY_set(&xop_assert_croak, xop_class, OA_UNOP);
257 11           Perl_custom_op_register(aTHX_ &pp_assert_croak, &xop_assert_croak);
258              
259 11           register_xs_parse_keyword("assert", &hooks_assert, NULL);
260              
261             {
262 11           const char *enabledstr = getenv("PERL_ASSERT_ENABLED");
263 11 100         if(enabledstr) {
264 1           SV *sv = newSVpvn(enabledstr, strlen(enabledstr));
265 1 50         if(!SvTRUE(sv))
266 1           assert_enabled = FALSE;
267 1           SvREFCNT_dec(sv);
268             }
269             }
270