Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
//
// qt_fuzz_gen — generates a fuzz test .cpp and optional CMakeLists.txt for
// a single Qt class.
//
// When --submodule is provided the tool detects whether the class has the
// Q_OBJECT macro and selects the appropriate fuzzing strategy:
// - Q_OBJECT present : introspection (QMetaObject) + direct-call.
// - Q_OBJECT absent : direct-call only.
//
// Without --submodule the tool falls back to direct-call only (safe for all
// classes regardless of QObject ancestry).
//
// Usage:
// qt_fuzz_gen <ClassName> [options]
//
// Options:
// --out <file> Output .cpp (default: fuzz_<Class>.cpp)
// --cmake <file> Also generate CMakeLists.txt
// --submodule <path> Path to Qt submodule (used for module/AppType and Q_OBJECT detection)
// --qt-prefix <path> Installed Qt prefix (auto-detected if omitted)
// --time <sec> Fuzz duration passed to ctest (default: 30)
#include "skiplist.h"
#include "codegen.h"
#include "modulemap.h"
#include "qtdetect.h"
#include "scanner.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
namespace fs = std::filesystem;
static void usage(const char *prog)
{
std::cerr << "Usage: " << prog << " <ClassName> [options]\n"
<< "Options:\n"
<< " --out <file> Output .cpp (default: fuzz_<Class>.cpp)\n"
<< " --cmake <file> Also emit a CMakeLists.txt\n"
<< " --submodule <path> Qt submodule root (enables Q_OBJECT detection and module "
"lookup)\n"
<< " --qt-prefix <path> Qt install prefix (auto-detected)\n"
<< " --skiplist <path> Path to skip list file (optional)\n"
<< " --time <sec> Fuzz duration for ctest (default: 30)\n"
<< " --qml Also search _p.h files for QML-exposed private classes\n";
}
static std::string readFile(const fs::path &p)
{
std::ifstream f(p);
if (!f)
return { };
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
int main(int argc, char *argv[])
{
if (argc < 2) {
usage(argv[0]);
return 1;
}
std::string className;
std::string outFile;
std::string cmakeFile;
fs::path submoduleRoot;
fs::path skipListFile;
std::string qtPrefix;
int timeSec = 30;
bool includeQml = false;
for (int i = 1; i < argc; i++) {
std::string a(argv[i]);
if (a == "--help" || a == "-h") {
usage(argv[0]);
return 0;
} else if (a == "--out" && i + 1 < argc) {
outFile = argv[++i];
} else if (a == "--cmake" && i + 1 < argc) {
cmakeFile = argv[++i];
} else if (a == "--submodule" && i + 1 < argc) {
submoduleRoot = argv[++i];
} else if (a == "--qt-prefix" && i + 1 < argc) {
qtPrefix = argv[++i];
} else if (a == "--skiplist" && i + 1 < argc) {
skipListFile = argv[++i];
} else if ((a == "--time" || a == "-t") && i + 1 < argc) {
timeSec = std::atoi(argv[++i]);
} else if (a == "--qml") {
includeQml = true;
} else if (a.rfind("--", 0) != 0) {
className = a;
}
}
if (className.empty()) {
usage(argv[0]);
return 1;
}
if (outFile.empty())
outFile = "fuzz_" + className + ".cpp";
// Resolve module info (and AppType) if qtbase is available.
// Fall back to Core when it is not — always safe, if suboptimal.
QtFuzz::AppType appType = QtFuzz::AppType::Core;
QtFuzz::ModuleInfo mod = { "Core", "Qt6::Core", { }, QtFuzz::AppType::Core };
bool hasQObject = false; // default: direct-call only (always compiles)
std::string classInclude; // empty = use default <ClassName> form
bool isQmlExposed = false;
if (!submoduleRoot.empty() && fs::exists(submoduleRoot)) {
auto dcOpt = QtFuzz::scanSingleClass(className, submoduleRoot, includeQml);
if (dcOpt) {
hasQObject = dcOpt->hasQObject;
mod = dcOpt->module;
appType = mod.appType;
isQmlExposed = dcOpt->isQmlExposed;
if (isQmlExposed && !dcOpt->privateHeaderInclude.empty())
classInclude = "<" + dcOpt->privateHeaderInclude + ">";
} else {
std::cerr << "WARNING: class " << className
<< " not found under " << submoduleRoot
<< ". Using Qt6::Core and direct-call-only as fallback.\n";
}
}
QtFuzz::SkipList skipList(skipListFile);
if (skipList.isClassSkipped(className)) {
std::cerr << "[qt_fuzz_gen] " << className
<< " is in the skip list — skipping generation.\n";
return 0;
}
std::cout << "[qt_fuzz_gen] Class : " << className << "\n"
<< "[qt_fuzz_gen] Strategy : "
<< (hasQObject ? "introspection + direct-call" : "direct-call only")
<< "\n";
if (isQmlExposed)
std::cout << "[qt_fuzz_gen] QML-exposed private class: " << classInclude << "\n";
// Generate fuzz .cpp
QtFuzz::FuzzCppGenerator cppGen(className, outFile, appType, {}, hasQObject, classInclude);
if (!cppGen.generate())
return 1;
std::cout << "Written: " << outFile
<< " (app: " << QtFuzz::appTypeInfo(appType).className << ")\n";
// Optionally generate CMakeLists.txt
if (!cmakeFile.empty()) {
if (qtPrefix.empty())
qtPrefix = QtFuzz::detectQtPrefix();
auto components = QtFuzz::resolveComponents(mod);
const std::string fuzzFilename = fs::path(outFile).filename().string();
std::vector<std::string> privateComponents;
if (isQmlExposed)
privateComponents.push_back(mod.component + "Private");
QtFuzz::CMakeGenerator::Config cfg{
className, fuzzFilename, mod, components, qtPrefix, timeSec,
privateComponents
};
QtFuzz::CMakeGenerator cmakeGen(std::move(cfg), cmakeFile);
if (!cmakeGen.generate())
return 1;
std::cout << "Written: " << cmakeFile << "\n";
}
return 0;
}