diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index fd17b53c7..c849b2cd3 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -112,6 +112,11 @@ bool CppCheckExecutor::parseFromArgs(Settings &settings, int argc, const char* c return false; } + // Libraries must be loaded before FileLister is executed to ensure markup files will be + // listed properly. + if (!loadLibraries(settings)) + return false; + // Check that all include paths exist { for (std::list::iterator iter = settings.includePaths.begin(); @@ -256,9 +261,6 @@ int CppCheckExecutor::check_internal(CppCheck& cppcheck) { Settings& settings = cppcheck.settings(); - if (!loadLibraries(settings)) - return EXIT_FAILURE; - if (settings.reportProgress) mLatestProgressOutputTime = std::time(nullptr); diff --git a/lib/checkunusedfunctions.cpp b/lib/checkunusedfunctions.cpp index 54d5266d0..83dd5dd51 100644 --- a/lib/checkunusedfunctions.cpp +++ b/lib/checkunusedfunctions.cpp @@ -72,36 +72,38 @@ static std::string stripTemplateParameters(const std::string& funcName) { void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const char FileName[], const Settings *settings) { const bool doMarkup = settings->library.markupFile(FileName); - const SymbolDatabase* symbolDatabase = tokenizer.getSymbolDatabase(); // Function declarations.. - for (const Scope* scope : symbolDatabase->functionScopes) { - const Function* func = scope->function; - if (!func || !func->token || scope->bodyStart->fileIndex() != 0) - continue; + if (!doMarkup) { + const SymbolDatabase* symbolDatabase = tokenizer.getSymbolDatabase(); + for (const Scope* scope : symbolDatabase->functionScopes) { + const Function* func = scope->function; + if (!func || !func->token || scope->bodyStart->fileIndex() != 0) + continue; - // Don't warn about functions that are marked by __attribute__((constructor)) or __attribute__((destructor)) - if (func->isAttributeConstructor() || func->isAttributeDestructor() || func->type != Function::eFunction || func->isOperator()) - continue; + // Don't warn about functions that are marked by __attribute__((constructor)) or __attribute__((destructor)) + if (func->isAttributeConstructor() || func->isAttributeDestructor() || func->type != Function::eFunction || func->isOperator()) + continue; - if (func->isExtern()) - continue; + if (func->isExtern()) + continue; - mFunctionDecl.emplace_back(func); + mFunctionDecl.emplace_back(func); - FunctionUsage &usage = mFunctions[stripTemplateParameters(func->name())]; + FunctionUsage &usage = mFunctions[stripTemplateParameters(func->name())]; - if (!usage.lineNumber) - usage.lineNumber = func->token->linenr(); + if (!usage.lineNumber) + usage.lineNumber = func->token->linenr(); - // No filename set yet.. - if (usage.filename.empty()) { - usage.filename = tokenizer.list.getSourceFilePath(); - } - // Multiple files => filename = "+" - else if (usage.filename != tokenizer.list.getSourceFilePath()) { - //func.filename = "+"; - usage.usedOtherFile |= usage.usedSameFile; + // No filename set yet.. + if (usage.filename.empty()) { + usage.filename = tokenizer.list.getSourceFilePath(); + } + // Multiple files => filename = "+" + else if (usage.filename != tokenizer.list.getSourceFilePath()) { + //func.filename = "+"; + usage.usedOtherFile |= usage.usedSameFile; + } } } @@ -211,7 +213,9 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const char Fi const Token *funcname = nullptr; - if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% (")) { + if (doMarkup) + funcname = Token::Match(tok, "%name% (") ? tok : nullptr; + else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% (")) { funcname = tok; } else if ((lambdaEndToken || tok->scope()->isExecutable()) && Token::Match(tok, "%name% <") && Token::simpleMatch(tok->linkAt(1), "> (")) { funcname = tok; diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 9510299a1..95a3effa6 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -698,6 +698,13 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string return mExitCode; } + if (mSettings.library.markupFile(filename)) { + Tokenizer tokenizer(&mSettings, this, &preprocessor); + tokenizer.createTokens(std::move(tokens1)); + checkUnusedFunctions.getFileInfo(&tokenizer, &mSettings); + return EXIT_SUCCESS; + } + if (!preprocessor.loadFiles(tokens1, files)) return mExitCode; diff --git a/test/cli/QML-Samples-TableView/README b/test/cli/QML-Samples-TableView/README new file mode 100644 index 000000000..fed52488a --- /dev/null +++ b/test/cli/QML-Samples-TableView/README @@ -0,0 +1,2 @@ +Downloaded from here: +https://github.com/HamedMasafi/QML-Samples diff --git a/test/cli/QML-Samples-TableView/TableColumn.qml b/test/cli/QML-Samples-TableView/TableColumn.qml new file mode 100644 index 000000000..a29f2a7b1 --- /dev/null +++ b/test/cli/QML-Samples-TableView/TableColumn.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQml.Models 2.12 + +QtObject { + property string title + property string role + property bool fillWidth: false + property int size: -1 +} diff --git a/test/cli/QML-Samples-TableView/TableRow.qml b/test/cli/QML-Samples-TableView/TableRow.qml new file mode 100644 index 000000000..2b1c7b0c4 --- /dev/null +++ b/test/cli/QML-Samples-TableView/TableRow.qml @@ -0,0 +1,25 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQml.Models 2.12 + +ItemDelegate { + height: 40 + width: parent.width + property bool isHeader: false + RowLayout { + property var _d: model + anchors.fill: parent + anchors.leftMargin: 9 + + Repeater { + model: root.columns + Label { + text: parent.parent.isHeader ? modelData.title : parent._d[modelData.role] + Layout.fillWidth: modelData.fillWidth + Layout.preferredWidth: modelData.size !== '*' + ? modelData.size : 20 + } + } + } +} diff --git a/test/cli/QML-Samples-TableView/TableView.pro b/test/cli/QML-Samples-TableView/TableView.pro new file mode 100644 index 000000000..4dc16d5dc --- /dev/null +++ b/test/cli/QML-Samples-TableView/TableView.pro @@ -0,0 +1,27 @@ +QT += quick + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + samplemodel.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Additional import path used to resolve QML modules just for Qt Quick Designer +QML_DESIGNER_IMPORT_PATH = + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + samplemodel.h diff --git a/test/cli/QML-Samples-TableView/TableView.qml b/test/cli/QML-Samples-TableView/TableView.qml new file mode 100644 index 000000000..a30b7c8d7 --- /dev/null +++ b/test/cli/QML-Samples-TableView/TableView.qml @@ -0,0 +1,30 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQml.Models 2.12 + +Item { + id: root + + property alias model: tableView.model + default property list columns + + TableRow { + id: header + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + background: null + isHeader: true + height: 60 + } + + ListView { + id: tableView + clip: true + + anchors.fill: parent + anchors.topMargin: header.height + delegate: TableRow {} + } +} diff --git a/test/cli/QML-Samples-TableView/main.cpp b/test/cli/QML-Samples-TableView/main.cpp new file mode 100644 index 000000000..1c5779942 --- /dev/null +++ b/test/cli/QML-Samples-TableView/main.cpp @@ -0,0 +1,25 @@ +#include +#include +#include "samplemodel.h" + +int main(int argc, char *argv[]) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + qmlRegisterType("Test", 1, 0, "SampleModel"); + + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/test/cli/QML-Samples-TableView/main.qml b/test/cli/QML-Samples-TableView/main.qml new file mode 100644 index 000000000..6a8cc43f9 --- /dev/null +++ b/test/cli/QML-Samples-TableView/main.qml @@ -0,0 +1,37 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import Test 1.0 +import '.' as Here + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("Hello World") + + Component.onCompleted: sampleModel.fillSampleData(50) + SampleModel { + id: sampleModel + } + + Here.TableView { + anchors.fill: parent + model: sampleModel + + TableColumn { + title: qsTr("Id") + role: 'id' + size: 30 + } + TableColumn { + title: qsTr("Name") + role: 'name' + fillWidth: true + } + TableColumn { + title: qsTr("Grade") + role: 'grade' + size: 50 + } + } +} diff --git a/test/cli/QML-Samples-TableView/qml.qrc b/test/cli/QML-Samples-TableView/qml.qrc new file mode 100644 index 000000000..cf94b15b8 --- /dev/null +++ b/test/cli/QML-Samples-TableView/qml.qrc @@ -0,0 +1,8 @@ + + + main.qml + TableView.qml + TableRow.qml + TableColumn.qml + + diff --git a/test/cli/QML-Samples-TableView/samplemodel.cpp b/test/cli/QML-Samples-TableView/samplemodel.cpp new file mode 100644 index 000000000..de7a5b139 --- /dev/null +++ b/test/cli/QML-Samples-TableView/samplemodel.cpp @@ -0,0 +1,68 @@ +#include "samplemodel.h" + +#include +#include + +SampleModel::SampleModel(QObject *parent) : QAbstractListModel(parent) +{ + +} + +int SampleModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return _data.size(); +} + +QVariant SampleModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() < 0 || index.row() > _data.count() - 1) + return QVariant(); + + auto row = _data.at(index.row()); + + switch (role) { + case IdRole: + return index.row(); + + case NameRole: + return row.first; + + case GradeRole: + return row.second; + } + + return QVariant(); +} + +QHash SampleModel::roleNames() const +{ + return { + {IdRole, "id"}, + {NameRole, "name"}, + {GradeRole, "grade"} + }; +} + +void SampleModel::fillSampleData(int size) +{ + QString abs = "qwertyuiopasdfghjklzxcvbnm"; + QRandomGenerator r; + for (auto i = 0; i < size; i++) { + Row row; + auto nameLen = r.bounded(3, 8); + QString name; + for (int c = 0; c < nameLen; ++c) + name.append(abs.at(r.bounded(0, abs.size() - 1))); + + row.first = name; + row.second = r.bounded(0, 20); + _data.append(row); + } + + qDebug() << _data.size() << "item(s) added as sample data"; + beginInsertRows(QModelIndex(), 0, _data.size() - 1); + endInsertRows(); +} diff --git a/test/cli/QML-Samples-TableView/samplemodel.h b/test/cli/QML-Samples-TableView/samplemodel.h new file mode 100644 index 000000000..2bb9107e5 --- /dev/null +++ b/test/cli/QML-Samples-TableView/samplemodel.h @@ -0,0 +1,30 @@ +#ifndef SAMPLEMODEL_H +#define SAMPLEMODEL_H + +#include + +class SampleModel : public QAbstractListModel +{ + Q_OBJECT + + typedef QPair Row; + QList _data; + +public: + enum Role { + IdRole = Qt::UserRole + 1, + NameRole, + GradeRole + }; + + SampleModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QHash roleNames() const; + +public slots: + void fillSampleData(int size); +}; + +#endif // SAMPLEMODEL_H diff --git a/test/cli/test-qml.py b/test/cli/test-qml.py new file mode 100644 index 000000000..d16017bcf --- /dev/null +++ b/test/cli/test-qml.py @@ -0,0 +1,14 @@ + +# python3 -m pytest test-qml.py + +import os +from testutils import cppcheck + +PROJECT_DIR = 'QML-Samples-TableView' + +def test_unused_functions(): + ret, stdout, stderr = cppcheck(['--library=qt', '--enable=unusedFunction', PROJECT_DIR]) + # there are unused functions. But fillSampleData is not unused because that is referenced from main.qml + assert '[unusedFunction]' in stderr + assert 'fillSampleData' not in stderr + diff --git a/test/testprocessexecutor.cpp b/test/testprocessexecutor.cpp index 5019a0a24..c81402a1c 100644 --- a/test/testprocessexecutor.cpp +++ b/test/testprocessexecutor.cpp @@ -189,7 +189,8 @@ private: "file_1.cp1", "file_2.cpp", "file_3.cp1", "file_4.cpp" }; - check(2, 4, 4, + // the checks are not executed on the markup files => expected result is 2 + check(2, 4, 2, "int main()\n" "{\n" " char *a = malloc(10);\n" diff --git a/test/testsingleexecutor.cpp b/test/testsingleexecutor.cpp index cf384a1b5..c3d647f74 100644 --- a/test/testsingleexecutor.cpp +++ b/test/testsingleexecutor.cpp @@ -193,7 +193,8 @@ private: "file_1.cp1", "file_2.cpp", "file_3.cp1", "file_4.cpp" }; - check(4, 4, + // checks are not executed on markup files => expected result is 2 + check(4, 2, "int main()\n" "{\n" " char *a = malloc(10);\n" diff --git a/test/testthreadexecutor.cpp b/test/testthreadexecutor.cpp index 68d961fa8..3e92b3200 100644 --- a/test/testthreadexecutor.cpp +++ b/test/testthreadexecutor.cpp @@ -187,7 +187,8 @@ private: "file_1.cp1", "file_2.cpp", "file_3.cp1", "file_4.cpp" }; - check(2, 4, 4, + // checks are not executed on markup files => expected result is 2 + check(2, 4, 2, "int main()\n" "{\n" " char *a = malloc(10);\n"