/src/CMake/Tests/Fuzzing/cmJSONParserFuzzer.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | /* |
5 | | * Fuzzer for CMake's JSON parsing (via jsoncpp) |
6 | | * |
7 | | * CMake parses JSON files for CMakePresets.json, compile_commands.json, |
8 | | * and various other configuration files. This fuzzer tests the JSON |
9 | | * parser for crashes and undefined behavior. |
10 | | * |
11 | | * Coverage targets: |
12 | | * - JSON value parsing (strings, numbers, booleans, null) |
13 | | * - Array and object parsing |
14 | | * - Nested structures |
15 | | * - Unicode handling |
16 | | * - Error recovery |
17 | | */ |
18 | | |
19 | | #include <cstddef> |
20 | | #include <cstdint> |
21 | | #include <sstream> |
22 | | #include <string> |
23 | | |
24 | | #include <cm3p/json/value.h> |
25 | | |
26 | | #include "cmJSONState.h" |
27 | | |
28 | | // Limit input size |
29 | | static constexpr size_t kMaxInputSize = 256 * 1024; // 256KB |
30 | | |
31 | | // Recursive helper to access all values (exercises accessor code) |
32 | | static void TraverseValue(Json::Value const& value, int depth = 0) |
33 | 168k | { |
34 | | // Prevent stack overflow on deeply nested structures |
35 | 168k | if (depth > 100) { |
36 | 600 | return; |
37 | 600 | } |
38 | | |
39 | 167k | switch (value.type()) { |
40 | 3.92k | case Json::nullValue: |
41 | 3.92k | (void)value.isNull(); |
42 | 3.92k | break; |
43 | 107k | case Json::intValue: |
44 | 107k | (void)value.asInt64(); |
45 | 107k | break; |
46 | 561 | case Json::uintValue: |
47 | 561 | (void)value.asUInt64(); |
48 | 561 | break; |
49 | 3.13k | case Json::realValue: |
50 | 3.13k | (void)value.asDouble(); |
51 | 3.13k | break; |
52 | 1.40k | case Json::stringValue: |
53 | 1.40k | (void)value.asString(); |
54 | 1.40k | break; |
55 | 3.34k | case Json::booleanValue: |
56 | 3.34k | (void)value.asBool(); |
57 | 3.34k | break; |
58 | 33.0k | case Json::arrayValue: |
59 | 153k | for (Json::ArrayIndex i = 0; i < value.size() && i < 1000; ++i) { |
60 | 120k | TraverseValue(value[i], depth + 1); |
61 | 120k | } |
62 | 33.0k | break; |
63 | 14.4k | case Json::objectValue: |
64 | 41.6k | for (auto const& name : value.getMemberNames()) { |
65 | 41.6k | (void)name; |
66 | 41.6k | TraverseValue(value[name], depth + 1); |
67 | 41.6k | } |
68 | 14.4k | break; |
69 | 167k | } |
70 | 167k | } |
71 | | |
72 | | extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) |
73 | 3.52k | { |
74 | 3.52k | if (size == 0 || size > kMaxInputSize) { |
75 | 6 | return 0; |
76 | 6 | } |
77 | | |
78 | 3.51k | std::string input(reinterpret_cast<char const*>(data), size); |
79 | | |
80 | 3.51k | { |
81 | 3.51k | Json::Value root; |
82 | 3.51k | std::istringstream stream(input); |
83 | | |
84 | | // Try parsing with default settings |
85 | 3.51k | cmJSONState parseState(stream, &root, cmJSONState::StrictMode::Relaxed); |
86 | 3.51k | if (!parseState.errors.empty()) { |
87 | | // Traverse the parsed structure |
88 | 2.64k | TraverseValue(root); |
89 | 2.64k | } |
90 | 3.51k | } |
91 | | |
92 | 3.51k | { |
93 | 3.51k | Json::Value root; |
94 | 3.51k | std::istringstream stream(input); |
95 | | |
96 | | // Also try with strict mode |
97 | 3.51k | cmJSONState parseState(stream, &root, cmJSONState::StrictMode::Strict); |
98 | 3.51k | if (!parseState.errors.empty()) { |
99 | | // Traverse the parsed structure |
100 | 3.48k | TraverseValue(root); |
101 | 3.48k | } |
102 | 3.51k | } |
103 | | |
104 | 3.51k | return 0; |
105 | 3.52k | } |