/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 | 66.1k | { |
33 | | // Prevent stack overflow on deeply nested structures |
34 | 66.1k | if (depth > 100) { |
35 | 200 | return; |
36 | 200 | } |
37 | | |
38 | 65.9k | switch (value.type()) { |
39 | 77 | case Json::nullValue: |
40 | 77 | (void)value.isNull(); |
41 | 77 | break; |
42 | 57.2k | case Json::intValue: |
43 | 57.2k | (void)value.asInt64(); |
44 | 57.2k | break; |
45 | 138 | case Json::uintValue: |
46 | 138 | (void)value.asUInt64(); |
47 | 138 | break; |
48 | 623 | case Json::realValue: |
49 | 623 | (void)value.asDouble(); |
50 | 623 | break; |
51 | 1.56k | case Json::stringValue: |
52 | 1.56k | (void)value.asString(); |
53 | 1.56k | break; |
54 | 1.66k | case Json::booleanValue: |
55 | 1.66k | (void)value.asBool(); |
56 | 1.66k | break; |
57 | 3.25k | case Json::arrayValue: |
58 | 59.7k | for (Json::ArrayIndex i = 0; i < value.size() && i < 1000; ++i) { |
59 | 56.4k | TraverseValue(value[i], depth + 1); |
60 | 56.4k | } |
61 | 3.25k | break; |
62 | 1.34k | case Json::objectValue: |
63 | 8.25k | for (auto const& name : value.getMemberNames()) { |
64 | 8.25k | (void)name; |
65 | 8.25k | TraverseValue(value[name], depth + 1); |
66 | 8.25k | } |
67 | 1.34k | break; |
68 | 65.9k | } |
69 | 65.9k | } |
70 | | |
71 | | extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) |
72 | 3.43k | { |
73 | 3.43k | if (size == 0 || size > kMaxInputSize) { |
74 | 6 | return 0; |
75 | 6 | } |
76 | | |
77 | 3.42k | std::string input(reinterpret_cast<char const*>(data), size); |
78 | | |
79 | 3.42k | Json::Value root; |
80 | 3.42k | Json::CharReaderBuilder builder; |
81 | 3.42k | std::string errors; |
82 | | |
83 | 3.42k | std::istringstream stream(input); |
84 | | |
85 | | // Try parsing with default settings |
86 | 3.42k | bool success = Json::parseFromStream(builder, stream, &root, &errors); |
87 | | |
88 | 3.42k | if (success) { |
89 | | // Traverse the parsed structure |
90 | 1.13k | TraverseValue(root); |
91 | 1.13k | } |
92 | | |
93 | | // Also try with strict mode |
94 | 3.42k | builder["strictRoot"] = true; |
95 | 3.42k | builder["allowComments"] = false; |
96 | 3.42k | builder["allowTrailingCommas"] = false; |
97 | | |
98 | 3.42k | stream.clear(); |
99 | 3.42k | stream.str(input); |
100 | | |
101 | 3.42k | success = Json::parseFromStream(builder, stream, &root, &errors); |
102 | 3.42k | if (success) { |
103 | 279 | TraverseValue(root); |
104 | 279 | } |
105 | | |
106 | 3.42k | return 0; |
107 | 3.43k | } |