Coverage Report

Created: 2026-09-15 16:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/univalue/lib/univalue_read.cpp
Line
Count
Source
1
// Copyright 2014 BitPay Inc.
2
// Copyright (c) 2015-present The Bitcoin Core developers
3
// Distributed under the MIT software license, see the accompanying
4
// file COPYING or https://opensource.org/licenses/mit-license.php.
5
6
#include <univalue.h>
7
#include <univalue_utffilter.h>
8
9
#include <cstdint>
10
#include <cstring>
11
#include <string>
12
#include <string_view>
13
#include <utility>
14
#include <vector>
15
16
/*
17
 * According to stackexchange, the original json test suite wanted
18
 * to limit depth to 22.  Widely-deployed PHP bails at depth 512,
19
 * so we will follow PHP's lead, which should be more than sufficient
20
 * (further stackexchange comments indicate depth > 32 rarely occurs).
21
 */
22
static constexpr size_t MAX_JSON_DEPTH = 512;
23
24
static bool json_isdigit(int ch)
25
0
{
26
0
    return ((ch >= '0') && (ch <= '9'));
  Branch (26:13): [True: 0, False: 0]
  Branch (26:28): [True: 0, False: 0]
27
0
}
28
29
// convert hexadecimal string to unsigned integer
30
static const char *hatoui(const char *first, const char *last,
31
                          unsigned int& out)
32
0
{
33
0
    unsigned int result = 0;
34
0
    for (; first != last; ++first)
  Branch (34:12): [True: 0, False: 0]
35
0
    {
36
0
        int digit;
37
0
        if (json_isdigit(*first))
  Branch (37:13): [True: 0, False: 0]
38
0
            digit = *first - '0';
39
40
0
        else if (*first >= 'a' && *first <= 'f')
  Branch (40:18): [True: 0, False: 0]
  Branch (40:35): [True: 0, False: 0]
41
0
            digit = *first - 'a' + 10;
42
43
0
        else if (*first >= 'A' && *first <= 'F')
  Branch (43:18): [True: 0, False: 0]
  Branch (43:35): [True: 0, False: 0]
44
0
            digit = *first - 'A' + 10;
45
46
0
        else
47
0
            break;
48
49
0
        result = 16 * result + digit;
50
0
    }
51
0
    out = result;
52
53
0
    return first;
54
0
}
55
56
enum jtokentype getJsonToken(std::string& tokenVal, unsigned int& consumed,
57
                            const char *raw, const char *end)
