Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QCoreApplication>
++ #include <QFile>
++ #include <QHelpContentItem>
++ #include <QHelpEngineCore>
++ #include <QHelpLink>
++ #include <QTemporaryDir>
++
++ #include <algorithm>
++
++ // Registers the input as a documentation file (.qch) in an empty collection file and reads
++ // everything back. Documentation files are SQLite databases, which may come from third
++ // parties, so a seed corpus of valid files, e.g. generated with qhelpgenerator, is needed
++ // to get past the SQLite file format.
++
++ // silence warnings
++ static QtMessageHandler mh = qInstallMessageHandler([](QtMsgType, const QMessageLogContext &,
++ const QString &) {});
++
++ // Bounds the work done for a single input.
++ static constexpr qsizetype MaxItems = 100;
++
++ static bool writeFile(const QString &fileName, const QByteArray &contents)
++ {
++ QFile file(fileName);
++ return file.open(QIODevice::WriteOnly | QIODevice::Truncate)
++ && file.write(contents) == contents.size();
++ }
++
++ static void visit(const QHelpContentItem *item, int depth)
++ {
++ if (!item || depth > MaxItems)
++ return;
++ const int count = std::min(item->childCount(), int(MaxItems));
++ for (int i = 0; i < count; ++i)
++ visit(item->child(i), depth + 1);
++ }
++
++ extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
++ static int argc = 1;
++ static char arg1[] = "fuzzer";
++ static char *argv[] = {arg1, nullptr};
++ static QCoreApplication app(argc, argv);
++ static QTemporaryDir dir;
++ static const QString qchFile = dir.filePath(QStringLiteral("fuzz.qch"));
++ static const QString colFile = dir.filePath(QStringLiteral("fuzz.qhc"));
++ static const QByteArray emptyCollection = [] {
++ {
++ QHelpEngineCore c(colFile);
++ c.setReadOnly(false);
++ c.setupData();
++ }
++ QFile file(colFile);
++ return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
++ }();
++
++ if (!dir.isValid() || emptyCollection.isEmpty()
++ || !writeFile(qchFile, QByteArray::fromRawData(Data, Size))
++ || !writeFile(colFile, emptyCollection)) {
++ return 0;
++ }
++
++ QHelpEngineCore::namespaceName(qchFile);
++ QHelpEngineCore::metaData(qchFile, QStringLiteral("author"));
++
++ QHelpEngineCore c(colFile);
++ c.setReadOnly(false);
++ c.setUsesFilterEngine(true);
++ if (!c.setupData() || !c.registerDocumentation(qchFile))
++ return 0;
++
++ for (const QString &ns : c.registeredDocumentations()) {
++ c.filterAttributeSets(ns);
++ const QList<QUrl> files = c.files(ns, QString());
++ for (qsizetype i = 0; i < std::min(files.size(), MaxItems); ++i)
++ c.fileData(c.findFile(files.at(i)));
++ }
++
++ const QStringList keywords = c.requestIndex(QString()).result();
++ for (qsizetype i = 0; i < std::min(keywords.size(), MaxItems); ++i)
++ c.documentsForKeyword(keywords.at(i));
++
++ const std::shared_ptr<QHelpContentItem> root = c.requestContent(QString()).result();
++ visit(root.get(), 0);
++ return 0;
++ }