blob: 0b94ba660ef949c2647c0b8c06dcb1cabcdf49b5 [file] [edit]
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
/*
* Fuzzer for generator expression evaluation.
*
* cmGeneratorExpressionFuzzer only tokenizes and preprocesses $<...>, and
* cmScriptFuzzer runs in script mode where no generator exists, so the
* evaluator (cmGeneratorExpressionNode.cxx, the DAG checker, and the
* transitive-property / link-interface code in cmGeneratorTarget) is never
* reached by any fuzzer.
*
* Each input gets a fresh project-mode fixture: a base cmGlobalGenerator, a
* makefile populated from a fixed listfile (interface, imported, alias,
* object, static, executable and custom targets with genex-bearing usage
* requirements), and generator targets created the same way as
* "cmake --find-package" does. C and CXX are marked enabled with
* GNU-like compiler variables so that language- and compiler-dependent
* expressions have something to evaluate against. No compiler is run.
*
* Input layout:
* byte 0: bits 0-1 mode, bits 2-4 head target, bits 5-6 config,
* bit 7 EvaluateForBuildsystem
* byte 1: bits 0-2 language, bits 3-4 property, bits 5-7 current target
* rest: generator expression (or property value in Injection mode)
*
* Modes (see Mode below):
* Evaluate cmGeneratorExpression::Evaluate
* Compiled Parse once, evaluate for two configs, query collected
* metadata Interpreter cmGeneratorExpressionInterpreter with a
* transitive-property DAG Injection Store the input in a usage-requirement
* property of a fixture target, then read it back through a consumer so it is
* evaluated transitively
*/
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <cm/memory>
#include "cmGenExContext.h"
#include "cmGeneratorExpression.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageMetadata.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmake.h"
static constexpr size_t kHeaderSize = 2;
// Evaluation is recursive in nesting depth; keep inputs short.
static constexpr size_t kMaxExprSize = 4 * 1024;
static std::string g_srcDir;
static std::string g_binDir;
namespace {
// Stand-in for the variables project() would load after compiler detection.
char const kToolchain[] = R"cmake(
cmake_minimum_required(VERSION 4.0)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_BUILD_TYPE Debug)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
set(CMAKE_STATIC_LIBRARY_PREFIX lib)
set(CMAKE_STATIC_LIBRARY_SUFFIX .a)
set(CMAKE_SHARED_LIBRARY_PREFIX lib)
set(CMAKE_SHARED_LIBRARY_SUFFIX .so)
set(CMAKE_SHARED_MODULE_PREFIX lib)
set(CMAKE_SHARED_MODULE_SUFFIX .so)
set(CMAKE_EXECUTABLE_SUFFIX "")
foreach(lang IN ITEMS C CXX)
set(CMAKE_${lang}_COMPILER_ID GNU)
set(CMAKE_${lang}_COMPILER_VERSION 13.2.0)
set(CMAKE_${lang}_COMPILER_FRONTEND_VARIANT GNU)
set(CMAKE_${lang}_COMPILER_LINKER_ID GNU)
set(CMAKE_${lang}_COMPILER_LINKER_FRONTEND_VARIANT GNU)
set(CMAKE_${lang}_OUTPUT_EXTENSION .o)
set(CMAKE_${lang}_STANDARD_DEFAULT 17)
endforeach()
set(CMAKE_C_COMPILER /usr/bin/cc)
set(CMAKE_C_SOURCE_FILE_EXTENSIONS c m)
set(CMAKE_C_IGNORE_EXTENSIONS h H o O obj OBJ def DEF rc RC)
set(CMAKE_C_LINKER_PREFERENCE 10)
set(CMAKE_C_COMPILE_FEATURES
c_std_90 c_std_99 c_std_11 c_std_17 c_std_23
c_function_prototypes c_restrict c_static_assert c_variadic_macros)
set(CMAKE_CXX_COMPILER /usr/bin/c++)
set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS
C M c++ cc cpp cxx mm mpp CPP ixx cppm ccm cxxm c++m)
set(CMAKE_CXX_IGNORE_EXTENSIONS inl h hpp HPP H o O obj OBJ def DEF rc RC)
set(CMAKE_CXX_LINKER_PREFERENCE 30)
set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1)
set(CMAKE_CXX_COMPILE_FEATURES
cxx_std_98 cxx_std_11 cxx_std_14 cxx_std_17 cxx_std_20 cxx_std_23
cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_variadic_templates)
)cmake";
char const kProject[] = R"cmake(
cmake_minimum_required(VERSION 4.0)
add_library(fuzz_iface INTERFACE)
target_compile_definitions(fuzz_iface INTERFACE
IFACE_DEF=1
$<$<CONFIG:Debug>:IFACE_DEBUG>
$<$<COMPILE_LANGUAGE:CXX>:IFACE_CXX>)
target_include_directories(fuzz_iface INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_options(fuzz_iface INTERFACE
$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall>)
target_link_options(fuzz_iface INTERFACE
$<$<LINK_LANGUAGE:CXX>:-Wl,--as-needed>)
target_sources(fuzz_iface INTERFACE
FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
FILES ${CMAKE_CURRENT_SOURCE_DIR}/fuzz.h)
set_target_properties(fuzz_iface PROPERTIES
INTERFACE_FUZZ_BOOL ON
INTERFACE_FUZZ_STRING fuzz
INTERFACE_FUZZ_NUMBER 3
INTERFACE_FUZZ_CUSTOM "custom;$<CONFIG>"
COMPATIBLE_INTERFACE_BOOL FUZZ_BOOL
COMPATIBLE_INTERFACE_STRING FUZZ_STRING
COMPATIBLE_INTERFACE_NUMBER_MAX FUZZ_NUMBER
TRANSITIVE_COMPILE_PROPERTIES FUZZ_CUSTOM)
add_library(fuzz_dep INTERFACE)
target_link_libraries(fuzz_dep INTERFACE
fuzz_iface
$<LINK_ONLY:fuzz_imported_static>
$<$<CONFIG:Release>:Fuzz::shared>)
add_library(fuzz_imported_static STATIC IMPORTED)
set_target_properties(fuzz_imported_static PROPERTIES
IMPORTED_CONFIGURATIONS "DEBUG;RELEASE"
IMPORTED_LOCATION_DEBUG /opt/fuzz/lib/libfuzz_d.a
IMPORTED_LOCATION_RELEASE /opt/fuzz/lib/libfuzz.a
MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_COMPILE_DEFINITIONS IMPORTED_STATIC)
add_library(fuzz_imported_shared SHARED IMPORTED GLOBAL)
set_target_properties(fuzz_imported_shared PROPERTIES
IMPORTED_LOCATION /opt/fuzz/lib/libfuzz_shared.so.1.2.3
IMPORTED_SONAME libfuzz_shared.so.1
INTERFACE_INCLUDE_DIRECTORIES /opt/fuzz/include
INTERFACE_LINK_LIBRARIES fuzz_imported_static)
add_library(Fuzz::shared ALIAS fuzz_imported_shared)
add_executable(fuzz_imported_exe IMPORTED)
set_property(TARGET fuzz_imported_exe PROPERTY
IMPORTED_LOCATION /opt/fuzz/bin/fuzz_tool)
add_library(fuzz_obj OBJECT fuzz.c fuzz.cpp)
target_compile_definitions(fuzz_obj PRIVATE OBJ_DEF)
add_library(fuzz_static STATIC fuzz.c)
target_link_libraries(fuzz_static
PUBLIC fuzz_dep
PRIVATE fuzz_obj Fuzz::shared)
set_target_properties(fuzz_static PROPERTIES
OUTPUT_NAME fuzzy
OUTPUT_NAME_DEBUG fuzzy_d
POSITION_INDEPENDENT_CODE ON
COMPILE_DEFINITIONS "STATIC_OWN=$<CONFIG>")
add_library(Fuzz::static ALIAS fuzz_static)
add_executable(fuzz_exe fuzz.cpp)
target_link_libraries(fuzz_exe PRIVATE Fuzz::static)
set_property(TARGET fuzz_exe PROPERTY FUZZ_STRING fuzz)
add_custom_target(fuzz_custom
COMMAND fuzz_imported_exe $<TARGET_FILE:fuzz_exe>)
set_source_files_properties(fuzz.c PROPERTIES
COMPILE_DEFINITIONS "SRC_DEF=$<CONFIG>"
FUZZ_SRC_PROP value)
set_property(GLOBAL PROPERTY FUZZ_GLOBAL "global;value")
set_property(DIRECTORY PROPERTY FUZZ_DIR_PROP dir)
)cmake";
char const* const kConfigs[] = { "", "Debug", "Release", "RelWithDebInfo" };
char const* const kLanguages[] = { "", "C", "CXX", "CUDA",
"Fortran", "HIP", "OBJC", "Swift" };
// Index 0 means "no target".
char const* const kTargets[] = {
nullptr, "fuzz_exe", "fuzz_static",
"fuzz_obj", "fuzz_iface", "fuzz_imported_static",
"Fuzz::shared", "fuzz_custom",
};
char const* const kFixtureTargets[] = {
"fuzz_exe", "fuzz_static", "Fuzz::static",
"fuzz_obj", "fuzz_iface", "fuzz_dep",
"fuzz_imported_exe", "fuzz_imported_static", "fuzz_imported_shared",
"Fuzz::shared", "fuzz_custom",
};
// Properties that generators pass to cmGeneratorExpressionInterpreter.
char const* const kInterpreterProperties[] = {
"COMPILE_OPTIONS",
"COMPILE_DEFINITIONS",
"INCLUDE_DIRECTORIES",
"COMPILE_FLAGS",
};
struct Injection
{
char const* Target;
char const* Property;
// Evaluated with fuzz_exe as head so the injected value is reached
// through the link closure.
char const* Reader;
};
Injection const kInjections[] = {
{ "fuzz_dep", "INTERFACE_COMPILE_DEFINITIONS",
"$<TARGET_PROPERTY:COMPILE_DEFINITIONS>" },
{ "fuzz_dep", "INTERFACE_INCLUDE_DIRECTORIES",
"$<TARGET_PROPERTY:INCLUDE_DIRECTORIES>" },
{ "fuzz_dep", "INTERFACE_LINK_LIBRARIES",
"$<TARGET_PROPERTY:COMPILE_OPTIONS>" },
{ "fuzz_exe", "LINK_LIBRARIES", "$<TARGET_PROPERTY:LINK_OPTIONS>" },
};
// Which evaluation entry point the input exercises (bits 0-1 of byte 0).
enum class Mode
{
Evaluate, // cmGeneratorExpression::Evaluate (one-shot)
Compiled, // Parse once, evaluate for two configs, query metadata
Interpreter, // cmGeneratorExpressionInterpreter (generator property path)
Injection, // Store in a usage requirement, evaluate transitively
};
bool WriteFile(std::string const& path, char const* content)
{
FILE* fp = fopen(path.c_str(), "wb");
if (!fp) {
return false;
}
bool const ok = fputs(content, fp) >= 0;
fclose(fp);
return ok;
}
struct Fixture
{
cmake CMake{ cmState::Role::Project };
cmGlobalGenerator* GG = nullptr;
cmMakefile* MF = nullptr;
cmLocalGenerator* LG = nullptr;
bool Build(Injection const* inject, std::string const& value)
{
this->CMake.SetHomeDirectory(g_srcDir);
this->CMake.SetHomeOutputDirectory(g_binDir);
this->CMake.SetGlobalGenerator(
cm::make_unique<cmGlobalGenerator>(&this->CMake));
this->GG = this->CMake.GetGlobalGenerator();
cmStateSnapshot snapshot = this->CMake.GetCurrentSnapshot();
snapshot.GetDirectory().SetCurrentSource(g_srcDir);
snapshot.GetDirectory().SetCurrentBinary(g_binDir);
snapshot.SetDefaultDefinitions();
auto mf = cm::make_unique<cmMakefile>(this->GG, snapshot);
this->MF = mf.get();
this->GG->AddMakefile(std::move(mf));
if (!this->MF->ReadListFileAsString(kToolchain, "fuzz_toolchain.cmake")) {
return false;
}
this->GG->SetLanguageEnabled("C", this->MF);
this->GG->SetLanguageEnabled("CXX", this->MF);
if (!this->MF->ReadListFileAsString(kProject, "CMakeLists.txt") ||
cmSystemTools::GetFatalErrorOccurred()) {
return false;
}
if (inject) {
cmTarget* target = this->MF->FindLocalNonAliasTarget(inject->Target);
if (!target) {
return false;
}
target->SetProperty(inject->Property, value);
}
this->GG->CreateGenerationObjects();
if (this->GG->GetLocalGenerators().empty()) {
return false;
}
this->LG = this->GG->GetLocalGenerators().front().get();
return true;
}
cmGeneratorTarget const* Find(char const* name) const
{
return name ? this->LG->FindGeneratorTargetToUse(name) : nullptr;
}
};
} // namespace
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
{
(void)argc;
(void)argv;
char tmpl[] = "/tmp/cmake_fuzz_genex_eval_XXXXXX";
char* dir = mkdtemp(tmpl);
g_srcDir = dir ? dir : "/tmp/cmake_fuzz_genex_eval";
g_binDir = g_srcDir + "/build";
cmSystemTools::MakeDirectory(g_srcDir + "/include");
cmSystemTools::MakeDirectory(g_binDir);
WriteFile(g_srcDir + "/fuzz.c", "int fuzz_c(void) { return 0; }\n");
WriteFile(g_srcDir + "/fuzz.cpp", "int main() { return 0; }\n");
WriteFile(g_srcDir + "/fuzz.h", "#pragma once\n");
// A broken fixture would make every input a no-op, so fail loudly here
// while diagnostics are still printed.
{
Fixture fx;
bool ok = fx.Build(nullptr, std::string()) &&
!cmSystemTools::GetFatalErrorOccurred();
for (char const* name : kFixtureTargets) {
if (ok && !fx.Find(name)) {
fprintf(stderr, "fixture target missing: %s\n", name);
ok = false;
}
}
if (!ok) {
fprintf(stderr, "cmGeneratorExpressionEvalFuzzer: fixture failed\n");
abort();
}
}
cmSystemTools::ResetErrorOccurredFlag();
cmSystemTools::SetMessageCallback(
[](std::string const&, cmMessageMetadata const&) {});
cmSystemTools::SetStdoutCallback([](std::string const&) {});
cmSystemTools::SetStderrCallback([](std::string const&) {});
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
if (size < kHeaderSize || size > kHeaderSize + kMaxExprSize) {
return 0;
}
uint8_t const sel0 = data[0];
uint8_t const sel1 = data[1];
Mode const mode = static_cast<Mode>(sel0 & 0x3u);
char const* const headName = kTargets[(sel0 >> 2) & 0x7u];
std::string const config = kConfigs[(sel0 >> 5) & 0x3u];
std::string const otherConfig = kConfigs[((sel0 >> 5) + 1) & 0x3u];
bool const forBuildsystem = (sel0 & 0x80u) != 0;
std::string const language = kLanguages[sel1 & 0x7u];
unsigned const propIndex = (sel1 >> 3) & 0x3u;
char const* const currentName = kTargets[(sel1 >> 5) & 0x7u];
std::string expr(reinterpret_cast<char const*>(data + kHeaderSize),
size - kHeaderSize);
// The fatal-error flag is global and would stop the next fixture's
// listfile from running.
cmSystemTools::ResetErrorOccurredFlag();
{
Fixture fx;
Injection const* inject =
mode == Mode::Injection ? &kInjections[propIndex] : nullptr;
if (fx.Build(inject, expr)) {
cmGeneratorTarget const* head = fx.Find(headName);
switch (mode) {
case Mode::Evaluate: {
(void)cmGeneratorExpression::Evaluate(
std::move(expr), fx.LG, config, head, nullptr, nullptr, language);
} break;
case Mode::Compiled: {
cmGeneratorExpression ge(fx.CMake);
std::unique_ptr<cmCompiledGeneratorExpression> cge =
ge.Parse(std::move(expr));
cge->SetEvaluateForBuildsystem(forBuildsystem);
cmGeneratorTarget const* current = fx.Find(currentName);
cm::GenEx::Context ctx(fx.LG, config, language);
(void)cge->Evaluate(ctx, nullptr, head, current);
(void)cge->GetTargets();
(void)cge->GetAllTargetsSeen();
(void)cge->GetSeenTargetProperties();
(void)cge->GetHadContextSensitiveCondition();
(void)cge->GetHadHeadSensitiveCondition();
(void)cge->GetHadLinkLanguageSensitiveCondition();
(void)cge->GetSourceSensitiveTargets();
if (head) {
std::map<std::string, std::string> standards;
cge->GetMaxLanguageStandard(head, standards);
}
cm::GenEx::Context otherCtx(fx.LG, otherConfig, language);
(void)cge->Evaluate(otherCtx, nullptr, head, current);
} break;
case Mode::Interpreter: {
if (!head) {
head = fx.Find("fuzz_exe");
}
cmGeneratorExpressionInterpreter interpreter(fx.LG, config, head,
language);
(void)interpreter.Evaluate(std::move(expr),
kInterpreterProperties[propIndex]);
} break;
case Mode::Injection: {
Injection const& inj = kInjections[propIndex];
(void)cmGeneratorExpression::Evaluate(inj.Reader, fx.LG, config,
fx.Find("fuzz_exe"), nullptr,
nullptr, language);
std::string direct = "$<TARGET_PROPERTY:";
direct += inj.Target;
direct += ',';
direct += inj.Property;
direct += '>';
(void)cmGeneratorExpression::Evaluate(std::move(direct), fx.LG,
config);
} break;
}
}
}
cmSystemTools::ResetErrorOccurredFlag();
return 0;
}