Revert "Clang import; This experimental feature didn't "take off" much. After a lot of work we are still far fram the goal. I remove it now but don't rule out completely that it could ever be added again."

This reverts commit 207361b174.
This commit is contained in:
Daniel Marjamäki 2021-04-30 16:47:02 +02:00
parent 59ef06819a
commit d2d2124238
32 changed files with 3505 additions and 8 deletions

View File

@ -121,6 +121,7 @@ jobs:
copy .\bin\cppcheck.exe .\cppcheck.exe || exit /b !errorlevel!
copy .\bin\cppcheck-core.dll .\cppcheck-core.dll || exit /b !errorlevel!
cd test/cli || exit /b !errorlevel!
python -m pytest --suppress-no-test-exit-code test-clang-import.py || exit /b !errorlevel!
python -m pytest test-helloworld.py || exit /b !errorlevel!
python -m pytest test-inline-suppress.py || exit /b !errorlevel!
python -m pytest test-more-projects.py || exit /b !errorlevel!

View File

@ -185,6 +185,7 @@ LIBOBJ = $(libcppdir)/analyzerinfo.o \
$(libcppdir)/checkunusedfunctions.o \
$(libcppdir)/checkunusedvar.o \
$(libcppdir)/checkvaarg.o \
$(libcppdir)/clangimport.o \
$(libcppdir)/cppcheck.o \
$(libcppdir)/ctu.o \
$(libcppdir)/errorlogger.o \
@ -232,6 +233,7 @@ TESTOBJ = test/options.o \
test/testbufferoverrun.o \
test/testbughuntingchecks.o \
test/testcharvar.o \
test/testclangimport.o \
test/testclass.o \
test/testcmdlineparser.o \
test/testcondition.o \
@ -491,7 +493,10 @@ $(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp externals/simplecpp/simple
$(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/astutils.h lib/check.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/checkvaarg.o $(libcppdir)/checkvaarg.cpp
$(libcppdir)/cppcheck.o: lib/cppcheck.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/astutils.h lib/check.h lib/checkunusedfunctions.h lib/config.h lib/cppcheck.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/exprengine.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/version.h
$(libcppdir)/clangimport.o: lib/clangimport.cpp lib/astutils.h lib/clangimport.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/clangimport.o $(libcppdir)/clangimport.cpp
$(libcppdir)/cppcheck.o: lib/cppcheck.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/astutils.h lib/check.h lib/checkunusedfunctions.h lib/clangimport.h lib/config.h lib/cppcheck.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/exprengine.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/version.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/cppcheck.o $(libcppdir)/cppcheck.cpp
$(libcppdir)/ctu.o: lib/ctu.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
@ -617,6 +622,9 @@ test/testbughuntingchecks.o: test/testbughuntingchecks.cpp lib/astutils.h lib/co
test/testcharvar.o: test/testcharvar.cpp lib/astutils.h lib/check.h lib/checkother.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testcharvar.o test/testcharvar.cpp
test/testclangimport.o: test/testclangimport.cpp lib/astutils.h lib/clangimport.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testclangimport.o test/testclangimport.cpp
test/testclass.o: test/testclass.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/checkclass.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testclass.o test/testclass.cpp

View File

@ -235,7 +235,10 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[])
}
else if (std::strncmp(argv[i], "--clang", 7) == 0) {
printMessage("Cppcheck: Clang import has been removed");
mSettings->clang = true;
if (std::strncmp(argv[i], "--clang=", 8) == 0) {
mSettings->clangExecutable = argv[i] + 8;
}
}
else if (std::strncmp(argv[i], "--config-exclude=",17) ==0) {
@ -989,11 +992,18 @@ void CmdLineParser::printHelp()
" * faster analysis; Cppcheck will reuse the results if\n"
" the hash for a file is unchanged.\n"
" * some useful debug information, i.e. commands used to\n"
" execute clang-tidy/addons.\n"
" execute clang/clang-tidy/addons.\n"
" --check-config Check cppcheck configuration. The normal code\n"
" analysis is disabled by this flag.\n"
" --check-library Show information messages when library files have\n"
" incomplete info.\n"
" --clang=<path> Experimental: Use Clang parser instead of the builtin Cppcheck\n"
" parser. Takes the executable as optional parameter and\n"
" defaults to `clang`. Cppcheck will run the given Clang\n"
" executable, import the Clang AST and convert it into\n"
" Cppcheck data. After that the normal Cppcheck analysis is\n"
" used. You must have the executable in PATH if no path is\n"
" given.\n"
" --config-exclude=<dir>\n"
" Path (prefix) to be excluded from configuration\n"
" checking. Preprocessor configurations defined in\n"

View File

@ -873,6 +873,7 @@ Settings MainWindow::getCppcheckSettings()
result.userDefines += define.toStdString();
}
result.clang = mProjectFile->clangParser;
result.bugHunting = mProjectFile->bugHunting;
result.bugHuntingReport = " ";

