Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Tests/Fuzzing/cmGeneratorExpressionEvalFuzzer.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 generator expression evaluation.
6
 *
7
 * cmGeneratorExpressionFuzzer only tokenizes and preprocesses $<...>, and
8
 * cmScriptFuzzer runs in script mode where no generator exists, so the
9
 * evaluator (cmGeneratorExpressionNode.cxx, the DAG checker, and the
10
 * transitive-property / link-interface code in cmGeneratorTarget) is never
11
 * reached by any fuzzer.
12
 *
13
 * Each input gets a fresh project-mode fixture: a base cmGlobalGenerator, a
14
 * makefile populated from a fixed listfile (interface, imported, alias,
15
 * object, static, executable and custom targets with genex-bearing usage
16
 * requirements), and generator targets created the same way as
17
 * "cmake --find-package" does.  C and CXX are marked enabled with
18
 * GNU-like compiler variables so that language- and compiler-dependent
19
 * expressions have something to evaluate against.  No compiler is run.
20
 *
21
 * Input layout:
22
 *   byte 0: bits 0-1 mode, bits 2-4 head target, bits 5-6 config,
23
 *           bit 7 EvaluateForBuildsystem
24
 *   byte 1: bits 0-2 language, bits 3-4 property, bits 5-7 current target
25
 *   rest:   generator expression (or property value in Injection mode)
26
 *
27
 * Modes (see Mode below):
28
 *   Evaluate     cmGeneratorExpression::Evaluate
29
 *   Compiled     Parse once, evaluate for two configs, query collected
30
 * metadata Interpreter  cmGeneratorExpressionInterpreter with a
31
 * transitive-property DAG Injection    Store the input in a usage-requirement
32
 * property of a fixture target, then read it back through a consumer so it is
33
 *                evaluated transitively
34
 */
35
36
#include <cstddef>
37
#include <cstdint>
38
#include <cstdio>
39
#include <cstdlib>
40
#include <map>
41
#include <memory>
42
#include <string>
43
#include <utility>
44
45
#include <cm/memory>
46
47
#include "cmGenExContext.h"
48
#include "cmGeneratorExpression.h"
49
#include "cmGlobalGenerator.h"
50
#include "cmLocalGenerator.h"
51
#include "cmMakefile.h"
52
#include "cmMessageMetadata.h"
53
#include "cmState.h"
54
#include "cmStateDirectory.h"
55
#include "cmStateSnapshot.h"
56
#include "cmSystemTools.h"
57
#include "cmTarget.h"
58
#include "cmake.h"
59
60
static constexpr size_t kHeaderSize = 2;
61
// Evaluation is recursive in nesting depth; keep inputs short.
62
static constexpr size_t kMaxExprSize = 4 * 1024;
63
64
static std::string g_srcDir;
65
static std::string g_binDir;
66
67
namespace {
68
69
// Stand-in for the variables project() would load after compiler detection.
70
char const kToolchain[] = R"cmake(
71
cmake_minimum_required(VERSION 4.0)
72
set(CMAKE_SYSTEM_NAME Linux)
73
set(CMAKE_SYSTEM_PROCESSOR x86_64)
74
set(CMAKE_BUILD_TYPE Debug)
75
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
76
set(CMAKE_STATIC_LIBRARY_PREFIX lib)
77
set(CMAKE_STATIC_LIBRARY_SUFFIX .a)
78
set(CMAKE_SHARED_LIBRARY_PREFIX lib)
79
set(CMAKE_SHARED_LIBRARY_SUFFIX .so)
80
set(CMAKE_SHARED_MODULE_PREFIX lib)
81
set(CMAKE_SHARED_MODULE_SUFFIX .so)
82
set(CMAKE_EXECUTABLE_SUFFIX "")
83
foreach(lang IN ITEMS C CXX)
84
  set(CMAKE_${lang}_COMPILER_ID GNU)
85
  set(CMAKE_${lang}_COMPILER_VERSION 13.2.0)
86
  set(CMAKE_${lang}_COMPILER_FRONTEND_VARIANT GNU)
87
  set(CMAKE_${lang}_COMPILER_LINKER_ID GNU)
88
  set(CMAKE_${lang}_COMPILER_LINKER_FRONTEND_VARIANT GNU)
89
  set(CMAKE_${lang}_OUTPUT_EXTENSION .o)
90
  set(CMAKE_${lang}_STANDARD_DEFAULT 17)
91
endforeach()
92
set(CMAKE_C_COMPILER /usr/bin/cc)
93
set(CMAKE_C_SOURCE_FILE_EXTENSIONS c m)
94
set(CMAKE_C_IGNORE_EXTENSIONS h H o O obj OBJ def DEF rc RC)
95
set(CMAKE_C_LINKER_PREFERENCE 10)
96
set(CMAKE_C_COMPILE_FEATURES
97
  c_std_90 c_std_99 c_std_11 c_std_17 c_std_23
98
  c_function_prototypes c_restrict c_static_assert c_variadic_macros)
99
set(CMAKE_CXX_COMPILER /usr/bin/c++)
100
set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS
101
  C M c++ cc cpp cxx mm mpp CPP ixx cppm ccm cxxm c++m)
