/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2024 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include "settings.h" #include "path.h" #include "summaries.h" #include "vfvalue.h" #include #include #include #include #include "json.h" #ifndef _WIN32 #include // for getpid() #else #include // for getpid() #endif std::atomic Settings::mTerminated; const char Settings::SafeChecks::XmlRootName[] = "safe-checks"; const char Settings::SafeChecks::XmlClasses[] = "class-public"; const char Settings::SafeChecks::XmlExternalFunctions[] = "external-functions"; const char Settings::SafeChecks::XmlInternalFunctions[] = "internal-functions"; const char Settings::SafeChecks::XmlExternalVariables[] = "external-variables"; static int getPid() { #ifndef _WIN32 return getpid(); #else return _getpid(); #endif } Settings::Settings() { severity.setEnabled(Severity::error, true); certainty.setEnabled(Certainty::normal, true); setCheckLevel(Settings::CheckLevel::exhaustive); executor = defaultExecutor(); pid = getPid(); } std::string Settings::loadCppcheckCfg(Settings& settings, Suppressions& suppressions) { // TODO: this always needs to be run *after* the Settings has been filled static const std::string cfgFilename = "cppcheck.cfg"; std::string fileName; #ifdef FILESDIR if (Path::isFile(Path::join(FILESDIR, cfgFilename))) fileName = Path::join(FILESDIR, cfgFilename); #endif // cppcheck-suppress knownConditionTrueFalse if (fileName.empty()) { // TODO: make sure that exename is set fileName = Path::getPathFromFilename(settings.exename) + cfgFilename; if (!Path::isFile(fileName)) return ""; } std::ifstream fin(fileName); if (!fin.is_open()) return "could not open file"; picojson::value json; fin >> json; { 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"; settings.cppcheckCfgProductName = 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"; settings.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)) settings.addons.emplace(Path::join(Path::getPathFromFilename(fileName), s)); else settings.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 = suppressions.nomsg.addSuppressionLine(s); if (!err.empty()) return "could not parse suppression '" + s + "' - " + err; } } } { const picojson::object::const_iterator it = obj.find("safety"); if (it != obj.cend()) { const auto& v = it->second; if (!v.is()) return "'safety' is not a bool"; settings.safety = settings.safety || v.get(); } } return ""; } std::pair Settings::getNameAndVersion(const std::string& productName) { if (productName.empty()) return {}; const std::string::size_type pos1 = productName.rfind(' '); if (pos1 == std::string::npos) return {}; if (pos1 + 2 >= productName.length()) return {}; for (auto pos2 = pos1 + 1; pos2 < productName.length(); ++pos2) { const char c = productName[pos2]; const char prev = productName[pos2-1]; if (std::isdigit(c)) continue; if (c == '.' && std::isdigit(prev)) continue; if (c == 's' && pos2 + 1 == productName.length() && std::isdigit(prev)) continue; return {}; } return {productName.substr(0, pos1), productName.substr(pos1+1)}; } std::string Settings::parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups) { // Enable parameters may be comma separated... if (str.find(',') != std::string::npos) { std::string::size_type prevPos = 0; std::string::size_type pos = 0; while ((pos = str.find(',', pos)) != std::string::npos) { if (pos == prevPos) return std::string("--enable parameter is empty"); std::string errmsg(parseEnabled(str.substr(prevPos, pos - prevPos), groups)); if (!errmsg.empty()) return errmsg; ++pos; prevPos = pos; } if (prevPos >= str.length()) return std::string("--enable parameter is empty"); return parseEnabled(str.substr(prevPos), groups); } auto& severity = std::get<0>(groups); auto& checks = std::get<1>(groups); if (str == "all") { // "error" is always enabled and cannot be controlled - so exclude it from "all" SimpleEnableGroup newSeverity; newSeverity.fill(); newSeverity.disable(Severity::error); severity.enable(newSeverity); checks.enable(Checks::missingInclude); checks.enable(Checks::unusedFunction); } else if (str == "warning") { severity.enable(Severity::warning); } else if (str == "style") { severity.enable(Severity::style); } else if (str == "performance") { severity.enable(Severity::performance); } else if (str == "portability") { severity.enable(Severity::portability); } else if (str == "information") { severity.enable(Severity::information); } else if (str == "unusedFunction") { checks.enable(Checks::unusedFunction); } else if (str == "missingInclude") { checks.enable(Checks::missingInclude); } #ifdef CHECK_INTERNAL else if (str == "internal") { checks.enable(Checks::internalCheck); } #endif else { // the actual option is prepending in the applyEnabled() call if (str.empty()) return " parameter is empty"; return " parameter with the unknown name '" + str + "'"; } return ""; } std::string Settings::addEnabled(const std::string &str) { return applyEnabled(str, true); } std::string Settings::removeEnabled(const std::string &str) { return applyEnabled(str, false); } std::string Settings::applyEnabled(const std::string &str, bool enable) { std::tuple, SimpleEnableGroup> groups; std::string errmsg = parseEnabled(str, groups); if (!errmsg.empty()) return (enable ? "--enable" : "--disable") + errmsg; const auto s = std::get<0>(groups); const auto c = std::get<1>(groups); if (enable) { severity.enable(s); checks.enable(c); } else { severity.disable(s); checks.disable(c); } // FIXME: hack to make sure "error" is always enabled severity.enable(Severity::error); return errmsg; } bool Settings::isEnabled(const ValueFlow::Value *value, bool inconclusiveCheck) const { if (!severity.isEnabled(Severity::warning) && (value->condition || value->defaultArg)) return false; if (!certainty.isEnabled(Certainty::inconclusive) && (inconclusiveCheck || value->isInconclusive())) return false; return true; } void Settings::loadSummaries() { Summaries::loadReturn(buildDir, summaryReturn); } void Settings::setCheckLevel(CheckLevel level) { if (level == CheckLevel::normal) { // Checking should finish in reasonable time. checkLevel = level; performanceValueFlowMaxSubFunctionArgs = 8; performanceValueFlowMaxIfCount = 100; } else if (level == CheckLevel::exhaustive) { // Checking can take a little while. ~ 10 times slower than normal analysis is OK. checkLevel = CheckLevel::exhaustive; performanceValueFlowMaxIfCount = -1; performanceValueFlowMaxSubFunctionArgs = 256; } } // TODO: auto generate these tables static const std::set autosarCheckers{ "accessMoved", "argumentSize", "arrayIndexOutOfBounds", "arrayIndexOutOfBoundsCond", "arrayIndexThenCheck", "bufferAccessOutOfBounds", "comparePointers", "constParameter", "cstyleCast", "ctuOneDefinitionViolation", "doubleFree", "duplInheritedMember", "duplicateBreak", "funcArgNamesDifferent", "functionConst", "functionStatic", "invalidContainer", "memleak", "mismatchAllocDealloc", "missingReturn", "negativeIndex", "noExplicitConstructor", "nullPointer", "nullPointerArithmetic", "nullPointerArithmeticRedundantCheck", "nullPointerDefaultArg", "nullPointerRedundantCheck", "objectIndex", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", "pointerOutOfBoundsCond", "preprocessorErrorDirective", "redundantAssignment", "redundantInitialization", "returnDanglingLifetime", "shiftTooManyBits", "sizeofSideEffects", "throwInDestructor", "throwInNoexceptFunction", "uninitData", "uninitMember", "unreachableCode", "unreadVariable", "unsignedLessThanZero", "unusedFunction", "unusedStructMember", "unusedValue", "unusedVariable", "useInitializerList", "variableScope", "virtualCallInConstructor", "zerodiv", "zerodivcond" }; static const std::set certCCheckers{ "IOWithoutPositioning", "autoVariables", "autovarInvalidDeallocation", "bitwiseOnBoolean", "comparePointers", "danglingLifetime", "deallocret", "deallocuse", "doubleFree", "floatConversionOverflow", "invalidFunctionArg", "invalidLengthModifierError", "invalidLifetime", "invalidScanfFormatWidth", "invalidscanf", "leakReturnValNotUsed", "leakUnsafeArgAlloc", "memleak", "memleakOnRealloc", "mismatchAllocDealloc", "missingReturn", "nullPointer", "nullPointerArithmetic", "nullPointerArithmeticRedundantCheck", "nullPointerDefaultArg", "nullPointerRedundantCheck", "preprocessorErrorDirective", "resourceLeak", "sizeofCalculation", "stringLiteralWrite", "uninitStructMember", "uninitdata", "uninitvar", "unknownEvaluationOrder", "useClosedFile", "wrongPrintfScanfArgNum", "wrongPrintfScanfParameterPositionError" }; static const std::set certCppCheckers{ "IOWithoutPositioning", "accessMoved", "comparePointers", "containerOutOfBounds", "ctuOneDefinitionViolation", "deallocMismatch", "deallocThrow", "deallocuse", "doubleFree", "eraseDereference", "exceptThrowInDestructor", "initializerList", "invalidContainer", "lifetime", "memleak", "missingReturn", "nullPointer", "operatorEqToSelf", "sizeofCalculation", "uninitvar", "virtualCallInConstructor", "virtualDestructor" }; static const std::set misrac2012Checkers{ "alwaysFalse", "alwaysTrue", "argumentSize", "autovarInvalidDeallocation", "bufferAccessOutOfBounds", "comparePointers", "compareValueOutOfTypeRangeError", "constPointer", "danglingLifetime", "duplicateBreak", "error", "funcArgNamesDifferent", "incompatibleFileOpen", "invalidFunctionArg", "knownConditionTrueFalse", "leakNoVarFunctionCall", "leakReturnValNotUsed", "memleak", "memleakOnRealloc", "missingReturn", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", "preprocessorErrorDirective", "redundantAssignInSwitch", "redundantAssignment", "redundantCondition", "resourceLeak", "shadowVariable", "sizeofCalculation", "syntaxError", "uninitvar", "unknownEvaluationOrder", "unreadVariable", "unusedLabel", "unusedVariable", "useClosedFile", "writeReadOnlyFile" }; static const std::set misrac2023Checkers{ "alwaysFalse", "alwaysTrue", "argumentSize", "autovarInvalidDeallocation", "bufferAccessOutOfBounds", "comparePointers", "compareValueOutOfTypeRangeError", "constPointer", "danglingLifetime", "duplicateBreak", "error", "funcArgNamesDifferent", "incompatibleFileOpen", "invalidFunctionArg", "knownConditionTrueFalse", "leakNoVarFunctionCall", "leakReturnValNotUsed", "memleak", "memleakOnRealloc", "missingReturn", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", "preprocessorErrorDirective", "redundantAssignInSwitch", "redundantAssignment", "redundantCondition", "resourceLeak", "shadowVariable", "sizeofCalculation", "syntaxError", "uninitvar", "unknownEvaluationOrder", "unreadVariable", "unusedLabel", "unusedVariable", "useClosedFile", "writeReadOnlyFile" }; static const std::set misracpp2008Checkers{ "autoVariables", "comparePointers", "constParameter", "constVariable", "cstyleCast", "ctuOneDefinitionViolation", "danglingLifetime", "duplInheritedMember", "duplicateBreak", "exceptThrowInDestructor", "funcArgNamesDifferent", "functionConst", "functionStatic", "missingReturn", "noExplicit", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", "preprocessorErrorDirective", "redundantAssignment", "redundantInitialization", "returnReference", "returnTempReference", "shadowVariable", "shiftTooManyBits", "sizeofSideEffects", "throwInDestructor", "uninitDerivedMemberVar", "uninitDerivedMemberVarPrivate", "uninitMemberVar", "uninitMemberVarPrivate", "uninitStructMember", "uninitdata", "uninitvar", "unknownEvaluationOrder", "unreachableCode", "unreadVariable", "unsignedLessThanZero", "unusedFunction", "unusedStructMember", "unusedVariable", "varScope", "variableScope", "virtualCallInConstructor" }; bool Settings::isPremiumEnabled(const char id[]) const { if (premiumArgs.find("autosar") != std::string::npos && autosarCheckers.count(id)) return true; if (premiumArgs.find("cert-c-") != std::string::npos && certCCheckers.count(id)) return true; if (premiumArgs.find("cert-c++") != std::string::npos && certCppCheckers.count(id)) return true; if (premiumArgs.find("misra-c-") != std::string::npos && (misrac2012Checkers.count(id) || misrac2023Checkers.count(id))) return true; if (premiumArgs.find("misra-c++") != std::string::npos && misracpp2008Checkers.count(id)) return true; return false; } void Settings::setMisraRuleTexts(const ExecuteCmdFn& executeCommand) { if (premiumArgs.find("--misra-c-20") != std::string::npos) { const auto it = std::find_if(addonInfos.cbegin(), addonInfos.cend(), [](const AddonInfo& a) { return a.name == "premiumaddon.json"; }); if (it != addonInfos.cend()) { std::string arg; if (premiumArgs.find("--misra-c-2023") != std::string::npos) arg = "--misra-c-2023-rule-texts"; else arg = "--misra-c-2012-rule-texts"; std::string output; executeCommand(it->executable, {std::move(arg)}, "2>&1", output); setMisraRuleTexts(output); } } } void Settings::setMisraRuleTexts(const std::string& data) { mMisraRuleTexts.clear(); std::istringstream istr(data); std::string line; while (std::getline(istr, line)) { std::string::size_type pos = line.find(' '); if (pos == std::string::npos) continue; std::string id = line.substr(0, pos); std::string text = line.substr(pos + 1); if (id.empty() || text.empty()) continue; mMisraRuleTexts[id] = std::move(text); } } std::string Settings::getMisraRuleText(const std::string& id, const std::string& text) const { if (id.compare(0, 9, "misra-c20") != 0) return text; const auto it = mMisraRuleTexts.find(id.substr(id.rfind('-') + 1)); return it != mMisraRuleTexts.end() ? it->second : text; } Settings::ExecutorType Settings::defaultExecutor() { static constexpr ExecutorType defaultExecutor = #if defined(HAS_THREADING_MODEL_FORK) ExecutorType::Process; #elif defined(HAS_THREADING_MODEL_THREAD) ExecutorType::Thread; #endif return defaultExecutor; }