Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[])
std::list<FileWithDetails> filesResolved;
// Execute recursiveAddFiles() to each given file parameter
// TODO: verbose log which files were ignored?
const PathMatch matcher(ignored, Path::getCurrentPath());
PathMatch matcher(ignored, Path::getCurrentPath());
for (const std::string &pathname : pathnamesRef) {
const std::string err = FileLister::recursiveAddFiles(filesResolved, Path::toNativeSeparators(pathname), mSettings.library.markupExtensions(), matcher, mSettings.debugignore);
if (!err.empty()) {
Expand All @@ -264,6 +264,12 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[])
return false;
}

const auto& unmatched = matcher.unmatched();
if (!unmatched.empty()) {
mLogger.printError("unused ignore/exclude path '" + unmatched.front() + "' to hide warnings in certain files use suppressions instead.");
return false;
}

std::list<FileWithDetails> files;
if (!mSettings.fileFilters.empty()) {
files = filterFiles(mSettings.fileFilters, filesResolved);
Expand Down Expand Up @@ -1723,12 +1729,16 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
mPathNames = project.guiProject.pathNames;

if (!project.fileSettings.empty()) {
project.ignorePaths(mIgnoredPaths, mSettings.debugignore);
const auto& unmatched = project.ignorePaths(mIgnoredPaths, mSettings.debugignore);
if (project.fileSettings.empty()) {
mLogger.printError("no C or C++ source files found.");
mLogger.printMessage("all paths were ignored"); // TODO: log this differently?
return Result::Fail;
}
if (!unmatched.empty()) {
mLogger.printError("unused ignore/exclude path '" + unmatched.front() + "' to hide warnings in certain files use suppressions instead.");
return Result::Fail;
}
mFileSettings = project.fileSettings;
}

Expand Down
6 changes: 3 additions & 3 deletions cli/filelister.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ static std::string addFiles2(std::list<FileWithDetails> &files,
const std::string &path,
const std::set<std::string> &extra,
bool recursive,
const PathMatch& ignored,
PathMatch& ignored,
bool debug)
{
if (ignored.match(path))
Expand Down Expand Up @@ -280,7 +280,7 @@ static std::string addFiles2(std::list<FileWithDetails> &files,
return "";
}

std::string FileLister::addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, const PathMatch& ignored, bool debug)
std::string FileLister::addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, PathMatch& ignored, bool debug)
{
if (path.empty())
return "no path specified";
Expand All @@ -304,7 +304,7 @@ std::string FileLister::addFiles(std::list<FileWithDetails> &files, const std::s

#endif

std::string FileLister::recursiveAddFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, const PathMatch& ignored, bool debug)
std::string FileLister::recursiveAddFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, PathMatch& ignored, bool debug)
{
return addFiles(files, path, extra, true, ignored, debug);
}
4 changes: 2 additions & 2 deletions cli/filelister.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class FileLister {
* @param debug log if path was ignored
* @return On success, an empty string is returned. On error, a error message is returned.
*/
static std::string recursiveAddFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, const PathMatch& ignored, bool debug = false);
static std::string recursiveAddFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, PathMatch& ignored, bool debug = false);

/**
* @brief (Recursively) add source files to a map.
Expand All @@ -59,7 +59,7 @@ class FileLister {
* @param debug log when a path was ignored
* @return On success, an empty string is returned. On error, a error message is returned.
*/
static std::string addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, const PathMatch& ignored, bool debug = false);
static std::string addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, PathMatch& ignored, bool debug = false);
};

/// @}
Expand Down
18 changes: 15 additions & 3 deletions gui/filelist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,14 @@ QStringList FileList::getFileList() const
}
return names;
}
return applyExcludeList();
QStringList unmatched;
return applyExcludeList(unmatched);
}

QStringList FileList::getUnmatchedExcludes() const {
QStringList unmatched;
(void)applyExcludeList(unmatched);
return unmatched;
}