102
set(CMAKE_CXX_IGNORE_EXTENSIONS inl h hpp HPP H o O obj OBJ def DEF rc RC)
103
set(CMAKE_CXX_LINKER_PREFERENCE 30)
104
set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1)
105
set(CMAKE_CXX_COMPILE_FEATURES
106
  cxx_std_98 cxx_std_11 cxx_std_14 cxx_std_17 cxx_std_20 cxx_std_23
107
  cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_variadic_templates)
108
)cmake";
109
110
char const kProject[] = R"cmake(
111
cmake_minimum_required(VERSION 4.0)
112
113
add_library(fuzz_iface INTERFACE)
114
target_compile_definitions(fuzz_iface INTERFACE
115
  IFACE_DEF=1
116
  $<$<CONFIG:Debug>:IFACE_DEBUG>
117
  $<$<COMPILE_LANGUAGE:CXX>:IFACE_CXX>)
118
target_include_directories(fuzz_iface INTERFACE
119
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
120
  $<INSTALL_INTERFACE:include>)
121
target_compile_options(fuzz_iface INTERFACE
122
  $<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall>)
123
target_link_options(fuzz_iface INTERFACE
124
  $<$<LINK_LANGUAGE:CXX>:-Wl,--as-needed>)
125
target_sources(fuzz_iface INTERFACE
126
  FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
127
  FILES ${CMAKE_CURRENT_SOURCE_DIR}/fuzz.h)
128
set_target_properties(fuzz_iface PROPERTIES
129
  INTERFACE_FUZZ_BOOL ON
130
  INTERFACE_FUZZ_STRING fuzz
131
  INTERFACE_FUZZ_NUMBER 3
132
  INTERFACE_FUZZ_CUSTOM "custom;$<CONFIG>"
133
  COMPATIBLE_INTERFACE_BOOL FUZZ_BOOL
134
  COMPATIBLE_INTERFACE_STRING FUZZ_STRING
135
  COMPATIBLE_INTERFACE_NUMBER_MAX FUZZ_NUMBER
136
  TRANSITIVE_COMPILE_PROPERTIES FUZZ_CUSTOM)
137
138
add_library(fuzz_dep INTERFACE)
139
target_link_libraries(fuzz_dep INTERFACE
140
  fuzz_iface
141
  $<LINK_ONLY:fuzz_imported_static>
142
  $<$<CONFIG:Release>:Fuzz::shared>)
