Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Tests/Fuzzing/cmPackageInfoReaderFuzzer.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 CPS (Common Package Specification) reader.
6
 *
7
 * cmPackageInfoReader.cxx is 694 lines at 0.00% coverage. It parses the .cps
8
 * JSON files that third-party packages install and that find_package() picks
9
 * up from whatever is on the search path, so the input is supplied by the
10
 * packages found on the machine rather than by the project being built.
11
 *
12
 * cmJSONParserFuzzer already covers jsoncpp itself. What is untested is
13
 * everything the reader does with the decoded value: schema-version gating,
14
 * prefix resolution against the file's own location, the "simple"/pep440
15
 * version grammar, requirement specs, and -- the bulk of the file -- turning
16
 * each entry under "components" into an imported target, with per-
17
 * configuration properties, link/compile features, definitions, file sets and
18
 * C++ module metadata.
19
 *
20
 * Reaching the target-import half needs a real cmMakefile, so each input gets
21
 * a fresh cmake/cmGlobalGenerator/cmMakefile fixture. That also keeps targets
22
 * created by one input from colliding with the next.
23
 *
24
 * The reader resolves its prefix by matching "cps_path" against the directory
25
 * the file was read from, so the input has to be a real file in a real
26
 * directory: the fixture writes it to <tmp>/cps/fuzz.cps, mirroring the layout
27
 * the upstream CPS tests use ("cps_path": "@prefix@/cps").
28
 *
29
 * Both the primary and the appendix read run on every input. An appendix is a
30
 * supplemental file read with an already-parsed package as its parent, which
31
 * skips the schema-version check and inherits the parent's prefix, components
32
 * and default configurations -- a different path through Read() that no
33
 * primary read can reach.
34
 */
35
36
#include <cstddef>
37
#include <cstdint>
38
#include <cstdio>
39
#include <memory>
40
#include <string>
41
42
#include <cm/memory>
43
#include <cm/optional>
44
45
#include "cmExecutionStatus.h"
46
#include "cmGlobalGenerator.h"
47
#include "cmMakefile.h"
48
#include "cmMessenger.h"
49
#include "cmPackageInfoReader.h"
50
#include "cmState.h"
51
#include "cmStateDirectory.h"
52
#include "cmStateSnapshot.h"
53
#include "cmSystemTools.h"
54
#include "cmTargetTypes.h"
55
#include "cmake.h"
56
57
static constexpr size_t kMaxInputSize = 256 * 1024;
58
59
static std::string g_prefixDir;
60
static std::string g_cpsFile;
61
62
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
63
12
{
64
12
  (void)argc;
65
12
  (void)argv;
66
67
12
  cmSystemTools::SetMessageCallback(
68
24.1k
    [](std::string const&, cmMessageMetadata const&) {});
69
12
  cmSystemTools::SetStdoutCallback([](std::string const&) {});
70
12
  cmSystemTools::SetStderrCallback([](std::string const&) {});
71
72
12
  char tmpl[] = "/tmp/cmake_fuzz_cps_XXXXXX";
73
12
  char* dir = mkdtemp(tmpl);
74
12
  if (dir) {
75
12
    g_prefixDir = dir;
76
12
  } else {
77
0
    g_prefixDir = "/tmp/cmake_fuzz_cps";
78
0
  }
79
80
12
  cmSystemTools::MakeDirectory(g_prefixDir + "/cps");
81
12
  g_cpsFile = g_prefixDir + "/cps/fuzz.cps";
82
83
12
  return 0;
84
12
}
85
86
namespace {
87
88
struct Fixture
89
{
90
  cmake CMake{ cmState::Role::Project };
91
  std::unique_ptr<cmGlobalGenerator> GG;
92
  std::unique_ptr<cmMakefile> MF;
93
94
  Fixture()
95
13.9k
  {
96
13.9k
    this->CMake.SetHomeDirectory(g_prefixDir);
97
13.9k
    this->CMake.SetHomeOutputDirectory(g_prefixDir);
98
13.9k
    this->GG = cm::make_unique<cmGlobalGenerator>(&this->CMake);
99
13.9k
    cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot();
100
13.9k
    snapshot.GetDirectory().SetCurrentBinary(g_prefixDir);
101
13.9k
    snapshot.GetDirectory().SetCurrentSource(g_prefixDir);
102
13.9k
    this->MF = cm::make_unique<cmMakefile>(this->GG.get(), snapshot);
103
13.9k
  }
104
};
105
106
void Consume(cmPackageInfoReader const& reader)
107
8.55k
{
108
8.55k
  (void)reader.GetName();
109
110
8.55k
  cm::optional<std::string> const version = reader.GetVersion();
111
8.55k
  cm::optional<std::string> const compatVersion = reader.GetCompatVersion();
112
8.55k
  (void)reader.ParseVersion(version);
113
8.55k
  (void)reader.ParseVersion(compatVersion);
114
115
8.55k
  for (cmPackageRequirement const& req : reader.GetRequirements()) {
116
3.17k
    (void)req.Name;
117
3.17k
    (void)req.Version;
118
3.17k
    (void)req.Components;
119
3.17k
    (void)req.Hints;
120
3.17k
  }
121
8.55k
  (void)reader.GetComponentNames();
122
8.55k
}
123
124
} // namespace
125
126
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
127
6.99k
{
128
6.99k
  if (size == 0 || size > kMaxInputSize) {
129
5
    return 0;
130
5
  }
131
132
6.98k
  {
133
6.98k
    FILE* fp = fopen(g_cpsFile.c_str(), "wb");
134
6.98k
    if (!fp) {
135
0
      return 0;
136
0
    }
137
6.98k
    bool const written = fwrite(data, 1, size, fp) == size;
138
6.98k
    fclose(fp);
139
6.98k
    if (!written) {
140
0
      return 0;
141
0
    }
142
6.98k
  }
143
144
6.98k
  {
145
6.98k
    Fixture fx;
146
6.98k
    std::unique_ptr<cmPackageInfoReader> reader =
147
6.98k
      cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile);
148
6.98k
    if (reader) {
149
4.27k
      Consume(*reader);
150
151
4.27k
      cmExecutionStatus status(*fx.MF);
152
4.27k
      if (reader->ImportTargets(fx.MF.get(), status,
153
4.27k
                                cm::ImportedTargetScope::Local)) {
154
4.27k
        reader->ImportTargetConfigurations(fx.MF.get(), status);
155
4.27k
      }
156
4.27k
    }
157
6.98k
  }
158
159
6.98k
  {
160
6.98k
    Fixture fx;
161
6.98k
    std::unique_ptr<cmPackageInfoReader> parent =
162
6.98k
      cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile);
163
6.98k
    if (parent) {
164
4.27k
      std::unique_ptr<cmPackageInfoReader> appendix =
165
4.27k
        cmPackageInfoReader::Read(fx.MF.get(), g_cpsFile, parent.get());
166
4.27k
      if (appendix) {
167
4.27k
        Consume(*appendix);
168
169
4.27k
        cmExecutionStatus status(*fx.MF);
170
4.27k
        if (appendix->ImportTargets(fx.MF.get(), status,
171
4.27k
                                    cm::ImportedTargetScope::Global)) {
172
4.27k
          appendix->ImportTargetConfigurations(fx.MF.get(), status);
173
4.27k
        }
174
4.27k
      }
175
4.27k
    }
176
6.98k
  }
177
178
6.98k
  return 0;
179
6.98k
}