/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/reader.h> |
25 | | #include <cm3p/json/value.h> |
26 | | |
27 | | // Limit input size |
28 | | static constexpr size_t kMaxInputSize = 256 * 1024; // 256KB |
29 | | |
30 | | // Recursive helper to access all values (exercises accessor code) |
31 | | static void TraverseValue(Json::Value const& value, int depth = 0) |
32 | 50.5k | { |
33 | | // Prevent stack overflow on deeply nested structures |
34 | 50.5k | if (depth > 100) { |
35 | 74 | return; |
36 | 74 | } |
37 | | |
38 | 50.4k | switch (value.type()) { |
39 | 186 | case Json::nullValue: |
40 | 186 | (void)value.isNull(); |
41 | 186 | break; |
42 | 41.9k | case Json::intValue: |
43 | 41.9k | (void)value.asInt64(); |
44 | 41.9k | break; |
45 | 111 | case Json::uintValue: |
46 | 111 | (void)value.asUInt64(); |
47 | 111 | break; |
48 | 1.02k | case Json::realValue: |
49 | 1.02k | (void)value.asDouble(); |
50 | 1.02k | break; |
51 | 1.91k | case Json::stringValue: |
52 | 1.91k | (void)value.asString(); |
53 | 1.91k | break; |
54 | 739 | case Json::booleanValue: |
55 | 739 | (void)value.asBool(); |
56 | 739 | break; |
57 | 2.65k | case Json::arrayValue: |
58 | 42.6k | for (Json::ArrayIndex i = 0; i < value.size() && i < 1000; ++i) { |
59 | 40.0k | TraverseValue(value[i], depth + 1); |
60 | 40.0k | } |
61 | 2.65k | break; |
62 | 1.89k | case Json::objectValue: |
63 | 9.15k | for (auto const& name : value.getMemberNames()) { |
64 | 9.15k | (void)name; |
65 | 9.15k | TraverseValue(value[name], depth + 1); |
66 | 9.15k | } |
67 | 1.89k | break; |
68 | 50.4k | } |
69 | 50.4k | } |
70 | | |
71 | | extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) |
72 | 3.23k | { |
73 | 3.23k | if (size == 0 || size > kMaxInputSize) { |
74 | 6 | return 0; |
75 | 6 | } |
76 | | |
77 | 3.22k | std::string input(reinterpret_cast<char const*>(data), size); |
78 | | |
79 | 3.22k | Json::Value root; |
80 | 3.22k | Json::CharReaderBuilder builder; |
81 | 3.22k | std::string errors; |
82 | | |
83 | 3.22k | std::istringstream stream(input); |
84 | | |
85 | | // Try parsing with default settings |
86 | 3.22k | bool success = Json::parseFromStream(builder, stream, &root, &errors); |
87 | | |
88 | 3.22k | if (success) { |
89 | | // Traverse the parsed structure |
90 | 1.04k | TraverseValue(root); |
91 | 1.04k | } |
92 | | |
93 | | // Also try with strict mode |
94 | 3.22k | builder["strictRoot"] = true; |
95 | 3.22k | builder["allowComments"] = false; |
96 | 3.22k | builder["allowTrailingCommas"] = false; |
97 | | |
98 | 3.22k | stream.clear(); |
99 | 3.22k | stream.str(input); |
100 | | |
101 | 3.22k | success = Json::parseFromStream(builder, stream, &root, &errors); |
102 | 3.22k | if (success) { |
103 | 298 | TraverseValue(root); |
104 | 298 | } |
105 | | |
106 | 3.22k | return 0; |
107 | 3.23k | } |