143
144
add_library(fuzz_imported_static STATIC IMPORTED)
145
set_target_properties(fuzz_imported_static PROPERTIES
146
  IMPORTED_CONFIGURATIONS "DEBUG;RELEASE"
147
  IMPORTED_LOCATION_DEBUG /opt/fuzz/lib/libfuzz_d.a
148
  IMPORTED_LOCATION_RELEASE /opt/fuzz/lib/libfuzz.a
149
  MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
150
  IMPORTED_LINK_INTERFACE_LANGUAGES C
151
  INTERFACE_COMPILE_DEFINITIONS IMPORTED_STATIC)
152
153
add_library(fuzz_imported_shared SHARED IMPORTED GLOBAL)
154
set_target_properties(fuzz_imported_shared PROPERTIES
155
  IMPORTED_LOCATION /opt/fuzz/lib/libfuzz_shared.so.1.2.3
156
  IMPORTED_SONAME libfuzz_shared.so.1
157
  INTERFACE_INCLUDE_DIRECTORIES /opt/fuzz/include
158
  INTERFACE_LINK_LIBRARIES fuzz_imported_static)
159
add_library(Fuzz::shared ALIAS fuzz_imported_shared)
160
161
add_executable(fuzz_imported_exe IMPORTED)
162
set_property(TARGET fuzz_imported_exe PROPERTY
163
  IMPORTED_LOCATION /opt/fuzz/bin/fuzz_tool)
164
165
add_library(fuzz_obj OBJECT fuzz.c fuzz.cpp)
166
target_compile_definitions(fuzz_obj PRIVATE OBJ_DEF)
167
168
add_library(fuzz_static STATIC fuzz.c)
169
target_link_libraries(fuzz_static
170
  PUBLIC fuzz_dep
171
  PRIVATE fuzz_obj Fuzz::shared)
172
set_target_properties(fuzz_static PROPERTIES
173
  OUTPUT_NAME fuzzy
174
  OUTPUT_NAME_DEBUG fuzzy_d
175
  POSITION_INDEPENDENT_CODE ON
176
  COMPILE_DEFINITIONS "STATIC_OWN=$<CONFIG>")
177
add_library(Fuzz::static ALIAS fuzz_static)
178
179
add_executable(fuzz_exe fuzz.cpp)
180
target_link_libraries(fuzz_exe PRIVATE Fuzz::static)
181
set_property(TARGET fuzz_exe PROPERTY FUZZ_STRING fuzz)
182
183
add_custom_target(fuzz_custom
184
  COMMAND fuzz_imported_exe $<TARGET_FILE:fuzz_exe>)
185
186
set_source_files_properties(fuzz.c PROPERTIES
187
  COMPILE_DEFINITIONS "SRC_DEF=$<CONFIG>"
188
  FUZZ_SRC_PROP value)
189
set_property(GLOBAL PROPERTY FUZZ_GLOBAL "global;value")
190
set_property(DIRECTORY PROPERTY FUZZ_DIR_PROP dir)
191
)cmake";
192
193
char const* const kConfigs[] = { "", "Debug", "Release", "RelWithDebInfo" };
194
195
char const* const kLanguages[] = { "",        "C",   "CXX",  "CUDA",
196
                                   "Fortran", "HIP", "OBJC", "Swift" };
