File Coverage

c/jsonfmt.c
Criterion Covered Total %
statement 186 197 94.4
branch 162 226 71.6
condition n/a
subroutine n/a
pod n/a
total 348 423 82.2


line stmt bran cond sub pod time code
1             typedef struct {
2             fustr *out;
3             UV depth;
4             int canon;
5             int pretty; /* <0 when disabled, current nesting level otherwise */
6             int htmlsafe;
7             } fujson_fmt_ctx;
8              
9             static void fujson_fmt(pTHX_ fujson_fmt_ctx *, SV *);
10              
11 2135           static void fujson_fmt_indent(pTHX_ fujson_fmt_ctx *ctx) {
12 2135 100         if (ctx->pretty >= 0) {
13 15           char *buf = fustr_write_buf(ctx->out, 1 + ctx->pretty*3);
14 15           *buf = '\n';
15 15           memset(buf+1, ' ', ctx->pretty*3);
16             }
17 2135           }
18              
19 1211           static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t len, int utf8) {
20 1211           size_t off = 0, loff;
21 1211           const unsigned char *str = (const unsigned char *)stri;
22             unsigned char *buf;
23 1211           unsigned char x = 0;
24              
25 1211           fustr_write_ch(ctx->out, '\"');
26 1211           fustr_reserve(ctx->out, len);
27              
28 1937 100         while (off < len) {
29             /* Fast path: no escaping needed */
30 727           loff = off;
31              
32             #define SKIPUNTIL(cond) \
33             while (off < len) { \
34             x = str[off]; \
35             if (x <= 0x1f || x == '"' || x == '\\' || cond) break; \
36             off++;\
37             }
38 727 100         if (utf8) {
39 47 50         if (!ctx->htmlsafe) { SKIPUNTIL(x == 0x7f) }
    100          
    50          
    50          
    50          
    100          
40 0 0         else { SKIPUNTIL(x == 0x7f || x == '<' || x == '>' || x == '&') }
    0          
    0          
    0          
    0          
    0          
    0          
    0          
41             } else {
42 4743859 100         if (!ctx->htmlsafe) { SKIPUNTIL(x >= 0x7f) }
    100          
    50          
    50          
    100          
    100          
43 15 50         else { SKIPUNTIL(x >= 0x7f || x == '<' || x == '>' || x == '&') }
    50          
    50          
    50          
    100          
    100          
    100          
    50          
44             }
45             #undef SKIPUNTIL
46              
47 727           fustr_write(ctx->out, (char *)str+loff, off-loff);
48              
49 727 100         if (off < len) { /* early break, which means current byte needs special processing */
50 24           switch (x) {
51 0           case '"': fustr_write(ctx->out, "\\\"", 2); break;
52 0           case '\\': fustr_write(ctx->out, "\\\\", 2); break;
53 0           case 0x08: fustr_write(ctx->out, "\\b", 2); break;
54 0           case 0x09: fustr_write(ctx->out, "\\t", 2); break;
55 4           case 0x0a: fustr_write(ctx->out, "\\n", 2); break;
56 0           case 0x0c: fustr_write(ctx->out, "\\f", 2); break;
57 4           case 0x0d: fustr_write(ctx->out, "\\r", 2); break;
58 16           default:
59 16 100         if (x < 0x80) {
60 9           buf = (unsigned char *)fustr_write_buf(ctx->out, 6);
61 9           memcpy(buf, "\\u00", 4);
62 9           buf[4] = PL_hexdigit[(x >> 4) & 0x0f];
63 9           buf[5] = PL_hexdigit[x & 0x0f];
64             } else { /* x >= 0x80, !utf8, so encode as 2-byte UTF-8 */
65 7           buf = (unsigned char *)fustr_write_buf(ctx->out, 2);
66 7           buf[0] = 0xc0 | (x >> 6);
67 7           buf[1] = 0x80 | (x & 0x3f);
68             }
69             }
70 24           off++;
71             }
72             }
73              
74 1210           fustr_write_ch(ctx->out, '\"');
75 1210           }
76              
77             static const char fujson_digits[] =
78             "00010203040506070809"
79             "10111213141516171819"
80             "20212223242526272829"
81             "30313233343536373839"
82             "40414243444546474849"
83             "50515253545556575859"
84             "60616263646566676869"
85             "70717273747576777879"
86             "80818283848586878889"
87             "90919293949596979899";
88              
89 4074           static void fujson_fmt_int(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
90             char buf[32];
91 4074           char *r = buf+31;
92 4074           int neg = 0;
93             IV iv;
94             UV uv;
95              
96 4074 100         if (SvIsUV(val)) { /* Why is this macro not documented? */
97 2           uv = SvUV_nomg(val);
98             } else {
99 4072           iv = SvIV_nomg(val);
100 4072           neg = iv < 0;
101 4072 100         uv = neg ? -iv : iv;
102             }
103              
104 4074 100         if (uv == 0) {
105 9           fustr_write_ch(ctx->out, '0');
106 9           return;
107             }
108              
109 8156 100         while (uv >= 10) {
110 4091           r -= 2;
111 4091           memcpy(r, fujson_digits + ((uv % 100)<<1), 2);
112 4091           uv /= 100;
113             }
114 4065 100         if (uv > 0) *(--r) = '0' + (uv % 10);
115 4065 100         if (neg) *(--r) = '-';
116 4065           fustr_write(ctx->out, r, 31 - (r - buf));
117             }
118              
119 532           static void fujson_fmt_av(pTHX_ fujson_fmt_ctx *ctx, AV *av) {
120 532           int i, len = av_count(av);
121 532           fustr_write_ch(ctx->out, '[');
122 532           ctx->pretty++;
123 1083 100         for (i=0; i
124 552 100         if (i) fustr_write_ch(ctx->out, ',');
125 552           fujson_fmt_indent(aTHX_ ctx);
126 552           SV **sv = av_fetch(av, i, 0);
127 552 50         if (sv) fujson_fmt(aTHX_ ctx, *sv); /* sv will have magic if av is tied, but fujson_fmt() handles that. */
128 0           else fustr_write(ctx->out, "null", 4);
129             }
130 531           ctx->pretty--;
131 531 100         if (i) fujson_fmt_indent(aTHX_ ctx);
132 531           fustr_write_ch(ctx->out, ']');
133 531           }
134              
135 63           static int fujson_fmt_hvcmp(const void *pa, const void *pb) {
136             dTHX;
137 63           HE *a = *(HE **)pa;
138 63           HE *b = *(HE **)pb;
139             STRLEN alen, blen;
140 63 50         char *astr = HePV(a, alen);
141 63 50         char *bstr = HePV(b, blen);
142 63 50         int autf = HeUTF8(a);
143 63 50         int butf = HeUTF8(b);
144              
145 63 100         if (autf == butf) {
146 58           int cmp = memcmp(bstr, astr, alen < blen ? alen : blen);
147 58 100         return cmp != 0 ? cmp : blen < alen ? -1 : blen == alen ? 0 : 1;
    100          
148             }
149 2           return autf ? bytes_cmp_utf8((const U8*)bstr, blen, (const U8*)astr, alen)
150 7 100         : -bytes_cmp_utf8((const U8*)astr, alen, (const U8*)bstr, blen);
151             }
152              
153 546           static void fujson_fmt_hvkv(pTHX_ fujson_fmt_ctx *ctx, HV *hv, HE *he, char **hestr) {
154             STRLEN helen;
155 546 100         if (*hestr) fustr_write_ch(ctx->out, ',');
156 546           fujson_fmt_indent(aTHX_ ctx);
157 546 100         *hestr = HePV(he, helen);
158 546 100         fujson_fmt_str(aTHX_ ctx, *hestr, helen, HeUTF8(he));
159 546 100         if (ctx->pretty > 0) fustr_write(ctx->out, " : ", 3);
160 538           else fustr_write_ch(ctx->out, ':');
161 546 100         fujson_fmt(aTHX_ ctx, UNLIKELY(SvMAGICAL(hv)) ? hv_iterval(hv, he) : HeVAL(he));
162 546           }
163              
164 517           static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
165             HE *he;
166 517           char *hestr = NULL;
167              
168 517           int numkeys = hv_iterinit(hv);
169 517           fustr_write_ch(ctx->out, '{');
170 517           ctx->pretty++;
171              
172             /* Canonical order on tied hashes is not supported. Cpanel::JSON::XS has
173             * code to deal with that case and it's absolutely horrifying. */
174 526 100         if (ctx->canon && !(SvMAGICAL(hv) && SvTIED_mg((SV*)hv, PERL_MAGIC_tied))) {
    50          
    0          
    0          
175 9           SAVETMPS;
176 9 100         if (numkeys < 4) numkeys = 4;
177 9 50         if (SvMAGICAL(hv)) numkeys = 32;
178              
179 9           SV *keys_sv = sv_2mortal(newSV(numkeys * sizeof(HE*)));
180 9           HE **keys = (HE **)SvPVX(keys_sv);
181 9           int i = 0;
182              
183 45 100         while ((he = hv_iternext(hv))) {
184 36 50         if (i >= numkeys) {
185 0           numkeys += numkeys >> 1;
186 0 0         keys = (HE **)SvGROW(keys_sv, numkeys * sizeof(HE*));
    0          
187 0           numkeys = SvLEN(keys_sv) / sizeof(HE*);
188             }
189 36           keys[i++] = he;
190             }
191 9           qsort(keys, i, sizeof(HE *), fujson_fmt_hvcmp);
192 45 100         while (i--) fujson_fmt_hvkv(aTHX_ ctx, hv, keys[i], &hestr);
193 9 100         FREETMPS;
194              
195             } else {
196 1018 100         while ((he = hv_iternext(hv))) fujson_fmt_hvkv(aTHX_ ctx, hv, he, &hestr);
197             }
198 517           ctx->pretty--;
199 517 100         if (hestr) fujson_fmt_indent(aTHX_ ctx);
200 517           fustr_write_ch(ctx->out, '}');
201 517           }
202              
203 8           static void fujson_fmt_obj(pTHX_ fujson_fmt_ctx *ctx, SV *rv, SV *obj) {
204 8           dSP;
205              
206 8           GV *method = gv_fetchmethod_autoload(SvSTASH(obj), "TO_JSON", 0);
207 8 100         if (!method) croak("unable to format '%s' object as JSON", HvNAME(SvSTASH(obj)));
    50          
    50          
    50          
    0          
    50          
    50          
208              
209 7           ENTER;
210 7           SAVETMPS;
211              
212 7 50         PUSHMARK(SP);
213 7 50         XPUSHs(rv);
214              
215 7           PUTBACK;
216 7           call_sv((SV *)GvCV(method), G_SCALAR);
217 7           SPAGAIN;
218              
219             /* JSON::XS describes this error as "surprisingly common"... I'd be
220             * surprised indeed if it happens at all, but I suppose it can't hurt to
221             * copy their check; this sounds like be a pain to debug otherwise. */
222 7 50         if (SvROK(TOPs) && SvRV(TOPs) == obj)
    100          
223 1 50         croak("%s::TO_JSON method returned same object as was passed instead of a new one", HvNAME(SvSTASH(obj)));
    50          
    50          
    0          
    50          
    50          
224              
225 6           obj = POPs;
226 6           PUTBACK;
227 6           fujson_fmt(aTHX_ ctx, obj);
228              
229 6 50         FREETMPS;
230 6           LEAVE;
231 6           }
232              
233 5839           static void fujson_fmt(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
234 5839           SvGETMAGIC(val);
235              
236 5839           int r = fu_2bool(aTHX_ val);
237 5839 100         if (r != -1) { /* Must check SvISBOOL() before IOKp & POKp, because it implies both flags */
238 15 100         if (r) fustr_write(ctx->out, "true", 4);
239 5           else fustr_write(ctx->out, "false", 5);
240 5824 100         } else if (SvPOKp(val)) {
241 665           fujson_fmt_str(aTHX_ ctx, SvPVX(val), SvCUR(val), SvUTF8(val));
242 5159 100         } else if (SvNOKp(val)) { /* Must check before IOKp, because integer conversion might have been lossy */
243 15           NV nv = SvNV_nomg(val);
244 15 100         if (isinfnan(nv)) croak("unable to format floating point NaN or Inf as JSON");
245             /* XXX: Cpanel::JSON::XS appears to always append a ".0" for round numbers, other modules do not. */
246             /* XXX#2: This doesn't support quadmath. Makefile.PL checks for that */
247 13           fustr_reserve(ctx->out, NV_DIG+32);
248 13           Gconvert(nv, NV_DIG, 0, ctx->out->cur);
249 13           ctx->out->cur += strlen(ctx->out->cur);
250 5144 100         } else if (SvIOKp(val)) {
251 4074           fujson_fmt_int(aTHX_ ctx, val);
252 1070 100         } else if (SvROK(val)) {
253             /* Simply consider every reference a form of nesting. TO_JSON may
254             * return a scalar, but it may also return another TO_JSON object and
255             * cause a stack overflow that way. */
256 1059 100         if (--ctx->depth == 0) croak("max_depth exceeded while formatting JSON");
257 1058           SV *rv = SvRV(val);
258 1058           SvGETMAGIC(rv);
259 1058 100         if (UNLIKELY(SvOBJECT(rv))) fujson_fmt_obj(aTHX_ ctx, val, rv);
260 1050 100         else if (SvTYPE(rv) == SVt_PVHV) fujson_fmt_hv(aTHX_ ctx, (HV *)rv);
261 533 100         else if (SvTYPE(rv) == SVt_PVAV) fujson_fmt_av(aTHX_ ctx, (AV *)rv);
262 1           else croak("unable to format reference '%s' as JSON", SvPV_nolen(val));
263 1054           ctx->depth++;
264 11 100         } else if (!SvOK(val)) {
265 10           fustr_write(ctx->out, "null", 4);
266             } else {
267 1           croak("unable to format unknown value '%s' as JSON", SvPV_nolen(val));
268             }
269 5830           }
270              
271              
272 4735           static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
273 4735           I32 i = 1;
274 4735           int encutf8 = 0;
275             char *arg;
276             SV *r;
277             fustr out;
278             fujson_fmt_ctx ctx;
279              
280 4735           out.maxlen = 0;
281 4735           ctx.out = &out;
282 4735           ctx.depth = 0;
283 4735           ctx.pretty = INT_MIN;
284 4735           ctx.canon = ctx.htmlsafe = 0;
285 6785 100         while (i < argc) {
286 2050           arg = SvPV_nolen(ST(i));
287 2050           i++;
288 2050 50         if (i == argc) croak("Odd name/value argument for json_format()");
289 2050           r = ST(i);
290 2050           i++;
291              
292 2050 100         if (strcmp(arg, "canonical") == 0) ctx.canon = SvTRUEx(r);
293 2045 100         else if (strcmp(arg, "pretty") == 0) ctx.pretty = SvTRUEx(r) ? 0 : INT_MIN;
    50          
294 2044 100         else if (strcmp(arg, "html_safe") == 0) ctx.htmlsafe = !!SvTRUEx(r);
295 2043 100         else if (strcmp(arg, "utf8") == 0) encutf8 = SvTRUEx(r);
296 2 100         else if (strcmp(arg, "max_size") == 0) out.maxlen = SvUV(r);
297 1 50         else if (strcmp(arg, "max_depth") == 0) ctx.depth = SvUV(r);
298 0           else croak("Unknown flag: '%s'", arg);
299             }
300 4735 100         if (out.maxlen == 0) out.maxlen = 1<<30;
301 4735 100         if (ctx.depth == 0) ctx.depth = 512;
302              
303 4735           fustr_init(&out, sv_newmortal(), out.maxlen);
304 4735           fujson_fmt(aTHX_ &ctx, val);
305 4727 100         if (ctx.pretty >= 0) fustr_write_ch(&out, '\n');
306 4727           r = fustr_done(&out);
307 4727 100         if (!encutf8) SvUTF8_on(r);
308 4727           return r;
309             }