Coverage Report

Created: 2026-03-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/thirdparty/mujs/json.c
Line
Count
Source
1
#include "jsi.h"
2
#include "utf.h"
3
4
int js_isnumberobject(js_State *J, int idx)
5
0
{
6
0
  return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CNUMBER;
7
0
}
8
9
int js_isstringobject(js_State *J, int idx)
10
0
{
11
0
  return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CSTRING;
12
0
}
13
14
int js_isbooleanobject(js_State *J, int idx)
15
0
{
16
0
  return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CBOOLEAN;
17
0
}
18
19
int js_isdateobject(js_State *J, int idx)
20
0
{
21
0
  return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CDATE;
22
0
}
23
24
static void jsonnext(js_State *J)
25
0
{
26
0
  J->lookahead = jsY_lexjson(J);
27
0
}
28
29
static int jsonaccept(js_State *J, int t)
30
0
{
31
0
  if (J->lookahead == t) {
32
0
    jsonnext(J);
33
0
    return 1;
34
0
  }
35
0
  return 0;
36
0
}
37
38
static void jsonexpect(js_State *J, int t)
39
0
{
40
0
  if (!jsonaccept(J, t))
41
0
    js_syntaxerror(J, "JSON: unexpected token: %s (expected %s)",
42
0
        jsY_tokenstring(J->lookahead), jsY_tokenstring(t));
43
0
}
44
45
static void jsonvalue(js_State *J)
46
0
{
47
0
  int i;
48
49
0
  switch (J->lookahead) {
50
0
  case TK_STRING:
51
0
    js_pushstring(J, J->text);
52
0
    jsonnext(J);
53
0
    break;
54
55
0
  case TK_NUMBER:
56
0
    js_pushnumber(J, J->number);
57
0
    jsonnext(J);
58
0
    break;
59
60
0
  case '{':
61
0
    js_newobject(J);
62
0
    jsonnext(J);
63
0
    if (jsonaccept(J, '}'))
64
0
      return;
65
0
    do {
66
0
      if (J->lookahead != TK_STRING)
67
0
        js_syntaxerror(J, "JSON: unexpected token: %s (expected string)", jsY_tokenstring(J->lookahead));
68
0
      js_pushstring(J, J->text);
69
0
      jsonnext(J);
70
0
      jsonexpect(J, ':');
71
0
      jsonvalue(J);
72
0
      js_setproperty(J, -3, js_tostring(J, -2));
73
0
      js_pop(J, 1);
74
0
    } while (jsonaccept(J, ','));
75
0
    jsonexpect(J, '}');
76
0
    break;
77
78
0
  case '[':
79
0
    js_newarray(J);
80
0
    jsonnext(J);
81
0
    i = 0;
82
0
    if (jsonaccept(J, ']'))
83
0
      return;
84
0
    do {
85
0
      jsonvalue(J);
86
0
      js_setindex(J, -2, i++);
87
0
    } while (jsonaccept(J, ','));
88
0
    jsonexpect(J, ']');
89
0
    break;
90
91
0
  case TK_TRUE:
92
0
    js_pushboolean(J, 1);
93
0
    jsonnext(J);
94
0
    break;
95
96
0
  case TK_FALSE:
97
0
    js_pushboolean(J, 0);
98
0
    jsonnext(J);
99
0
    break;
100
101
0
  case TK_NULL:
102
0
    js_pushnull(J);
103
0
    jsonnext(J);
104
0
    break;
105
106
0
  default:
107
0
    js_syntaxerror(J, "JSON: unexpected token: %s", jsY_tokenstring(J->lookahead));
108
0
  }
109
0
}
110
111
static void jsonrevive(js_State *J, const char *name)
112
0
{
113
0
  const char *key;
114
0
  char buf[32];
115
116
  /* revive is in 2 */
117
  /* holder is in -1 */
118
119
0
  js_getproperty(J, -1, name); /* get value from holder */
120
121
0
  if (js_isobject(J, -1)) {
122
0
    if (js_isarray(J, -1)) {
123
0
      int i = 0;
124
0
      int n = js_getlength(J, -1);
125
0
      for (i = 0; i < n; ++i) {
126
0
        jsonrevive(J, js_itoa(buf, i));
127
0
        if (js_isundefined(J, -1)) {
128
0
          js_pop(J, 1);
129
0
          js_delproperty(J, -1, buf);
130
0
        } else {
131
0
          js_setproperty(J, -2, buf);
132
0
        }
133
0
      }
134
0
    } else {
135
0
      js_pushiterator(J, -1, 1);
136
0
      while ((key = js_nextiterator(J, -1))) {
137
0
        js_rot2(J);
138
0
        jsonrevive(J, key);
139
0
        if (js_isundefined(J, -1)) {
140
0
          js_pop(J, 1);
141
0
          js_delproperty(J, -1, key);
142
0
        } else {
143
0
          js_setproperty(J, -2, key);
144
0
        }
145
0
        js_rot2(J);
146
0
      }
147
0
      js_pop(J, 1);
148
0
    }
149
0
  }
150
151
0
  js_copy(J, 2); /* reviver function */
152
0
  js_copy(J, -3); /* holder as this */
153
0
  js_pushstring(J, name); /* name */
154
0
  js_copy(J, -4); /* value */
155
0
  js_call(J, 2);
156
0
  js_rot2pop1(J); /* pop old value, leave new value on stack */
157
0
}
158
159
static void JSON_parse(js_State *J)
160
0
{
161
0
  const char *source = js_tostring(J, 1);
162
0
  jsY_initlex(J, "JSON", source);
163
0
  jsonnext(J);
164
165
0
  if (js_iscallable(J, 2)) {
166
0
    js_newobject(J);
167
0
    jsonvalue(J);
168
0
    js_defproperty(J, -2, "", 0);
169
0
    jsonrevive(J, "");
170
0
  } else {
171
0
    jsonvalue(J);
172
0
  }
173
0
}
174
175
static void fmtnum(js_State *J, js_Buffer **sb, double n)
176
0
{
177
0
  if (isnan(n)) js_puts(J, sb, "null");
178
0
  else if (isinf(n)) js_puts(J, sb, "null");
179
0
  else if (n == 0) js_puts(J, sb, "0");
180
0
  else {
181
0
    char buf[40];
182
0
    js_puts(J, sb, jsV_numbertostring(J, buf, n));
183
0
  }
184
0
}
185
186
static void fmtstr(js_State *J, js_Buffer **sb, const char *s)
187
0
{
188
0
  static const char *HEX = "0123456789abcdef";
189
0
  int i, n;
190
0
  Rune c;
191
0
  js_putc(J, sb, '"');
192
0
  while (*s) {
193
0
    n = chartorune(&c, s);
194
0
    switch (c) {
195
0
    case '"': js_puts(J, sb, "\\\""); break;
196
0
    case '\\': js_puts(J, sb, "\\\\"); break;
197
0
    case '\b': js_puts(J, sb, "\\b"); break;
198
0
    case '\f': js_puts(J, sb, "\\f"); break;
199
0
    case '\n': js_puts(J, sb, "\\n"); break;
200
0
    case '\r': js_puts(J, sb, "\\r"); break;
201
0
    case '\t': js_puts(J, sb, "\\t"); break;
202
0
    default:
203
0
      if (c < ' ' || (c >= 0xd800 && c <= 0xdfff)) {
204
0
        js_putc(J, sb, '\\');
205
0
        js_putc(J, sb, 'u');
206
0
        js_putc(J, sb, HEX[(c>>12)&15]);
207
0
        js_putc(J, sb, HEX[(c>>8)&15]);
208
0
        js_putc(J, sb, HEX[(c>>4)&15]);
209
0
        js_putc(J, sb, HEX[c&15]);
210
0
      } else if (c < 128) {
211
0
        js_putc(J, sb, c);
212
0
      } else {
213
0
        for (i = 0; i < n; ++i)
214
0
          js_putc(J, sb, s[i]);
215
0
      }
216
0
      break;
217
0
    }
218
0
    s += n;
219
0
  }
220
0
  js_putc(J, sb, '"');
221
0
}
222
223
static void fmtindent(js_State *J, js_Buffer **sb, const char *gap, int level)
224
0
{
225
0
  js_putc(J, sb, '\n');
226
0
  while (level--)
227
0
    js_puts(J, sb, gap);
228
0
}
229
230
static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level);
231
232
static int filterprop(js_State *J, const char *key)
233
0
{
234
0
  int i, n, found;
235
  /* replacer/property-list is in stack slot 2 */
236
0
  if (js_isarray(J, 2)) {
237
0
    found = 0;
238
0
    n = js_getlength(J, 2);
239
0
    for (i = 0; i < n && !found; ++i) {
240
0
      js_getindex(J, 2, i);
241
0
      if (js_isstring(J, -1) || js_isnumber(J, -1) ||
242
0
        js_isstringobject(J, -1) || js_isnumberobject(J, -1))
243
0
        found = !strcmp(key, js_tostring(J, -1));
244
0
      js_pop(J, 1);
245
0
    }
246
0
    return found;
247
0
  }
248
0
  return 1;
249
0
}
250
251
static void fmtobject(js_State *J, js_Buffer **sb, js_Object *obj, const char *gap, int level)
252
0
{
253
0
  const char *key;
254
0
  int save;
255
0
  int i, n;
256
257
0
  n = js_gettop(J) - 1;
258
0
  for (i = 4; i < n; ++i)
259
0
    if (js_isobject(J, i))
260
0
      if (js_toobject(J, i) == js_toobject(J, -1))
261
0
        js_typeerror(J, "cyclic object value");
262
263
0
  n = 0;
264
0
  js_putc(J, sb, '{');
265
0
  js_pushiterator(J, -1, 1);
266
0
  while ((key = js_nextiterator(J, -1))) {
267
0
    if (filterprop(J, key)) {
268
0
      save = (*sb)->n;
269
0
      if (n) js_putc(J, sb, ',');
270
0
      if (gap) fmtindent(J, sb, gap, level + 1);
271
0
      fmtstr(J, sb, key);
272
0
      js_putc(J, sb, ':');
273
0
      if (gap)
274
0
        js_putc(J, sb, ' ');
275
0
      js_rot2(J);
276
0
      if (!fmtvalue(J, sb, key, gap, level + 1))
277
0
        (*sb)->n = save;
278
0
      else
279
0
        ++n;
280
0
      js_rot2(J);
281
0
    }
282
0
  }
283
0
  js_pop(J, 1);
284
0
  if (gap && n) fmtindent(J, sb, gap, level);
285
0
  js_putc(J, sb, '}');
286
0
}
287
288
static void fmtarray(js_State *J, js_Buffer **sb, const char *gap, int level)
289
0
{
290
0
  int n, i;
291
0
  char buf[32];
292
293
0
  n = js_gettop(J) - 1;
294
0
  for (i = 4; i < n; ++i)
295
0
    if (js_isobject(J, i))
296
0
      if (js_toobject(J, i) == js_toobject(J, -1))
297
0
        js_typeerror(J, "cyclic object value");
298
299
0
  js_putc(J, sb, '[');
300
0
  n = js_getlength(J, -1);
301
0
  for (i = 0; i < n; ++i) {
302
0
    if (i) js_putc(J, sb, ',');
303
0
    if (gap) fmtindent(J, sb, gap, level + 1);
304
0
    if (!fmtvalue(J, sb, js_itoa(buf, i), gap, level + 1))
305
0
      js_puts(J, sb, "null");
306
0
  }
307
0
  if (gap && n) fmtindent(J, sb, gap, level);
308
0
  js_putc(J, sb, ']');
309
0
}
310
311
static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level)
312
0
{
313
  /* replacer/property-list is in 2 */
314
  /* holder is in -1 */
315
316
0
  js_getproperty(J, -1, key);
317
318
0
  if (js_isobject(J, -1)) {
319
0
    if (js_hasproperty(J, -1, "toJSON")) {
320
0
      if (js_iscallable(J, -1)) {
321
0
        js_copy(J, -2);
322
0
        js_pushstring(J, key);
323
0
        js_call(J, 1);
324
0
        js_rot2pop1(J);
325
0
      } else {
326
0
        js_pop(J, 1);
327
0
      }
328
0
    }
329
0
  }
330
331
0
  if (js_iscallable(J, 2)) {
332
0
    js_copy(J, 2); /* replacer function */
333
0
    js_copy(J, -3); /* holder as this */
334
0
    js_pushstring(J, key); /* name */
335
0
    js_copy(J, -4); /* old value */
336
0
    js_call(J, 2);
337
0
    js_rot2pop1(J); /* pop old value, leave new value on stack */
338
0
  }
339
340
0
  if (js_isobject(J, -1) && !js_iscallable(J, -1)) {
341
0
    js_Object *obj = js_toobject(J, -1);
342
0
    switch (obj->type) {
343
0
    case JS_CNUMBER: fmtnum(J, sb, obj->u.number); break;
344
0
    case JS_CSTRING: fmtstr(J, sb, obj->u.s.string); break;
345
0
    case JS_CBOOLEAN: js_puts(J, sb, obj->u.boolean ? "true" : "false"); break;
346
0
    case JS_CARRAY: fmtarray(J, sb, gap, level); break;
347
0
    default: fmtobject(J, sb, obj, gap, level); break;
348
0
    }
349
0
  }
350
0
  else if (js_isboolean(J, -1))
351
0
    js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false");
352
0
  else if (js_isnumber(J, -1))
353
0
    fmtnum(J, sb, js_tonumber(J, -1));
354
0
  else if (js_isstring(J, -1))
355
0
    fmtstr(J, sb, js_tostring(J, -1));
356
0
  else if (js_isnull(J, -1))
357
0
    js_puts(J, sb, "null");
358
0
  else {
359
0
    js_pop(J, 1);
360
0
    return 0;
361
0
  }
362
363
0
  js_pop(J, 1);
364
0
  return 1;
365
0
}
366
367
static void JSON_stringify(js_State *J)
368
0
{
369
0
  js_Buffer *sb = NULL;
370
0
  char buf[12];
371
  /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */
372
0
  const char * volatile gap;
373
0
  const char *s;
374
0
  int n;
375
376
0
  gap = NULL;
377
378
0
  if (js_isnumber(J, 3) || js_isnumberobject(J, 3)) {
379
0
    n = js_tointeger(J, 3);
380
0
    if (n < 0) n = 0;
381
0
    if (n > 10) n = 10;
382
0
    memset(buf, ' ', n);
383
0
    buf[n] = 0;
384
0
    if (n > 0) gap = buf;
385
0
  } else if (js_isstring(J, 3) || js_isstringobject(J, 3)) {
386
0
    s = js_tostring(J, 3);
387
0
    n = strlen(s);
388
0
    if (n > 10) n = 10;
389
0
    memcpy(buf, s, n);
390
0
    buf[n] = 0;
391
0
    if (n > 0) gap = buf;
392
0
  }
393
394
0
  if (js_try(J)) {
395
0
    js_free(J, sb);
396
0
    js_throw(J);
397
0
  }
398
399
0
  js_newobject(J); /* wrapper */
400
0
  js_copy(J, 1);
401
0
  js_defproperty(J, -2, "", 0);
402
0
  if (!fmtvalue(J, &sb, "", gap, 0)) {
403
0
    js_pushundefined(J);
404
0
  } else {
405
0
    js_putc(J, &sb, 0);
406
0
    js_pushstring(J, sb ? sb->s : "");
407
0
    js_rot2pop1(J);
408
0
  }
409
410
0
  js_endtry(J);
411
0
  js_free(J, sb);
412
0
}
413
414
void jsB_initjson(js_State *J)
415
0
{
416
0
  js_pushobject(J, jsV_newobject(J, JS_CJSON, J->Object_prototype));
417
0
  {
418
0
    jsB_propf(J, "JSON.parse", JSON_parse, 2);
419
0
    jsB_propf(J, "JSON.stringify", JSON_stringify, 3);
420
0
  }
421
0
  js_defglobal(J, "JSON", JS_DONTENUM);
422
0
}