58
0
{
59
0
    tokenVal.clear();
60
0
    consumed = 0;
61
62
0
    const char *rawStart = raw;
63
64
0
    while (raw < end && (json_isspace(*raw)))          // skip whitespace
  Branch (64:12): [True: 0, False: 0]
  Branch (64:25): [True: 0, False: 0]
65
0
        raw++;
66
67
0
    if (raw >= end)
  Branch (67:9): [True: 0, False: 0]
68
0
        return JTOK_NONE;
69
70
0
    switch (*raw) {
71
72
0
    case '{':
  Branch (72:5): [True: 0, False: 0]
73
0
        raw++;
74
0
        consumed = (raw - rawStart);
75
0
        return JTOK_OBJ_OPEN;
76
0
    case '}':
  Branch (76:5): [True: 0, False: 0]
77
0
        raw++;
78
0
        consumed = (raw - rawStart);
79
0
        return JTOK_OBJ_CLOSE;
80
0
    case '[':
  Branch (80:5): [True: 0, False: 0]
81
0
        raw++;
82
0
        consumed = (raw - rawStart);
83
0
        return JTOK_ARR_OPEN;
84
0
    case ']':
  Branch (84:5): [True: 0, False: 0]
85
0
        raw++;
86
0
        consumed = (raw - rawStart);
87
0
        return JTOK_ARR_CLOSE;
88
89
0
    case ':':
  Branch (89:5): [True: 0, False: 0]
90
0
        raw++;
91
0
        consumed = (raw - rawStart);
92
0
        return JTOK_COLON;
93
0
    case ',':
  Branch (93:5): [True: 0, False: 0]
94
0
        raw++;
95
0
        consumed = (raw - rawStart);
96
0
        return JTOK_COMMA;
97
98
0
    case 'n':
  Branch (98:5): [True: 0, False: 0]
99
0
    case 't':
  Branch (99:5): [True: 0, False: 0]
100
0
    case 'f':
  Branch (100:5): [True: 0, False: 0]
101
0
        if (!strncmp(raw, "null", 4)) {
  Branch (101:13): [True: 0, False: 0]
102
0
            raw += 4;
103
0
            consumed = (raw - rawStart);
104
0
            return JTOK_KW_NULL;
105
0
        } else if (!strncmp(raw, "true", 4)) {
  Branch (105:20): [True: 0, False: 0]
106
0
            raw += 4;
107
0
            consumed = (raw - rawStart);
108
0
            return JTOK_KW_TRUE;
109
0
        } else if (!strncmp(raw, "false", 5)) {
  Branch (109:20): [True: 0, False: 0]
110
0
            raw += 5;
111
0
            consumed = (raw - rawStart);
112
0
            return JTOK_KW_FALSE;
113
0
        } else
114
0
            return JTOK_ERR;
115
116
0
    case '-':
  Branch (116:5): [True: 0, False: 0]
117
0
    case '0':
  Branch (117:5): [True: 0, False: 0]
118
0
    case '1':
  Branch (118:5): [True: 0, False: 0]
119
0
    case '2':
  Branch (119:5): [True: 0, False: 0]
120
0
    case '3':
  Branch (120:5): [True: 0, False: 0]
121
0
    case '4':
  Branch (121:5): [True: 0, False: 0]
122
0
    case '5':
  Branch (122:5): [True: 0, False: 0]
123
0
    case '6':
  Branch (123:5): [True: 0, False: 0]
124
0
    case '7':
  Branch (124:5): [True: 0, False: 0]
125
0
    case '8':
  Branch (125:5): [True: 0, False: 0]
126
0
    case '9': {
  Branch (126:5): [True: 0, False: 0]
127
        // part 1: int
128
0
        std::string numStr;
129
130
0
        const char *first = raw;
131
132
0
        const char *firstDigit = first;
133
0
        if (!json_isdigit(*firstDigit))
  Branch (133:13): [True: 0, False: 0]
134
0
            firstDigit++;
135
0
        if ((*firstDigit == '0') && json_isdigit(firstDigit[1]))
  Branch (135:13): [True: 0, False: 0]
  Branch (135:37): [True: 0, False: 0]
136
0
            return JTOK_ERR;
137
138
0
        numStr += *raw;                       // copy first char
139
0
        raw++;
140
141
0
        if ((*first == '-') && (raw < end) && (!json_isdigit(*raw)))
  Branch (141:13): [True: 0, False: 0]
  Branch (141:32): [True: 0, False: 0]
  Branch (141:47): [True: 0, False: 0]
142
0
            return JTOK_ERR;
143
144
0
        while (raw < end && json_isdigit(*raw)) {  // copy digits
  Branch (144:16): [True: 0, False: 0]
  Branch (144:29): [True: 0, False: 0]
145
0
            numStr += *raw;
146
0
            raw++;
147
0
        }
148
149
        // part 2: frac
150
0
        if (raw < end && *raw == '.') {
  Branch (150:13): [True: 0, False: 0]
  Branch (150:26): [True: 0, False: 0]
151
0
            numStr += *raw;                   // copy .
152
0
            raw++;
153
154
0
            if (raw >= end || !json_isdigit(*raw))
  Branch (154:17): [True: 0, False: 0]
  Branch (154:31): [True: 0, False: 0]
155
0
                return JTOK_ERR;
156
0
            while (raw < end && json_isdigit(*raw)) { // copy digits
  Branch (156:20): [True: 0, False: 0]
  Branch (156:33): [True: 0, False: 0]
157
0
                numStr += *raw;
158
0
                raw++;
159
0
            }
160
0
        }
161
162
        // part 3: exp
163
0
        if (raw < end && (*raw == 'e' || *raw == 'E')) {
  Branch (163:13): [True: 0, False: 0]
  Branch (163:27): [True: 0, False: 0]
  Branch (163:42): [True: 0, False: 0]
164
0
            numStr += *raw;                   // copy E
165
0
            raw++;
166
167
0
            if (raw < end && (*raw == '-' || *raw == '+')) { // copy +/-
  Branch (167:17): [True: 0, False: 0]
  Branch (167:31): [True: 0, False: 0]
  Branch (167:46): [True: 0, False: 0]
168
0
                numStr += *raw;
169
0
                raw++;
170
0
            }
171
172
0
            if (raw >= end || !json_isdigit(*raw))
  Branch (172:17): [True: 0, False: 0]
  Branch (172:31): [True: 0, False: 0]
173
0
                return JTOK_ERR;
174
0
            while (raw < end && json_isdigit(*raw)) { // copy digits
  Branch (174:20): [True: 0, False: 0]
  Branch (174:33): [True: 0, False: 0]
175
0
                numStr += *raw;
176
0
                raw++;
177
0
            }
178
0
        }
179
180
0
        tokenVal = numStr;
181
0
        consumed = (raw - rawStart);
182
0
        return JTOK_NUMBER;
183
0
        }
184
185
0
    case '"': {
  Branch (185:5): [True: 0, False: 0]
186
0
        raw++;                                // skip "
187
188
0
        std::string valStr;
189
0
        JSONUTF8StringFilter writer(valStr);
190
191
0
        while (true) {
  Branch (191:16): [Folded - Ignored]
192
0
            if (raw >= end || (unsigned char)*raw < 0x20)
  Branch (192:17): [True: 0, False: 0]
  Branch (192:31): [True: 0, False: 0]
193
0
                return JTOK_ERR;
194
195
0
            else if (*raw == '\\') {
  Branch (195:22): [True: 0, False: 0]
196
0
                raw++;                        // skip backslash
197
198
0
                if (raw >= end)
  Branch (198:21): [True: 0, False: 0]
199
0
                    return JTOK_ERR;
200
201
0
                switch (*raw) {
202
0
                case '"':  writer.push_back('\"'); break;
  Branch (202:17): [True: 0, False: 0]
203
0
                case '\\': writer.push_back('\\'); break;
  Branch (203:17): [True: 0, False: 0]
204
0
                case '/':  writer.push_back('/'); break;
  Branch (204:17): [True: 0, False: 0]
205
0
                case 'b':  writer.push_back('\b'); break;
  Branch (205:17): [True: 0, False: 0]
206
0
                case 'f':  writer.push_back('\f'); break;
  Branch (206:17): [True: 0, False: 0]
207
0
                case 'n':  writer.push_back('\n'); break;
  Branch (207:17): [True: 0, False: 0]
208
0
                case 'r':  writer.push_back('\r'); break;
  Branch (208:17): [True: 0, False: 0]
209
0
                case 't':  writer.push_back('\t'); break;
  Branch (209:17): [True: 0, False: 0]
210
211
0
                case 'u': {
  Branch (211:17): [True: 0, False: 0]
212
0
                    unsigned int codepoint;
213
0
                    if (raw + 1 + 4 >= end ||
  Branch (213:25): [True: 0, False: 0]
214
0
                        hatoui(raw + 1, raw + 1 + 4, codepoint) !=
  Branch (214:25): [True: 0, False: 0]
215
0
                               raw + 1 + 4)
216
0
                        return JTOK_ERR;
217
0
                    writer.push_back_u(codepoint);
218
0
                    raw += 4;
219
0
                    break;
220
0
                    }
221
0
                default:
  Branch (221:17): [True: 0, False: 0]
222
0
                    return JTOK_ERR;
223
224
0
                }
225
226
0
                raw++;                        // skip esc'd char
227
0
            }
228
229
0
            else if (*raw == '"') {
  Branch (229:22): [True: 0, False: 0]
230
0
                raw++;                        // skip "
231
0
                break;                        // stop scanning
232
0
            }
233
234
0
            else {
235
0
                writer.push_back(static_cast<unsigned char>(*raw));
236
0
                raw++;
237
0
            }
238
0
        }
239
240
0
        if (!writer.finalize())
  Branch (240:13): [True: 0, False: 0]
241
0
            return JTOK_ERR;
242
0
        tokenVal = valStr;
243
0
        consumed = (raw - rawStart);
244
0
        return JTOK_STRING;
245
0
        }
246
247
0
    default:
  Branch (247:5): [True: 0, False: 0]
248
0
        return JTOK_ERR;
249
0
    }
250
0
}
251
252
enum expect_bits : unsigned {
253
    EXP_OBJ_NAME = (1U << 0),
254
    EXP_COLON = (1U << 1),
255
    EXP_ARR_VALUE = (1U << 2),
256
    EXP_VALUE = (1U << 3),
257
    EXP_NOT_VALUE = (1U << 4),
258
};
259
260
0
#define expect(bit) (expectMask & (EXP_##bit))
261
0
#define setExpect(bit) (expectMask |= EXP_##bit)
262
0
#define clearExpect(bit) (expectMask &= ~EXP_##bit)
263
264
bool UniValue::read(std::string_view json)
265
0
{
266
0
    UniValue parsed;
267
0
    if (!parsed.read_impl(json)) {
  Branch (267:9): [True: 0, False: 0]
268
0
        setNull();
269
0
        return false;
270
0
    }
271
0
    *this = std::move(parsed);
272
0
    return true;
273
0
}
274
275
bool UniValue::read_impl(std::string_view str_in)
276
0
{
277
0
    clear();
278
279
0
    uint32_t expectMask = 0;
280
0
    std::vector<UniValue*> stack;
281
282
0
    std::string tokenVal;
283
0
    unsigned int consumed;
284
0
    enum jtokentype tok = JTOK_NONE;
285
0
    enum jtokentype last_tok = JTOK_NONE;
286
0
    const char* raw{str_in.data()};
287
0
    const char* end{raw + str_in.size()};
288
0
    do {
289
0
        last_tok = tok;
290
291
0
        tok = getJsonToken(tokenVal, consumed, raw, end);
292
0
        if (tok == JTOK_NONE || tok == JTOK_ERR)
  Branch (292:13): [True: 0, False: 0]
  Branch (292:33): [True: 0, False: 0]
293
0
            return false;
294
0
        raw += consumed;
295
296
0
        bool isValueOpen = jsonTokenIsValue(tok) ||
  Branch (296:28): [True: 0, False: 0]
297
0
            tok == JTOK_OBJ_OPEN || tok == JTOK_ARR_OPEN;
  Branch (297:13): [True: 0, False: 0]
  Branch (297:37): [True: 0, False: 0]
298
299
0
        if (expect(VALUE)) {
300
0
            if (!isValueOpen)
  Branch (300:17): [True: 0, False: 0]
301
0
                return false;
302
0
            clearExpect(VALUE);
303
304
0
        } else if (expect(ARR_VALUE)) {
305
0
            bool isArrValue = isValueOpen || (tok == JTOK_ARR_CLOSE);
  Branch (305:31): [True: 0, False: 0]
  Branch (305:46): [True: 0, False: 0]
306
0
            if (!isArrValue)
  Branch (306:17): [True: 0, False: 0]
307
0
                return false;
308
309
0
            clearExpect(ARR_VALUE);
310
311
0
        } else if (expect(OBJ_NAME)) {
312
0
            bool isObjName = (tok == JTOK_OBJ_CLOSE || tok == JTOK_STRING);
  Branch (312:31): [True: 0, False: 0]
  Branch (312:56): [True: 0, False: 0]
313
0
            if (!isObjName)
  Branch (313:17): [True: 0, False: 0]
314
0
                return false;
315
316
0
        } else if (expect(COLON)) {
317
0
            if (tok != JTOK_COLON)
  Branch (317:17): [True: 0, False: 0]
318
0
                return false;
319
0
            clearExpect(COLON);
320
321
0
        } else if (!expect(COLON) && (tok == JTOK_COLON)) {
  Branch (321:20): [True: 0, False: 0]
  Branch (321:38): [True: 0, False: 0]
322
0
            return false;
323
0
        }
324
325
0
        if (expect(NOT_VALUE)) {
326
0
            if (isValueOpen)
  Branch (326:17): [True: 0, False: 0]
327
0
                return false;
328
0
            clearExpect(NOT_VALUE);
329
0
        }
330
331
0
        switch (tok) {
332
333
0
        case JTOK_OBJ_OPEN:
  Branch (333:9): [True: 0, False: 0]
334
0
        case JTOK_ARR_OPEN: {
  Branch (334:9): [True: 0, False: 0]
335
0
            VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR);
  Branch (335:27): [True: 0, False: 0]
336
0
            if (!stack.size()) {
  Branch (336:17): [True: 0, False: 0]
337
0
                if (utyp == VOBJ)
  Branch (337:21): [True: 0, False: 0]
338
0
                    setObject();
339
0
                else
340
0
                    setArray();
341
0
                stack.push_back(this);
342
0
            } else {
343
0
                UniValue tmpVal(utyp);
344
0
                UniValue *top = stack.back();
345
0
                top->values.push_back(tmpVal);
346
347
0
                UniValue *newTop = &(top->values.back());
348
0
                stack.push_back(newTop);
349
0
            }
350
351
0
            if (stack.size() > MAX_JSON_DEPTH)
  Branch (351:17): [True: 0, False: 0]
352
0
                return false;
353
354
0
            if (utyp == VOBJ)
  Branch (354:17): [True: 0, False: 0]
355
0
                setExpect(OBJ_NAME);
356
0
            else
357
0
                setExpect(ARR_VALUE);
358
0
            break;
359
0
            }
360
361
0
        case JTOK_OBJ_CLOSE:
  Branch (361:9): [True: 0, False: 0]
362
0
        case JTOK_ARR_CLOSE: {
  Branch (362:9): [True: 0, False: 0]
363
0
            if (!stack.size() || (last_tok == JTOK_COMMA))
  Branch (363:17): [True: 0, False: 0]
  Branch (363:34): [True: 0, False: 0]
364
0
                return false;
365
366
0
            VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR);
  Branch (366:27): [True: 0, False: 0]
367
0
            UniValue *top = stack.back();
368
0
            if (utyp != top->getType())
  Branch (368:17): [True: 0, False: 0]
369
0
                return false;
370
371
0
            stack.pop_back();
372
0
            clearExpect(OBJ_NAME);
373
0
            setExpect(NOT_VALUE);
374
0
            break;
375
0
            }
376
377
0
        case JTOK_COLON: {
  Branch (377:9): [True: 0, False: 0]
378
0
            if (!stack.size())
  Branch (378:17): [True: 0, False: 0]
379
0
                return false;
380
381
0
            UniValue *top = stack.back();
382
0
            if (top->getType() != VOBJ)
  Branch (382:17): [True: 0, False: 0]
383
0
                return false;
384
385
0
            setExpect(VALUE);
386
0
            break;
387
0
            }
388
389
0
        case JTOK_COMMA: {
  Branch (389:9): [True: 0, False: 0]
390
0
            if (!stack.size() ||
  Branch (390:17): [True: 0, False: 0]
391
0
                (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN))
  Branch (391:17): [True: 0, False: 0]
  Branch (391:45): [True: 0, False: 0]
392
0
                return false;
393
394
0
            UniValue *top = stack.back();
395
0
            if (top->getType() == VOBJ)
  Branch (395:17): [True: 0, False: 0]
396
0
                setExpect(OBJ_NAME);
397
0
            else
398
0
                setExpect(ARR_VALUE);
399
0
            break;
400
0
            }
401
402
0
        case JTOK_KW_NULL:
  Branch (402:9): [True: 0, False: 0]
403
0
        case JTOK_KW_TRUE:
  Branch (403:9): [True: 0, False: 0]
404
0
        case JTOK_KW_FALSE: {
  Branch (404:9): [True: 0, False: 0]
405
0
            UniValue tmpVal;
406
0
            switch (tok) {
407
0
            case JTOK_KW_NULL:
  Branch (407:13): [True: 0, False: 0]
408
                // do nothing more
409
0
                break;
410
0
            case JTOK_KW_TRUE:
  Branch (410:13): [True: 0, False: 0]
411
0
                tmpVal.setBool(true);
412
0
                break;
413
0
            case JTOK_KW_FALSE:
  Branch (413:13): [True: 0, False: 0]
414
0
                tmpVal.setBool(false);
415
0
                break;
416
0
            default: /* impossible */ break;
  Branch (416:13): [True: 0, False: 0]
417
0
            }
418
419
0
            if (!stack.size()) {
  Branch (419:17): [True: 0, False: 0]
420
0
                *this = tmpVal;
421
0
                break;
422
0
            }
423
424
0
            UniValue *top = stack.back();
425
0
            top->values.push_back(tmpVal);
426
427
0
            setExpect(NOT_VALUE);
428
0
            break;
429
0
            }
430
431
0
        case JTOK_NUMBER: {
  Branch (431:9): [True: 0, False: 0]
432
0
            UniValue tmpVal(VNUM, tokenVal);
433
0
            if (!stack.size()) {
  Branch (433:17): [True: 0, False: 0]
434
0
                *this = tmpVal;
435
0
                break;
436
0
            }
437
438
0
            UniValue *top = stack.back();
439
0
            top->values.push_back(tmpVal);
440
441
0
            setExpect(NOT_VALUE);
442
0
            break;
443
0
            }
444
445
0
        case JTOK_STRING: {
  Branch (445:9): [True: 0, False: 0]
446
0
            if (expect(OBJ_NAME)) {
447
0
                UniValue *top = stack.back();
448
0
                top->keys.push_back(tokenVal);
449
0
                clearExpect(OBJ_NAME);
450
0
                setExpect(COLON);
451
0
            } else {
452
0
                UniValue tmpVal(VSTR, tokenVal);
453
0
                if (!stack.size()) {
  Branch (453:21): [True: 0, False: 0]
454
0
                    *this = tmpVal;
455
0
                    break;
456
0
                }
457
0
                UniValue *top = stack.back();
458
0
                top->values.push_back(tmpVal);
459
0
            }
460
461
0
            setExpect(NOT_VALUE);
462
0
            break;
463
0
            }
464
465
0
        default:
  Branch (465:9): [True: 0, False: 0]
466
0
            return false;
467
0
        }
468
0
    } while (!stack.empty ());
  Branch (468:14): [True: 0, False: 0]
469
470
    /* Check that nothing follows the initial construct (parsed above).  */
471
0
    tok = getJsonToken(tokenVal, consumed, raw, end);
472
0
    if (tok != JTOK_NONE)
  Branch (472:9): [True: 0, False: 0]
473
0
        return false;
474
475
0
    return true;
476
0
}