View File

@ -45,6 +45,7 @@ ProjectFile::ProjectFile(const QString &filename, QObject *parent) :
void ProjectFile::clear()
{
const Settings settings;
clangParser = false;
bugHunting = false;
mRootPath.clear();
mBuildDir.clear();
@ -61,7 +62,7 @@ void ProjectFile::clear()
mPlatform.clear();
mSuppressions.clear();
mAddons.clear();
mClangTidy = false;
mClangAnalyzer = mClangTidy = false;
mAnalyzeAllVsConfigs = false;
mCheckHeaders = true;
mCheckUnusedTemplates = true;
@ -117,6 +118,9 @@ bool ProjectFile::read(const QString &filename)
if (xmlReader.name() == CppcheckXml::AnalyzeAllVsConfigsElementName)
mAnalyzeAllVsConfigs = readBool(xmlReader);
if (xmlReader.name() == CppcheckXml::Parser)
clangParser = true;
if (xmlReader.name() == CppcheckXml::BugHunting)
bugHunting = true;
@ -182,6 +186,7 @@ bool ProjectFile::read(const QString &filename)
if (xmlReader.name() == CppcheckXml::ToolsElementName) {
QStringList tools;
readStringList(tools, xmlReader, CppcheckXml::ToolElementName);
mClangAnalyzer = tools.contains(CLANG_ANALYZER);
mClangTidy = tools.contains(CLANG_TIDY);
}
@ -870,6 +875,12 @@ bool ProjectFile::write(const QString &filename)
xmlWriter.writeCharacters(mAnalyzeAllVsConfigs ? "true" : "false");
xmlWriter.writeEndElement();
if (clangParser) {
xmlWriter.writeStartElement(CppcheckXml::Parser);
xmlWriter.writeCharacters("clang");
xmlWriter.writeEndElement();
}
if (bugHunting) {
xmlWriter.writeStartElement(CppcheckXml::BugHunting);
xmlWriter.writeEndElement();
@ -1005,6 +1016,8 @@ bool ProjectFile::write(const QString &filename)
CppcheckXml::AddonElementName);
QStringList tools;
if (mClangAnalyzer)
tools << CLANG_ANALYZER;
if (mClangTidy)
tools << CLANG_TIDY;
writeStringList(xmlWriter,
@ -1064,6 +1077,8 @@ QStringList ProjectFile::fromNativeSeparators(const QStringList &paths)
QStringList ProjectFile::getAddonsAndTools() const
{
QStringList ret(mAddons);
if (mClangAnalyzer)
ret << CLANG_ANALYZER;
if (mClangTidy)
ret << CLANG_TIDY;
return ret;

View File

@ -192,6 +192,14 @@ public:
*/
QStringList getAddonsAndTools() const;
bool getClangAnalyzer() const {
return false; //mClangAnalyzer;
}
void setClangAnalyzer(bool c) {
mClangAnalyzer = c;
}
bool getClangTidy() const {
return mClangTidy;
}
@ -380,6 +388,9 @@ public:
mCheckUnknownFunctionReturn = s;
}
/** Use Clang parser */
bool clangParser;
/** Bug hunting */
bool bugHunting;
protected:
@ -572,6 +583,9 @@ private:
*/
QStringList mAddons;
/** @brief Execute clang analyzer? */
bool mClangAnalyzer;
/** @brief Execute clang-tidy? */
bool mClangTidy;

View File

@ -270,6 +270,10 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
mUI.mCheckUnusedTemplates->setChecked(projectFile->getCheckUnusedTemplates());
mUI.mMaxCtuDepth->setValue(projectFile->getMaxCtuDepth());
mUI.mMaxTemplateRecursion->setValue(projectFile->getMaxTemplateRecursion());
if (projectFile->clangParser)
mUI.mBtnClangParser->setChecked(true);
else
mUI.mBtnCppcheckParser->setChecked(true);
mUI.mBtnSafeClasses->setChecked(projectFile->safeChecks.classes);
mUI.mBtnBugHunting->setChecked(projectFile->bugHunting);
setExcludedPaths(projectFile->getExcludedPaths());
@ -337,6 +341,7 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
mUI.mAddonMisra->setText(mUI.mAddonMisra->text() + ' ' + tr("(no rule texts file)"));
}
mUI.mToolClangAnalyzer->setChecked(projectFile->getClangAnalyzer());
mUI.mToolClangTidy->setChecked(projectFile->getClangTidy());
if (CheckThread::clangTidyCmd().isEmpty()) {
mUI.mToolClangTidy->setText(tr("Clang-tidy (not found)"));
@ -363,6 +368,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
projectFile->setCheckPaths(getCheckPaths());
projectFile->setExcludedPaths(getExcludedPaths());
projectFile->setLibraries(getLibraries());
projectFile->clangParser = mUI.mBtnClangParser->isChecked();
projectFile->safeChecks.classes = mUI.mBtnSafeClasses->isChecked();
projectFile->bugHunting = mUI.mBtnBugHunting->isChecked();
if (mUI.mComboBoxPlatform->currentText().endsWith(".xml"))
@ -402,6 +408,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
if (mUI.mAddonMisra->isChecked())
list << "misra";
projectFile->setAddons(list);
projectFile->setClangAnalyzer(mUI.mToolClangAnalyzer->isChecked());
projectFile->setClangTidy(mUI.mToolClangTidy->isChecked());
projectFile->setTags(mUI.mEditTags->text().split(";", QString::SkipEmptyParts));
}

View File

@ -426,6 +426,32 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Parser</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QRadioButton" name="mBtnCppcheckParser">
<property name="text">
<string>Cppcheck (built in)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="mBtnClangParser">
<property name="text">
<string>Clang (experimental)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_11">
<property name="title">

View File

@ -69,6 +69,10 @@ void StatsDialog::setProject(const ProjectFile* projectFile)
QChartView *chartView;
chartView = createChart(statsFile, "cppcheck");
mUI.mTabHistory->layout()->addWidget(chartView);
if (projectFile->getClangAnalyzer()) {
chartView = createChart(statsFile, CLANG_ANALYZER);
mUI.mTabHistory->layout()->addWidget(chartView);
}
if (projectFile->getClangTidy()) {
chartView = createChart(statsFile, CLANG_TIDY);
mUI.mTabHistory->layout()->addWidget(chartView);

View File

@ -211,7 +211,7 @@ static void divByZero(const Token *tok, const ExprEngine::Value &value, ExprEngi
if (tok->astParent()->astOperand2() == tok && value.isEqual(dataBase, 0)) {
const char * const id = (tok->valueType() && tok->valueType()->isFloat()) ? "bughuntingDivByZeroFloat" : "bughuntingDivByZero";
const bool bailout = (value.type == ExprEngine::ValueType::BailoutValue);
dataBase->reportError(tok->astParent(),
dataBase->reportError(dataBase->settings->clang ? tok : tok->astParent(),
Severity::SeverityType::error,
id,
"There is division, cannot determine that there can't be a division by zero.",

View File

@ -188,6 +188,9 @@ void CheckLeakAutoVar::doubleFreeError(const Token *tok, const Token *prevFreeTo
void CheckLeakAutoVar::check()
{
if (mSettings->clang)
return;
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
// Local variables that are known to be non-zero.

View File

@ -758,6 +758,9 @@ void CheckMemoryLeakInClass::publicAllocationError(const Token *tok, const std::
void CheckMemoryLeakStructMember::check()
{
if (mSettings->clang)
return;
const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Variable* var : symbolDatabase->variableList()) {
if (!var || !var->isLocal() || var->isStatic() || var->isReference())

View File

@ -884,6 +884,9 @@ void CheckOther::unreachableCodeError(const Token *tok, bool inconclusive)
//---------------------------------------------------------------------------
void CheckOther::checkVariableScope()
{
if (mSettings->clang)
return;
if (!mSettings->severity.isEnabled(Severity::style))
return;

View File

@ -65,6 +65,9 @@ public:
/** @brief Run checks against the normal token list */
void runChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) OVERRIDE {
if (settings->clang)
return;
CheckUninitVar checkUninitVar(tokenizer, settings, errorLogger);
checkUninitVar.check();
checkUninitVar.valueFlowUninit();

View File

@ -1145,6 +1145,9 @@ void CheckUnusedVar::checkFunctionVariableUsage()
if (!mSettings->severity.isEnabled(Severity::style))
return;
if (mSettings->clang)
return;
// Parse all executing scopes..
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();

View File

@ -95,6 +95,8 @@ void CheckVaarg::referenceAs_va_start_error(const Token *tok, const std::string&
void CheckVaarg::va_list_usage()
{
if (mSettings->clang)
return;
const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Variable* var : symbolDatabase->variableList()) {
if (!var || var->isPointer() || var->isReference() || var->isArray() || !var->scope() || var->typeStartToken()->str() != "va_list")

1616
lib/clangimport.cpp Normal file

File diff suppressed because it is too large Load Diff

33
lib/clangimport.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2020 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
#ifndef clangimportH
#define clangimportH
//---------------------------------------------------------------------------
#include <istream>
class Tokenizer;
namespace clangimport {
void parseClangAstDump(Tokenizer *tokenizer, std::istream &f);
}
#endif

View File

@ -19,6 +19,7 @@
#include "check.h"
#include "checkunusedfunctions.h"
#include "clangimport.h"
#include "ctu.h"
#include "library.h"
#include "mathlib.h"
@ -319,8 +320,154 @@ const char * CppCheck::extraVersion()
return ExtraVersion;
}
static bool reportClangErrors(std::istream &is, std::function<void(const ErrorMessage&)> reportErr)
{
std::string line;
while (std::getline(is, line)) {
if (line.empty() || line[0] == ' ' || line[0] == '`' || line[0] == '-')
continue;
std::string::size_type pos3 = line.find(": error: ");
if (pos3 == std::string::npos)
pos3 = line.find(": fatal error:");
if (pos3 == std::string::npos)
continue;
// file:line:column: error: ....
const std::string::size_type pos2 = line.rfind(":", pos3 - 1);
const std::string::size_type pos1 = line.rfind(":", pos2 - 1);
if (pos1 >= pos2 || pos2 >= pos3)
continue;
const std::string filename = line.substr(0, pos1);
const std::string linenr = line.substr(pos1+1, pos2-pos1-1);
const std::string colnr = line.substr(pos2+1, pos3-pos2-1);
const std::string msg = line.substr(line.find(":", pos3+1) + 2);
std::list<ErrorMessage::FileLocation> locationList;
ErrorMessage::FileLocation loc;
loc.setfile(Path::toNativeSeparators(filename));
loc.line = std::atoi(linenr.c_str());
loc.column = std::atoi(colnr.c_str());
locationList.push_back(loc);
ErrorMessage errmsg(locationList,
loc.getfile(),
Severity::error,
msg,
"syntaxError",
Certainty::normal);
reportErr(errmsg);
return true;
}
return false;
}
unsigned int CppCheck::check(const std::string &path)
{
if (mSettings.clang) {
if (!mSettings.quiet)
mErrorLogger.reportOut(std::string("Checking ") + path + "...");
const std::string lang = Path::isCPP(path) ? "-x c++" : "-x c";
const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, "");
const std::string clangcmd = analyzerInfo + ".clang-cmd";
const std::string clangStderr = analyzerInfo + ".clang-stderr";
const std::string clangAst = analyzerInfo + ".clang-ast";
std::string exe = mSettings.clangExecutable;
#ifdef _WIN32
// append .exe if it is not a path
if (Path::fromNativeSeparators(mSettings.clangExecutable).find('/') == std::string::npos) {
exe += ".exe";
}
#endif
std::string flags(lang + " ");
if (Path::isCPP(path) && !mSettings.standards.stdValue.empty())
flags += "-std=" + mSettings.standards.stdValue + " ";
for (const std::string &i: mSettings.includePaths)
flags += "-I" + i + " ";
flags += getDefinesFlags(mSettings.userDefines);
const std::string args2 = "-fsyntax-only -Xclang -ast-dump -fno-color-diagnostics " + flags + path;
const std::string redirect2 = analyzerInfo.empty() ? std::string("2>&1") : ("2> " + clangStderr);
if (!mSettings.buildDir.empty()) {
std::ofstream fout(clangcmd);
fout << exe << " " << args2 << " " << redirect2 << std::endl;
} else if (mSettings.verbose && !mSettings.quiet) {
mErrorLogger.reportOut(exe + " " + args2);
}
std::string output2;
if (!mExecuteCommand(exe,split(args2),redirect2,&output2) || output2.find("TranslationUnitDecl") == std::string::npos) {
std::cerr << "Failed to execute '" << exe << " " << args2 << " " << redirect2 << "'" << std::endl;
return 0;
}
// Ensure there are not syntax errors...
if (!mSettings.buildDir.empty()) {
std::ifstream fin(clangStderr);
auto reportError = [this](const ErrorMessage& errorMessage) {
reportErr(errorMessage);
};
if (reportClangErrors(fin, reportError))
return 0;
} else {
std::istringstream istr(output2);
auto reportError = [this](const ErrorMessage& errorMessage) {
reportErr(errorMessage);
};
if (reportClangErrors(istr, reportError))
return 0;
}
if (!mSettings.buildDir.empty()) {
std::ofstream fout(clangAst);
fout << output2 << std::endl;
}
try {
std::istringstream ast(output2);
Tokenizer tokenizer(&mSettings, this);
tokenizer.list.appendFileIfNew(path);
clangimport::parseClangAstDump(&tokenizer, ast);
ValueFlow::setValues(&tokenizer.list, const_cast<SymbolDatabase *>(tokenizer.getSymbolDatabase()), this, &mSettings);
if (mSettings.debugnormal)
tokenizer.printDebugOutput(1);
checkNormalTokens(tokenizer);
// create dumpfile
std::ofstream fdump;
std::string dumpFile;
createDumpFile(mSettings, path, tokenizer.list.getFiles(), nullptr, fdump, dumpFile);
if (fdump.is_open()) {
fdump << "<dump cfg=\"\">" << std::endl;
fdump << " <standards>" << std::endl;
fdump << " <c version=\"" << mSettings.standards.getC() << "\"/>" << std::endl;
fdump << " <cpp version=\"" << mSettings.standards.getCPP() << "\"/>" << std::endl;
fdump << " </standards>" << std::endl;
tokenizer.dump(fdump);
fdump << "</dump>" << std::endl;
fdump << "</dumps>" << std::endl;
fdump.close();
}
// run addons
executeAddons(dumpFile);
} catch (const InternalError &e) {
internalError(path, e.errorMessage);
mExitCode = 1; // e.g. reflect a syntax error
} catch (const std::exception &e) {
internalError(path, e.what());
}
return mExitCode;
}
std::ifstream fin(path);
return checkFile(Path::simplifyPath(path), emptyString, fin);
}
@ -337,7 +484,10 @@ unsigned int CppCheck::check(const ImportProject::FileSettings &fs)
temp.mSettings = mSettings;
if (!temp.mSettings.userDefines.empty())
temp.mSettings.userDefines += ';';
temp.mSettings.userDefines += fs.cppcheckDefines();
if (mSettings.clang)
temp.mSettings.userDefines += fs.defines;
else
temp.mSettings.userDefines += fs.cppcheckDefines();
temp.mSettings.includePaths = fs.includePaths;
temp.mSettings.userUndefs.insert(fs.undefs.cbegin(), fs.undefs.cend());
if (fs.standard.find("++") != std::string::npos)
@ -346,6 +496,10 @@ unsigned int CppCheck::check(const ImportProject::FileSettings &fs)
temp.mSettings.standards.setC(fs.standard);
if (fs.platformType != Settings::Unspecified)
temp.mSettings.platform(fs.platformType);
if (mSettings.clang) {
temp.mSettings.includePaths.insert(temp.mSettings.includePaths.end(), fs.systemIncludePaths.cbegin(), fs.systemIncludePaths.cend());
return temp.check(Path::simplifyPath(fs.filename));
}
std::ifstream fin(fs.filename);
unsigned int returnValue = temp.checkFile(Path::simplifyPath(fs.filename), fs.cfg, fin);
mSettings.nomsg.addSuppressions(temp.mSettings.nomsg.getSuppressions());
@ -800,6 +954,10 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer)
check->runChecks(&tokenizer, &mSettings, this);
}
if (mSettings.clang)
// TODO: Use CTU for Clang analysis
return;
// Analyse the tokens..
CTU::FileInfo *fi1 = CTU::getFileInfo(&tokenizer);

View File

@ -75,6 +75,7 @@
<ClCompile Include="checkunusedfunctions.cpp" />
<ClCompile Include="checkunusedvar.cpp" />
<ClCompile Include="checkvaarg.cpp" />
<ClCompile Include="clangimport.cpp" />
<ClCompile Include="cppcheck.cpp" />
<ClCompile Include="ctu.cpp" />
<ClCompile Include="errorlogger.cpp" />
@ -135,6 +136,7 @@
<ClInclude Include="checkunusedfunctions.h" />
<ClInclude Include="checkunusedvar.h" />
<ClInclude Include="checkvaarg.h" />
<ClInclude Include="clangimport.h" />
<ClInclude Include="config.h" />
<ClInclude Include="cppcheck.h" />
<ClInclude Include="ctu.h" />
@ -570,4 +572,4 @@ xcopy "$(SolutionDir)platforms" "$(OutDir)platforms" /E /I /D /Y</Command>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>
</Project>

View File

@ -1143,6 +1143,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
guiProject.platform = node->GetText();
else if (strcmp(node->Name(), CppcheckXml::AnalyzeAllVsConfigsElementName) == 0)
guiProject.analyzeAllVsConfigs = node->GetText();
else if (strcmp(node->Name(), CppcheckXml::Parser) == 0)
temp.clang = true;
else if (strcmp(node->Name(), CppcheckXml::AddonsElementName) == 0)
temp.addons = readXmlStringList(node, "", CppcheckXml::AddonElementName, nullptr);
else if (strcmp(node->Name(), CppcheckXml::TagsElementName) == 0)
@ -1188,6 +1190,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
settings->userDefines = temp.userDefines;
settings->userUndefs = temp.userUndefs;
settings->addons = temp.addons;
settings->clang = temp.clang;
settings->clangTidy = temp.clangTidy;
for (const std::string &p : paths)

View File

@ -125,6 +125,7 @@ namespace CppcheckXml {
const char BuildDirElementName[] = "builddir";
const char ImportProjectElementName[] = "importproject";
const char AnalyzeAllVsConfigsElementName[] = "analyze-all-vs-configs";
const char Parser[] = "parser";
const char BugHunting[] = "bug-hunting";
const char IncludeDirElementName[] = "includedir";
const char DirElementName[] = "dir";

View File

@ -32,6 +32,7 @@ HEADERS += $${PWD}/analyzerinfo.h \
$${PWD}/checkunusedfunctions.h \
$${PWD}/checkunusedvar.h \
$${PWD}/checkvaarg.h \
$${PWD}/clangimport.h \
$${PWD}/cppcheck.h \
$${PWD}/ctu.h \
$${PWD}/errorlogger.h \
@ -89,6 +90,7 @@ SOURCES += $${PWD}/analyzerinfo.cpp \
$${PWD}/checkunusedfunctions.cpp \
$${PWD}/checkunusedvar.cpp \
$${PWD}/checkvaarg.cpp \
$${PWD}/clangimport.cpp \
$${PWD}/cppcheck.cpp \
$${PWD}/ctu.cpp \
$${PWD}/errorlogger.cpp \

View File

@ -36,6 +36,8 @@ Settings::Settings()
checkHeaders(true),
checkLibrary(false),
checkUnusedTemplates(true),
clang(false),
clangExecutable("clang"),
clangTidy(false),
daca(false),
debugBugHunting(false),

View File

@ -140,6 +140,12 @@ public:
/** Check unused/uninstantiated templates */
bool checkUnusedTemplates;
/** Use Clang */
bool clang;
/** Custom Clang executable */
std::string clangExecutable;
/** Use clang-tidy */
bool clangTidy;

View File

@ -1823,6 +1823,69 @@ void SymbolDatabase::validate() const
//validateVariables();
}
void SymbolDatabase::clangSetVariables(const std::vector<const Variable *> &variableList)
{
mVariableList = variableList;
}
Variable::Variable(const Token *name_, const std::string &clangType, const Token *typeStart,
const Token *typeEnd, nonneg int index_, AccessControl access_,
const Type *type_, const Scope *scope_)
: mNameToken(name_),
mTypeStartToken(typeStart),
mTypeEndToken(typeEnd),
mIndex(index_),
mAccess(access_),
mFlags(0),
mType(type_),
mScope(scope_),
mValueType(nullptr)
{
if (!mTypeStartToken && mTypeEndToken) {
mTypeStartToken = mTypeEndToken;
while (Token::Match(mTypeStartToken->previous(), "%type%|*|&"))
mTypeStartToken = mTypeStartToken->previous();
}
while (Token::Match(mTypeStartToken, "const|struct|static")) {
if (mTypeStartToken->str() == "static")
setFlag(fIsStatic, true);
mTypeStartToken = mTypeStartToken->next();
}
if (Token::simpleMatch(mTypeEndToken, "&"))
setFlag(fIsReference, true);
else if (Token::simpleMatch(mTypeEndToken, "&&")) {
setFlag(fIsReference, true);
setFlag(fIsRValueRef, true);
}
std::string::size_type pos = clangType.find("[");
if (pos != std::string::npos) {
setFlag(fIsArray, true);
do {
const std::string::size_type pos1 = pos+1;
pos = clangType.find("]", pos1);
Dimension dim;
dim.tok = nullptr;
dim.known = pos > pos1;
if (pos > pos1)
dim.num = MathLib::toLongNumber(clangType.substr(pos1, pos-pos1));
else
dim.num = 0;
mDimensions.push_back(dim);
++pos;
} while (pos < clangType.size() && clangType[pos] == '[');
}
// Is there initialization in variable declaration
const Token *initTok = mNameToken ? mNameToken->next() : nullptr;
while (initTok && initTok->str() == "[")
initTok = initTok->link()->next();
if (Token::Match(initTok, "=|{") || (initTok && initTok->isSplittedVarDeclEq()))
setFlag(fIsInit, true);
}
Variable::Variable(const Variable &var, const Scope *scope)
: mValueType(nullptr)
{
@ -2170,6 +2233,39 @@ Function::Function(const Tokenizer *mTokenizer,
}
}
Function::Function(const Token *tokenDef, const std::string &clangType)
: tokenDef(tokenDef),
argDef(nullptr),
token(nullptr),
arg(nullptr),
retDef(nullptr),
retType(nullptr),
functionScope(nullptr),
nestedIn(nullptr),
initArgCount(0),
type(eFunction),
access(AccessControl::Public),
noexceptArg(nullptr),
throwArg(nullptr),
templateDef(nullptr),
functionPointerUsage(nullptr),
mFlags(0)
{
// operator function
if (::isOperator(tokenDef)) {
isOperator(true);
// 'operator =' is special
if (tokenDef->str() == "operator=")
type = Function::eOperatorEqual;
}
setFlags(tokenDef, tokenDef->scope());
if (endsWith(clangType, " const", 6))
isConst(true);
}
const Token *Function::setFlags(const Token *tok1, const Scope *scope)
{
// look for end of previous statement
@ -5951,7 +6047,30 @@ static const Token * parsedecl(const Token *type, ValueType * const valuetype, V
parsedecl(type->type()->typeStart, valuetype, defaultSignedness, settings);
else if (type->str() == "const")
valuetype->constness |= (1 << (valuetype->pointer - pointer0));
else if (const Library::Container *container = settings->library.detectContainer(type)) {
else if (settings->clang && type->str().size() > 2 && type->str().find("::") < type->str().find("<")) {
TokenList typeTokens(settings);
std::string::size_type pos1 = 0;
do {
std::string::size_type pos2 = type->str().find("::", pos1);
if (pos2 == std::string::npos) {
typeTokens.addtoken(type->str().substr(pos1), 0, 0, 0, false);
break;
}
typeTokens.addtoken(type->str().substr(pos1, pos2 - pos1), 0, 0, 0, false);
typeTokens.addtoken("::", 0, 0, 0, false);
pos1 = pos2 + 2;
} while (pos1 < type->str().size());
const Library::Container *container = settings->library.detectContainer(typeTokens.front());
if (container) {
valuetype->type = ValueType::Type::CONTAINER;
valuetype->container = container;
} else {
const Scope *scope = type->scope();
valuetype->typeScope = scope->check->findScope(typeTokens.front(), scope);
if (valuetype->typeScope)
valuetype->type = (scope->type == Scope::ScopeType::eClass) ? ValueType::Type::RECORD : ValueType::Type::NONSTD;
}
} else if (const Library::Container *container = settings->library.detectContainer(type)) {
valuetype->type = ValueType::Type::CONTAINER;
valuetype->container = container;
while (Token::Match(type, "%name%|::|<")) {

View File

@ -237,6 +237,10 @@ public:
evaluate(settings);
}
Variable(const Token *name_, const std::string &clangType, const Token *typeStart,
const Token *typeEnd, nonneg int index_, AccessControl access_,
const Type *type_, const Scope *scope_);
Variable(const Variable &var, const Scope *scope);
Variable(const Variable &var);
@ -742,6 +746,7 @@ public:
enum Type { eConstructor, eCopyConstructor, eMoveConstructor, eOperatorEqual, eDestructor, eFunction, eLambda };
Function(const Tokenizer *mTokenizer, const Token *tok, const Scope *scope, const Token *tokDef, const Token *tokArgDef);
Function(const Token *tokenDef, const std::string &clangType);
const std::string &name() const {
return tokenDef->str();
@ -1379,6 +1384,7 @@ public:
/** Set array dimensions when valueflow analysis is completed */
void setArrayDimensionsUsingValueFlow();
void clangSetVariables(const std::vector<const Variable *> &variableList);
void createSymbolDatabaseExprIds();
private:

View File

@ -169,6 +169,11 @@ int TokenList::appendFileIfNew(const std::string &fileName)
return mFiles.size() - 1;
}
void TokenList::clangSetOrigFiles()
{
mOrigFiles = mFiles;
}
void TokenList::deleteTokens(Token *tok)
{
while (tok) {

View File

@ -186,6 +186,8 @@ public:
*/
void simplifyStdType();
void clangSetOrigFiles();
bool isKeyword(const std::string &str) const;
private:

View File

@ -132,6 +132,18 @@ directory individually. The following command ignores both the src/b and src/c d
cppcheck -isrc/b -isrc/c
### Clang parser (experimental)
By default Cppcheck uses an internal C/C++ parser. However there is an experimental option to use the Clang parser instead.
Install `clang`. Then use Cppcheck option `--clang`.
Technically, Cppcheck will execute `clang` with its `-ast-dump` option. The Clang output is then imported and converted into
the normal Cppcheck format. And then normal Cppcheck analysis is performed on that.
You can also pass a custom Clang executable to the option by using for example `--clang=clang-10`. You can also pass it
with a path. On Windows it will append the `.exe` extension unless you use a path.
## Severities
The possible severities for messages are:

View File

@ -0,0 +1,124 @@
# python -m pytest test-clang-import.py
import os
import re
import subprocess
import pytest
from testutils import cppcheck
try:
subprocess.call(['clang', '--version'])
except OSError:
pytest.skip("'clang' does not exist", allow_module_level=True)
def get_debug_section(title, stdout):
s = re.sub(r'0x[0-9a-fA-F]+', '0x12345678', stdout)
s = re.sub(r'nestedIn: Struct', 'nestedIn: Class', s)
s = re.sub(r'classDef: struct', 'classDef: class', s)
s = re.sub(r'isInline: [a-z]+', 'isInline: ---', s)
s = re.sub(r'needInitialization: .*', 'needInitialization: ---', s)
s = re.sub(r'functionOf: .*', 'functionOf: ---', s)
s = re.sub(r'0x12345678 Struct', '0x12345678 Class', s)
if title == '##AST':
# TODO set types
s = re.sub(r"return '[a-zA-Z0-9: *]+'", "return", s)
pos1 = s.find(title)
assert pos1 > 0, 'title not found'
pos1 = s.find('\n', pos1) + 1
assert pos1 > 0
pos2 = s.find("\n##", pos1)
if pos2 < 0:
return s[pos1:]
return s[pos1:pos2-1]
def check_symbol_database(code):
testfile = 'test.cpp'
with open(testfile, 'w+t') as f:
f.write(code)
ret1, stdout1, _ = cppcheck(['--clang', '--debug', '-v', testfile])
ret2, stdout2, _ = cppcheck(['--debug', '-v', testfile])
os.remove(testfile)
assert 0 == ret1, stdout1
assert 0 == ret2, stdout2
assert get_debug_section('### Symbol database', stdout1) == get_debug_section('### Symbol database', stdout2)
def check_ast(code):
testfile = 'test.cpp'
with open(testfile, 'w+t') as f:
f.write(code)
ret1, stdout1, _ = cppcheck(['--clang', '--debug', '-v', testfile])
ret2, stdout2, _ = cppcheck(['--debug', '-v', testfile])
os.remove(testfile)
assert 0 == ret1, stdout1
assert 0 == ret2, stdout1
assert get_debug_section('##AST', stdout1) == get_debug_section('##AST', stdout2)
def todo_check_ast(code):
testfile = 'test.cpp'
with open(testfile, 'w+t') as f:
f.write(code)
ret1, stdout1, _ = cppcheck(['--clang', '--debug', '-v', testfile])
ret2, stdout2, _ = cppcheck(['--debug', '-v', testfile])
os.remove(testfile)
assert 0 == ret1, stdout1
assert 0 == ret2, stdout2
assert get_debug_section('##AST', stdout1) != get_debug_section('##AST', stdout2)
def test_symbol_database_1():
check_symbol_database('int main(){return 0;}')
def test_symbol_database_2():
check_symbol_database('struct Foo { void f(); }; void Foo::f() {}')
def test_symbol_database_3():
check_symbol_database('struct Fred { int a; }; int b; void f(int c, int d) { int e; }')
def test_symbol_database_4():
check_symbol_database('void f(const int x) {}')
def test_symbol_database_5():
check_symbol_database('void f(int);')
def test_symbol_database_6():
check_symbol_database('inline static int foo(int x) { return x; }')
def test_symbol_database_7():
check_symbol_database('struct S {int x;}; void f(struct S *s) {}')
def test_symbol_database_class_access_1():
check_symbol_database('class Fred { void foo ( ) {} } ;')
def test_symbol_database_class_access_2():
check_symbol_database('class Fred { protected: void foo ( ) {} } ;')
def test_symbol_database_class_access_3():
check_symbol_database('class Fred { public: void foo ( ) {} } ;')
def test_symbol_database_operator():
check_symbol_database('struct Fred { void operator=(int x); };')
def test_symbol_database_struct_1():
check_symbol_database('struct S {};')
def test_ast_calculations():
check_ast('int x = 5; int y = (x + 4) * 2;')
check_ast('long long dostuff(int x) { return x ? 3 : 5; }')
def test_ast_control_flow():
check_ast('void foo(int x) { if (x > 5){} }')
check_ast('int dostuff() { for (int x = 0; x < 10; x++); }')
check_ast('void foo(int x) { switch (x) {case 1: break; } }')
check_ast('void foo(int a, int b, int c) { foo(a,b,c); }')
def test_ast():
check_ast('struct S { int x; }; S* foo() { return new S(); }')

1303
test/testclangimport.cpp Normal file

File diff suppressed because it is too large Load Diff