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_tree (installed as qt_fuzzgen) — scans a Qt submodule source tree for
// default-constructible Qt classes and generates the complete
// <submodule>/tests/fuzzing/ tree:
//
// <submodule>/tests/fuzzing/
// CMakeLists.txt (top-level; adds all module subdirs)
// core/
// CMakeLists.txt (adds all class subdirs)
// QTimer/
// fuzz_QTimer.cpp (introspection + direct — has Q_OBJECT)
// CMakeLists.txt
// QDir/
// fuzz_QDir.cpp (direct-call only — no Q_OBJECT)
// CMakeLists.txt
// network/
// ...
//
// Classes with Q_OBJECT are fuzzed via QMetaObject introspection (slots /
// Q_INVOKABLE) as well as direct public-method calls.
// Classes without Q_OBJECT are fuzzed via direct public-method calls only.
// Classes that are abstract, not default-constructible, or have neither
// Q_OBJECT nor any extracted public methods are skipped.
//
// Usage:
// qt_fuzzgen --submodule <path> [options]
//
// Options:
// --submodule <path> Path to Qt submodule root (required)
// --out <path> Output root (default: <submodule>/tests/fuzzing)
// --modules a,b,... Comma-separated module src-dir filter (default: all)
// --qt-prefix <path> Installed Qt prefix (auto-detected)
// --time <sec> Fuzz duration per test (default: 30)
// --verbose Print every discovered class
#include "skiplist.h"
#include "codegen.h"
#include "qtdetect.h"
#include "scanner.h"
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
static void usage(const char *prog)
{
std::cerr
<< "Usage: " << prog << " --submodule <path> [options]\n"
<< "Options:\n"
<< " --submodule <path> Qt submodule source root (required)\n"
<< " --out <path> Output root (default: <submodule>/tests/fuzzing)\n"
<< " --modules a,b,... Module filter, e.g. corelib,network,widgets\n"
<< " --skiplist <path> Skip list file (optional; may be given multiple times)\n"
<< " --qt-prefix <path> Qt install prefix (auto-detected)\n"
<< " --time <sec> Fuzz duration in seconds (default: 30)\n"
<< " --qml Also include QML-exposed private classes (_p.h)\n"
<< " --embedded Omit project()/find_package() for parent-project inclusion\n"
<< " --verbose Verbose output\n";
}
static std::vector<std::string> splitComma(const std::string &s)
{
std::vector<std::string> result;
std::istringstream ss(s);
std::string tok;
while (std::getline(ss, tok, ',')) {
if (!tok.empty())
result.push_back(tok);
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 2) {
usage(argv[0]);
return 1;
}
fs::path submoduleRoot;
fs::path outputRoot;
std::vector<fs::path> skipListFiles;
std::string qtPrefix;
std::vector<std::string> moduleFilter;
int timeSec = 30;
bool verbose = false;
bool includeQml = false;
bool embedded = 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 == "--submodule" && i + 1 < argc) {
submoduleRoot = argv[++i];
} else if (a == "--out" && i + 1 < argc) {
outputRoot = argv[++i];
} else if (a == "--modules" && i + 1 < argc) {
moduleFilter = splitComma(argv[++i]);
} else if (a == "--skiplist" && i + 1 < argc) {
skipListFiles.push_back(argv[++i]);
} else if (a == "--qt-prefix" && i + 1 < argc) {
qtPrefix = 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 == "--embedded") {
embedded = true;
} else if (a == "--verbose" || a == "-v") {
verbose = true;
}
}
if (submoduleRoot.empty()) {
const char *qtdir = std::getenv("QTDIR");
if (qtdir)
submoduleRoot = fs::path(qtdir) / "qtbase";
}
if (submoduleRoot.empty() || !fs::exists(submoduleRoot)) {
std::cerr << "[qt_fuzzgen] ERROR: --submodule must point to a valid Qt submodule directory.\n";
usage(argv[0]);
return 1;
}
if (outputRoot.empty())
outputRoot = submoduleRoot / "tests" / "fuzzing";
if (qtPrefix.empty())
qtPrefix = QtFuzz::detectQtPrefix();
std::cout << "[qt_fuzzgen] submodule: " << submoduleRoot << "\n"
<< "[qt_fuzzgen] output : " << outputRoot << "\n";
if (!qtPrefix.empty())
std::cout << "[qt_fuzzgen] Qt pfx : " << qtPrefix << "\n";
for (const auto &sl : skipListFiles)
std::cout << "[qt_fuzzgen] skiplist: " << sl << "\n";
if (!moduleFilter.empty()) {
std::cout << "[qt_fuzzgen] Modules : ";
for (const auto &m : moduleFilter)
std::cout << m << " ";
std::cout << "\n";
}
QtFuzz::SkipList skipList;
for (const auto &sl : skipListFiles)
skipList.load(sl);
if (includeQml)
std::cout << "[qt_fuzzgen] QML mode: including QML-exposed private classes.\n";
std::cout << "[qt_fuzzgen] Scanning submodule/src for default-constructible Qt classes...\n";
auto classes = QtFuzz::scanClasses(submoduleRoot, moduleFilter, verbose, skipList, includeQml);
if (classes.empty()) {
std::cerr << "[qt_fuzzgen] No classes found. "
"Check that --submodule points to a populated Qt submodule tree.\n";
return 1;
}
std::cout << "[qt_fuzzgen] Found " << classes.size() << " classes.\n";
QtFuzz::TreeGenerator::Options opts{ submoduleRoot, outputRoot, qtPrefix, timeSec,
verbose, std::move(skipList), embedded };
QtFuzz::TreeGenerator treeGen(opts);
if (!treeGen.generate(classes)) {
std::cerr << "[qt_fuzzgen] Tree generation had errors.\n";
return 1;
}
std::cout << "\n[qt_fuzzgen] Done.\n";
if (embedded) {
std::cout << "\n[qt_fuzzgen] Embedded mode: add the generated tree to your parent\n"
<< "CMake project with:\n"
<< " add_subdirectory(" << outputRoot << " <build_sub_dir>)\n";
} else {
std::cout << "\nNext steps:\n"
<< " cmake -B build -S " << outputRoot;
if (!qtPrefix.empty())
std::cout << " -DCMAKE_PREFIX_PATH=" << qtPrefix;
std::cout << "\n"
<< " cmake --build build\n"
<< " ctest --test-dir build --output-on-failure -L fuzz\n";
}
return 0;
}