Coverage Report

Created: 2026-09-14 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Tests/Fuzzing/cmCMakePresetsFuzzer.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 CMakePresets.json / CMakeUserPresets.json handling
6
 *
7
 * cmJSONParserFuzzer already covers the jsoncpp syntax layer. This fuzzer
8
 * targets the layer above it, cmCMakePresetsGraph, which turns a parsed
9
 * document into a resolved preset graph.
10
 *
11
 * That code runs before any build logic does: `cmake --list-presets` reads
12
 * these files without configuring anything, and IDEs parse them to populate
13
 * their UI as soon as a folder is opened. The input is therefore untrusted
14
 * data rather than the trusted, executable build code that CMakeLists.txt is.
15
 *
16
 * Coverage targets:
17
 * - schema validation of configure/build/test/package/workflow presets
18
 * - "include" chains between preset files, and their cycle detection
19
 * - "inherits" resolution, and its cycle detection
20
 * - macro expansion ($env{}, $penv{}, ${sourceDir}, ...)
21
 * - version gating and the project/user file interaction
22
 */
23
24
#include <cstddef>
25
#include <cstdint>
26
#include <cstdio>
27
#include <cstdlib>
28
#include <initializer_list>
29
#include <string>
30
31
#include <unistd.h>
32
33
#include "cmCMakePresetsGraph.h"
34
#include "cmMessageMetadata.h"
35
#include "cmSystemTools.h"
36
37
static constexpr size_t kMaxInputSize = 64 * 1024;
38
39
static std::string g_testDir;
40
static std::string g_projectFile;
41
static std::string g_userFile;
42
static std::string g_explicitFile;
43
static std::string g_includedFile;
44
45
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
46
10
{
47
10
  (void)argc;
48
10
  (void)argv;
49
50
  // Suppress output during fuzzing (set once at init)
51
10
  cmSystemTools::SetMessageCallback(
52
10
    [](std::string const&, cmMessageMetadata const&) {});
53
10
  cmSystemTools::SetStdoutCallback([](std::string const&) {});
54
10
  cmSystemTools::SetStderrCallback([](std::string const&) {});
55
56
10
  char tmpl[] = "/tmp/cmake_fuzz_presets_XXXXXX";
57
10
  char* dir = mkdtemp(tmpl);
58
10
  if (dir) {
59
10
    g_testDir = dir;
60
10
  } else {
61
0
    g_testDir = "/tmp/cmake_fuzz_presets";
62
0
    cmSystemTools::MakeDirectory(g_testDir);
63
0
  }
64
65
10
  g_projectFile = g_testDir + "/CMakePresets.json";
66
10
  g_userFile = g_testDir + "/CMakeUserPresets.json";
67
10
  g_explicitFile = g_testDir + "/explicit.json";
68
10
  g_includedFile = g_testDir + "/included.json";
69
70
10
  return 0;
71
10
}
72
73
namespace {
74
75
// Which root file is on disk decides which reader runs, so the layout *is* the
76
// mode -- see the comment in RunOneLayout().
77
enum class RootLayout
78
{
79
  Project,
80
  User,
81
  Explicit,
82
};
83
84
bool WriteWholeFile(std::string const& path, uint8_t const* data, size_t size)
85
45.0k
{
86
45.0k
  FILE* fp = fopen(path.c_str(), "wb");
87
45.0k
  if (!fp) {
88
0
    return false;
89
0
  }
90
45.0k
  bool const ok = fwrite(data, 1, size, fp) == size;
91
45.0k
  fclose(fp);
92
45.0k
  return ok;
93
45.0k
}
94
95
// Run the whole pipeline once, for one root layout, from a clean directory.
96
void RunOneLayout(RootLayout layout, uint8_t const* data, size_t size)
97
33.7k
{
98
33.7k
  unlink(g_projectFile.c_str());
99
33.7k
  unlink(g_userFile.c_str());
100
33.7k
  unlink(g_explicitFile.c_str());
101
102
33.7k
  std::string presetsFileArg;
103
33.7k
  switch (layout) {
104
11.2k
    case RootLayout::Project:
105
11.2k
      if (!WriteWholeFile(g_projectFile, data, size)) {
106
0
        return;
107
0
      }
108
11.2k
      break;
109
11.2k
    case RootLayout::User:
110
11.2k
      if (!WriteWholeFile(g_userFile, data, size)) {
111
0
        return;
112
0
      }
113
11.2k
      break;
114
11.2k
    case RootLayout::Explicit:
115
11.2k
      if (!WriteWholeFile(g_explicitFile, data, size)) {
116
0
        return;
117
0
      }
118
11.2k
      presetsFileArg = g_explicitFile;
119
11.2k
      break;
120
33.7k
  }
121
122
  // A fresh graph per layout: ClearPresets() empties the preset maps but
123
  // leaves "errors" and "parseState" behind, so a reused graph would carry
124
  // diagnostic state from one layout into the next.
125
33.7k
  cmCMakePresetsGraph graph;
126
33.7k
  if (!graph.ReadProjectPresets(g_testDir, presetsFileArg)) {
127
27.0k
    return;
128
27.0k
  }
129
130
  // Walking the graph reaches the inheritance and macro-expansion results, not
131
  // just the parse that produced them.
132
21.4k
  for (auto const& it : graph.ConfigurePresets) {
133
21.4k
    (void)graph.GetGeneratorForPreset(it.first);
134
21.4k
    (void)it.second.Expanded.has_value();
135
21.4k
  }
136
6.74k
  for (auto const& it : graph.BuildPresets) {
137
279
    (void)graph.GetGeneratorForPreset(it.first);
138
279
    (void)it.second.Expanded.has_value();
139
279
  }
140
6.74k
  for (auto const& it : graph.TestPresets) {
141
234
    (void)graph.GetGeneratorForPreset(it.first);
142
234
    (void)it.second.Expanded.has_value();
143
234
  }
144
6.74k
  for (auto const& it : graph.PackagePresets) {
145
42
    (void)it.second.Expanded.has_value();
146
42
  }
147
6.74k
  for (auto const& it : graph.WorkflowPresets) {
148
39
    (void)it.second.Expanded.has_value();
149
39
  }
150
6.74k
}
151
152
}
153
154
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
155
11.2k
{
156
11.2k
  if (size == 0 || size > kMaxInputSize) {
157
7
    return 0;
158
7
  }
159
160
11.2k
  if (!WriteWholeFile(g_includedFile, data, size)) {
161
0
    return 0;
162
0
  }
163
164
11.2k
  for (RootLayout layout :
165
33.7k
       { RootLayout::Project, RootLayout::User, RootLayout::Explicit }) {
166
33.7k
    RunOneLayout(layout, data, size);
167
33.7k
  }
168
169
11.2k
  return 0;
170
11.2k
}