void FileList::addExcludeList(const QStringList &paths)
Expand All @@ -117,9 +124,9 @@ static std::vector<std::string> toStdStringList(const QStringList &stringList)
return ret;
}

QStringList FileList::applyExcludeList() const
QStringList FileList::applyExcludeList(QStringList& unmatched) const
{
const PathMatch pathMatch(toStdStringList(mExcludedPaths), QDir::currentPath().toStdString());
PathMatch pathMatch(toStdStringList(mExcludedPaths), QDir::currentPath().toStdString());

QStringList paths;
for (const QFileInfo& item : mFileList) {
Expand All @@ -129,5 +136,10 @@ QStringList FileList::applyExcludeList() const
if (!pathMatch.match(canonical.toStdString()))
paths << canonical;
}

for (const std::string& excludePath: pathMatch.unmatched()) {
unmatched << QString::fromStdString(excludePath);
}

return paths;
}
11 changes: 10 additions & 1 deletion gui/filelist.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <QString>
#include <QStringList>

class PathMatch;

/**
* @brief A class for listing files and directories to check.
* This class creates a list of files to check. If directory name is given then
Expand Down Expand Up @@ -64,6 +66,12 @@ class FileList {
*/
QStringList getFileList() const;

/**
* @brief Return list of unmatched excludes
* @return list of unmatched excludes
*/
QStringList getUnmatchedExcludes() const;

/**
* @brief Add list of paths to exclusion list.
* @param paths Paths to exclude.
Expand All @@ -89,9 +97,10 @@ class FileList {
* This method takes the list of paths and applies the exclude lists to
* it. And then returns the list of paths that did not match the
* exclude filters.
* @param unmatched output of unmatched exclude paths
* @return Filtered list of paths.
*/
QStringList applyExcludeList() const;
QStringList applyExcludeList(QStringList& unmatched) const;

private:
QFileInfoList mFileList;
Expand Down
11 changes: 11 additions & 0 deletions gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,17 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
return;
}

const QStringList& unmatchedExcludePaths = pathList.getUnmatchedExcludes();
if (!unmatchedExcludePaths.isEmpty()) {
QMessageBox msg(QMessageBox::Warning,
"Cppcheck",
tr("Unused exclude paths:\n%1\nto hide warnings in certain files use suppressions instead").arg(unmatchedExcludePaths.join("\n")),
QMessageBox::Ok,
this);
msg.exec();
return;
}

std::list<FileWithDetails> fdetails = enrichFilesForAnalysis(fileNames, checkSettings);

// TODO: lock UI here?
Expand Down
35 changes: 35 additions & 0 deletions gui/test/filelist/testfilelist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,39 @@ void TestFileList::filterFiles5() const
QVERIFY(!files.contains(base + "/dir1/dir11/foo11.cpp"));
}

void TestFileList::unmatchedExcludes1() const
{
FileList list;
QStringList filters;
filters << "foo1.cpp" << "foo3.cc";
list.addExcludeList(filters);
list.addDirectory(QString(SRCDIR) + "/../data/files");
QVERIFY(list.getUnmatchedExcludes().isEmpty());
}

void TestFileList::unmatchedExcludes2() const
{
FileList list;
QStringList filters;
filters << "foo1.cpp" << "bar.cpp" << "dir3/";
list.addExcludeList(filters);
list.addDirectory(QString(SRCDIR) + "/../data/files", true);
const QStringList unmatched = list.getUnmatchedExcludes();
QCOMPARE(unmatched.size(), 2);
QCOMPARE(unmatched[0], QString("bar.cpp"));
QCOMPARE(unmatched[1], QString("dir3/"));
// unmatched excludes does not affect the file list
QCOMPARE(list.getFileList().size(), 9);
}

void TestFileList::unmatchedExcludes3() const
{
FileList list;
QStringList filters;
filters << "dir1/";
list.addExcludeList(filters);
list.addDirectory(QString(SRCDIR) + "/../data/files", true);
QVERIFY(list.getUnmatchedExcludes().isEmpty());
}