197
198
// Index 0 means "no target".
199
char const* const kTargets[] = {
200
  nullptr,        "fuzz_exe",    "fuzz_static",
201
  "fuzz_obj",     "fuzz_iface",  "fuzz_imported_static",
202
  "Fuzz::shared", "fuzz_custom",
203
};
204
205
char const* const kFixtureTargets[] = {
206
  "fuzz_exe",          "fuzz_static",          "Fuzz::static",
207
  "fuzz_obj",          "fuzz_iface",           "fuzz_dep",
208
  "fuzz_imported_exe", "fuzz_imported_static", "fuzz_imported_shared",
209
  "Fuzz::shared",      "fuzz_custom",
210
};
211
212
// Properties that generators pass to cmGeneratorExpressionInterpreter.
213
char const* const kInterpreterProperties[] = {
214
  "COMPILE_OPTIONS",
215
  "COMPILE_DEFINITIONS",
216
  "INCLUDE_DIRECTORIES",
217
  "COMPILE_FLAGS",
218
};
219
220
struct Injection
221
{
222
  char const* Target;
223
  char const* Property;
224
  // Evaluated with fuzz_exe as head so the injected value is reached
225
  // through the link closure.
226
  char const* Reader;
227
};
228
229
Injection const kInjections[] = {
230
  { "fuzz_dep", "INTERFACE_COMPILE_DEFINITIONS",
231
    "$<TARGET_PROPERTY:COMPILE_DEFINITIONS>" },
232
  { "fuzz_dep", "INTERFACE_INCLUDE_DIRECTORIES",
233
    "$<TARGET_PROPERTY:INCLUDE_DIRECTORIES>" },
234
  { "fuzz_dep", "INTERFACE_LINK_LIBRARIES",
235
    "$<TARGET_PROPERTY:COMPILE_OPTIONS>" },
236
  { "fuzz_exe", "LINK_LIBRARIES", "$<TARGET_PROPERTY:LINK_OPTIONS>" },
237
};
238
239
// Which evaluation entry point the input exercises (bits 0-1 of byte 0).
240
enum class Mode
241
{
242
  Evaluate,    // cmGeneratorExpression::Evaluate (one-shot)
243
  Compiled,    // Parse once, evaluate for two configs, query metadata
244
  Interpreter, // cmGeneratorExpressionInterpreter (generator property path)
245
  Injection,   // Store in a usage requirement, evaluate transitively
246
};
247
248
bool WriteFile(std::string const& path, char const* content)
249
6
{
250
6
  FILE* fp = fopen(path.c_str(), "wb");
251
6
  if (!fp) {
252
0
    return false;
253
0
  }
254
6
  bool const ok = fputs(content, fp) >= 0;
255
6
  fclose(fp);
256
6
  return ok;
257
6
}
258
259
struct Fixture
260
{
261
  cmake CMake{ cmState::Role::Project };
262
  cmGlobalGenerator* GG = nullptr;
263
  cmMakefile* MF = nullptr;
264
  cmLocalGenerator* LG = nullptr;
265
266
  bool Build(Injection const* inject, std::string const& value)
267
5.92k
  {
268
5.92k
    this->CMake.SetHomeDirectory(g_srcDir);
269
5.92k
    this->CMake.SetHomeOutputDirectory(g_binDir);
270
5.92k
    this->CMake.SetGlobalGenerator(
271
5.92k
      cm::make_unique<cmGlobalGenerator>(&this->CMake));
272
5.92k
    this->GG = this->CMake.GetGlobalGenerator();
273
274
5.92k
    cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot();
275
5.92k
    snapshot.GetDirectory().SetCurrentSource(g_srcDir);
276
5.92k
    snapshot.GetDirectory().SetCurrentBinary(g_binDir);
277
5.92k
    snapshot.SetDefaultDefinitions();
278
279
5.92k
    auto mf = cm::make_unique<cmMakefile>(this->GG, snapshot);
280
5.92k
    this->MF = mf.get();
281
5.92k
    this->GG->AddMakefile(std::move(mf));
282
283
5.92k
    if (!this->MF->ReadListFileAsString(kToolchain, "fuzz_toolchain.cmake")) {
284
0
      return false;
285
0
    }
286
5.92k
    this->GG->SetLanguageEnabled("C", this->MF);
287
5.92k
    this->GG->SetLanguageEnabled("CXX", this->MF);
288
5.92k
    if (!this->MF->ReadListFileAsString(kProject, "CMakeLists.txt") ||
289
5.92k
        cmSystemTools::GetFatalErrorOccurred()) {
290
0
      return false;
291
0
    }
292
293
5.92k
    if (inject) {
294
4.05k
      cmTarget* target = this->MF->FindLocalNonAliasTarget(inject->Target);
295
4.05k
      if (!target) {
296
0
        return false;
297
0
      }
298
4.05k
      target->SetProperty(inject->Property, value);
299
4.05k
    }
300
301
5.92k
    this->GG->CreateGenerationObjects();
302
5.92k
    if (this->GG->GetLocalGenerators().empty()) {
303
0
      return false;
304
0
    }
305
5.92k
    this->LG = this->GG->GetLocalGenerators().front().get();
306
5.92k
    return true;
307
5.92k
  }
308
309
  cmGeneratorTarget const* Find(char const* name) const
310
10.7k
  {
311
10.7k
    return name ? this->LG->FindGeneratorTargetToUse(name) : nullptr;
312
10.7k
  }
313
};
314
315
} // namespace
316
317
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
318
2
{
319
2
  (void)argc;
320
2
  (void)argv;
321
322
2
  char tmpl[] = "/tmp/cmake_fuzz_genex_eval_XXXXXX";
323
2
  char* dir = mkdtemp(tmpl);
324
2
  g_srcDir = dir ? dir : "/tmp/cmake_fuzz_genex_eval";
325
2
  g_binDir = g_srcDir + "/build";
326
2
  cmSystemTools::MakeDirectory(g_srcDir + "/include");
327
2
  cmSystemTools::MakeDirectory(g_binDir);
328
2
  WriteFile(g_srcDir + "/fuzz.c", "int fuzz_c(void) { return 0; }\n");
329
2
  WriteFile(g_srcDir + "/fuzz.cpp", "int main() { return 0; }\n");
330
2
  WriteFile(g_srcDir + "/fuzz.h", "#pragma once\n");
331
332
  // A broken fixture would make every input a no-op, so fail loudly here
333
  // while diagnostics are still printed.
334
2
  {
335
2
    Fixture fx;
336
2
    bool ok = fx.Build(nullptr, std::string()) &&
337
2
      !cmSystemTools::GetFatalErrorOccurred();
338
22
    for (char const* name : kFixtureTargets) {
339
22
      if (ok && !fx.Find(name)) {
340
0
        fprintf(stderr, "fixture target missing: %s\n", name);
341
0
        ok = false;
342
0
      }
343
22
    }
344
2
    if (!ok) {
345
0
      fprintf(stderr, "cmGeneratorExpressionEvalFuzzer: fixture failed\n");
346
0
      abort();
347
0
    }
348
2
  }
349
2
  cmSystemTools::ResetErrorOccurredFlag();
350
351
2
  cmSystemTools::SetMessageCallback(
352
13.1k
    [](std::string const&, cmMessageMetadata const&) {});
353
2
  cmSystemTools::SetStdoutCallback([](std::string const&) {});
354
2
  cmSystemTools::SetStderrCallback([](std::string const&) {});
355
356
2
  return 0;
357
2
}
358
359
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
360
5.92k
{
361
5.92k
  if (size < kHeaderSize || size > kHeaderSize + kMaxExprSize) {
362
1
    return 0;
363
1
  }
364
365
5.92k
  uint8_t const sel0 = data[0];
366
5.92k
  uint8_t const sel1 = data[1];
367
5.92k
  Mode const mode = static_cast<Mode>(sel0 & 0x3u);
368
5.92k
  char const* const headName = kTargets[(sel0 >> 2) & 0x7u];
369
5.92k
  std::string const config = kConfigs[(sel0 >> 5) & 0x3u];
370
5.92k
  std::string const otherConfig = kConfigs[((sel0 >> 5) + 1) & 0x3u];
371
5.92k
  bool const forBuildsystem = (sel0 & 0x80u) != 0;
372
5.92k
  std::string const language = kLanguages[sel1 & 0x7u];
373
5.92k
  unsigned const propIndex = (sel1 >> 3) & 0x3u;
374
5.92k
  char const* const currentName = kTargets[(sel1 >> 5) & 0x7u];
375
376
5.92k
  std::string expr(reinterpret_cast<char const*>(data + kHeaderSize),
377
5.92k
                   size - kHeaderSize);
378
379
  // The fatal-error flag is global and would stop the next fixture's
380
  // listfile from running.
381
5.92k
  cmSystemTools::ResetErrorOccurredFlag();
382
5.92k
  {
383
5.92k
    Fixture fx;
384
5.92k
    Injection const* inject =
385
5.92k
      mode == Mode::Injection ? &kInjections[propIndex] : nullptr;
386
5.92k
    if (fx.Build(inject, expr)) {
387
5.92k
      cmGeneratorTarget const* head = fx.Find(headName);
388
389
5.92k
      switch (mode) {
390
939
        case Mode::Evaluate: {
391
939
          (void)cmGeneratorExpression::Evaluate(
392
939
            std::move(expr), fx.LG, config, head, nullptr, nullptr, language);
393
939
        } break;
394
395
552
        case Mode::Compiled: {
396
552
          cmGeneratorExpression ge(fx.CMake);
397
552
          std::unique_ptr<cmCompiledGeneratorExpression> cge =
398
552
            ge.Parse(std::move(expr));
399
552
          cge->SetEvaluateForBuildsystem(forBuildsystem);
400
552
          cmGeneratorTarget const* current = fx.Find(currentName);
401
402
552
          cm::GenEx::Context ctx(fx.LG, config, language);
403
552
          (void)cge->Evaluate(ctx, nullptr, head, current);
404
552
          (void)cge->GetTargets();
405
552
          (void)cge->GetAllTargetsSeen();
406
552
          (void)cge->GetSeenTargetProperties();
407
552
          (void)cge->GetHadContextSensitiveCondition();
408
552
          (void)cge->GetHadHeadSensitiveCondition();
409
552
          (void)cge->GetHadLinkLanguageSensitiveCondition();
410
552
          (void)cge->GetSourceSensitiveTargets();
411
552
          if (head) {
412
491
            std::map<std::string, std::string> standards;
413
491
            cge->GetMaxLanguageStandard(head, standards);
414
491
          }
415
416
552
          cm::GenEx::Context otherCtx(fx.LG, otherConfig, language);
417
552
          (void)cge->Evaluate(otherCtx, nullptr, head, current);
418
552
        } break;
419
420
375
        case Mode::Interpreter: {
421
375
          if (!head) {
422
158
            head = fx.Find("fuzz_exe");
423
158
          }
424
375
          cmGeneratorExpressionInterpreter interpreter(fx.LG, config, head,
425
375
                                                       language);
426
375
          (void)interpreter.Evaluate(std::move(expr),
427
375
                                     kInterpreterProperties[propIndex]);
428
375
        } break;
429
430
4.05k
        case Mode::Injection: {
431
4.05k
          Injection const& inj = kInjections[propIndex];
432
4.05k
          (void)cmGeneratorExpression::Evaluate(inj.Reader, fx.LG, config,
433
4.05k
                                                fx.Find("fuzz_exe"), nullptr,
434
4.05k
                                                nullptr, language);
435
4.05k
          std::string direct = "$<TARGET_PROPERTY:";
436
4.05k
          direct += inj.Target;
437
4.05k
          direct += ',';
438
4.05k
          direct += inj.Property;
439
4.05k
          direct += '>';
440
4.05k
          (void)cmGeneratorExpression::Evaluate(std::move(direct), fx.LG,
441
4.05k
                                                config);
442
4.05k
        } break;
443
5.92k
      }
444
5.92k
    }
445
5.92k
  }
446
5.92k
  cmSystemTools::ResetErrorOccurredFlag();
447
448
5.92k
  return 0;
449
5.92k
}