diff --git a/.github/workflows/cppcheck-premium.yml b/.github/workflows/cppcheck-premium.yml index 11d6f3b8cac..2f9863d5e44 100644 --- a/.github/workflows/cppcheck-premium.yml +++ b/.github/workflows/cppcheck-premium.yml @@ -40,4 +40,4 @@ jobs: - name: Check run: | cppcheckpremium-${{ env.PREMIUM_VERSION }}/premiumaddon --check-loc-license cppcheck.lic > cppcheck-premium-loc - cppcheckpremium-${{ env.PREMIUM_VERSION }}/cppcheck -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2008 --premium=cert-c++-2016 --error-exitcode=1 lib + cppcheckpremium-${{ env.PREMIUM_VERSION }}/cppcheck -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2008 --premium=cert-c++-2016 --inline-suppr --error-exitcode=1 lib diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index dd6d10278ec..e796155c544 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -2,7 +2,10 @@ # Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners name: include-what-you-use -on: workflow_dispatch +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: permissions: contents: read diff --git a/.selfcheck_suppressions b/.selfcheck_suppressions index b6478d8717a..03e54874475 100644 --- a/.selfcheck_suppressions +++ b/.selfcheck_suppressions @@ -1,12 +1,9 @@ missingIncludeSystem -shadowFunction -bitwiseOnBoolean # temporary suppressions - fix the warnings! simplifyUsing:lib/valueptr.h varid0:gui/projectfile.cpp naming-privateMemberVariable:gui/test/cppchecklibrarydata/testcppchecklibrarydata.h -templateInstantiation symbolDatabaseWarning:*/moc_*.cpp simplifyUsing:*/moc_*.cpp @@ -19,10 +16,13 @@ functionStatic:*/ui_fileview.h valueFlowBailout valueFlowBailoutIncompleteVar autoNoType -bailoutUninitVar naming-varname:externals/simplecpp/simplecpp.h naming-privateMemberVariable:externals/simplecpp/simplecpp.h -# TODO: use more granular suppressions - might expose false positives -*:externals/picojson/* -*:externals/tinyxml2/* + +# these warnings need to be addressed upstream +uninitMemberVar:externals/tinyxml2/tinyxml2.h +noExplicitConstructor:externals/tinyxml2/tinyxml2.h +missingOverride:externals/tinyxml2/tinyxml2.h +invalidPrintfArgType_sint:externals/tinyxml2/tinyxml2.h +naming-privateMemberVariable:externals/tinyxml2/tinyxml2.h \ No newline at end of file diff --git a/AUTHORS b/AUTHORS index 442ad24f44a..a3a02656e08 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ Changkyoon Kim Chris Lalancette Christian Ehrlicher Christian Franke +Christoph GrĂ¼ninger Christoph Schmidt Christoph Strehle Chuck Larson diff --git a/Makefile b/Makefile index 09c40c0e234..8c698501da1 100644 --- a/Makefile +++ b/Makefile @@ -518,7 +518,7 @@ $(libcppdir)/checkfunctions.o: lib/checkfunctions.cpp lib/addoninfo.h lib/astuti $(libcppdir)/checkinternal.o: lib/checkinternal.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkinternal.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkinternal.cpp -$(libcppdir)/checkio.o: lib/checkio.cpp lib/addoninfo.h lib/check.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkio.o: lib/checkio.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkio.cpp $(libcppdir)/checkleakautovar.o: lib/checkleakautovar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkleakautovar.h lib/checkmemoryleak.h lib/checknullpointer.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h @@ -728,7 +728,7 @@ test/testclangimport.o: test/testclangimport.cpp lib/addoninfo.h lib/check.h lib test/testclass.o: test/testclass.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkclass.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testclass.cpp -test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcmdlineparser.cpp test/testcolor.o: test/testcolor.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h @@ -809,7 +809,7 @@ test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprocessexecutor.cpp -test/testsettings.o: test/testsettings.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h +test/testsettings.o: test/testsettings.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsettings.cpp test/testsimplifytemplate.o: test/testsimplifytemplate.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h @@ -887,7 +887,7 @@ test/testvaarg.o: test/testvaarg.cpp lib/addoninfo.h lib/check.h lib/checkvaarg. test/testvalueflow.o: test/testvalueflow.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp -test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvarid.cpp externals/simplecpp/simplecpp.o: externals/simplecpp/simplecpp.cpp externals/simplecpp/simplecpp.h diff --git a/addons/cppcheckdata.py b/addons/cppcheckdata.py index d8bfcddd12f..9bfd45972f8 100755 --- a/addons/cppcheckdata.py +++ b/addons/cppcheckdata.py @@ -569,6 +569,7 @@ class Scope: function = None nestedInId = None nestedIn = None + nestedList = None type = None isExecutable = None varlistId = None @@ -586,6 +587,7 @@ def __init__(self, element): self.bodyEnd = None self.nestedInId = element.get('nestedIn') self.nestedIn = None + self.nestedList = list() self.type = element.get('type') self.definedType = element.get('definedType') self.isExecutable = (self.type in ('Function', 'If', 'Else', 'For', 'While', 'Do', @@ -606,6 +608,8 @@ def setId(self, IdMap): self.bodyStart = IdMap[self.bodyStartId] self.bodyEnd = IdMap[self.bodyEndId] self.nestedIn = IdMap[self.nestedInId] + if self.nestedIn: + self.nestedIn.nestedList.append(self) self.function = IdMap[self.functionId] for v in self.varlistId: value = IdMap.get(v) diff --git a/addons/misra_9.py b/addons/misra_9.py index b844170662e..f35becad7bf 100644 --- a/addons/misra_9.py +++ b/addons/misra_9.py @@ -500,11 +500,29 @@ def createRecordChildrenDefs(ed, var): child = ElementDef("pointer", var.nameToken, var.nameToken.valueType) ed.addChild(child) return + child_dict = {} for variable in valueType.typeScope.varlist: if variable is var: continue child = getElementDef(variable.nameToken) - ed.addChild(child) + child_dict[variable.nameToken] = child + for scopes in valueType.typeScope.nestedList: + varscope = False + if scopes.nestedIn == valueType.typeScope: + for variable in valueType.typeScope.varlist: + if variable.nameToken and variable.nameToken.valueType and variable.nameToken.valueType.typeScope == scopes: + varscope = True + break + if not varscope: + ed1 = ElementDef("record", scopes.Id, valueType) + for variable in scopes.varlist: + child = getElementDef(variable.nameToken) + ed1.addChild(child) + child_dict[scopes.bodyStart] = ed1 + sorted_keys = sorted(list(child_dict.keys()), key=lambda k: "%s %s %s" % (k.file, k.linenr, k.column)) + for _key in sorted_keys: + ed.addChild(child_dict[_key]) + def getElementByDesignator(ed, token): if not token.str in [ '.', '[' ]: diff --git a/addons/test/misra/misra-test.c b/addons/test/misra/misra-test.c index 60295a837f1..e8168ed5fda 100644 --- a/addons/test/misra/misra-test.c +++ b/addons/test/misra/misra-test.c @@ -418,6 +418,21 @@ static void misra_8_14(char * restrict str) {(void)str;} // 8.14 struct S_9_3 { struct S_9_3* p; int x; }; struct S_9_3* s_9_3_array[] = { x, NULL }; // 8.4 +// #10854 +struct Entry_9_2{ + union{ // 19.2 + const int *p; + int x; + }; + int y; +}; + +static void misra_9_2_10854(void){ + struct Entry_9_2 e1[] = + { + {{ .x = 1 }, .y = 2 } + }; +} static void misra_9_empty_or_zero_initializers(void) { int a[2] = {}; // 9.2 int b[2][2] = {}; // 9.2 diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index be5b3468f2d..dc8a9006337 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -282,13 +282,71 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) // TODO: error out on all missing given files/paths CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const argv[]) { + mSettings.exename = Path::getCurrentExecutablePath(argv[0]); + + if (argc <= 1) { + printHelp(); + return Result::Exit; + } + + // check for exclusive options + for (int i = 1; i < argc; i++) { + // documentation.. + if (std::strcmp(argv[i], "--doc") == 0) { + std::ostringstream doc; + // Get documentation.. + for (const Check * it : Check::instances()) { + const std::string& name(it->name()); + const std::string info(it->classInfo()); + if (!name.empty() && !info.empty()) + doc << "## " << name << " ##\n" + << info << "\n"; + } + + mLogger.printRaw(doc.str()); + return Result::Exit; + } + + // print all possible error messages.. + if (std::strcmp(argv[i], "--errorlist") == 0) { + if (!loadCppcheckCfg()) + return Result::Fail; + { + XMLErrorMessagesLogger xmlLogger; + std::cout << ErrorMessage::getXMLHeader(mSettings.cppcheckCfgProductName); + CppCheck::getErrorMessages(xmlLogger); + std::cout << ErrorMessage::getXMLFooter() << std::endl; + } + return Result::Exit; + } + + // Print help + if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0) { + printHelp(); + return Result::Exit; + } + + if (std::strcmp(argv[i], "--version") == 0) { + if (!loadCppcheckCfg()) + return Result::Fail; + if (!mSettings.cppcheckCfgProductName.empty()) { + mLogger.printRaw(mSettings.cppcheckCfgProductName); + } else { + const char * const extraVersion = CppCheck::extraVersion(); + if (*extraVersion != '\0') + mLogger.printRaw(std::string("Cppcheck ") + CppCheck::version() + " ("+ extraVersion + ')'); + else + mLogger.printRaw(std::string("Cppcheck ") + CppCheck::version()); + } + return Result::Exit; + } + } + bool def = false; bool maxconfigs = false; ImportProject project; - mSettings.exename = Path::getCurrentExecutablePath(argv[0]); - for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { // User define @@ -466,23 +524,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } } - // documentation.. - else if (std::strcmp(argv[i], "--doc") == 0) { - // TODO: make an exclusive option - std::ostringstream doc; - // Get documentation.. - for (const Check * it : Check::instances()) { - const std::string& name(it->name()); - const std::string info(it->classInfo()); - if (!name.empty() && !info.empty()) - doc << "## " << name << " ##\n" - << info << "\n"; - } - - mLogger.printRaw(doc.str()); - return Result::Exit; - } - // dump cppcheck data else if (std::strcmp(argv[i], "--dump") == 0) mSettings.dump = true; @@ -506,19 +547,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } } - // print all possible error messages.. - else if (std::strcmp(argv[i], "--errorlist") == 0) { - // TODO: make this an exclusive option - mSettings.loadCppcheckCfg(); - { - XMLErrorMessagesLogger xmlLogger; - std::cout << ErrorMessage::getXMLHeader(mSettings.cppcheckCfgProductName); - CppCheck::getErrorMessages(xmlLogger); - std::cout << ErrorMessage::getXMLFooter() << std::endl; - } - return Result::Exit; - } - // --error-exitcode=1 else if (std::strncmp(argv[i], "--error-exitcode=", 17) == 0) { if (!parseNumberArg(argv[i], 17, mSettings.exitCode)) @@ -592,13 +620,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(argv[i], "--funsigned-char") == 0) mSettings.platform.defaultSign = 'u'; - // Print help - else if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0) { - // TODO: make this an exclusive option - printHelp(); - return Result::Exit; - } - // Ignored paths else if (std::strncmp(argv[i], "-i", 2) == 0) { std::string path; @@ -1150,21 +1171,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(argv[i], "-v") == 0 || std::strcmp(argv[i], "--verbose") == 0) mSettings.verbose = true; - else if (std::strcmp(argv[i], "--version") == 0) { - // TODO: make this an exclusive parameter - mSettings.loadCppcheckCfg(); - if (!mSettings.cppcheckCfgProductName.empty()) { - mLogger.printRaw(mSettings.cppcheckCfgProductName); - } else { - const char * const extraVersion = CppCheck::extraVersion(); - if (*extraVersion != '\0') - mLogger.printRaw(std::string("Cppcheck ") + CppCheck::version() + " ("+ extraVersion + ')'); - else - mLogger.printRaw(std::string("Cppcheck ") + CppCheck::version()); - } - return Result::Exit; - } - // Write results in results.xml else if (std::strcmp(argv[i], "--xml") == 0) mSettings.xml = true; @@ -1199,7 +1205,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } } - mSettings.loadCppcheckCfg(); + if (!loadCppcheckCfg()) + return Result::Fail; // Default template format.. if (mSettings.templateFormat.empty()) { @@ -1226,11 +1233,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mLogger.printMessage("unusedFunction check can't be used with '-j' option. Disabling unusedFunction check."); } - if (argc <= 1) { - printHelp(); - return Result::Exit; - } - if (!mPathNames.empty() && project.projectType != ImportProject::Type::NONE) { mLogger.printError("--project cannot be used in conjunction with source files."); return Result::Fail; @@ -1610,10 +1612,10 @@ void CmdLineParser::printHelp() const mLogger.printRaw(oss.str()); } -bool CmdLineParser::isCppcheckPremium() const { - if (mSettings.cppcheckCfgProductName.empty()) - mSettings.loadCppcheckCfg(); - return startsWith(mSettings.cppcheckCfgProductName, "Cppcheck Premium"); +bool CmdLineParser::isCppcheckPremium() { + Settings settings; + settings.loadCppcheckCfg(); // TODO: how to handle errors? + return startsWith(settings.cppcheckCfgProductName, "Cppcheck Premium"); } bool CmdLineParser::tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename) @@ -1702,3 +1704,14 @@ bool CmdLineParser::loadAddons(Settings& settings) } return result; } + +bool CmdLineParser::loadCppcheckCfg() +{ + const std::string cfgErr = mSettings.loadCppcheckCfg(); + if (!cfgErr.empty()) { + mLogger.printError("could not load cppcheck.cfg - " + cfgErr); + return false; + } + return true; +} + diff --git a/cli/cmdlineparser.h b/cli/cmdlineparser.h index db3bb963890..739d2b21b09 100644 --- a/cli/cmdlineparser.h +++ b/cli/cmdlineparser.h @@ -111,7 +111,7 @@ class CmdLineParser { void printHelp() const; private: - bool isCppcheckPremium() const; + static bool isCppcheckPremium(); template bool parseNumberArg(const char* const arg, std::size_t offset, T& num, bool mustBePositive = false) @@ -150,6 +150,8 @@ class CmdLineParser { */ bool loadAddons(Settings& settings); + bool loadCppcheckCfg(); + CmdLineLogger &mLogger; std::vector mPathNames; diff --git a/gui/compliancereportdialog.cpp b/gui/compliancereportdialog.cpp index daae1df064d..adcb25b4d6a 100644 --- a/gui/compliancereportdialog.cpp +++ b/gui/compliancereportdialog.cpp @@ -95,6 +95,18 @@ ComplianceReportDialog::ComplianceReportDialog(ProjectFile* projectFile, QString mUI->setupUi(this); mUI->mEditProjectName->setText(projectFile->getProjectName()); connect(mUI->buttonBox, &QDialogButtonBox::clicked, this, &ComplianceReportDialog::buttonClicked); + mUI->mCodingStandard->clear(); + if (projectFile->getCodingStandards().contains("misra-c-2023")) + mUI->mCodingStandard->addItem("Misra C 2023"); + else if (projectFile->getAddons().contains("misra")) + mUI->mCodingStandard->addItem("Misra C 2012"); + if (projectFile->getCodingStandards().contains("misra-c++-2008")) + mUI->mCodingStandard->addItem("Misra C++ 2008"); + if (projectFile->getCodingStandards().contains("cert-c-2016")) + mUI->mCodingStandard->addItem("Cert C"); + if (projectFile->getCodingStandards().contains("cert-c++-2016")) + mUI->mCodingStandard->addItem("Cert C++"); + mUI->mCodingStandard->addItems(projectFile->getCodingStandards()); } ComplianceReportDialog::~ComplianceReportDialog() @@ -197,8 +209,9 @@ void ComplianceReportDialog::save() QStringList args{"--project-name=" + projectName, "--project-version=" + projectVersion, - "--output-file=" + outFile, - "--suppressions=" + suppressions.join(",")}; + "--output-file=" + outFile}; + if (!suppressions.isEmpty()) + args << "--suppressions=" + suppressions.join(","); args << ("--" + std); @@ -215,4 +228,13 @@ void ComplianceReportDialog::save() process.start(appPath + "/compliance-report", args); #endif process.waitForFinished(); + const QString output = process.readAll(); + if (!output.isEmpty()) { + QMessageBox msg(QMessageBox::Critical, + tr("Save compliance report"), + output, + QMessageBox::Ok, + this); + msg.exec(); + } } diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 2835f8983c6..3be305ee5f8 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -119,7 +119,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : { Settings tempSettings; tempSettings.exename = QCoreApplication::applicationFilePath().toStdString(); - tempSettings.loadCppcheckCfg(); + tempSettings.loadCppcheckCfg(); // TODO: how to handle error? mCppcheckCfgProductName = QString::fromStdString(tempSettings.cppcheckCfgProductName); mCppcheckCfgAbout = QString::fromStdString(tempSettings.cppcheckCfgAbout); } @@ -903,8 +903,7 @@ bool MainWindow::tryLoadLibrary(Library *library, const QString& filename) return true; } -Settings MainWindow::getCppcheckSettings() -{ +Settings MainWindow::getCppcheckSettings() { saveSettings(); // Save settings Settings result; @@ -915,7 +914,11 @@ Settings MainWindow::getCppcheckSettings() if (!std) QMessageBox::critical(this, tr("Error"), tr("Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir= at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured.").arg("std.cfg")); - result.loadCppcheckCfg(); + { + const QString cfgErr = QString::fromStdString(result.loadCppcheckCfg()); + if (!cfgErr.isEmpty()) + QMessageBox::critical(this, tr("Error"), tr("Failed to load %1 - %2").arg("cppcheck.cfg").arg(cfgErr)); + } // If project file loaded, read settings from it if (mProjectFile) { diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 71494a19d0f..e73475cf8ca 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -643,8 +643,8 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) } //Create an action for the application - QAction *recheckSelectedFiles = new QAction(tr("Recheck"), &menu); - QAction *copy = new QAction(tr("Copy"), &menu); + QAction *recheckAction = new QAction(tr("Recheck"), &menu); + QAction *copyAction = new QAction(tr("Copy"), &menu); QAction *hide = new QAction(tr("Hide"), &menu); QAction *hideallid = new QAction(tr("Hide all with id"), &menu); QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu); @@ -654,13 +654,13 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) opencontainingfolder->setDisabled(true); } if (mThread->isChecking()) - recheckSelectedFiles->setDisabled(true); + recheckAction->setDisabled(true); else - recheckSelectedFiles->setDisabled(false); + recheckAction->setDisabled(false); - menu.addAction(recheckSelectedFiles); + menu.addAction(recheckAction); menu.addSeparator(); - menu.addAction(copy); + menu.addAction(copyAction); menu.addSeparator(); menu.addAction(hide); menu.addAction(hideallid); @@ -672,8 +672,8 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); menu.addAction(opencontainingfolder); - connect(recheckSelectedFiles, SIGNAL(triggered()), this, SLOT(recheckSelectedFiles())); - connect(copy, SIGNAL(triggered()), this, SLOT(copy())); + connect(recheckAction, SIGNAL(triggered()), this, SLOT(recheckAction())); + connect(copyAction, SIGNAL(triggered()), this, SLOT(copyAction())); connect(hide, SIGNAL(triggered()), this, SLOT(hideResult())); connect(hideallid, SIGNAL(triggered()), this, SLOT(hideAllIdResult())); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); diff --git a/lib/astutils.cpp b/lib/astutils.cpp index f411e2d1572..46527c6e03f 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -3258,6 +3258,11 @@ static ExprUsage getFunctionUsage(const Token* tok, int indirect, const Settings return ExprUsage::Used; } else if (ftok->str() == "{") { return indirect == 0 ? ExprUsage::Used : ExprUsage::Inconclusive; + } else if (ftok->variable() && ftok == ftok->variable()->nameToken()) { // variable init/constructor call + if (ftok->variable()->type() && ftok->variable()->type()->needInitialization == Type::NeedInitialization::True) + return ExprUsage::Used; + if (ftok->variable()->isStlType() || (ftok->variable()->valueType() && ftok->variable()->valueType()->container)) // STL types or containers don't initialize external variables + return ExprUsage::Used; } else { const bool isnullbad = settings->library.isnullargbad(ftok, argnr + 1); if (indirect == 0 && astIsPointer(tok) && !addressOf && isnullbad) diff --git a/lib/checkbufferoverrun.cpp b/lib/checkbufferoverrun.cpp index d9acf389218..c0c5ddb64c8 100644 --- a/lib/checkbufferoverrun.cpp +++ b/lib/checkbufferoverrun.cpp @@ -953,6 +953,7 @@ Check::FileInfo *CheckBufferOverrun::getFileInfo(const Tokenizer *tokenizer, con Check::FileInfo * CheckBufferOverrun::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const { + // cppcheck-suppress shadowFunction - TODO: fix this const std::string arrayIndex("array-index"); const std::string pointerArith("pointer-arith"); diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index c6005eca1ee..245455ad4ca 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -2479,6 +2479,7 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return false; const Token* assignTok = end->next()->astParent(); if (var && assignTok && assignTok->isAssignmentOp() && assignTok->astOperand1() && assignTok->astOperand1()->variable()) { + // cppcheck-suppress shadowFunction - TODO: fix this const Variable* assignVar = assignTok->astOperand1()->variable(); if (assignVar->isPointer() && !assignVar->isConst() && var->typeScope()) { const auto& funcMap = var->typeScope()->functionMap; diff --git a/lib/checkio.cpp b/lib/checkio.cpp index 2d52f9ad4e1..a3805dc7ac3 100644 --- a/lib/checkio.cpp +++ b/lib/checkio.cpp @@ -19,6 +19,7 @@ //--------------------------------------------------------------------------- #include "checkio.h" +#include "astutils.h" #include "errortypes.h" #include "library.h" #include "mathlib.h" @@ -152,6 +153,10 @@ void CheckIO::checkFileUsage() for (const Scope * scope : symbolDatabase->functionScopes) { int indent = 0; for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { + if (Token::Match(tok, "%name% (") && isUnevaluated(tok)) { + tok = tok->linkAt(1); + continue; + } if (tok->str() == "{") indent++; else if (tok->str() == "}") { diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index d99a28d030d..bbfcd4e247f 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -263,7 +263,7 @@ static bool isLocalVarNoAutoDealloc(const Token *varTok, const bool isCpp) // non-pod variable if (isCpp) { // Possibly automatically deallocated memory - if (isAutoDealloc(var) && Token::Match(varTok, "%var% = new")) + if (isAutoDealloc(var) && Token::Match(varTok, "%var% [=({] new")) return false; if (!var->isPointer() && !var->typeStartToken()->isStandardType()) return false; @@ -328,7 +328,8 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, // check each token { - const Token * nextTok = checkTokenInsideExpression(tok, varInfo); + const bool isInit = Token::Match(tok, "%var% {|(") && tok->variable() && tok == tok->variable()->nameToken(); + const Token * nextTok = isInit ? nullptr : checkTokenInsideExpression(tok, varInfo); if (nextTok) { tok = nextTok; continue; @@ -337,26 +338,33 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, // look for end of statement - if (!Token::Match(tok, "[;{},]") || Token::Match(tok->next(), "[;{},]")) + const bool isInit = Token::Match(tok->tokAt(-1), "%var% {|(") && tok->tokAt(-1)->variable() && tok->tokAt(-1) == tok->tokAt(-1)->variable()->nameToken(); + if ((!Token::Match(tok, "[;{},]") || Token::Match(tok->next(), "[;{},]")) && !(isInit && tok->str() == "(")) continue; if (Token::Match(tok, "[;{},] %var% [")) continue; - tok = tok->next(); + if (!isInit) + tok = tok->next(); if (!tok || tok == endToken) break; + if (Token::Match(tok, "%name% (") && isUnevaluated(tok)) { + tok = tok->linkAt(1); + continue; + } + if (Token::Match(tok, "const %type%")) tok = tok->tokAt(2); - while (tok->str() == "(") + while (!isInit && tok->str() == "(") tok = tok->next(); while (tok->isUnaryOp("*") && tok->astOperand1()->isUnaryOp("&")) tok = tok->astOperand1()->astOperand1(); // parse statement, skip to last member - const Token *varTok = tok; + const Token* varTok = isInit ? tok->tokAt(-1) : tok; while (Token::Match(varTok, "%name% ::|. %name% !!(")) varTok = varTok->tokAt(2); @@ -381,7 +389,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, }; // assignment.. - if (const Token* const tokAssignOp = isAssignment(varTok)) { + if (const Token* const tokAssignOp = isInit ? varTok : isAssignment(varTok)) { if (Token::simpleMatch(tokAssignOp->astOperand1(), ".")) continue; diff --git a/lib/checknullpointer.cpp b/lib/checknullpointer.cpp index 86a6542f0b6..cf70f7293eb 100644 --- a/lib/checknullpointer.cpp +++ b/lib/checknullpointer.cpp @@ -514,6 +514,7 @@ static std::string arithmeticTypeString(const Token *tok) void CheckNullPointer::pointerArithmeticError(const Token* tok, const ValueFlow::Value *value, bool inconclusive) { + // cppcheck-suppress shadowFunction - TODO: fix this std::string arithmetic = arithmeticTypeString(tok); std::string errmsg; if (tok && tok->str()[0] == '-') { @@ -532,6 +533,7 @@ void CheckNullPointer::pointerArithmeticError(const Token* tok, const ValueFlow: void CheckNullPointer::redundantConditionWarning(const Token* tok, const ValueFlow::Value *value, const Token *condition, bool inconclusive) { + // cppcheck-suppress shadowFunction - TODO: fix this std::string arithmetic = arithmeticTypeString(tok); std::string errmsg; if (tok && tok->str()[0] == '-') { diff --git a/lib/checkunusedfunctions.cpp b/lib/checkunusedfunctions.cpp index 24151998ce1..eb789fdf727 100644 --- a/lib/checkunusedfunctions.cpp +++ b/lib/checkunusedfunctions.cpp @@ -459,8 +459,10 @@ void CheckUnusedFunctions::analyseWholeProgram(const Settings &settings, ErrorLo } if (std::strcmp(e2->Name(),"functiondecl") == 0) { const char* lineNumber = e2->Attribute("lineNumber"); - if (lineNumber) + if (lineNumber) { + // cppcheck-suppress templateInstantiation - TODO: fix this - see #11631 decls[functionName] = Location(sourcefile, strToInt(lineNumber)); + } } } } diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index f70bca725a2..2d772662d69 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1062,6 +1062,7 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer) const std::time_t maxTime = mSettings.checksMaxTime > 0 ? std::time(nullptr) + mSettings.checksMaxTime : 0; // call all "runChecks" in all registered Check classes + // cppcheck-suppress shadowFunction - TODO: fix this for (Check *check : Check::instances()) { if (Settings::terminated()) return; @@ -1104,6 +1105,7 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer) mAnalyzerInformation.setFileInfo("ctu", fi1->toString()); } + // cppcheck-suppress shadowFunction - TODO: fix this for (const Check *check : Check::instances()) { if (doUnusedFunctionOnly && dynamic_cast(check) == nullptr) continue; @@ -1735,6 +1737,7 @@ bool CppCheck::analyseWholeProgram() ctu.nestedCalls.insert(ctu.nestedCalls.end(), fi2->nestedCalls.cbegin(), fi2->nestedCalls.cend()); } } + // cppcheck-suppress shadowFunction - TODO: fix this for (Check *check : Check::instances()) errors |= check->analyseWholeProgram(&ctu, mFileInfo, mSettings, *this); // TODO: ctu return errors && (mExitCode > 0); @@ -1785,6 +1788,7 @@ void CppCheck::analyseWholeProgram(const std::string &buildDir, const std::list< ctuFileInfo.loadFromXml(e); continue; } + // cppcheck-suppress shadowFunction - TODO: fix this for (const Check *check : Check::instances()) { if (checkClassAttr == check->name()) fileInfoList.push_back(check->loadFileInfoFromXml(e)); @@ -1796,6 +1800,7 @@ void CppCheck::analyseWholeProgram(const std::string &buildDir, const std::list< CTU::maxCtuDepth = mSettings.maxCtuDepth; // Analyse the tokens + // cppcheck-suppress shadowFunction - TODO: fix this for (Check *check : Check::instances()) check->analyseWholeProgram(&ctuFileInfo, fileInfoList, mSettings, *this); diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index a8a18b6c365..09aeba231a6 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -169,6 +169,7 @@ ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) severity = attr ? severityFromString(attr) : Severity::none; attr = errmsg->Attribute("cwe"); + // cppcheck-suppress templateInstantiation - TODO: fix this - see #11631 cwe.id = attr ? strToInt(attr) : 0; attr = errmsg->Attribute("inconclusive"); diff --git a/lib/fwdanalysis.cpp b/lib/fwdanalysis.cpp index 1dd44759d02..e43b5dfec08 100644 --- a/lib/fwdanalysis.cpp +++ b/lib/fwdanalysis.cpp @@ -317,6 +317,7 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * // ({ .. }) if (hasGccCompoundStatement(parent->astParent()->astOperand2())) return Result(Result::Type::BAILOUT); + // cppcheck-suppress shadowFunction - TODO: fix this const bool reassign = isSameExpression(mCpp, false, expr, parent, mLibrary, false, false, nullptr); if (reassign) return Result(Result::Type::WRITE, parent->astParent()); diff --git a/lib/library.cpp b/lib/library.cpp index 9ce1a4f36aa..f1058c7883a 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -392,8 +392,10 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) if (end) mExecutableBlocks[extension].setEnd(end); const char * offset = blocknode->Attribute("offset"); - if (offset) + if (offset) { + // cppcheck-suppress templateInstantiation - TODO: fix this - see #11631 mExecutableBlocks[extension].setOffset(strToInt(offset)); + } } else @@ -706,6 +708,7 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co mReturnValueType[name] = type; if (const char *container = functionnode->Attribute("container")) mReturnValueContainer[name] = strToInt(container); + // cppcheck-suppress shadowFunction - TODO: fix this if (const char *unknownReturnValues = functionnode->Attribute("unknownValues")) { if (std::strcmp(unknownReturnValues, "all") == 0) { std::vector values{LLONG_MIN, LLONG_MAX}; @@ -1305,6 +1308,7 @@ bool Library::isCompliantValidationExpression(const char* p) error |= (*(p + 1) == '-'); } else if (*p == ':') { + // cppcheck-suppress bitwiseOnBoolean - TODO: fix this error |= range | (*(p + 1) == '.'); range = true; has_dot = false; @@ -1319,6 +1323,7 @@ bool Library::isCompliantValidationExpression(const char* p) has_dot = false; has_E = false; } else if (*p == '.') { + // cppcheck-suppress bitwiseOnBoolean - TODO: fix this error |= has_dot | (!std::isdigit(*(p + 1))); has_dot = true; } else if (*p == 'E' || *p == 'e') { diff --git a/lib/programmemory.cpp b/lib/programmemory.cpp index fccfe409958..aaef10b6ad2 100644 --- a/lib/programmemory.cpp +++ b/lib/programmemory.cpp @@ -208,6 +208,8 @@ static bool evaluateCondition(const std::string& op, evaluateCondition(op, r, condition->astOperand2(), pm, settings)) { return true; } + if (!pm.hasValue(condition->exprId())) + return false; } MathLib::bigint result = 0; bool error = false; diff --git a/lib/settings.cpp b/lib/settings.cpp index eb1e9847c96..54c96f672f2 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -40,40 +40,87 @@ Settings::Settings() setCheckLevelNormal(); } -// TODO: report error when the config is invalid -void Settings::loadCppcheckCfg() +std::string Settings::loadCppcheckCfg() { - std::string fileName = Path::getPathFromFilename(exename) + "cppcheck.cfg"; + static const std::string cfgFilename = "cppcheck.cfg"; + std::string fileName; #ifdef FILESDIR - if (Path::isFile(FILESDIR "/cppcheck.cfg")) - fileName = FILESDIR "/cppcheck.cfg"; + if (Path::isFile(Path::join(FILESDIR, cfgFilename))) + fileName = Path::join(FILESDIR, cfgFilename); #endif + // cppcheck-suppress knownConditionTrueFalse + if (fileName.empty()) { + fileName = Path::getPathFromFilename(exename) + cfgFilename; + if (!Path::isFile(fileName)) + return ""; + } std::ifstream fin(fileName); if (!fin.is_open()) - return; + return "could not open file"; picojson::value json; fin >> json; - if (!picojson::get_last_error().empty()) - return; - picojson::object obj = json.get(); - if (obj.count("productName") && obj["productName"].is()) - cppcheckCfgProductName = obj["productName"].get(); - if (obj.count("about") && obj["about"].is()) - cppcheckCfgAbout = obj["about"].get(); - if (obj.count("addons") && obj["addons"].is()) { - for (const picojson::value &v : obj["addons"].get()) { - const std::string &s = v.get(); - if (!Path::isAbsolute(s)) - addons.emplace(Path::getPathFromFilename(fileName) + s); - else - addons.emplace(s); + { + const std::string& lastErr = picojson::get_last_error(); + if (!lastErr.empty()) + return "not a valid JSON - " + lastErr; + } + const picojson::object& obj = json.get(); + { + const picojson::object::const_iterator it = obj.find("productName"); + if (it != obj.cend()) { + const auto& v = it->second; + if (!v.is()) + return "'productName' is not a string"; + cppcheckCfgProductName = v.get(); } } - if (obj.count("suppressions") && obj["suppressions"].is()) { - for (const picojson::value &v : obj["suppressions"].get()) - nomsg.addSuppressionLine(v.get()); + { + const picojson::object::const_iterator it = obj.find("about"); + if (it != obj.cend()) { + const auto& v = it->second; + if (!v.is()) + return "'about' is not a string"; + cppcheckCfgAbout = v.get(); + } + } + { + const picojson::object::const_iterator it = obj.find("addons"); + if (it != obj.cend()) { + const auto& entry = it->second; + if (!entry.is()) + return "'addons' is not an array"; + for (const picojson::value &v : entry.get()) + { + if (!v.is()) + return "'addons' array entry is not a string"; + const std::string &s = v.get(); + if (!Path::isAbsolute(s)) + addons.emplace(Path::join(Path::getPathFromFilename(fileName), s)); + else + addons.emplace(s); + } + } } + { + const picojson::object::const_iterator it = obj.find("suppressions"); + if (it != obj.cend()) { + const auto& entry = it->second; + if (!entry.is()) + return "'suppressions' is not an array"; + for (const picojson::value &v : entry.get()) + { + if (!v.is()) + return "'suppressions' array entry is not a string"; + const std::string &s = v.get(); + const std::string err = nomsg.addSuppressionLine(s); + if (!err.empty()) + return "could not parse suppression '" + s + "' - " + err; + } + } + } + + return ""; } std::string Settings::parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups) diff --git a/lib/settings.h b/lib/settings.h index 96317f8b4a7..6833d8569aa 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -99,7 +99,7 @@ class CPPCHECKLIB WARN_UNUSED Settings { public: Settings(); - void loadCppcheckCfg(); + std::string loadCppcheckCfg(); /** @brief addons, either filename of python/json file or json data */ std::unordered_set addons; diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index ac053cfe52d..a3278f3f191 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -1571,16 +1571,101 @@ static std::string getIncompleteNameID(const Token* tok) return result + tok->expressionString(); } +namespace { + struct ExprIdKey { + std::string parentOp; + nonneg int operand1; + nonneg int operand2; + bool operator<(const ExprIdKey& k) const { + return std::tie(parentOp, operand1, operand2) < std::tie(k.parentOp, k.operand1, k.operand2); + } + }; + using ExprIdMap = std::map; + void setParentExprId(Token* tok, bool cpp, ExprIdMap& exprIdMap, nonneg int &id) { + if (!tok->astParent() || tok->astParent()->isControlFlowKeyword()) + return; + const Token* op1 = tok->astParent()->astOperand1(); + if (op1 && op1->exprId() == 0) + return; + const Token* op2 = tok->astParent()->astOperand2(); + if (op2 && op2->exprId() == 0) + return; + + if (tok->astParent()->isExpandedMacro() || Token::Match(tok->astParent(), "++|--")) { + tok->astParent()->exprId(id); + ++id; + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + return; + } + + ExprIdKey key; + key.parentOp = tok->astParent()->str(); + key.operand1 = op1 ? op1->exprId() : 0; + key.operand2 = op2 ? op2->exprId() : 0; + + if (tok->astParent()->isCast() && tok->astParent()->str() == "(") { + const Token* typeStartToken; + const Token* typeEndToken; + if (tok->astParent()->astOperand2()) { + typeStartToken = tok->astParent()->astOperand1(); + typeEndToken = tok; + } else { + typeStartToken = tok->astParent()->next(); + typeEndToken = tok->astParent()->link(); + } + std::string type; + for (const Token* t = typeStartToken; t != typeEndToken; t = t->next()) { + type += " " + t->str(); + } + key.parentOp += type; + } + + for (const auto& ref: followAllReferences(op1)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand1 = ref.token->exprId(); + break; + } + } + for (const auto& ref: followAllReferences(op2)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand2 = ref.token->exprId(); + break; + } + } + + if (key.operand1 > key.operand2 && key.operand2 && + Token::Match(tok->astParent(), "%or%|%oror%|+|*|&|&&|^|==|!=")) { + // In C++ the order of operands of + might matter + if (!cpp || + key.parentOp != "+" || + !tok->astParent()->valueType() || + tok->astParent()->valueType()->isIntegral() || + tok->astParent()->valueType()->isFloat() || + tok->astParent()->valueType()->pointer > 0) + std::swap(key.operand1, key.operand2); + } + + const auto it = exprIdMap.find(key); + if (it == exprIdMap.end()) { + exprIdMap[key] = id; + tok->astParent()->exprId(id); + ++id; + } else { + tok->astParent()->exprId(it->second); + } + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + } +} + void SymbolDatabase::createSymbolDatabaseExprIds() { - nonneg int base = 0; // Find highest varId - for (const Variable *var : mVariableList) { - if (!var) - continue; - base = std::max(base, var->declarationId()); + nonneg int maximumVarId = 0; + for (const Token* tok = mTokenizer.list.front(); tok; tok = tok->next()) { + if (tok->varId() > maximumVarId) + maximumVarId = tok->varId(); } - nonneg int id = base + 1; + nonneg int id = maximumVarId + 1; // Find incomplete vars that are used in constant context std::unordered_map unknownConstantIds; const Token* inConstExpr = nullptr; @@ -1611,7 +1696,6 @@ void SymbolDatabase::createSymbolDatabaseExprIds() }); for (const Scope * scope : exprScopes) { - nonneg int thisId = 0; std::unordered_map> exprs; std::unordered_map unknownIds; @@ -1638,62 +1722,65 @@ void SymbolDatabase::createSymbolDatabaseExprIds() } // Assign IDs + ExprIdMap exprIdMap; + std::map baseIds; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { if (tok->varId() > 0) { tok->exprId(tok->varId()); - } else if (isExpression(tok)) { - exprs[tok->str()].push_back(tok); - tok->exprId(id++); + if (tok->astParent() && tok->astParent()->exprId() == 0) + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); + } else if (tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->tokType() == Token::Type::eBracket) + continue; + if (tok->astParent()->isAssignmentOp()) + continue; + if (tok->isControlFlowKeyword()) + continue; - if (id == std::numeric_limits::max() / 4) { - throw InternalError(nullptr, "Ran out of expression ids.", InternalError::INTERNAL); - } - } else if (isCPP() && Token::simpleMatch(tok, "this")) { - if (thisId == 0) - thisId = id++; - tok->exprId(thisId); - } - } - - // Apply CSE - for (const auto& p:exprs) { - const std::vector& tokens = p.second; - const std::size_t N = tokens.size(); - for (std::size_t i = 0; i < N; ++i) { - Token* const tok1 = tokens[i]; - for (std::size_t j = i + 1; j < N; ++j) { - Token* const tok2 = tokens[j]; - if (tok1->exprId() == tok2->exprId()) - continue; - if (!isSameExpression(isCPP(), true, tok1, tok2, mSettings.library, false, false)) - continue; - nonneg int const cid = std::min(tok1->exprId(), tok2->exprId()); - tok1->exprId(cid); - tok2->exprId(cid); + if (Token::Match(tok, "%name% <") && tok->next()->link()) { + tok->exprId(id); + ++id; + } else { + const auto it = baseIds.find(tok->str()); + if (it != baseIds.end()) { + tok->exprId(it->second); + } else { + baseIds[tok->str()] = id; + tok->exprId(id); + ++id; + } } + + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); } } - // Mark expressions that are unique - std::unordered_map exprMap; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { - if (tok->exprId() == 0) - continue; - auto p = exprMap.emplace(tok->exprId(), tok); - // Already exists so set it to null - if (!p.second) { - p.first->second = nullptr; + if (tok->varId() == 0 && tok->exprId() > 0 && tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->isNumber() || tok->isKeyword() || Token::Match(tok->astParent(), ".|::") || + (Token::simpleMatch(tok->astParent(), "(") && precedes(tok, tok->astParent()))) + tok->exprId(0); } } - for (const auto& p : exprMap) { - if (!p.second) + } + + // Mark expressions that are unique + std::vector> uniqueExprId(id); + for (Token* tok = const_cast(mTokenizer.list.front()); tok; tok = tok->next()) { + const auto id2 = tok->exprId(); + if (id2 == 0 || id2 <= maximumVarId) + continue; + uniqueExprId[id2].first = tok; + uniqueExprId[id2].second++; + } + for (const auto& p : uniqueExprId) { + if (!p.first || p.second != 1) + continue; + if (p.first->variable()) { + const Variable* var = p.first->variable(); + if (var->nameToken() != p.first) continue; - if (p.second->variable()) { - const Variable* var = p.second->variable(); - if (var->nameToken() != p.second) - continue; - } - p.second->setUniqueExprId(); } + p.first->setUniqueExprId(); } } @@ -2364,6 +2451,7 @@ const Type* Variable::smartPointerType() const while (Token::Match(typeTok, "%name%|::")) typeTok = typeTok->next(); if (Token::Match(typeTok, "< %name% >")) { + // cppcheck-suppress shadowFunction - TODO: fix this const Scope* scope = typeTok->scope(); const Type* ptrType{}; while (scope && !ptrType) { diff --git a/lib/templatesimplifier.cpp b/lib/templatesimplifier.cpp index e1af536d3e8..e156e4be792 100644 --- a/lib/templatesimplifier.cpp +++ b/lib/templatesimplifier.cpp @@ -2946,6 +2946,7 @@ bool TemplateSimplifier::matchSpecialization( startToken = startToken->previous(); if (!Token::simpleMatch(startToken, "template <")) continue; + // cppcheck-suppress shadowFunction - TODO: fix this std::vector templateParameters; getTemplateParametersInDeclaration(startToken->tokAt(2), templateParameters); diff --git a/lib/token.cpp b/lib/token.cpp index be6d7730e97..6565b01e1d6 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -316,11 +316,13 @@ void Token::swapWithNext() std::swap(mFlags, mNext->mFlags); std::swap(mImpl, mNext->mImpl); if (mImpl->mTemplateSimplifierPointers) + // cppcheck-suppress shadowFunction - TODO: fix this for (auto *templateSimplifierPointer : *mImpl->mTemplateSimplifierPointers) { templateSimplifierPointer->token(this); } if (mNext->mImpl->mTemplateSimplifierPointers) + // cppcheck-suppress shadowFunction - TODO: fix this for (auto *templateSimplifierPointer : *mNext->mImpl->mTemplateSimplifierPointers) { templateSimplifierPointer->token(mNext); } @@ -341,6 +343,7 @@ void Token::takeData(Token *fromToken) mImpl = fromToken->mImpl; fromToken->mImpl = nullptr; if (mImpl->mTemplateSimplifierPointers) + // cppcheck-suppress shadowFunction - TODO: fix this for (auto *templateSimplifierPointer : *mImpl->mTemplateSimplifierPointers) { templateSimplifierPointer->token(this); } @@ -636,6 +639,7 @@ bool Token::simpleMatch(const Token *tok, const char pattern[], size_t pattern_l return false; // shortcut const char *current = pattern; const char *end = pattern + pattern_len; + // cppcheck-suppress shadowFunction - TODO: fix this const char *next = static_cast(std::memchr(pattern, ' ', pattern_len)); if (!next) next = end; @@ -781,6 +785,7 @@ nonneg int Token::getStrLength(const Token *tok) assert(tok->mTokType == eString); int len = 0; + // cppcheck-suppress shadowFunction - TODO: fix this const std::string str(getStringLiteral(tok->str())); std::string::const_iterator it = str.cbegin(); const std::string::const_iterator end = str.cend(); @@ -808,6 +813,7 @@ nonneg int Token::getStrArraySize(const Token *tok) { assert(tok != nullptr); assert(tok->tokType() == eString); + // cppcheck-suppress shadowFunction - TODO: fix this const std::string str(getStringLiteral(tok->str())); int sizeofstring = 1; for (int i = 0; i < (int)str.size(); i++) { @@ -1110,6 +1116,7 @@ Token* Token::insertToken(const std::string& tokenStr, const std::string& origin tok1 = tok1->previous()->findOpeningBracket(); if (tok1 && Token::Match(tok1->tokAt(-3), "%name% :: %name%")) { tok1 = tok1->tokAt(-2); + // cppcheck-suppress shadowFunction - TODO: fix this std::string scope = tok1->strAt(-1); while (Token::Match(tok1->tokAt(-2), ":: %name%")) { scope = tok1->strAt(-3) + " :: " + scope; @@ -1262,7 +1269,10 @@ std::string Token::stringify(const stringifyOptions& options) const } else if (options.exprid && mImpl->mExprId != 0) { ret += '@'; ret += (options.idtype ? "expr" : ""); - ret += std::to_string(mImpl->mExprId); + if ((mImpl->mExprId & (1U << efIsUnique)) != 0) + ret += "UNIQUE"; + else + ret += std::to_string(mImpl->mExprId); } return ret; @@ -1285,6 +1295,7 @@ std::string Token::stringifyList(const stringifyOptions& options, const std::vec std::string ret; unsigned int lineNumber = mImpl->mLineNumber - (options.linenumbers ? 1U : 0U); + // cppcheck-suppress shadowFunction - TODO: fix this unsigned int fileIndex = options.files ? ~0U : mImpl->mFileIndex; std::map lineNumbers; for (const Token *tok = this; tok != end; tok = tok->next()) { @@ -1701,6 +1712,7 @@ void Token::printValueFlow(bool xml, std::ostream &out) const { std::string outs; + // cppcheck-suppress shadowFunction int fileIndex = -1; int line = 0; if (xml) @@ -1708,6 +1720,7 @@ void Token::printValueFlow(bool xml, std::ostream &out) const else outs += "\n\n##Value flow\n"; for (const Token *tok = this; tok; tok = tok->next()) { + // cppcheck-suppress shadowFunction - TODO: fix this const auto* const values = tok->mImpl->mValues; if (!values) continue; @@ -2246,6 +2259,7 @@ void Token::assignProgressValues(Token *tok) void Token::assignIndexes() { + // cppcheck-suppress shadowFunction - TODO: fix this int index = (mPrevious ? mPrevious->mImpl->mIndex : 0) + 1; for (Token *tok = this; tok; tok = tok->next()) tok->mImpl->mIndex = index++; @@ -2283,9 +2297,11 @@ const ::Type* Token::typeOf(const Token* tok, const Token** typeTok) if (tok->function()) return tok->function()->retType; if (Token::simpleMatch(tok, "return")) { + // cppcheck-suppress shadowFunction - TODO: fix this const Scope *scope = tok->scope(); if (!scope) return nullptr; + // cppcheck-suppress shadowFunction - TODO: fix this const Function *function = scope->function; if (!function) return nullptr; @@ -2395,15 +2411,18 @@ std::pair Token::typeDecl(const Token* tok, bool poi return {var->typeStartToken(), var->typeEndToken()->next()}; } if (Token::simpleMatch(tok, "return")) { + // cppcheck-suppress shadowFunction - TODO: fix this const Scope* scope = tok->scope(); if (!scope) return {}; + // cppcheck-suppress shadowFunction - TODO: fix this const Function* function = scope->function; if (!function) return {}; return { function->retDef, function->returnDefEnd() }; } if (tok->previous() && tok->previous()->function()) { + // cppcheck-suppress shadowFunction - TODO: fix this const Function *function = tok->previous()->function(); return {function->retDef, function->returnDefEnd()}; } diff --git a/lib/token.h b/lib/token.h index 9d35486349c..cf7b30cd5ad 100644 --- a/lib/token.h +++ b/lib/token.h @@ -907,10 +907,7 @@ class CPPCHECKLIB Token { bool isUniqueExprId() const { - if (mImpl->mExprId > 0) { - return (mImpl->mExprId & (1 << efIsUnique)) != 0; - } - return false; + return (mImpl->mExprId & (1 << efIsUnique)) != 0; } /** diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 7358a088fe6..2d82e02cdad 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -1947,7 +1947,7 @@ void Tokenizer::simplifyTypedefCpp() tok2->str(typeStart->str()); // restore qualification if it was removed - if (typeStart->str() == "struct" || structRemoved) { + if (Token::Match(typeStart, "class|struct|union") || structRemoved) { if (structRemoved) tok2 = tok2->previous(); diff --git a/man/manual.md b/man/manual.md index b0a9a6088f4..819760925a7 100644 --- a/man/manual.md +++ b/man/manual.md @@ -321,7 +321,7 @@ If you can generate a compile database, then it is possible to import that in Cp In Linux you can use for instance the `bear` (build ear) utility to generate a compile database from arbitrary build tools: - bear make + bear -- make # Preprocessor Settings @@ -954,11 +954,65 @@ This allows you to create and manage multiple configuration files for different # Library configuration -When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc, Cppcheck doesn't know how the external functions behave. Cppcheck then fails to detect various problems such as memory leaks, buffer overflows, possible null pointer dereferences, etc. But this can be fixed with configuration files. - -Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, std.cfg, is always loaded by cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you upload it to us. - -## Using your own custom .cfg file +When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc, Cppcheck has no information about functions, types, or macros contained in those libraries. Cppcheck then fails to detect various problems in the code, or might even abort the analysis. But this can be fixed by using the appropriate configuration files. + +Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, std.cfg, is always loaded by cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you supplied it to the cppcheck project. + +## Using a .cfg file + +To use a .cfg file shipped with cppcheck, pass the `--library=` option. The table below shows the currently existing libraries: +| .cfg file | Library | Comment | +| ------------- | ------------- | ------------- | +| avr.cfg | | +| bento4.cfg | [Bento4](http://www.bento4.com/) | +| boost.cfg | [Boost](http://www.boost.org/)| +| bsd.cfg | [BSD](https://www.freebsd.org/) | +| cairo.cfg | [cairo](https://www.cairographics.org/) | +| cppcheck-lib.cfg | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of the Cppcheck code base +| cppunit.cfg | [CppUnit](https://sourceforge.net/projects/cppunit/) | +| dpdk.cfg | | +| embedded_sql.cfg | | +| emscripten.cfg | | +| ginac.cfg | | +| gnu.cfg | [GNU](https://www.gnu.org/) | +| googletest.cfg | [GoogleTest](https://github.com/google/googletest) | +| gtk.cfg | [GTK](https://www.gtk.org/) | +| icu.cfg | | +| kde.cfg | [KDE](https://kde.org/) | +| libcerror.cfg | [libcerror](https://github.com/libyal/libcerror) | +| libcurl.cfg | [libcurl](https://curl.se/libcurl/) | +| libsigc++.cfg | [libsigc++](https://github.com/libsigcplusplus/libsigcplusplus) | +| lua.cfg | | +| mfc.cfg | [MFC](https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications) | +| microsoft_atl.cfg | [ATL](https://learn.microsoft.com/en-us/cpp/atl/active-template-library-atl-concepts) | +| microsoft_sal.cfg | [SAL annotations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations) | +| microsoft_unittest.cfg | [CppUnitTest](https://learn.microsoft.com/en-us/visualstudio/test/microsoft-visualstudio-testtools-cppunittestframework-api-reference) | +| motif.cfg | | +| nspr.cfg | | +| ntl.cfg | | +| opencv2.cfg | [OpenCV](https://opencv.org/) | +| opengl.cfg | [OpenGL](https://opengl.org/) | +| openmp.cfg | [OpenMP](https://www.openmp.org/) | +| openssl.cfg | [OpenSSL](https://www.openssl.org/) | +| pcre.cfg | [PCRE](https://pcre.org/) | +| posix.cfg | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | +| python.cfg | | +| qt.cfg | [Qt](https://doc.qt.io/qt.html) | +| ruby.cfg | | +| sdl.cfg | | +| sfml.cfg | | +| sqlite3.cfg | [SQLite](https://www.sqlite.org/) | +| std.cfg | C/C++ standard library | Loaded by default +| tinyxml2.cfg | [TinyXML-2](https://github.com/leethomason/tinyxml2) | +| vcl.cfg | | +| windows.cfg | [Win32 API](https://learn.microsoft.com/en-us/windows/win32/) | +| wxsqlite3.cfg | | +| wxsvg.cfg | | +| wxwidgets.cfg | [wxWidgets](https://www.wxwidgets.org/) | +| zephyr.cfg | | +| zlib.cfg | [zlib](https://www.zlib.net) | + +## Creating a custom .cfg file You can create and use your own .cfg files for your projects. Use `--check-library` to get hints about what you should configure. diff --git a/releasenotes.txt b/releasenotes.txt index 6144ba8695a..5e9b0d6db0e 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -32,3 +32,4 @@ Other: - You can suppress all warnings where macro is used using "-macro" - fixed CMake build with UBSAN and GCC - Added command-line options "--fsigned-char" and "--funsigned-char" to control the signess of the "char" type. This overrides previously specified "--platform" options and is overrides by following ones. +- An error is now reported when the "cppcheck.cfg" is invalid. The CLI version will also exit with a failure in that case. diff --git a/test/cfg/qt.cpp b/test/cfg/qt.cpp index 63ccf3b1d72..46310e32171 100644 --- a/test/cfg/qt.cpp +++ b/test/cfg/qt.cpp @@ -321,6 +321,13 @@ void duplicateExpression_QString_Compare(QString style) //#8723 {} } +void QVector_uninit() +{ + int i; + // cppcheck-suppress [uninitvar, unreadVariable] + QVector v(i); +} + void QStack1(QStack intStackArg) { for (int i = 0; i <= intStackArg.size(); ++i) { diff --git a/test/cfg/std.cpp b/test/cfg/std.cpp index b0926a0eed5..030f46f3f9c 100644 --- a/test/cfg/std.cpp +++ b/test/cfg/std.cpp @@ -602,6 +602,15 @@ void nullPointer_localtime_s(const std::time_t *restrict time, struct tm *restri (void)std::localtime_s(time, NULL); (void)std::localtime_s(time, result); } + +void memleak_localtime_s(const std::time_t *restrict time, struct tm *restrict result) +{ + const time_t t = time(0); + const struct tm* const now = new tm(); + if (localtime_s(now, &t) == 0) + std::cout << now->tm_mday << std::endl; + // cppcheck-suppress memleak +} #endif // __STDC_LIB_EXT1__ size_t nullPointer_strftime(char *s, size_t max, const char *fmt, const struct tm *p) @@ -1043,8 +1052,8 @@ void uninitvar_isxdigit(void) void uninitvar_proj(void) { double d; + // cppcheck-suppress uninitvar const std::complex dc(d,d); - // TODO cppcheck-suppress uninitvar (void)std::proj(dc); } diff --git a/test/cli/test-other.py b/test/cli/test-other.py index 47b2f4c1d0b..0cbed9e7e07 100644 --- a/test/cli/test-other.py +++ b/test/cli/test-other.py @@ -152,51 +152,13 @@ def test_progress_j(tmpdir): assert stdout == "Checking {} ...\n".format(test_file) assert stderr == "" - -@pytest.mark.timeout(10) -def test_slow_array_many_floats(tmpdir): - # 11649 - # cppcheck valueflow takes a long time when an array has many floats - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const float f[] = {\n") - for i in range(20000): - f.write(' 13.6f,\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_array_many_strings(tmpdir): - # 11901 - # cppcheck valueflow takes a long time when analyzing a file with many strings - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const char *strings[] = {\n") - for i in range(20000): - f.write(' "abc",\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_long_line(tmpdir): - # simplecpp #314 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("#define A() static const int a[] = {\\\n") - for i in range(5000): - f.write(" -123, 456, -789,\\\n") - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - @pytest.mark.timeout(10) def test_slow_initlist_varchanged(tmpdir): # #12235 filename = os.path.join(tmpdir, 'hang.cpp') with open(filename, 'wt') as f: f.write(r""" - struct T {\n + struct T { int* q; int nx, ny; }; @@ -230,78 +192,6 @@ def test_slow_initlist_varchanged(tmpdir): }""") cppcheck([filename]) # should not take more than ~1 second - -@pytest.mark.timeout(60) -def test_slow_large_constant_expression(tmpdir): - # 12182 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write(""" -#define FLAG1 0 -#define FLAG2 0 -#define FLAG3 0 -#define FLAG4 0 -#define FLAG5 0 -#define FLAG6 0 -#define FLAG7 0 -#define FLAG8 0 -#define FLAG9 0 -#define FLAG10 0 -#define FLAG11 0 -#define FLAG12 0 -#define FLAG13 0 -#define FLAG14 0 -#define FLAG15 0 -#define FLAG16 0 -#define FLAG17 0 -#define FLAG18 0 -#define FLAG19 0 -#define FLAG20 0 -#define FLAG21 0 -#define FLAG22 0 -#define FLAG23 0 -#define FLAG24 0 - -#define maxval(x, y) ((x) > (y) ? (x) : (y)) - -#define E_SAMPLE_SIZE maxval( FLAG1, \ - maxval( FLAG2, \ - maxval( FLAG3, \ - maxval( FLAG4, \ - maxval( FLAG5, \ - maxval( FLAG6, \ - maxval( FLAG7, \ - maxval( FLAG8, \ - maxval( FLAG9, \ - maxval( FLAG10, \ - maxval( FLAG11, \ - maxval( FLAG12, \ - maxval( FLAG13, \ - maxval( FLAG14, \ - FLAG15 )))))))))))))) - -#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ - maxval( sizeof(st), \ - maxval( FLAG16, \ - maxval( FLAG17, \ - maxval( FLAG18, \ - maxval( FLAG19, \ - maxval( FLAG20, \ - maxval( FLAG21, \ - maxval( FLAG22, \ - maxval( FLAG23, \ - FLAG24 )))))))))) - -typedef struct { - int n; -} st; - -x = SAMPLE_SIZE; - """) - - cppcheck([filename]) - - def test_execute_addon_failure(tmpdir): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: @@ -892,7 +782,7 @@ def test_valueflow_debug(tmpdir): ##file {} 2: void f2 ( ) 3: {{ -4: int i@var1 ; i@var1 =@expr1073741828 0 ; +4: int i@var1 ; i@var1 = 0 ; 5: }} ##file {} @@ -901,7 +791,7 @@ def test_valueflow_debug(tmpdir): 2: 3: void f1 ( ) 4: {{ -5: int i@var2 ; i@var2 =@expr1073741829 0 ; +5: int i@var2 ; i@var2 = 0 ; 6: }} ##file {} @@ -911,7 +801,7 @@ def test_valueflow_debug(tmpdir): 3: 4: void f ( ) 5: {{ -6: int i@var3 ; i@var3 =@expr1073741830 0 ; +6: int i@var3 ; i@var3 = 0 ; 7: }} diff --git a/test/cli/test-performance.py b/test/cli/test-performance.py new file mode 100644 index 00000000000..305994869c5 --- /dev/null +++ b/test/cli/test-performance.py @@ -0,0 +1,149 @@ + +# python -m pytest test-other.py + +import os +import sys +import pytest + +from testutils import cppcheck, assert_cppcheck + + + +@pytest.mark.timeout(10) +def test_slow_array_many_floats(tmpdir): + # 11649 + # cppcheck valueflow takes a long time when an array has many floats + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const float f[] = {\n") + for i in range(20000): + f.write(' 13.6f,\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_array_many_strings(tmpdir): + # 11901 + # cppcheck valueflow takes a long time when analyzing a file with many strings + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const char *strings[] = {\n") + for i in range(20000): + f.write(' "abc",\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_long_line(tmpdir): + # simplecpp #314 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("#define A() static const int a[] = {\\\n") + for i in range(5000): + f.write(" -123, 456, -789,\\\n") + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(60) +def test_slow_large_constant_expression(tmpdir): + # 12182 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +#define FLAG1 0 +#define FLAG2 0 +#define FLAG3 0 +#define FLAG4 0 +#define FLAG5 0 +#define FLAG6 0 +#define FLAG7 0 +#define FLAG8 0 +#define FLAG9 0 +#define FLAG10 0 +#define FLAG11 0 +#define FLAG12 0 +#define FLAG13 0 +#define FLAG14 0 +#define FLAG15 0 +#define FLAG16 0 +#define FLAG17 0 +#define FLAG18 0 +#define FLAG19 0 +#define FLAG20 0 +#define FLAG21 0 +#define FLAG22 0 +#define FLAG23 0 +#define FLAG24 0 + +#define maxval(x, y) ((x) > (y) ? (x) : (y)) + +#define E_SAMPLE_SIZE maxval( FLAG1, \ + maxval( FLAG2, \ + maxval( FLAG3, \ + maxval( FLAG4, \ + maxval( FLAG5, \ + maxval( FLAG6, \ + maxval( FLAG7, \ + maxval( FLAG8, \ + maxval( FLAG9, \ + maxval( FLAG10, \ + maxval( FLAG11, \ + maxval( FLAG12, \ + maxval( FLAG13, \ + maxval( FLAG14, \ + FLAG15 )))))))))))))) + +#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ + maxval( sizeof(st), \ + maxval( FLAG16, \ + maxval( FLAG17, \ + maxval( FLAG18, \ + maxval( FLAG19, \ + maxval( FLAG20, \ + maxval( FLAG21, \ + maxval( FLAG22, \ + maxval( FLAG23, \ + FLAG24 )))))))))) + +typedef struct { + int n; +} st; + +x = SAMPLE_SIZE; + """) + + cppcheck([filename]) + +@pytest.mark.timeout(10) +def test_slow_exprid(tmpdir): + # 11885 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +int foo(int a, int b) +{ +#define A0(a, b) ((a) + (b)) +#define A1(a, b) ((a) > (b)) ? A0((a) - (b), (b)) : A0((b) - (a), (a)) +#define A2(a, b) ((a) > (b)) ? A1((a) - (b), (b)) : A1((b) - (a), (a)) +#define A3(a, b) ((a) > (b)) ? A2((a) - (b), (b)) : A2((b) - (a), (a)) +#define A4(a, b) ((a) > (b)) ? A3((a) - (b), (b)) : A3((b) - (a), (a)) +#define A5(a, b) ((a) > (b)) ? A4((a) - (b), (b)) : A4((b) - (a), (a)) +#define A6(a, b) ((a) > (b)) ? A5((a) - (b), (b)) : A5((b) - (a), (a)) +#define A7(a, b) ((a) > (b)) ? A6((a) - (b), (b)) : A6((b) - (a), (a)) +#define A8(a, b) ((a) > (b)) ? A7((a) - (b), (b)) : A7((b) - (a), (a)) +#define A9(a, b) ((a) > (b)) ? A8((a) - (b), (b)) : A8((b) - (a), (a)) +#define A10(a, b) ((a) > (b)) ? A9((a) - (b), (b)) : A9((b) - (a), (a)) +#define A11(a, b) ((a) > (b)) ? A10((a) - (b), (b)) : A10((b) - (a), (a)) + return A8(a, b); +} + """) + + my_env = os.environ.copy() + my_env["DISABLE_VALUEFLOW"] = "1" + cppcheck([filename], env=my_env) + + + diff --git a/test/fixture.cpp b/test/fixture.cpp index a99a80a0d93..32863bbc637 100644 --- a/test/fixture.cpp +++ b/test/fixture.cpp @@ -34,9 +34,6 @@ #include "xml.h" -std::ostringstream errout; -std::ostringstream output; - /** * TestRegistry **/ diff --git a/test/fixture.h b/test/fixture.h index 1224e388dc1..dc1c62dcfe8 100644 --- a/test/fixture.h +++ b/test/fixture.h @@ -236,10 +236,16 @@ class TestFixture : public ErrorLogger { return SettingsBuilder(*this, std::move(settings)); } -public: + // TODO: make sure the output has been consumed in the test + std::ostringstream errout; + std::ostringstream output; + +private: void reportOut(const std::string &outmsg, Color c = Color::Reset) override; void reportErr(const ErrorMessage &msg) override; void run(const std::string &str); + +public: static void printHelp(); const std::string classname; @@ -248,12 +254,6 @@ class TestFixture : public ErrorLogger { static std::size_t runTests(const options& args); }; -// TODO: fix these -// NOLINTNEXTLINE(readability-redundant-declaration) -extern std::ostringstream errout; -// NOLINTNEXTLINE(readability-redundant-declaration) -extern std::ostringstream output; - // TODO: most asserts do not actually assert i.e. do not return #define TEST_CASE( NAME ) do { if (prepareTest(#NAME)) { setVerbose(false); NAME(); teardownTest(); } } while (false) #define ASSERT( CONDITION ) if (!assert_(__FILE__, __LINE__, (CONDITION))) return diff --git a/test/redirect.h b/test/redirect.h index 092e2c9bdd9..10e101c6101 100644 --- a/test/redirect.h +++ b/test/redirect.h @@ -19,12 +19,9 @@ #include #include +#include #include -// NOLINTNEXTLINE(readability-redundant-declaration) - TODO: fix this -extern std::ostringstream errout; -// NOLINTNEXTLINE(readability-redundant-declaration) - TODO: fix this -extern std::ostringstream output; /** * @brief Utility class for capturing cout and cerr to ostringstream buffers * for later use. Uses RAII to stop redirection when the object goes out of @@ -47,34 +44,35 @@ class RedirectOutputError { } /** Revert cout and cerr behaviour */ - ~RedirectOutputError() { + ~RedirectOutputError() noexcept(false) { std::cout.rdbuf(_oldCout); // restore cout's original streambuf std::cerr.rdbuf(_oldCerr); // restore cerrs's original streambuf - errout << _err.str(); - output << _out.str(); + { + const std::string s = _out.str(); + if (!s.empty()) + throw std::runtime_error("unconsumed stdout: " + s); // cppcheck-suppress exceptThrowInDestructor - FP #11031 + } + + { + const std::string s = _err.str(); + if (!s.empty()) + throw std::runtime_error("consumed stderr: " + s); + } } - /** Return what would be printed to cout. See also clearOutput() */ - std::string getOutput() const { - return _out.str(); - } - - /** Normally called after getOutput() to prevent same text to be returned - twice. */ - void clearOutput() { + /** Return what would be printed to cout. */ + std::string getOutput() { + std::string s = _out.str(); _out.str(""); + return s; } - /** Return what would be printed to cerr. See also clearErrout() */ - std::string getErrout() const { - return _err.str(); - } - - /** Normally called after getErrout() to prevent same text to be returned - twice. */ - void clearErrout() { + /** Return what would be printed to cerr. */ + std::string getErrout() { + std::string s = _err.str(); _err.str(""); + return s; } private: @@ -112,9 +110,7 @@ class SuppressOutput { #define REDIRECT RedirectOutputError redir #define GET_REDIRECT_OUTPUT redir.getOutput() -#define CLEAR_REDIRECT_OUTPUT redir.clearOutput() #define GET_REDIRECT_ERROUT redir.getErrout() -#define CLEAR_REDIRECT_ERROUT redir.clearErrout() #define SUPPRESS SuppressOutput supprout diff --git a/test/testclass.cpp b/test/testclass.cpp index 04a3f2f700c..df4a5b531aa 100644 --- a/test/testclass.cpp +++ b/test/testclass.cpp @@ -6621,6 +6621,17 @@ class TestClass : public TestFixture { ASSERT_EQUALS("[test.cpp:3]: (style, inconclusive) Technically the member function 'S::f' can be const.\n" "[test.cpp:8]: (performance, inconclusive) Technically the member function 'S::g' can be static (but you may consider moving to unnamed namespace).\n", errout.str()); + + checkConst("class C {\n" // #11653 + "public:\n" + " void f(bool b) const;\n" + "};\n" + "void C::f(bool b) const {\n" + " if (b)\n" + " f(false);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:3]: (performance, inconclusive) Technically the member function 'C::f' can be static (but you may consider moving to unnamed namespace).\n", + errout.str()); } void const90() { // #11637 diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index dc54eba807c..9c493b8aa34 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -22,6 +22,7 @@ #include "cppcheckexecutor.h" #include "errortypes.h" #include "helpers.h" +#include "path.h" #include "platform.h" #include "redirect.h" #include "settings.h" @@ -101,7 +102,6 @@ class TestCmdlineParser : public TestFixture { void teardownTestInternal() override { logger->destroy(); - // TODO: verify that the redirect output is empty } // add overload so the enums can be compared without a cast @@ -119,9 +119,13 @@ class TestCmdlineParser : public TestFixture { void run() override { TEST_CASE(nooptions); TEST_CASE(helpshort); + TEST_CASE(helpshortExclusive); TEST_CASE(helplong); + TEST_CASE(helplongExclusive); TEST_CASE(version); TEST_CASE(versionWithCfg); + TEST_CASE(versionExclusive); + TEST_CASE(versionWithInvalidCfg); TEST_CASE(onefile); TEST_CASE(onepath); TEST_CASE(optionwithoutfile); @@ -255,6 +259,7 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(xmlverunknown); TEST_CASE(xmlverinvalid); TEST_CASE(doc); + TEST_CASE(docExclusive); TEST_CASE(showtimeFile); TEST_CASE(showtimeFileTotal); TEST_CASE(showtimeTop5); @@ -264,6 +269,9 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(showtimeEmpty); TEST_CASE(showtimeInvalid); TEST_CASE(errorlist); + TEST_CASE(errorlistWithCfg); + TEST_CASE(errorlistExclusive); + TEST_CASE(errorlistWithInvalidCfg); TEST_CASE(ignorepathsnopath); #if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING) TEST_CASE(exceptionhandling); @@ -355,6 +363,8 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(cppcheckBuildDirExistent); TEST_CASE(cppcheckBuildDirNonExistent); TEST_CASE(cppcheckBuildDirEmpty); + + TEST_CASE(invalidCppcheckCfg); } void nooptions() { @@ -362,7 +372,6 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(1, argv)); ASSERT(startsWith(logger->str(), "Cppcheck - A tool for static C/C++ code analysis")); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void helpshort() { @@ -370,7 +379,13 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-h"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT(startsWith(logger->str(), "Cppcheck - A tool for static C/C++ code analysis")); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + void helpshortExclusive() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--library=missing", "-h"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(3, argv)); + ASSERT(startsWith(logger->str(), "Cppcheck - A tool for static C/C++ code analysis")); } void helplong() { @@ -378,7 +393,13 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--help"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT(startsWith(logger->str(), "Cppcheck - A tool for static C/C++ code analysis")); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + void helplongExclusive() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--library=missing", "--help"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(3, argv)); + ASSERT(startsWith(logger->str(), "Cppcheck - A tool for static C/C++ code analysis")); } void version() { @@ -386,12 +407,11 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--version"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT_EQUALS("Cppcheck 2.13 dev\n", logger->str()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void versionWithCfg() { REDIRECT; - ScopedFile file("cppcheck.cfg", + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), "{\n" "\"productName\": \"The Product\"" "}\n"); @@ -399,10 +419,25 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); // TODO: somehow the config is not loaded on some systems (void)logger->str(); //ASSERT_EQUALS("The Product\n", logger->str()); // TODO: include version? - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } - // TODO: test extraVersion + // TODO: test --version with extraVersion + + void versionExclusive() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--library=missing", "--version"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS("Cppcheck 2.13 dev\n", logger->str()); + } + + void versionWithInvalidCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "--version"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + } void onefile() { REDIRECT; @@ -410,7 +445,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(2, argv)); ASSERT_EQUALS(1, (int)parser->getPathNames().size()); ASSERT_EQUALS("file.cpp", parser->getPathNames().at(0)); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void onepath() { @@ -419,7 +453,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(2, argv)); ASSERT_EQUALS(1, (int)parser->getPathNames().size()); ASSERT_EQUALS("src", parser->getPathNames().at(0)); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void optionwithoutfile() { @@ -428,7 +461,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); ASSERT_EQUALS(0, (int)parser->getPathNames().size()); ASSERT_EQUALS("cppcheck: error: no C or C++ source files found.\n", logger->str()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void verboseshort() { @@ -437,7 +469,6 @@ class TestCmdlineParser : public TestFixture { settings->verbose = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->verbose); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void verboselong() { @@ -446,7 +477,6 @@ class TestCmdlineParser : public TestFixture { settings->verbose = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->verbose); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void debugSimplified() { @@ -455,7 +485,6 @@ class TestCmdlineParser : public TestFixture { settings->debugSimplified = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->debugSimplified); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void debugwarnings() { @@ -464,7 +493,6 @@ class TestCmdlineParser : public TestFixture { settings->debugwarnings = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->debugwarnings); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void forceshort() { @@ -473,7 +501,6 @@ class TestCmdlineParser : public TestFixture { settings->force = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->force); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void forcelong() { @@ -482,7 +509,6 @@ class TestCmdlineParser : public TestFixture { settings->force = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->force); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void relativePaths1() { @@ -491,7 +517,6 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-rp", "file.cpp"}; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->relativePaths); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void relativePaths2() { @@ -500,7 +525,6 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--relative-paths", "file.cpp"}; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->relativePaths); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void relativePaths3() { @@ -513,7 +537,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(2, settings->basePaths.size()); ASSERT_EQUALS("C:/foo", settings->basePaths[0]); ASSERT_EQUALS("C:/bar", settings->basePaths[1]); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void relativePaths4() { @@ -527,7 +550,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(2, settings->basePaths.size()); ASSERT_EQUALS("C:/foo", settings->basePaths[0]); ASSERT_EQUALS("C:/bar", settings->basePaths[1]); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void quietshort() { @@ -536,7 +558,6 @@ class TestCmdlineParser : public TestFixture { settings->quiet = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->quiet); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void quietlong() { @@ -545,7 +566,6 @@ class TestCmdlineParser : public TestFixture { settings->quiet = false; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(true, settings->quiet); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void defines_noarg() { @@ -578,7 +598,6 @@ class TestCmdlineParser : public TestFixture { settings->userDefines.clear(); ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS("_WIN32=1", settings->userDefines); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void defines2() { @@ -587,7 +606,6 @@ class TestCmdlineParser : public TestFixture { settings->userDefines.clear(); ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); ASSERT_EQUALS("_WIN32=1;NODEBUG=1", settings->userDefines); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void defines3() { @@ -596,7 +614,6 @@ class TestCmdlineParser : public TestFixture { settings->userDefines.clear(); ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); ASSERT_EQUALS("DEBUG=1", settings->userDefines); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void defines4() { @@ -605,7 +622,6 @@ class TestCmdlineParser : public TestFixture { settings->userDefines.clear(); ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS("DEBUG=", settings->userDefines); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void enforceLanguage1() { @@ -1574,7 +1590,13 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--doc"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT(startsWith(logger->str(), "## ")); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + void docExclusive() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--library=missing", "--doc"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(3, argv)); + ASSERT(startsWith(logger->str(), "## ")); } void showtimeSummary() { @@ -1653,11 +1675,39 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--errorlist"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT_EQUALS("", logger->str()); // empty since it is logged via ErrorLogger - ASSERT(startsWith(GET_REDIRECT_OUTPUT, "\n")); + const std::string errout_s = GET_REDIRECT_OUTPUT; + ASSERT(startsWith(errout_s, ErrorMessage::getXMLHeader(""))); + ASSERT(endsWith(errout_s, "\n")); + } + + void errorlistWithCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + R"({"productName": "The Product"}\n)"); + const char * const argv[] = {"cppcheck", "--errorlist"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("", logger->str()); // empty since it is logged via ErrorLogger + ASSERT(startsWith(GET_REDIRECT_OUTPUT, ErrorMessage::getXMLHeader("The Product"))); + } + + void errorlistExclusive() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--library=missing", "--errorlist"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS("", logger->str()); // empty since it is logged via ErrorLogger + const std::string errout_s = GET_REDIRECT_OUTPUT; + ASSERT(startsWith(errout_s, ErrorMessage::getXMLHeader(""))); + ASSERT(endsWith(errout_s, "\n")); } - // TODO: test --errorlist with product name + void errorlistWithInvalidCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "--errorlist"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + } void ignorepathsnopath() { REDIRECT; @@ -1918,7 +1968,6 @@ class TestCmdlineParser : public TestFixture { settings->typedefMaxTime = 0; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(12, settings->typedefMaxTime); - ASSERT_EQUALS("", logger->str()); } void typedefMaxTimeInvalid() { @@ -1994,7 +2043,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(1, settings->addons.size()); ASSERT_EQUALS("misra", *settings->addons.cbegin()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void addonMissing() { @@ -2005,7 +2053,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(1, settings->addons.size()); ASSERT_EQUALS("misra2", *settings->addons.cbegin()); ASSERT_EQUALS("Did not find addon misra2.py\n", logger->str()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void signedChar() { @@ -2014,7 +2061,6 @@ class TestCmdlineParser : public TestFixture { settings->platform.defaultSign = '\0'; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS('s', settings->platform.defaultSign); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void signedChar2() { @@ -2023,7 +2069,6 @@ class TestCmdlineParser : public TestFixture { settings->platform.defaultSign = '\0'; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); ASSERT_EQUALS('s', settings->platform.defaultSign); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void unsignedChar() { @@ -2032,7 +2077,6 @@ class TestCmdlineParser : public TestFixture { settings->platform.defaultSign = '\0'; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS('u', settings->platform.defaultSign); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void unsignedChar2() { @@ -2041,7 +2085,6 @@ class TestCmdlineParser : public TestFixture { settings->platform.defaultSign = '\0'; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); ASSERT_EQUALS('u', settings->platform.defaultSign); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void signedCharUnsignedChar() { @@ -2050,7 +2093,6 @@ class TestCmdlineParser : public TestFixture { settings->platform.defaultSign = '\0'; ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); ASSERT_EQUALS('u', settings->platform.defaultSign); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } #ifdef HAVE_RULES @@ -2124,7 +2166,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); ASSERT_EQUALS(1, settings->libraries.size()); ASSERT_EQUALS("posix", *settings->libraries.cbegin()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void libraryMissing() { @@ -2135,7 +2176,6 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(1, settings->libraries.size()); ASSERT_EQUALS("posix2", *settings->libraries.cbegin()); ASSERT_EQUALS("cppcheck: Failed to load library configuration file 'posix2'. File not found\n", logger->str()); - ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } void ignorepaths1() { @@ -2263,6 +2303,15 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); ASSERT_EQUALS("cppcheck: error: Directory '' specified by --cppcheck-build-dir argument has to be existent.\n", logger->str()); } + + void invalidCppcheckCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "test.cpp"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + } }; REGISTER_TEST(TestCmdlineParser) diff --git a/test/testio.cpp b/test/testio.cpp index 8f89f0a7bd5..4f01d6b6fc7 100644 --- a/test/testio.cpp +++ b/test/testio.cpp @@ -549,6 +549,14 @@ class TestIO : public TestFixture { " fclose(f[1]);\n" "}"); ASSERT_EQUALS("", errout.str()); + + // #12236 + check("void f() {\n" + " FILE* f = fopen(\"abc\", \"r\");\n" + " decltype(fclose(f)) y;\n" + " y = fclose(f);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); } void fileIOwithoutPositioning() { diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 700292c9c42..ed6983ec3ea 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -88,6 +88,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(assign22); // #9139 TEST_CASE(assign23); TEST_CASE(assign24); // #7440 + TEST_CASE(assign25); TEST_CASE(isAutoDealloc); @@ -110,6 +111,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(deallocuse10); TEST_CASE(deallocuse11); // #8302 TEST_CASE(deallocuse12); + TEST_CASE(deallocuse13); TEST_CASE(doublefree1); TEST_CASE(doublefree2); @@ -126,6 +128,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(doublefree13); // #11008 TEST_CASE(doublefree14); // #9708 TEST_CASE(doublefree15); + TEST_CASE(doublefree16); // exit TEST_CASE(exit1); @@ -571,6 +574,30 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("[test.c:5]: (error) Memory leak: p\n", errout.str()); } + void assign25() { + check("void f() {\n" // #11796 + " int* p{ new int };\n" + " int* q(new int);\n" + "}", true); + ASSERT_EQUALS("[test.cpp:4]: (error) Memory leak: p\n" + "[test.cpp:4]: (error) Memory leak: q\n", + errout.str()); + + check("struct S : B {\n" // #12239 + " void f();\n" + " void g();\n" + "};\n" + "void S::f() {\n" + " FD* fd(new FD(this));\n" + " fd->exec();\n" + "}\n" + "void S::g() {\n" + " FD* fd{ new FD(this) };\n" + " fd->exec();\n" + "}\n", true); + ASSERT_EQUALS("", errout.str()); + } + void isAutoDealloc() { check("void f() {\n" " char *p = new char[100];" @@ -883,6 +910,20 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout.str()); } + void deallocuse13() { + check("void f() {\n" // #9695 + " auto* a = new int[2];\n" + " delete[] a;\n" + " a[1] = 0;\n" + " auto* b = static_cast(malloc(8));\n" + " free(b);\n" + " b[1] = 0;\n" + "}\n", true); + ASSERT_EQUALS("[test.cpp:4]: (error) Dereferencing 'a' after it is deallocated / released\n" + "[test.cpp:7]: (error) Dereferencing 'b' after it is deallocated / released\n", + errout.str()); + } + void doublefree1() { // #3895 check("void f(char *p) {\n" " if (x)\n" @@ -1541,6 +1582,15 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout.str()); } + void doublefree16() { // #12236 + check("void f() {\n" + " FILE* f = fopen(\"abc\", \"r\");\n" + " decltype(fclose(f)) y;\n" + " y = fclose(f);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + void exit1() { check("void f() {\n" " char *p = malloc(10);\n" diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index b1757014e71..e23df116241 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -990,7 +990,7 @@ class TestNullPointer : public TestFixture { check("struct S { struct T { char c; } *p; };\n" // #6541 "char f(S* s) { return s->p ? 'a' : s->p->c; }\n"); - ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: p.\n", + ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: s->p.\n", errout.str()); } diff --git a/test/testsettings.cpp b/test/testsettings.cpp index 03cc6e7a671..c3598300837 100644 --- a/test/testsettings.cpp +++ b/test/testsettings.cpp @@ -20,6 +20,7 @@ #include "errortypes.h" #include "settings.h" #include "fixture.h" +#include "helpers.h" class TestSettings : public TestFixture { public: @@ -28,6 +29,7 @@ class TestSettings : public TestFixture { private: void run() override { TEST_CASE(simpleEnableGroup); + TEST_CASE(loadCppcheckCfg); } void simpleEnableGroup() const { @@ -85,6 +87,128 @@ class TestSettings : public TestFixture { ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); } + + void loadCppcheckCfg() + { + { + Settings s; + ASSERT_EQUALS("", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + "{}\n"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + "{\n"); + ASSERT_EQUALS("not a valid JSON - syntax error at line 1 near: ", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": ""}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("", s.cppcheckCfgProductName); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": "product"}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("product", s.cppcheckCfgProductName); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": 1}\n)"); + ASSERT_EQUALS("'productName' is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": ""}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("", s.cppcheckCfgAbout); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": "about"}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("about", s.cppcheckCfgAbout); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": 1}\n)"); + ASSERT_EQUALS("'about' is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": []}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(0, s.addons.size()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": 1}\n)"); + ASSERT_EQUALS("'addons' is not an array", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": ["addon"]}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(1, s.addons.size()); + ASSERT_EQUALS("addon", *s.addons.cbegin()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": [1]}\n)"); + ASSERT_EQUALS("'addons' array entry is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": []}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(0, s.addons.size()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": 1}\n)"); + ASSERT_EQUALS("'suppressions' is not an array", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": ["id"]}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(1, s.nomsg.getSuppressions().size()); + ASSERT_EQUALS("id", s.nomsg.getSuppressions().cbegin()->errorId); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": [""]}\n)"); + ASSERT_EQUALS("could not parse suppression '' - Failed to add suppression. No id.", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": [1]}\n)"); + ASSERT_EQUALS("'suppressions' array entry is not a string", s.loadCppcheckCfg()); + } + + // TODO: test with FILESDIR + } }; REGISTER_TEST(TestSettings) diff --git a/test/testsimplifytypedef.cpp b/test/testsimplifytypedef.cpp index d321026a384..8d3f0bb883c 100644 --- a/test/testsimplifytypedef.cpp +++ b/test/testsimplifytypedef.cpp @@ -3454,6 +3454,33 @@ class TestSimplifyTypedef : public TestFixture { " g(sizeof(struct N::S));\n" "}\n"; ASSERT_EQUALS("namespace N { struct S { } ; } void g ( int ) ; void f ( ) { g ( sizeof ( struct N :: S ) ) ; }", tok(code)); + + code = "namespace N {\n" + " typedef class C {} C;\n" + "}\n" + "void g(int);\n" + "void f() {\n" + " g(sizeof(class N::C));\n" + "}\n"; + ASSERT_EQUALS("namespace N { class C { } ; } void g ( int ) ; void f ( ) { g ( sizeof ( class N :: C ) ) ; }", tok(code)); + + code = "namespace N {\n" + " typedef union U {} U;\n" + "}\n" + "void g(int);\n" + "void f() {\n" + " g(sizeof(union N::U));\n" + "}\n"; + ASSERT_EQUALS("namespace N { union U { } ; } void g ( int ) ; void f ( ) { g ( sizeof ( union N :: U ) ) ; }", tok(code)); + + code = "namespace N {\n" + " typedef enum E {} E;\n" + "}\n" + "void g(int);\n" + "void f() {\n" + " g(sizeof(enum N::E));\n" + "}\n"; + ASSERT_EQUALS("namespace N { enum E { } ; } void g ( int ) ; void f ( ) { g ( sizeof ( enum N :: E ) ) ; }", tok(code)); } void simplifyTypedefFunction1() { diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index d363f32454f..a0965e3a82f 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +#include "errortypes.h" +#include "fixture.h" +#include "helpers.h" #include "library.h" #include "platform.h" #include "settings.h" +#include "sourcelocation.h" #include "symboldatabase.h" -#include "errortypes.h" -#include "fixture.h" -#include "helpers.h" #include "token.h" #include "tokenize.h" #include "tokenlist.h" @@ -76,12 +77,66 @@ class TestSymbolDatabase : public TestFixture { typetok = nullptr; } - const static SymbolDatabase* getSymbolDB_inner(Tokenizer& tokenizer, const char* code, const char* filename) { + const SymbolDatabase* getSymbolDB_inner(Tokenizer& tokenizer, const char* code, const char* filename) { errout.str(""); std::istringstream istr(code); return tokenizer.tokenize(istr, filename) ? tokenizer.getSymbolDatabase() : nullptr; } + static const Token* findToken(Tokenizer& tokenizer, const std::string& expr, unsigned int exprline) + { + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (Token::simpleMatch(tok, expr.c_str(), expr.size()) && tok->linenr() == exprline) { + return tok; + } + } + return nullptr; + } + + static std::string asExprIdString(const Token* tok) + { + return tok->expressionString() + "@" + std::to_string(tok->exprId()); + } + + std::string testExprIdEqual(const char code[], + const std::string& expr1, + unsigned int exprline1, + const std::string& expr2, + unsigned int exprline2, + SourceLocation loc = SourceLocation::current()) + { + Tokenizer tokenizer(&settings1, this); + std::istringstream istr(code); + ASSERT_LOC(tokenizer.tokenize(istr, "test.cpp"), loc.file_name(), loc.line()); + + const Token* tok1 = findToken(tokenizer, expr1, exprline1); + const Token* tok2 = findToken(tokenizer, expr2, exprline2); + + if (!tok1) + return "'" + expr1 + "'" + " not found"; + if (!tok2) + return "'" + expr2 + "'" + " not found"; + if (tok1->exprId() == 0) + return asExprIdString(tok1) + " has not exprId"; + if (tok2->exprId() == 0) + return asExprIdString(tok2) + " has not exprId"; + + if (tok1->exprId() != tok2->exprId()) + return asExprIdString(tok1) + " != " + asExprIdString(tok2); + + return ""; + } + bool testExprIdNotEqual(const char code[], + const std::string& expr1, + unsigned int exprline1, + const std::string& expr2, + unsigned int exprline2, + SourceLocation loc = SourceLocation::current()) + { + std::string result = testExprIdEqual(code, expr1, exprline1, expr2, exprline2, loc); + return !result.empty(); + } + static const Scope *findFunctionScopeByToken(const SymbolDatabase * db, const Token *tok) { std::list::const_iterator scope; @@ -530,6 +585,7 @@ class TestSymbolDatabase : public TestFixture { TEST_CASE(unionWithConstructor); TEST_CASE(incomplete_type); // #9255 (infinite recursion) + TEST_CASE(exprIds); } void array() { @@ -10000,6 +10056,214 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS("", errout.str()); } + + void exprIds() + { + const char* code; + + code = "int f(int a) {\n" + " return a +\n" + " a;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "a", 2U, "a", 3U)); + + code = "int f(int a, int b) {\n" + " return a +\n" + " b;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "a", 2U, "b", 3U)); + + code = "int f(int a) {\n" + " int x = a++;\n" + " int y = a++;\n" + " return x + a;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "++", 2U, "++", 3U)); + + code = "int f(int a) {\n" + " int x = a;\n" + " return x + a;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "x", 3U, "a", 3U)); + + code = "int f(int a) {\n" + " int& x = a;\n" + " return x + a;\n" + "}\n"; + TODO_ASSERT_EQUALS("", "x@2 != a@1", testExprIdEqual(code, "x", 3U, "a", 3U)); + + code = "int f(int a) {\n" + " int& x = a;\n" + " return (x + 1) +\n" + " (a + 1);\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+", 3U, "+", 4U)); + + code = "int& g(int& x) { return x; }\n" + "int f(int a) {\n" + " return (g(a) + 1) +\n" + " (a + 1);\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+", 3U, "+", 4U)); + + code = "int f(int a, int b) {\n" + " int x = (b-a)-a;\n" + " int y = (b-a)-a;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- a ;", 2U, "- a ;", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a )", 2U, "- a )", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "- a )", 2U, "- a ;", 3U)); + + code = "int f(int a, int b) {\n" + " int x = a-(b-a);\n" + " int y = a-(b-a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- ( b", 2U, "- ( b", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a )", 2U, "- a )", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "- a )", 2U, "- ( b", 3U)); + + code = "void f(int a, int b) {\n" + " int x = (b+a)+a;\n" + " int y = a+(b+a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ;", 2U, "+ ( b", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ) +", 2U, "+ a ) ;", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "+ a ;", 2U, "+ a )", 3U)); + + code = "void f(int a, int b) {\n" + " int x = (b+a)+a;\n" + " int y = a+(a+b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ;", 2U, "+ ( a", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ) +", 2U, "+ b ) ;", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "+ a ;", 2U, "+ b", 3U)); + + code = "struct A { int x; };\n" + "void f(A a, int b) {\n" + " int x = (b-a.x)-a.x;\n" + " int y = (b-a.x)-a.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x ;", 3U, "- a . x ;", 4U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x )", 3U, "- a . x )", 4U)); + + code = "struct A { int x; };\n" + "void f(A a, int b) {\n" + " int x = a.x-(b-a.x);\n" + " int y = a.x-(b-a.x);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- ( b", 3U, "- ( b", 4U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x )", 3U, "- a . x )", 4U)); + + code = "struct A { int x; };\n" + "void f(A a) {\n" + " int x = a.x;\n" + " int y = a.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x", 3U, ". x", 4U)); + + code = "struct A { int x; };\n" + "void f(A a, A b) {\n" + " int x = a.x;\n" + " int y = b.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". x", 3U, ". x", 4U)); + + code = "struct A { int y; };\n" + "struct B { A x; }\n" + "void f(B a) {\n" + " int x = a.x.y;\n" + " int y = a.x.y;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . y", 4U, ". x . y", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". y", 4U, ". y", 5U)); + + code = "struct A { int y; };\n" + "struct B { A x; }\n" + "void f(B a, B b) {\n" + " int x = a.x.y;\n" + " int y = b.x.y;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". x . y", 4U, ". x . y", 5U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". y", 4U, ". y", 5U)); + + code = "struct A { int g(); };\n" + "struct B { A x; }\n" + "void f(B a) {\n" + " int x = a.x.g();\n" + " int y = a.x.g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . g ( )", 4U, ". x . g ( )", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". g ( )", 4U, ". g ( )", 5U)); + + code = "struct A { int g(int, int); };\n" + "struct B { A x; }\n" + "void f(B a, int b, int c) {\n" + " int x = a.x.g(b, c);\n" + " int y = a.x.g(b, c);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . g ( b , c )", 4U, ". x . g ( b , c )", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". g ( b , c )", 4U, ". g ( b , c )", 5U)); + + code = "int g();\n" + "void f() {\n" + " int x = g();\n" + " int y = g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g();\n" + " int y = A::g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "int g();\n" + "void f(int a, int b) {\n" + " int x = g(a, b);\n" + " int y = g(a, b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g(a, b);\n" + " int y = A::g(a, b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "int g();\n" + "void f(int a, int b) {\n" + " int x = g(a, b);\n" + " int y = g(b, a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g(a, b);\n" + " int y = A::g(b, a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "(", 3U, "(", 4U)); + } }; REGISTER_TEST(TestSymbolDatabase) diff --git a/test/testtokenlist.cpp b/test/testtokenlist.cpp index 2e764608ee4..0b979aa2df6 100644 --- a/test/testtokenlist.cpp +++ b/test/testtokenlist.cpp @@ -57,7 +57,7 @@ class TestTokenList : public TestFixture { ASSERT_EQUALS("0xF0000000", tokenlist.front()->str()); } - void inc() const { + void inc() { const char code[] = "a++1;1++b;"; errout.str(""); diff --git a/test/testuninitvar.cpp b/test/testuninitvar.cpp index 37b2100f547..ff6490afb85 100644 --- a/test/testuninitvar.cpp +++ b/test/testuninitvar.cpp @@ -7251,6 +7251,24 @@ class TestUninitVar : public TestFixture { "[test.cpp:23]: (error) Uninitialized variable: s.t.j\n" "[test.cpp:27]: (error) Uninitialized variable: s.t.j\n", errout.str()); + + valueFlowUninit("struct S { int x; };\n" + "void f() {\n" + " int i;\n" + " S s(i);\n" + "}\n" + "void g() {\n" + " int i;\n" + " S t{ i };\n" + "}\n" + "void h() {\n" + " int i;\n" + " std::vector v(i);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4]: (error) Uninitialized variable: i\n" + "[test.cpp:8]: (error) Uninitialized variable: i\n" + "[test.cpp:12]: (error) Uninitialized variable: i\n", + errout.str()); } void uninitvar_memberfunction() { diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 6e835f2dfe9..29f46352ad4 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -17,6 +17,7 @@ */ #include "errortypes.h" +#include "helpers.h" #include "platform.h" #include "settings.h" #include "standards.h" @@ -236,6 +237,11 @@ class TestVarID : public TestFixture { TEST_CASE(exprid1); TEST_CASE(exprid2); + TEST_CASE(exprid3); + TEST_CASE(exprid4); + TEST_CASE(exprid5); + TEST_CASE(exprid6); + TEST_CASE(exprid7); TEST_CASE(structuredBindings); } @@ -260,9 +266,11 @@ class TestVarID : public TestFixture { std::string tokenizeExpr_(const char* file, int line, const char code[], const char filename[] = "test.cpp") { errout.str(""); + std::vector files(1, filename); Tokenizer tokenizer(&settings, this); - std::istringstream istr(code); - ASSERT_LOC((tokenizer.tokenize)(istr, filename), file, line); + PreprocessorHelper::preprocess(code, files, tokenizer); + + ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // result.. Token::stringifyOptions options = Token::stringifyOptions::forDebugExprId(); @@ -3859,9 +3867,9 @@ class TestVarID : public TestFixture { "2: int x ; int y ;\n" "3: } ;\n" "4: int f ( A a , A b ) {\n" - "5: int x@5 ; x@5 =@9 a@3 .@10 x@6 +@11 b@4 .@12 x@7 ;\n" - "6: int y@8 ; y@8 =@1073741837 b@4 .@12 x@7 +@11 a@3 .@10 x@6 ;\n" - "7: return x@5 +@1073741841 y@8 +@1073741842 a@3 .@1073741843 y@9 +@1073741844 b@4 .@1073741845 y@10 ;\n" + "5: int x@5 ; x@5 =@UNIQUE a@3 .@11 x@6 +@13 b@4 .@12 x@7 ;\n" + "6: int y@8 ; y@8 =@UNIQUE b@4 .@12 x@7 +@13 a@3 .@11 x@6 ;\n" + "7: return x@5 +@UNIQUE y@8 +@UNIQUE a@3 .@UNIQUE y@9 +@UNIQUE b@4 .@UNIQUE y@10 ;\n" "8: }\n"; ASSERT_EQUALS(expected, actual); @@ -3878,14 +3886,77 @@ class TestVarID : public TestFixture { const char expected[] = "1: struct S { std :: unique_ptr < int > u ; } ;\n" "2: auto f ; f = [ ] ( const S & s ) . std :: unique_ptr < int > {\n" - "3: if (@5 auto p@4 =@1073741830 s@3 .@1073741831 u@5 .@1073741832 get (@1073741833 ) ) {\n" - "4: return std ::@1073741834 make_unique < int > (@1073741835 *@1073741836 p@4 ) ; }\n" + "3: if ( auto p@4 =@UNIQUE s@3 .@UNIQUE u@5 .@UNIQUE get (@UNIQUE ) ) {\n" + "4: return std ::@UNIQUE make_unique < int > (@UNIQUE *@UNIQUE p@4 ) ; }\n" "5: return nullptr ;\n" "6: } ;\n"; ASSERT_EQUALS(expected, actual); } + void exprid3() { + const char code[] = "void f(bool b, int y) {\n" + " if (b && y > 0) {}\n" + " while (b && y > 0) {}\n" + "}\n"; + const char expected[] = "1: void f ( bool b , int y ) {\n" + "2: if ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "3: while ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid4() { + // expanded macro.. + const char code[] = "#define ADD(x,y) x+y\n" + "int f(int a, int b) {\n" + " return ADD(a,b) + ADD(a,b);\n" + "}\n"; + const char expected[] = "2: int f ( int a , int b ) {\n" + "3: return a@1 $+@UNIQUE b@2 +@UNIQUE a@1 $+@UNIQUE b@2 ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid5() { + // references.. + const char code[] = "int foo(int a) {\n" + " int& r = a;\n" + " return (a+a)*(r+r);\n" + "}\n"; + const char expected[] = "1: int foo ( int a ) {\n" + "2: int & r@2 =@UNIQUE a@1 ;\n" + "3: return ( a@1 +@4 a@1 ) *@UNIQUE ( r@2 +@4 r@2 ) ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid6() { + // ++ and -- should have UNIQUE exprid + const char code[] = "void foo(int *a) {\n" + " *a++ = 0;\n" + " if (*a++ == 32) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int * a ) {\n" + "2: *@UNIQUE a@1 ++@UNIQUE = 0 ;\n" + "3: if ( *@UNIQUE a@1 ++@UNIQUE ==@UNIQUE 32 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid7() { + // different casts + const char code[] = "void foo(int a) {\n" + " if ((char)a == (short)a) {}\n" + " if ((char)a == (short)a) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int a ) {\n" + "2: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "3: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + void structuredBindings() { const char code[] = "int foo() { auto [x,y] = xy(); return x+y; }"; ASSERT_EQUALS("1: int foo ( ) { auto [ x@1 , y@2 ] = xy ( ) ; return x@1 + y@2 ; }\n", diff --git a/tools/triage/mainwindow.cpp b/tools/triage/mainwindow.cpp index 22ffe8e536d..27aa47b5b6e 100644 --- a/tools/triage/mainwindow.cpp +++ b/tools/triage/mainwindow.cpp @@ -359,6 +359,7 @@ void MainWindow::findInFilesClicked() ui->inFilesResult->clear(); const QString text = ui->filterEdit->text(); + // cppcheck-suppress shadowFunction - TODO: fix this QStringList filter; if (ui->hFilesFilter->isChecked()) filter.append(hFiles);