QTEST_MAIN(TestFileList)
3 changes: 3 additions & 0 deletions gui/test/filelist/testfilelist.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ private slots:
void filterFiles3() const;
void filterFiles4() const;
void filterFiles5() const;
void unmatchedExcludes1() const;
void unmatchedExcludes2() const;
void unmatchedExcludes3() const;
};
3 changes: 2 additions & 1 deletion lib/importproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ void ImportProject::parseArgs(FileSettings &fs, const std::vector<std::string> &
fsSetDefines(fs, std::move(defs));
}

void ImportProject::ignorePaths(const std::vector<std::string> &ipaths, bool debug)
std::vector<std::string> ImportProject::ignorePaths(const std::vector<std::string> &ipaths, bool debug)
{
PathMatch matcher(ipaths, Path::getCurrentPath());
for (auto it = fileSettings.cbegin(); it != fileSettings.cend();) {
Expand All @@ -203,6 +203,7 @@ void ImportProject::ignorePaths(const std::vector<std::string> &ipaths, bool deb
else
++it;
}
return matcher.unmatched();
}

void ImportProject::ignoreOtherConfigs(const std::string &cfg)
Expand Down
2 changes: 1 addition & 1 deletion lib/importproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class CPPCHECKLIB WARN_UNUSED ImportProject {
std::string platform;
} guiProject;

void ignorePaths(const std::vector<std::string> &ipaths, bool debug = false);
std::vector<std::string> ignorePaths(const std::vector<std::string> &ipaths, bool debug = false);
void ignoreOtherConfigs(const std::string &cfg);

Type import(const std::string &filename, Settings *settings=nullptr, Suppressions *supprs=nullptr);
Expand Down
16 changes: 14 additions & 2 deletions lib/pathmatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ PathMatch::PathMatch(std::vector<std::string> patterns, std::string basepath, Sy
mPatterns(std::move(patterns)), mBasepath(std::move(basepath)), mSyntax(syntax)
{}

bool PathMatch::match(const std::string &path, Filemode mode) const
bool PathMatch::match(const std::string &path, Filemode mode)
{
return std::any_of(mPatterns.cbegin(), mPatterns.cend(), [&] (const std::string &pattern) {
return match(pattern, path, mBasepath, mode, mSyntax);
const bool res = match(pattern, path, mBasepath, mode, mSyntax);
if (res)
mMatchedPatterns.insert(pattern);
return res;
Comment on lines 36 to +40
});
}

Expand Down Expand Up @@ -149,3 +152,12 @@ bool PathMatch::match(const std::string &pattern, const std::string &path, const
return false;
}
}

std::vector<std::string> PathMatch::unmatched() const {
std::vector<std::string> ret;
for (const std::string& s: mPatterns) {
if (mMatchedPatterns.count(s) == 0)
ret.push_back(s);
}
return ret;
}
5 changes: 4 additions & 1 deletion lib/pathmatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class CPPCHECKLIB PathMatch {
* @param mode The file mode of the file named by the path.
* @return true if any of the masks match the path, false otherwise.
*/
bool match(const std::string &path, Filemode mode = Filemode::regular) const;
bool match(const std::string &path, Filemode mode = Filemode::regular);

/**
* @brief Match path against a single pattern.
Expand Down Expand Up @@ -169,12 +169,15 @@ class CPPCHECKLIB PathMatch {
return pattern;
}

std::vector<std::string> unmatched() const;

protected:
class PathIterator;

private:
/* List of patterns */
std::vector<std::string> mPatterns;
std::set<std::string> mMatchedPatterns;
/* Base path to with patterns and paths are relative */
std::string mBasepath;
/* The syntax to use */
Expand Down
3 changes: 2 additions & 1 deletion test/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ ScopedFile::~ScopedFile() {
// TODO: simplify the function call
// hack to be able to delete *.plist output files
std::list<FileWithDetails> files;
const std::string res = FileLister::addFiles(files, mPath, {".plist"}, false, PathMatch());
PathMatch ignored;
const std::string res = FileLister::addFiles(files, mPath, {".plist"}, false, ignored);
if (!res.empty()) {
std::cout << "ScopedFile(" << mPath + ") - generating file list failed (" << res << ")" << std::endl;
}
Expand Down
55 changes: 55 additions & 0 deletions test/testcmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,11 @@ class TestCmdlineParser : public TestFixture {
TEST_CASE(ignorefilepaths7);
TEST_CASE(ignorefilepaths8);
TEST_CASE(ignorefilepaths9);
TEST_CASE(ignoreUnused1);
TEST_CASE(ignoreUnused2);
TEST_CASE(ignoreUsed);
TEST_CASE(ignoreUnusedProject);
TEST_CASE(ignoreUsedProject);

TEST_CASE(nonexistentpath);

Expand Down Expand Up @@ -3649,6 +3654,56 @@ class TestCmdlineParser : public TestFixture {
ASSERT_EQUALS("cppcheck: error: could not find or open any of the paths given.\ncppcheck: Maybe all paths were ignored?\n", logger->str());
}

void ignoreUnused1() {
REDIRECT;
ScopedFile file("file.cpp", "");
const char * const argv[] = {"cppcheck", "-ifoo.cpp", "file.cpp"};
ASSERT(!fillSettingsFromArgs(argv));
ASSERT_EQUALS("cppcheck: error: unused ignore/exclude path 'foo.cpp' to hide warnings in certain files use suppressions instead.\n", logger->str());
}

void ignoreUnused2() {
REDIRECT;
ScopedFile file1("file1.cpp", "");
ScopedFile file2("file2.cpp", "");
const char * const argv[] = {"cppcheck", "-ifile1.cpp", "-ifoo.cpp", "file1.cpp", "file2.cpp"};
ASSERT(!fillSettingsFromArgs(argv));
ASSERT_EQUALS("cppcheck: error: unused ignore/exclude path 'foo.cpp' to hide warnings in certain files use suppressions instead.\n", logger->str());
}

void ignoreUsed() {
REDIRECT;
ScopedFile file1("file1.cpp", "");
ScopedFile file2("file2.cpp", "");
const char * const argv[] = {"cppcheck", "-ifile1.cpp", "file1.cpp", "file2.cpp"};
ASSERT(fillSettingsFromArgs(argv));
ASSERT_EQUALS("", logger->str());
ASSERT_EQUALS(1, parser->getFiles().size());
ASSERT_EQUALS("file2.cpp", parser->getFiles().cbegin()->path());
}

void ignoreUnusedProject() {
REDIRECT;
ScopedFile file("compile_commands.json",
R"([{"directory": "/tmp", "command": "gcc -c file1.c", "file": "file1.c"},
{"directory": "/tmp", "command": "gcc -c file2.c", "file": "file2.c"}])");
const char * const argv[] = {"cppcheck", "--project=compile_commands.json", "-ifile1.c", "-ifoo.c"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv));
ASSERT_EQUALS("cppcheck: error: unused ignore/exclude path 'foo.c' to hide warnings in certain files use suppressions instead.\n", logger->str());
}

void ignoreUsedProject() {
REDIRECT;
ScopedFile file("compile_commands.json",
R"([{"directory": "/tmp", "command": "gcc -c file1.c", "file": "file1.c"},
{"directory": "/tmp", "command": "gcc -c file2.c", "file": "file2.c"}])");
const char * const argv[] = {"cppcheck", "--project=compile_commands.json", "-ifile1.c"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv));
ASSERT_EQUALS("", logger->str());
ASSERT_EQUALS(1, parser->getFileSettings().size());
ASSERT_EQUALS("/tmp/file2.c", parser->getFileSettings().cbegin()->filename());
}

void nonexistentpath() {
REDIRECT;
const char * const argv[] = {"cppcheck", "file.cpp"};
Expand Down
Loading
Loading