Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "codegen.h"
#include "crashdiag_embed.h" // generated by CMake from include/crashdiag.h
#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <unordered_set>
namespace QtFuzz {
// ---------------------------------------------------------------------------
// Type-to-fuzz-expression mapper
//
// Maps a C++ parameter type string to a FuzzData expression that produces
// a suitable fuzzed value of that type.
//
// className is the enclosing class — used to qualify unscoped enum/type
// names that are nested inside the class (e.g. InsertPolicy becomes
// QComboBox::InsertPolicy).
// ---------------------------------------------------------------------------
// Qt types that are globally scoped (not nested in a class) and are always
// complete / default-constructible without needing className:: qualification.
static const std::unordered_set<std::string> kGlobalQtTypes = {
"QString", "QByteArray", "QVariant", "QStringList",
"QUrl", "QChar",
"QPoint", "QPointF", "QSize", "QSizeF",
"QRect", "QRectF", "QLine", "QLineF",
"QDate", "QTime", "QDateTime",
"QModelIndex", "QPersistentModelIndex",
"QLocale", "QMargins", "QMarginsF",
"QVector2D", "QVector3D", "QVector4D",
"QFont", "QIcon", "QPixmap", "QImage",
"QCursor", "QPalette", "QRegion", "QKeySequence",
"QColor",
"QDeadlineTimer",
"QAnyStringView",
"QCalendar",
"QTextCharFormat",
"QPainterPath",
"QPalette",
"QModelIndex",
"QPersistentModelIndex",
"QTimeZone",
"QPen",
"QBrush",
"QTransform",
"QMatrix4x4",
"QStringView",
"QKeyCombination",
"QAbstractAnimation",
// Global enum from qlogging.h/qglobal.h — not nested in any class. Its
// second letter is lowercase ('t'), so it doesn't match the "Q+uppercase
// = global Qt class" heuristic below and would otherwise be wrongly
// qualified as ClassName::QtMsgType.
"QtMsgType",
// These are passed by pointer so handled by the '*' branch, but listed
// here so we don't accidentally qualify them.
"QObject", "QWidget", "QAbstractItemModel", "QAbstractItemView",
};
// True when an unscoped single-word uppercase type needs className::
// qualification to compile — a nested enum, typedef, or alias such as
// OpenModeFlag. Global types must not be prefixed: Q+uppercase Qt classes
// (QColor, QFont, …), Q_ALLCAPS structs (Q_IPV6ADDR), OpenGL / Vulkan
// typedefs, and kGlobalQtTypes members. Types that are already qualified,
// templated, or pointers are left alone as well.
static bool needsClassQualification(const std::string &type)
{
if (type.empty() || !std::isupper(static_cast<unsigned char>(type[0])))
return false;
if (type.find(':') != std::string::npos || type.find('<') != std::string::npos
|| type.find('*') != std::string::npos)
return false;
if (type.size() >= 2 && type[0] == 'Q' && std::isupper(static_cast<unsigned char>(type[1])))
return false;
if (type.size() >= 3 && type[0] == 'Q' && type[1] == '_')
return false;
if (type.size() >= 2 && type[0] == 'G' && type[1] == 'L')
return false;
if (type.size() >= 3 && type[0] == 'V' && type[1] == 'k'
&& std::isupper(static_cast<unsigned char>(type[2])))
return false;
return !kGlobalQtTypes.count(type);
}
static std::string fuzzExprForType(const std::string &rawType,
const std::string &className)
{
// Normalise whitespace.
std::string type;
bool lastSpace = true;
for (char c : rawType) {
if (std::isspace(static_cast<unsigned char>(c))) {
if (!lastSpace) {
type += ' ';
lastSpace = true;
}
} else {
type += c;
lastSpace = false;
}
}
while (!type.empty() && type.back() == ' ')
type.pop_back();
// Primitives.
// Each expression is wrapped in static_cast<Type>() so that when a class
// has multiple overloads with different numeric first parameters (e.g.
// QLocale::toString(qlonglong,int,char32_t) vs toString(int,int,char32_t))
// the compiler can always select the intended overload unambiguously.
if (type == "bool")
return "static_cast<bool>(fd.nextBool())";
if (type == "int" || type == "qint32")
return "static_cast<int>(fd.nextInt())";
if (type == "uint" || type == "quint32" || type == "unsigned int") {
return "static_cast<uint>(fd.nextUInt())";
}
if (type == "qint64" || type == "qlonglong" || type == "long long") {
return "static_cast<" + type + ">(fd.nextInt64())";
}
if (type == "quint64" || type == "qulonglong" || type == "unsigned long long") {
return "static_cast<" + type + ">(fd.nextUInt64())";
}
if (type == "double")
return "static_cast<double>(fd.nextDouble())";
if (type == "float")
return "static_cast<float>(fd.nextFloat())";
if (type == "qreal")
return "static_cast<qreal>(fd.nextDouble())";
if (type == "char")
return "static_cast<char>(fd.nextByte())";
if (type == "uchar" || type == "unsigned char")
return "static_cast<uchar>(fd.nextByte())";
if (type == "char32_t")
return "static_cast<char32_t>(fd.nextInt())";
if (type == "short" || type == "qint16")
return "static_cast<short>(fd.nextRaw<short>())";
if (type == "ushort" || type == "quint16")
return "static_cast<ushort>(fd.nextRaw<ushort>())";
if (type == "long")
return "static_cast<long>(fd.nextRaw<long>())";
if (type == "ulong" || type == "unsigned long")
return "static_cast<ulong>(fd.nextRaw<ulong>())";
if (type == "qsizetype")
return "static_cast<qsizetype>(fd.nextInt64())";
if (type == "qptrdiff")
return "static_cast<qptrdiff>(fd.nextInt64())";
// QString variants.
if (type == "QString" || type == "const QString &" || type == "const QString&") {
return "fd.nextQString()";
}
// QByteArray variants.
if (type == "QByteArray" || type == "const QByteArray &" || type == "const QByteArray&") {
return "fd.nextQByteArray()";
}
// std::chrono durations.
if (type == "std::chrono::nanoseconds")
return "std::chrono::nanoseconds(fd.nextInt64())";
if (type == "std::chrono::milliseconds")
return "std::chrono::milliseconds(fd.nextInt64())";
if (type == "std::chrono::seconds")
return "std::chrono::seconds(fd.nextInt64())";
if (type.find("std::chrono::") == 0)
return type + "(fd.nextInt64())";
// Common Qt value types.
if (type == "QChar")
return "QChar(fd.nextRaw<ushort>())";
if (type == "QPoint")
return "QPoint(fd.nextInt(), fd.nextInt())";
if (type == "QPointF")
return "QPointF(fd.nextDouble(), fd.nextDouble())";
if (type == "QSize")
return "QSize(fd.nextInt(), fd.nextInt())";
if (type == "QSizeF")
return "QSizeF(fd.nextDouble(), fd.nextDouble())";
if (type == "QRect")
return "QRect(fd.nextInt(), fd.nextInt(), fd.nextInt(), fd.nextInt())";
if (type == "QRectF")
return "QRectF(fd.nextDouble(), fd.nextDouble(), fd.nextDouble(), fd.nextDouble())";
if (type == "QUrl" || type == "const QUrl &" || type == "const QUrl&") {
return "QUrl(fd.nextQString())";
}
if (type == "QColor" || type == "const QColor &" || type == "const QColor&") {
return "QColor(fd.nextByte(), fd.nextByte(), fd.nextByte(), fd.nextByte())";
}
if (type == "QDeadlineTimer")
return "QDeadlineTimer(fd.nextInt64())";
if (type == "QAnyStringView" || type == "const QAnyStringView &"
|| type == "const QAnyStringView&") {
return "QAnyStringView(fd.nextQString())";
}
// Pointer types — cast nullptr to the exact pointer type to avoid
// ambiguity when multiple overloads take different pointer types.
if (!type.empty() && type.back() == '*')
return "static_cast<" + type + ">(nullptr)";
// Reference types — strip to base type and default-construct a temporary.
if (!type.empty() && type.back() == '&') {
std::string base = type;
while (!base.empty() && (base.back() == '&' || base.back() == ' '))
base.pop_back();
if (base.size() >= 6 && base.substr(0, 6) == "const ")
base = base.substr(6);
while (!base.empty() && base.back() == ' ')
base.pop_back();
// If the base type is a template like QList<ExtraSelection>, qualify
// any unscoped nested type inside the angle brackets with className::.
auto ltPos = base.find('<');
auto gtPos = base.rfind('>');
if (ltPos != std::string::npos && gtPos != std::string::npos
&& gtPos > ltPos) {
std::string inner = base.substr(ltPos + 1, gtPos - ltPos - 1);
size_t ia = inner.find_first_not_of(" \t");
size_t ib = inner.find_last_not_of(" \t");
if (ia != std::string::npos)
inner = inner.substr(ia, ib - ia + 1);
if (needsClassQualification(inner)) {
base = base.substr(0, ltPos + 1)
+ className + "::" + inner
+ base.substr(gtPos);
}
}
// Qualify unscoped single-word uppercase types with className:: so the
// generated code compiles (see needsClassQualification).
if (needsClassQualification(base))
base = className + "::" + base;
return "/* ref */ " + base + "{}";
}
// Qt:: scoped enums/flags — cast a byte.
if (type.size() >= 4 && type.substr(0, 4) == "Qt::")
return "static_cast<" + type + ">(fd.nextByte())";
// Q<Class>:: nested types — may be an enum or a value-class (e.g.
// QFont::Tag has no integral constructor). Use default construction which
// is valid for both enums and default-constructible value classes.
if (type.size() >= 2 && type[0] == 'Q' && std::isupper(static_cast<unsigned char>(type[1]))
&& type.find("::") != std::string::npos)
return type + "{}";
// Other fully qualified types with :: — cast a byte (covers ClassName::Enum).
// Exclude std:: types: they are C++ standard-library classes/structs, not
// Qt enums, and must be default-constructed (e.g. std::exception_ptr{}).
if (type.find("::") != std::string::npos
&& !(type.size() >= 5 && type.substr(0, 5) == "std::"))
return "static_cast<" + type + ">(fd.nextByte())";
// Single-word type starting with uppercase letter.
if (!type.empty() && std::isupper(static_cast<unsigned char>(type[0]))
&& type.find('<') == std::string::npos) {
if (kGlobalQtTypes.count(type))
return type + "{}";
// Types that start with Q followed by a second uppercase letter are
// Qt classes in the global namespace (QByteArrayView, QStringView,
// QTextDocument, …). Do NOT qualify them with className:: — that
// would generate nonsense like QByteArray::QByteArrayView.
if (type.size() >= 2 && type[0] == 'Q'
&& std::isupper(static_cast<unsigned char>(type[1])))
return type + "{}";
// Q_ALLCAPS types (Q_ followed by underscore) are globally-defined
// Qt structs / typedefs (e.g. Q_IPV6ADDR). They live in the global
// namespace and must not be qualified with className::.
if (type.size() >= 3 && type[0] == 'Q' && type[1] == '_')
return type + "{}";
// OpenGL types (GLint, GLenum, …) are global C typedefs.
// GLsync is a pointer handle (struct __GLsync*), so use T{} for
// zero-initialization (covers integer types = 0 and pointer = nullptr).
if (type.size() >= 2 && type[0] == 'G' && type[1] == 'L')
return type + "{}";
// Vulkan types are either enums (VkResult) or opaque pointer handles
// (VkDevice, VkInstance, …). Dispatchable handles are pointer typedefs
// that cannot be constructed from an integer; zero-init covers both.
if (type.size() >= 3 && type[0] == 'V' && type[1] == 'k'
&& std::isupper(static_cast<unsigned char>(type[2])))
return type + "{}";
// Everything else (OpenModeFlag, Direction, Format, DebugFilter, …)
// is assumed to be a nested enum, typedef, or alias inside the class.
// Use default construction: valid for enums (value 0), QFlags (0),
// function pointers (nullptr), std::function (empty), and value types.
return className + "::" + type + "{}";
}
// std::optional<T> — use std::nullopt (implicitly converts to any optional).
if (type.find("std::optional") != std::string::npos)
return "std::nullopt";
// Other std:: types (std::exception_ptr, etc.) — default-construct.
if (type.size() >= 5 && type.substr(0, 5) == "std::")
return type + "{}";
// Fallback: lowercase leaked name → safe default.
if (!type.empty() && std::islower(static_cast<unsigned char>(type[0])))
return "fd.nextInt()";
return type + "{}";
}
// Wrapper that respects isNestedStruct — nested struct value types are
// default-constructed with {} rather than cast from a raw byte, because
// they may have no integral constructor (e.g. QFont::Tag).
// Reference types (const T &) are handled by fuzzExprForType which already
// strips the ref qualifier and emits T{}, so we delegate those.
static std::string fuzzExprForParam(const MethodParam &p, const std::string &className)
{
if (p.isNestedStruct) {
const std::string &t = p.type;
// Pointers to nested structs (e.g. GraphicsPipelineState *) must go
// through fuzzExprForType's pointer branch (static_cast<T*>(nullptr)),
// not be default-constructed as "T *{}", which isn't valid syntax.
bool isRefOrPtr = !t.empty() && (t.back() == '&' || t.back() == '*');
if (!isRefOrPtr)
return p.type + "{}";
}
return fuzzExprForType(p.type, className);
}
// ---------------------------------------------------------------------------
// Extra includes needed for method parameter and return types
// ---------------------------------------------------------------------------
// Scans a C++ type string for Qt class names (Q followed by an uppercase
// letter) and emits a corresponding system include for each one found.
// Also detects std::chrono types. This is applied to both parameter types
// and return types so that forward-declared companion types (e.g. QCborArray
// forward-declared in qcborvalue.h) are included when the generated code
// returns or receives them by value.
static void extractQtIncludes(const std::string &typeStr,
std::set<std::string> &includes)
{
if (typeStr.find("std::chrono") != std::string::npos)
includes.insert("<chrono>");
size_t i = 0;
while (i < typeStr.size()) {
if (typeStr[i] == 'Q'
&& i + 1 < typeStr.size()
&& std::isupper(static_cast<unsigned char>(typeStr[i + 1]))) {
size_t start = i;
while (i < typeStr.size()
&& (std::isalnum(static_cast<unsigned char>(typeStr[i])) || typeStr[i] == '_')) {
++i;
}
std::string word = typeStr.substr(start, i - start);
// Qt class names are CamelCase and never contain underscores.
// Names with underscores are macros (QT_CORE_CONSTEXPR_INLINE_SINCE,
// QT_GUI_REMOVED_SINCE, etc.) — skip them.
if (word.find('_') == std::string::npos)
includes.insert("<" + word + ">");
} else {
++i;
}
}
}
// Returns true if a type string represents a pointer type, i.e. the last
// non-space character is '*'. Template arguments are intentionally ignored
// so that QList<QObject *> (outer type is a value) is NOT treated as a pointer.
static bool isPointerType(const std::string &t)
{
size_t i = t.size();
while (i > 0 && t[i - 1] == ' ')
--i;
return i > 0 && t[i - 1] == '*';
}
static std::set<std::string> extraIncludesForMethods(const std::vector<MethodSignature> &methods)
{
std::set<std::string> includes;
for (const auto &sig : methods) {
// Return type: value and reference returns may need a full type
// definition (e.g. destructor on a temporary). Pointer returns only
// need a forward declaration — which the class's own header already
// provides — so skip them. This avoids bogus includes for private or
// namespace-qualified types such as QtPrivate::QPropertyBindingData *.
if (!sig.returnType.empty()
&& sig.returnType != "void"
&& !isPointerType(sig.returnType))
extractQtIncludes(sig.returnType, includes);
// Parameter types: same rule — pointer params need no include.
for (const auto &param : sig.params) {
if (!isPointerType(param.type))
extractQtIncludes(param.type, includes);
}
}
return includes;
}
// ---------------------------------------------------------------------------
// Direct-call fuzz function generation
// ---------------------------------------------------------------------------
// Escapes a string for safe embedding in a C string literal. Method/type
// names are not expected to contain '"' or '\', but this is defensive.
static std::string escapeForCString(const std::string &s)
{
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '"' || c == '\\')
out += '\\';
out += c;
}
return out;
}
// Builds "ClassName::method(type1, type2)" for a MethodSignature, for use as
// a compile-time crash-attribution string (see kDirectCallNames below and
// QtFuzzRuntime::setCurrentCall in crashdiag.h).
static std::string formatCallSignature(const std::string &className,
const MethodSignature &sig)
{
std::string s = className + "::" + sig.name + "(";
for (size_t i = 0; i < sig.params.size(); ++i) {
if (i > 0)
s += ", ";
s += sig.params[i].type;
}
s += ")";
return escapeForCString(s);
}
static std::string buildDirectFuzzFunction(
const std::string &className,
const std::vector<MethodSignature> &methods)
{
if (methods.empty())
return "";
// Compile-time call-attribution strings: callNames[i] names methods[i],
// by construction (both are built by iterating the same vector in the
// same order) — no separate index bookkeeping needed to keep them
// aligned with the switch's caseIdx below.
std::vector<std::string> callNames;
callNames.reserve(methods.size());
for (const auto &sig : methods)
callNames.push_back(formatCallSignature(className, sig));
std::ostringstream o;
o << "static void fuzz_direct(" << className << " &obj, FuzzData &fd)\n"
<< "{\n"
<< " switch (fd.nextByte() % " << methods.size() << ") {\n";
size_t caseIdx = 0;
bool anyRealCase = false;
for (const auto &sig : methods) {
bool needsLocals = false;
for (const auto &p : sig.params) {
if (p.isNonConstRef) {
needsLocals = true;
break;
}
}
bool allBraces = !sig.params.empty();
for (const auto &p : sig.params) {
std::string expr = fuzzExprForParam(p, className);
if (expr != "{}" && expr != "nullptr"
&& expr != "static_cast<QWidget *>(nullptr)"
&& expr != "fd.nextInt()")
allBraces = false;
}
if (allBraces) {
++caseIdx;
continue;
}
anyRealCase = true;
o << " case " << caseIdx << ":\n";
o << " QtFuzzRuntime::setCurrentCall(kDirectCallNames[" << caseIdx << "]);\n";
++caseIdx;
if (needsLocals) {
o << " {\n";
for (size_t i = 0; i < sig.params.size(); ++i) {
if (sig.params[i].isNonConstRef) {
std::string base = sig.params[i].type;
while (!base.empty() && (base.back() == '&' || base.back() == ' '))
base.pop_back();
if (base.size() >= 6 && base.substr(0, 6) == "const ")
base = base.substr(6);
while (!base.empty() && base.back() == ' ')
base.pop_back();
// Qualify unscoped uppercase non-Qt-global types with
// className:: (same logic as fuzzExprForType ref path).
if (needsClassQualification(base))
base = className + "::" + base;
o << " " << base << " _p" << i << "{};\n";
}
}
o << " (void)obj." << sig.name << "(";
for (size_t i = 0; i < sig.params.size(); ++i) {
if (i > 0)
o << ", ";
if (sig.params[i].isNonConstRef)
o << "_p" << i;
else
o << fuzzExprForParam(sig.params[i], className);
}
o << ");\n"
<< " }\n"
<< " break;\n";
} else {
o << " (void)obj." << sig.name << "(";
for (size_t i = 0; i < sig.params.size(); ++i) {
if (i > 0)
o << ", ";
o << fuzzExprForParam(sig.params[i], className);
}
o << ");\n"
<< " break;\n";
}
}
o << " }\n"
<< "}\n";
if (!anyRealCase)
return o.str();
std::ostringstream out;
out << "static const char *const kDirectCallNames[] = {\n";
for (const auto &name : callNames)
out << " \"" << name << "\",\n";
out << "};\n\n"
<< o.str();
return out.str();
}
// ---------------------------------------------------------------------------
// Shared FuzzData struct text (embedded verbatim in both templates)
// ---------------------------------------------------------------------------
static const char kFuzzDataStruct[] = R"FUZZDATA(
// ---------------------------------------------------------------------------
// FuzzData — wraps a byte buffer and vends typed fuzz values.
// ---------------------------------------------------------------------------
struct FuzzData
{
const uint8_t *data;
size_t size;
size_t pos = 0;
uint8_t nextByte()
{
if (size == 0) return 0;
return data[pos++ % size];
}
template <typename T>
T nextRaw()
{
T v = 0;
for (size_t i = 0; i < sizeof(T); i++)
v = static_cast<T>((v << 8) | nextByte());
return v;
}
bool nextBool() { return (nextByte() & 1) != 0; }
int nextInt() { return nextRaw<int>(); }
uint nextUInt() { return nextRaw<uint>(); }
qint64 nextInt64() { return nextRaw<qint64>(); }
quint64 nextUInt64() { return nextRaw<quint64>(); }
double nextDouble()
{
uint64_t bits = nextRaw<uint64_t>();
double v; std::memcpy(&v, &bits, sizeof(v));
return v;
}
float nextFloat()
{
uint32_t bits = nextRaw<uint32_t>();
float v; std::memcpy(&v, &bits, sizeof(v));
return v;
}
QString nextQString(size_t maxLen = 256)
{
size_t len = nextRaw<uint16_t>() % (maxLen + 1);
QByteArray ba;
ba.reserve(static_cast<int>(len));
for (size_t i = 0; i < len; i++)
ba.append(static_cast<char>(nextByte()));
return QString::fromUtf8(ba);
}
QByteArray nextQByteArray(size_t maxLen = 512)
{
size_t len = nextRaw<uint16_t>() % (maxLen + 1);
QByteArray ba;
ba.reserve(static_cast<int>(len));
for (size_t i = 0; i < len; i++)
ba.append(static_cast<char>(nextByte()));
return ba;
}
};
)FUZZDATA";
// ---------------------------------------------------------------------------
// Template for classes WITH Q_OBJECT
// Uses introspection (QMetaObject) + direct-call fuzzing.
// ---------------------------------------------------------------------------
const char *FuzzCppGenerator::kTemplateQObject = R"FUZZTEMPLATE(
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
//
// Fuzz test for @@CLASS@@
// Auto-generated by QtFuzz::FuzzCppGenerator — do not edit by hand.
// Strategy: introspection-based (QMetaObject) + direct-call.
//
// Run:
// ./fuzz_@@CLASS@@ [--time <seconds>] [--seed <uint64>] [corpus_file ...]
// Prevent Windows SDK min/max macros from shadowing std::max.
#if defined(_WIN32) || defined(_WIN64)
# ifndef NOMINMAX
# define NOMINMAX
# endif
#endif
@@CRASH_DIAGNOSTICS@@
#include @@CLASS_INCLUDE@@
#include @@APP_INCLUDE@@
#include <QMetaObject>
#include <QMetaMethod>
#include <QMetaType>
#include <QGenericArgument>
#include <QString>
#include <QByteArray>
#include <QUrl>
#include <QPoint>
#include <QPointF>
#include <QSize>
#include <QSizeF>
#include <QRect>
#include <QRectF>
#if QT_GUI_LIB
#include <QColor>
#endif
#include <QVariant>
#include <QChar>
#include <QDate>
#include <QTime>
#include <QDateTime>
@@EXTRA_INCLUDES@@
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
@@FUZZ_DATA_STRUCT@@
// QVariant generator for introspection-based fuzzing.
static QVariant nextVariantForType(FuzzData &fd, QMetaType metaType)
{
const int id = metaType.id();
switch (id) {
case QMetaType::Bool: return QVariant(fd.nextBool());
case QMetaType::Int: return QVariant(fd.nextInt());
case QMetaType::UInt: return QVariant(fd.nextUInt());
case QMetaType::LongLong: return QVariant(fd.nextInt64());
case QMetaType::ULongLong: return QVariant(fd.nextUInt64());
case QMetaType::Double: return QVariant(fd.nextDouble());
case QMetaType::Float: return QVariant(fd.nextFloat());
case QMetaType::Long: return QVariant::fromValue<long>(fd.nextRaw<long>());
case QMetaType::Short: return QVariant::fromValue<short>(fd.nextRaw<short>());
case QMetaType::Char: return QVariant::fromValue<char>(fd.nextRaw<char>());
case QMetaType::ULong: return QVariant::fromValue<ulong>(fd.nextRaw<ulong>());
case QMetaType::UShort: return QVariant::fromValue<ushort>(fd.nextRaw<ushort>());
case QMetaType::UChar: return QVariant::fromValue<uchar>(fd.nextRaw<uchar>());
case QMetaType::SChar: return QVariant::fromValue<signed char>(fd.nextRaw<signed char>());
case QMetaType::QString: return QVariant(fd.nextQString());
case QMetaType::QByteArray: return QVariant(fd.nextQByteArray());
case QMetaType::QChar: return QVariant(QChar(fd.nextRaw<ushort>()));
case QMetaType::QPoint: return QVariant(QPoint(fd.nextInt(), fd.nextInt()));
case QMetaType::QPointF: return QVariant(QPointF(fd.nextDouble(), fd.nextDouble()));
case QMetaType::QSize: return QVariant(QSize(fd.nextInt(), fd.nextInt()));
case QMetaType::QSizeF: return QVariant(QSizeF(fd.nextDouble(), fd.nextDouble()));
case QMetaType::QRect: return QVariant(QRect(fd.nextInt(), fd.nextInt(), fd.nextInt(), fd.nextInt()));
case QMetaType::QRectF: return QVariant(QRectF(fd.nextDouble(), fd.nextDouble(), fd.nextDouble(), fd.nextDouble()));
#if QT_GUI_LIB
case QMetaType::QColor: return QVariant(QColor(fd.nextByte(), fd.nextByte(), fd.nextByte(), fd.nextByte()));
#endif
case QMetaType::QUrl: return QVariant(QUrl(fd.nextQString()));
case QMetaType::QDate:
return QVariant(QDate(fd.nextRaw<int>() % 9999 + 1,
fd.nextByte() % 12 + 1, fd.nextByte() % 28 + 1));
case QMetaType::QTime:
return QVariant(QTime(fd.nextByte() % 24, fd.nextByte() % 60,
fd.nextByte() % 60, fd.nextRaw<uint16_t>() % 1000));
case QMetaType::QDateTime: {
QDate d(fd.nextRaw<int>() % 9999 + 1, fd.nextByte() % 12 + 1, fd.nextByte() % 28 + 1);
QTime t(fd.nextByte() % 24, fd.nextByte() % 60, fd.nextByte() % 60);
return QVariant(QDateTime(d, t));
}
case QMetaType::QVariant: return fd.nextQString();
default: return QVariant(metaType, nullptr);
}
}
// ---------------------------------------------------------------------------
// Introspection-based fuzzing (slots / Q_INVOKABLE via QMetaObject)
// ---------------------------------------------------------------------------
static void invokeWithFuzzData(QObject *obj,
const QMetaMethod &method,
FuzzData &fd)
{
const int paramCount = method.parameterCount();
if (paramCount > 10) return;
// Record the call before building its arguments — nextVariantForType()
// itself runs nontrivial Qt code (QDateTime/QVariant construction for
// unknown types) that could be the actual crash site, not just invoke().
static char metaCallBuf[256];
std::snprintf(metaCallBuf, sizeof(metaCallBuf), "%s::%s",
obj->metaObject()->className(), method.methodSignature().constData());
QtFuzzRuntime::setCurrentCall(metaCallBuf);
QVariant storage[10];
QGenericArgument args[10];
for (int i = 0; i < paramCount; i++) {
storage[i] = nextVariantForType(fd, method.parameterMetaType(i));
args[i] = QGenericArgument(storage[i].typeName(), storage[i].constData());
}
method.invoke(obj, Qt::DirectConnection,
args[0], args[1], args[2], args[3], args[4],
args[5], args[6], args[7], args[8], args[9]);
}
static std::vector<QMetaMethod> collectMethods(const QMetaObject *mo)
{
std::vector<QMetaMethod> result;
for (int i = mo->methodOffset(); i < mo->methodCount(); i++) {
QMetaMethod m = mo->method(i);
if (m.methodType() == QMetaMethod::Signal) continue;
QByteArray name = m.name();
if (name.startsWith("_q_")) continue;
if (name == "deleteLater") continue;
if (m.parameterCount() == 0) continue;
result.push_back(m);
}
return result;
}
static void fuzz_one(QObject *obj,
const std::vector<QMetaMethod> &methods,
const uint8_t *data, size_t size)
{
if (methods.empty()) return;
FuzzData fd{ data, size };
const size_t idx = fd.nextByte() % methods.size();
invokeWithFuzzData(obj, methods[idx], fd);
}
// ---------------------------------------------------------------------------
// Direct-call fuzzing (all public methods, incl. non-slots / non-Q_INVOKABLE)
// ---------------------------------------------------------------------------
@@DIRECT_FUZZ_FUNC@@
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char *argv[])
{
QtFuzzRuntime::installCrashHandler("@@CLASS@@");
@@APP_CLASS@@ app(argc, argv);
int timeLimitSec = 30;
uint64_t seed = 0xdeadbeefcafe1234ULL;
std::vector<std::string> corpusFiles;
for (int i = 1; i < argc; i++) {
std::string a(argv[i]);
if ((a == "--time" || a == "-t") && i + 1 < argc)
timeLimitSec = std::atoi(argv[++i]);
else if ((a == "--seed" || a == "-s") && i + 1 < argc)
seed = std::stoull(argv[++i]);
else if (a.rfind("--", 0) != 0)
corpusFiles.push_back(a);
}
@@CLASS@@ obj;
const QMetaObject *mo = obj.metaObject();
auto methods = collectMethods(mo);
std::cout << "[fuzz_@@CLASS@@] Class : " << mo->className() << "\n";
std::cout << "[fuzz_@@CLASS@@] Meta methods : " << methods.size() << "\n";
for (const auto &m : methods)
std::cout << " " << m.methodSignature().constData() << "\n";
for (const auto &path : corpusFiles) {
std::ifstream f(path, std::ios::binary);
if (!f) { std::cerr << "[fuzz_@@CLASS@@] Cannot open: " << path << "\n"; continue; }
std::vector<uint8_t> buf(std::istreambuf_iterator<char>(f), {});
fuzz_one(&obj, methods, buf.data(), buf.size());
@@DIRECT_FUZZ_CORPUS_CALL@@
}
constexpr size_t BUF_SIZE = 4096;
std::vector<uint8_t> buf(BUF_SIZE);
uint64_t iterations = 0;
auto t0 = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - t0 < std::chrono::seconds(timeLimitSec)) {
seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17;
uint64_t s = seed;
for (size_t i = 0; i < BUF_SIZE; i++) {
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
buf[i] = static_cast<uint8_t>(s & 0xff);
}
fuzz_one(&obj, methods, buf.data(), BUF_SIZE);
@@DIRECT_FUZZ_LOOP_CALL@@
iterations++;
QtFuzzRuntime::setIteration(iterations);
}
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();
std::cout << "[fuzz_@@CLASS@@] Done. "
<< iterations << " iterations in "
<< elapsed / 1000.0 << "s ("
<< (iterations * 1000ULL / std::max(elapsed, static_cast<decltype(elapsed)>(1)))
<< " iter/s)\n";
return 0;
}
)FUZZTEMPLATE";
// ---------------------------------------------------------------------------
// Template for classes WITHOUT Q_OBJECT
// Direct-call fuzzing only — no QMetaObject machinery.
// Suitable for any default-constructible Qt class regardless of ancestry.
// ---------------------------------------------------------------------------
const char *FuzzCppGenerator::kTemplateDirectOnly = R"FUZZTEMPLATE(
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
//
// Fuzz test for @@CLASS@@
// Auto-generated by QtFuzz::FuzzCppGenerator — do not edit by hand.
// Strategy: direct-call (class does not have Q_OBJECT).
//
// Run:
// ./fuzz_@@CLASS@@ [--time <seconds>] [--seed <uint64>] [corpus_file ...]
// Prevent Windows SDK min/max macros from shadowing std::max.
#if defined(_WIN32) || defined(_WIN64)
# ifndef NOMINMAX
# define NOMINMAX
# endif
#endif
@@CRASH_DIAGNOSTICS@@
#include @@CLASS_INCLUDE@@
#include @@APP_INCLUDE@@
#include <QString>
#include <QByteArray>
#include <QUrl>
#include <QPoint>
#include <QPointF>
#include <QSize>
#include <QSizeF>
#include <QRect>
#include <QRectF>
#if QT_GUI_LIB
#include <QColor>
#endif
#include <QChar>
#include <QDate>
#include <QTime>
#include <QDateTime>
@@EXTRA_INCLUDES@@
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
@@FUZZ_DATA_STRUCT@@
// ---------------------------------------------------------------------------
// Direct-call fuzzing (all public methods)
// ---------------------------------------------------------------------------
@@DIRECT_FUZZ_FUNC@@
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char *argv[])
{
QtFuzzRuntime::installCrashHandler("@@CLASS@@");
@@APP_CLASS@@ app(argc, argv);
int timeLimitSec = 30;
uint64_t seed = 0xdeadbeefcafe1234ULL;
std::vector<std::string> corpusFiles;
for (int i = 1; i < argc; i++) {
std::string a(argv[i]);
if ((a == "--time" || a == "-t") && i + 1 < argc)
timeLimitSec = std::atoi(argv[++i]);
else if ((a == "--seed" || a == "-s") && i + 1 < argc)
seed = std::stoull(argv[++i]);
else if (a.rfind("--", 0) != 0)
corpusFiles.push_back(a);
}
@@CLASS@@ obj;
std::cout << "[fuzz_@@CLASS@@] Class : @@CLASS@@\n";
for (const auto &path : corpusFiles) {
std::ifstream f(path, std::ios::binary);
if (!f) { std::cerr << "[fuzz_@@CLASS@@] Cannot open: " << path << "\n"; continue; }
std::vector<uint8_t> buf(std::istreambuf_iterator<char>(f), {});
@@DIRECT_FUZZ_CORPUS_CALL@@
}
constexpr size_t BUF_SIZE = 4096;
std::vector<uint8_t> buf(BUF_SIZE);
uint64_t iterations = 0;
auto t0 = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - t0 < std::chrono::seconds(timeLimitSec)) {
seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17;
uint64_t s = seed;
for (size_t i = 0; i < BUF_SIZE; i++) {
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
buf[i] = static_cast<uint8_t>(s & 0xff);
}
@@DIRECT_FUZZ_LOOP_CALL@@
iterations++;
QtFuzzRuntime::setIteration(iterations);
}
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();
std::cout << "[fuzz_@@CLASS@@] Done. "
<< iterations << " iterations in "
<< elapsed / 1000.0 << "s ("
<< (iterations * 1000ULL / std::max(elapsed, static_cast<decltype(elapsed)>(1)))
<< " iter/s)\n";
return 0;
}
)FUZZTEMPLATE";
// ---------------------------------------------------------------------------
// FuzzCppGenerator
// ---------------------------------------------------------------------------
FuzzCppGenerator::FuzzCppGenerator(std::string className,
fs::path outputPath,
AppType appType,
std::vector<MethodSignature> publicMethods,
bool hasQObject,
std::string classInclude)
: m_className(std::move(className))
, m_outputPath(std::move(outputPath))
, m_appType(appType)
, m_publicMethods(std::move(publicMethods))
, m_hasQObject(hasQObject)
, m_classInclude(classInclude.empty() ? "<" + m_className + ">" : std::move(classInclude))
{
}
std::string FuzzCppGenerator::buildSource() const
{
const std::string outFilename = m_outputPath.filename().string();
const AppTypeInfo ati = appTypeInfo(m_appType);
// Guard each extra include with __has_include so the generated file
// compiles even when a companion type has no public module header
// (e.g. QCborSimpleType, QtPrivate types, etc.).
std::ostringstream extraIncludes;
for (const auto &inc : extraIncludesForMethods(m_publicMethods)) {
extraIncludes << "#if __has_include(" << inc << ")\n"
<< "# include " << inc << "\n"
<< "#endif\n";
}
std::string directFuzzFunc = buildDirectFuzzFunction(m_className, m_publicMethods);
std::string directFuzzCorpusCall;
std::string directFuzzLoopCall;
if (!m_publicMethods.empty()) {
directFuzzCorpusCall =
" { FuzzData fd{ buf.data(), buf.size() }; fuzz_direct(obj, fd); }";
directFuzzLoopCall =
" { FuzzData fd{ buf.data(), BUF_SIZE }; fuzz_direct(obj, fd); }";
}
// Select template based on Q_OBJECT presence.
const char *tmpl = m_hasQObject ? kTemplateQObject : kTemplateDirectOnly;
std::string src(tmpl);
auto replace = [](std::string &s, const std::string &from, const std::string &to) {
size_t pos = 0;
while ((pos = s.find(from, pos)) != std::string::npos) {
s.replace(pos, from.size(), to);
pos += to.size();
}
};
replace(src, "@@CLASS@@", m_className);
replace(src, "@@CLASS_INCLUDE@@", m_classInclude);
replace(src, "@@OUTFILE@@", outFilename);
replace(src, "@@APP_INCLUDE@@", ati.includeHeader);
replace(src, "@@APP_CLASS@@", ati.className);
replace(src, "@@EXTRA_INCLUDES@@", extraIncludes.str());
replace(src, "@@FUZZ_DATA_STRUCT@@", kFuzzDataStruct);
replace(src, "@@CRASH_DIAGNOSTICS@@", kCrashDiagSource);
replace(src, "@@DIRECT_FUZZ_FUNC@@", directFuzzFunc);
replace(src, "@@DIRECT_FUZZ_CORPUS_CALL@@", directFuzzCorpusCall);
replace(src, "@@DIRECT_FUZZ_LOOP_CALL@@", directFuzzLoopCall);
return src;
}
bool FuzzCppGenerator::generate() const
{
std::error_code ec;
fs::create_directories(m_outputPath.parent_path(), ec);
std::ofstream out(m_outputPath);
if (!out) {
std::cerr << "[FuzzCppGenerator] ERROR: cannot write " << m_outputPath << "\n";
return false;
}
out << buildSource();
return true;
}
// ---------------------------------------------------------------------------
// CMakeGenerator
// ---------------------------------------------------------------------------
CMakeGenerator::CMakeGenerator(Config cfg, fs::path outputPath)
: m_cfg(std::move(cfg))
, m_outputPath(std::move(outputPath))
{
}
std::string CMakeGenerator::buildContent() const
{
const std::string &cn = m_cfg.className;
const std::string targetName = "fuzz_" + cn;
// Per-class timeout variable name — qualified with the class name to avoid
// scope collisions when multiple per-class CMakeLists.txt files are
// included from the same parent (embedded mode).
const std::string timeoutVar = "_fuzz_timeout_" + cn;
std::ostringstream o;
o << "# CMakeLists.txt — fuzz test for " << cn << "\n"
<< "# Auto-generated by QtFuzz::CMakeGenerator — do not edit by hand.\n"
<< "\n";
if (!m_cfg.embedded) {
o << "cmake_minimum_required(VERSION 3.19)\n"
<< "project(" << targetName << " LANGUAGES CXX)\n"
<< "\n"
<< "set(CMAKE_CXX_STANDARD 17)\n"
<< "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n"
<< "\n";
if (!m_cfg.qtPrefix.empty()) {
o << "if(NOT DEFINED CMAKE_PREFIX_PATH)\n"
<< " set(CMAKE_PREFIX_PATH \"" << m_cfg.qtPrefix << "\")\n"
<< "endif()\n\n";
}
o << "find_package(Qt6 REQUIRED COMPONENTS\n";
for (const auto &comp : m_cfg.components)
o << " " << comp << "\n";
for (const auto &priv : m_cfg.privateComponents)
o << " " << priv << "\n";
o << ")\n\n";
o << "# Sanitizers\n"
<< "option(ENABLE_ASAN \"Enable AddressSanitizer\" OFF)\n"
<< "if(ENABLE_ASAN)\n"
<< " if(MSVC)\n"
<< " add_compile_options(/fsanitize=address)\n"
<< " else()\n"
<< " add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)\n"
<< " add_link_options (-fsanitize=address,undefined)\n"
<< " endif()\n"
<< "endif()\n\n";
}
o << "add_executable(" << targetName << "\n"
<< " " << m_cfg.fuzzCppFilename << "\n"
<< ")\n\n"
<< "target_link_libraries(" << targetName << " PRIVATE\n";
for (const auto &comp : m_cfg.components)
o << " Qt6::" << comp << "\n";
for (const auto &priv : m_cfg.privateComponents)
o << " Qt6::" << priv << "\n";
o << ")\n\n";
if (!m_cfg.embedded) {
o << "set_target_properties(" << targetName << " PROPERTIES\n"
<< " AUTOMOC ON\n"
<< " AUTORCC ON\n"
<< ")\n\n";
}
o << "target_compile_options(" << targetName << " PRIVATE\n"
<< " $<$<CXX_COMPILER_ID:Clang,GNU>:-Wall -Wextra>\n"
<< ")\n\n";
if (!m_cfg.embedded) {
o << "include(CTest)\n"
<< "enable_testing()\n\n"
<< "set(FUZZ_TIME_SECONDS \"" << m_cfg.fuzzTimeSec << "\"\n"
<< " CACHE STRING \"Fuzz duration in seconds\")\n\n";
}
o << "add_test(\n"
<< " NAME " << targetName << "\n"
<< " COMMAND " << targetName << " --time ${FUZZ_TIME_SECONDS}\n"
<< ")\n\n"
<< "math(EXPR " << timeoutVar << " \"${FUZZ_TIME_SECONDS} + 10\")\n"
<< "set_tests_properties(" << targetName << " PROPERTIES\n"
<< " TIMEOUT ${" << timeoutVar << "}\n"
<< " LABELS \"fuzz;" << m_cfg.module.component << "\"\n"
<< " PASS_REGULAR_EXPRESSION \"\\\\[fuzz_" << cn << "\\\\] Done\\.\"\n"
<< ")\n\n"
<< "add_custom_target(" << targetName << "_run\n"
<< " COMMAND " << targetName << " --time ${FUZZ_TIME_SECONDS}\n"
<< " DEPENDS " << targetName << "\n"
<< " COMMENT \"Fuzzing " << cn << " for ${FUZZ_TIME_SECONDS}s...\"\n"
<< " VERBATIM\n"
<< ")\n";
return o.str();
}
bool CMakeGenerator::generate() const
{
std::error_code ec;
fs::create_directories(m_outputPath.parent_path(), ec);
std::ofstream out(m_outputPath);
if (!out) {
std::cerr << "[CMakeGenerator] ERROR: cannot write " << m_outputPath << "\n";
return false;
}
out << buildContent();
return true;
}
// ---------------------------------------------------------------------------
// TreeGenerator
// ---------------------------------------------------------------------------
TreeGenerator::TreeGenerator(Options opts)
: m_opts(std::move(opts))
{
}
bool TreeGenerator::writeTopLevel(const std::vector<std::string> &moduleDirs) const
{
const fs::path outPath = m_opts.outputRoot / "CMakeLists.txt";
std::ofstream out(outPath);
if (!out) {
std::cerr << "[TreeGenerator] ERROR: cannot write " << outPath << "\n";
return false;
}
if (m_opts.embedded) {
// Embedded mode: no project() or cmake_minimum_required().
// The parent project already provides Qt targets and CTest setup.
//
// The aggregate target is qualified with the submodule name (e.g.
// fuzz_all_qtdeclarative) because in a unified top-level Qt build,
// several submodules each add_subdirectory() their own generated
// fuzz tree into the *same* CMake project. An unqualified
// "fuzz_all" target would collide across submodules (CMP0002).
const std::string moduleTag = m_opts.submoduleRoot.filename().string();
out << "# Auto-generated by QtFuzz::TreeGenerator (embedded mode) — do not edit by hand.\n"
<< "\n"
<< "set(FUZZ_TIME_SECONDS \"" << m_opts.fuzzTimeSec << "\"\n"
<< " CACHE STRING \"Fuzz duration in seconds\")\n"
<< "include(CTest)\n"
<< "enable_testing()\n"
<< "\n";
for (const auto &dir : moduleDirs)
out << "add_subdirectory(" << dir << ")\n";
out << "\n"
<< "add_custom_target(fuzz_all_" << moduleTag << "\n"
<< " COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -L fuzz\n"
<< " COMMENT \"Running all fuzz tests...\"\n"
<< " VERBATIM\n"
<< ")\n";
} else {
out << "# tests/fuzzing/CMakeLists.txt\n"
<< "# Auto-generated by QtFuzz::TreeGenerator — do not edit by hand.\n"
<< "#\n"
<< "# Build the entire fuzz suite:\n"
<< "# cmake -B <builddir> -S <submodule>/tests/fuzzing"
<< (m_opts.qtPrefix.empty() ? "" : " -DCMAKE_PREFIX_PATH=" + m_opts.qtPrefix) << "\n"
<< "# cmake --build <builddir>\n"
<< "# ctest --test-dir <builddir> --output-on-failure -L fuzz\n"
<< "\n"
<< "cmake_minimum_required(VERSION 3.19)\n"
<< "project(QtFuzzSuite LANGUAGES CXX)\n"
<< "\n"
<< "set(CMAKE_CXX_STANDARD 17)\n"
<< "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n"
<< "\n";
if (!m_opts.qtPrefix.empty()) {
out << "if(NOT DEFINED CMAKE_PREFIX_PATH)\n"
<< " set(CMAKE_PREFIX_PATH \"" << m_opts.qtPrefix << "\")\n"
<< "endif()\n\n";
}
out << "set(FUZZ_TIME_SECONDS \"" << m_opts.fuzzTimeSec << "\"\n"
<< " CACHE STRING \"Default fuzz duration (seconds) passed to all tests\")\n\n"
<< "option(ENABLE_ASAN \"Enable AddressSanitizer + UBSan for all fuzz targets\" "
"OFF)\n\n"
<< "include(CTest)\n"
<< "enable_testing()\n\n";
for (const auto &dir : moduleDirs)
out << "add_subdirectory(" << dir << ")\n";
out << "\n"
<< "add_custom_target(fuzz_all\n"
<< " COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -L fuzz\n"
<< " COMMENT \"Running all fuzz tests...\"\n"
<< " VERBATIM\n"
<< ")\n";
}
return true;
}
bool TreeGenerator::writeModuleLevel(const std::string &moduleSrcDir,
const std::vector<std::string> &classDirs,
const ModuleInfo &mod) const
{
const fs::path outPath = m_opts.outputRoot / moduleSrcDir / "CMakeLists.txt";
std::ofstream out(outPath);
if (!out) {
std::cerr << "[TreeGenerator] ERROR: cannot write " << outPath << "\n";
return false;
}
out << "# tests/fuzzing/" << moduleSrcDir << "/CMakeLists.txt\n"
<< "# Module: Qt6::" << mod.component << "\n"
<< "# Auto-generated — do not edit by hand.\n"
<< "\n";
for (const auto &dir : classDirs)
out << "add_subdirectory(" << dir << ")\n";
return true;
}
bool TreeGenerator::generate(const std::vector<DiscoveredClass> &classes) const
{
std::error_code ec;
fs::create_directories(m_opts.outputRoot, ec);
if (ec) {
std::cerr << "[TreeGenerator] ERROR: cannot create " << m_opts.outputRoot
<< ": " << ec.message() << "\n";
return false;
}
#if defined(_WIN32) || defined(_WIN64)
constexpr Platform kCurrentPlatform = Platform::Windows;
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
constexpr Platform kCurrentPlatform = Platform::iOS;
#else
constexpr Platform kCurrentPlatform = Platform::macOS;
#endif
#elif defined(__ANDROID__)
constexpr Platform kCurrentPlatform = Platform::Android;
#elif defined(__EMSCRIPTEN__)
constexpr Platform kCurrentPlatform = Platform::WASM;
#elif defined(__linux__)
constexpr Platform kCurrentPlatform = Platform::Linux;
#else
constexpr Platform kCurrentPlatform = Platform::All;
#endif
bool ok = true;
size_t skipped = 0;
std::map<std::string, std::vector<const DiscoveredClass *>> byModule;
for (const auto &dc : classes) {
// Skip abstract and non-constructible classes.
if (dc.isAbstract || !dc.isDefaultConstructible) {
++skipped;
if (m_opts.verbose) {
std::cout << "[TreeGenerator] Skipping (abstract/non-constructible): "
<< dc.className << "\n";
}
continue;
}
// Skip platform-restricted classes not available on this platform.
if (dc.availableOn != Platform::All
&& !(dc.availableOn & kCurrentPlatform)) {
++skipped;
if (m_opts.verbose) {
std::cout << "[TreeGenerator] Skipping (platform-restricted): "
<< dc.className << "\n";
}
continue;
}
// For classes without Q_OBJECT, skip if no public methods were
// extracted — there is nothing to fuzz.
if (!dc.hasQObject && dc.publicMethods.empty()) {
++skipped;
if (m_opts.verbose) {
std::cout << "[TreeGenerator] Skipping (no Q_OBJECT, no public methods): "
<< dc.className << "\n";
}
continue;
}
// Skip classes that are in the skip list.
if (m_opts.skipList.isClassSkipped(dc.className)) {
++skipped;
if (m_opts.verbose)
std::cout << "[TreeGenerator] Skipping (skiplist): " << dc.className << "\n";
continue;
}
// QOpenGLFunctions and QOpenGLExtraFunctions expose the raw OpenGL C API.
// Every method call requires an active QOpenGLContext; without one the
// dispatch crashes immediately, making these classes useless as fuzz
// targets. Their GL* parameter types (GLenum, GLuint, …) also trigger
// spurious MSVC C2039 "not a member of QOpenGLFunctions" errors when
// compiled alongside <QGuiApplication> on Windows.
if (dc.className == "QOpenGLFunctions" || dc.className == "QOpenGLExtraFunctions") {
++skipped;
if (m_opts.verbose)
std::cout << "[TreeGenerator] Skipping (requires active GL context): "
<< dc.className << "\n";
continue;
}
// In non-embedded mode, skip classes whose installed Qt module header
// does not exist. QPA classes (QPlatformNativeInterface, etc.) live in
// the source tree but are not installed as public <ClassName> module
// headers, so #include <ClassName> would fail to compile.
// In embedded mode we skip this check because the parent Qt build
// already knows which headers are installed.
if (!m_opts.embedded && !m_opts.qtPrefix.empty()) {
const fs::path mhdr = fs::path(m_opts.qtPrefix) / "include"
/ ("Qt" + dc.module.component) / dc.className;
std::error_code ec2;
if (!fs::exists(mhdr, ec2)) {
++skipped;
if (m_opts.verbose)
std::cout << "[TreeGenerator] Skipping (no installed module header): "
<< dc.className << "\n";
continue;
}
}
byModule[dc.moduleSrcDir].push_back(&dc);
}
if (m_opts.verbose && skipped > 0)
std::cout << "[TreeGenerator] Skipped " << skipped << " class(es).\n";
std::vector<std::string> moduleDirs;
for (const auto &[srcDir, unused] : byModule)
moduleDirs.push_back(srcDir);
std::sort(moduleDirs.begin(), moduleDirs.end());
for (const auto &[srcDir, dcPtrs] : byModule) {
const ModuleInfo &mod = dcPtrs.front()->module;
std::vector<std::string> classDirs;
for (const auto *dc : dcPtrs) {
const fs::path classDir = m_opts.outputRoot / srcDir / dc->className;
const std::string fuzzFilename = "fuzz_" + dc->className + ".cpp";
const fs::path fuzzPath = classDir / fuzzFilename;
const fs::path cmakePath = classDir / "CMakeLists.txt";
fs::create_directories(classDir, ec);
// Exclude methods known to crash — not safe to call with random input.
std::vector<MethodSignature> methods;
for (const auto &sig : dc->publicMethods) {
if (m_opts.skipList.expectsCrash(dc->className, sig.name)) {
if (m_opts.verbose) {
std::cout << "[TreeGenerator] Skipping (expectCrash): " << dc->className
<< "::" << sig.name << "\n";
}
} else {
methods.push_back(sig);
}
}
// For QML-exposed private classes, pass the private header include path.
std::string classInclude;
if (dc->isQmlExposed && !dc->privateHeaderInclude.empty())
classInclude = "<" + dc->privateHeaderInclude + ">";
FuzzCppGenerator cpp(dc->className, fuzzPath, dc->module.appType, std::move(methods),
dc->hasQObject, classInclude);
if (!cpp.generate()) {
ok = false;
continue;
}
const auto components = resolveComponents(dc->module);
std::vector<std::string> privateComponents;
if (dc->isQmlExposed)
privateComponents.push_back(dc->module.component + "Private");
CMakeGenerator::Config cfg{
dc->className, fuzzFilename, dc->module,
components, m_opts.qtPrefix, m_opts.fuzzTimeSec,
privateComponents, m_opts.embedded
};
CMakeGenerator cmake(std::move(cfg), cmakePath);
if (!cmake.generate()) {
ok = false;
continue;
}
classDirs.push_back(dc->className);
if (m_opts.verbose) {
std::cout << "[TreeGenerator] " << srcDir << "/" << dc->className
<< " app=" << appTypeInfo(dc->module.appType).className
<< " strategy=" << (dc->hasQObject ? "introspection+direct" : "direct-only")
<< " directMethods=" << dc->publicMethods.size()
<< "\n";
}
}
const fs::path modDir = m_opts.outputRoot / srcDir;
fs::create_directories(modDir, ec);
ok &= writeModuleLevel(srcDir, classDirs, mod);
}
ok &= writeTopLevel(moduleDirs);
if (m_opts.verbose) {
std::cout << "[TreeGenerator] Tree written to " << m_opts.outputRoot << "\n"
<< "[TreeGenerator] Modules : " << byModule.size() << "\n"
<< "[TreeGenerator] Classes : "
<< (classes.size() - skipped) << " generated, "
<< skipped << " skipped\n";
}
return ok;
}
const char *fuzzDataStructSource()
{
return kFuzzDataStruct;
}
} // namespace QtFuzz