Line data Source code
1 : /*
2 : * Cppcheck - A tool for static C/C++ code analysis
3 : * Copyright (C) 2007-2024 Cppcheck team.
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation, either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 : */
18 :
19 : #include "config.h"
20 : #include "settings.h"
21 : #include "path.h"
22 : #include "summaries.h"
23 : #include "vfvalue.h"
24 :
25 : #include <cctype>
26 : #include <fstream>
27 : #include <sstream>
28 : #include <utility>
29 :
30 : #include "json.h"
31 :
32 : #ifndef _WIN32
33 : #include <unistd.h> // for getpid()
34 : #else
35 : #include <process.h> // for getpid()
36 : #endif
37 :
38 :
39 : std::atomic<bool> Settings::mTerminated;
40 :
41 : const char Settings::SafeChecks::XmlRootName[] = "safe-checks";
42 : const char Settings::SafeChecks::XmlClasses[] = "class-public";
43 : const char Settings::SafeChecks::XmlExternalFunctions[] = "external-functions";
44 : const char Settings::SafeChecks::XmlInternalFunctions[] = "internal-functions";
45 : const char Settings::SafeChecks::XmlExternalVariables[] = "external-variables";
46 :
47 4153 : static int getPid()
48 : {
49 : #ifndef _WIN32
50 4153 : return getpid();
51 : #else
52 : return _getpid();
53 : #endif
54 : }
55 :
56 8306 : Settings::Settings()
57 : {
58 4151 : severity.setEnabled(Severity::error, true);
59 4151 : certainty.setEnabled(Certainty::normal, true);
60 4153 : setCheckLevel(Settings::CheckLevel::exhaustive);
61 4153 : executor = defaultExecutor();
62 4153 : pid = getPid();
63 4153 : }
64 :
65 223 : std::string Settings::loadCppcheckCfg(Settings& settings, Suppressions& suppressions)
66 : {
67 : // TODO: this always needs to be run *after* the Settings has been filled
68 223 : static const std::string cfgFilename = "cppcheck.cfg";
69 446 : std::string fileName;
70 : #ifdef FILESDIR
71 : if (Path::isFile(Path::join(FILESDIR, cfgFilename)))
72 : fileName = Path::join(FILESDIR, cfgFilename);
73 : #endif
74 : // cppcheck-suppress knownConditionTrueFalse
75 223 : if (fileName.empty()) {
76 : // TODO: make sure that exename is set
77 223 : fileName = Path::getPathFromFilename(settings.exename) + cfgFilename;
78 223 : if (!Path::isFile(fileName))
79 198 : return "";
80 : }
81 :
82 50 : std::ifstream fin(fileName);
83 25 : if (!fin.is_open())
84 0 : return "could not open file";
85 50 : picojson::value json;
86 25 : fin >> json;
87 : {
88 25 : const std::string& lastErr = picojson::get_last_error();
89 25 : if (!lastErr.empty())
90 4 : return "not a valid JSON - " + lastErr;
91 : }
92 21 : const picojson::object& obj = json.get<picojson::object>();
93 : {
94 21 : const picojson::object::const_iterator it = obj.find("productName");
95 21 : if (it != obj.cend()) {
96 5 : const auto& v = it->second;
97 5 : if (!v.is<std::string>())
98 1 : return "'productName' is not a string";
99 4 : settings.cppcheckCfgProductName = v.get<std::string>();
100 : }
101 : }
102 : {
103 20 : const picojson::object::const_iterator it = obj.find("about");
104 20 : if (it != obj.cend()) {
105 3 : const auto& v = it->second;
106 3 : if (!v.is<std::string>())
107 1 : return "'about' is not a string";
108 2 : settings.cppcheckCfgAbout = v.get<std::string>();
109 : }
110 : }
111 : {
112 19 : const picojson::object::const_iterator it = obj.find("addons");
113 19 : if (it != obj.cend()) {
114 5 : const auto& entry = it->second;
115 5 : if (!entry.is<picojson::array>())
116 1 : return "'addons' is not an array";
117 5 : for (const picojson::value &v : entry.get<picojson::array>())
118 : {
119 2 : if (!v.is<std::string>())
120 1 : return "'addons' array entry is not a string";
121 1 : const std::string &s = v.get<std::string>();
122 1 : if (!Path::isAbsolute(s))
123 1 : settings.addons.emplace(Path::join(Path::getPathFromFilename(fileName), s));
124 : else
125 0 : settings.addons.emplace(s);
126 : }
127 : }
128 : }
129 : {
130 17 : const picojson::object::const_iterator it = obj.find("suppressions");
131 17 : if (it != obj.cend()) {
132 4 : const auto& entry = it->second;
133 4 : if (!entry.is<picojson::array>())
134 1 : return "'suppressions' is not an array";
135 4 : for (const picojson::value &v : entry.get<picojson::array>())
136 : {
137 3 : if (!v.is<std::string>())
138 1 : return "'suppressions' array entry is not a string";
139 2 : const std::string &s = v.get<std::string>();
140 2 : const std::string err = suppressions.nomsg.addSuppressionLine(s);
141 2 : if (!err.empty())
142 2 : return "could not parse suppression '" + s + "' - " + err;
143 : }
144 : }
145 : }
146 : {
147 14 : const picojson::object::const_iterator it = obj.find("safety");
148 14 : if (it != obj.cend()) {
149 2 : const auto& v = it->second;
150 2 : if (!v.is<bool>())
151 0 : return "'safety' is not a bool";
152 2 : settings.safety = settings.safety || v.get<bool>();
153 : }
154 : }
155 :
156 14 : return "";
157 : }
158 :
159 10 : std::pair<std::string, std::string> Settings::getNameAndVersion(const std::string& productName) {
160 10 : if (productName.empty())
161 6 : return {};
162 4 : const std::string::size_type pos1 = productName.rfind(' ');
163 4 : if (pos1 == std::string::npos)
164 0 : return {};
165 4 : if (pos1 + 2 >= productName.length())
166 0 : return {};
167 17 : for (auto pos2 = pos1 + 1; pos2 < productName.length(); ++pos2) {
168 15 : const char c = productName[pos2];
169 15 : const char prev = productName[pos2-1];
170 15 : if (std::isdigit(c))
171 8 : continue;
172 7 : if (c == '.' && std::isdigit(prev))
173 4 : continue;
174 3 : if (c == 's' && pos2 + 1 == productName.length() && std::isdigit(prev))
175 1 : continue;
176 2 : return {};
177 : }
178 2 : return {productName.substr(0, pos1), productName.substr(pos1+1)};
179 : }
180 :
181 254 : std::string Settings::parseEnabled(const std::string &str, std::tuple<SimpleEnableGroup<Severity>, SimpleEnableGroup<Checks>> &groups)
182 : {
183 : // Enable parameters may be comma separated...
184 254 : if (str.find(',') != std::string::npos) {
185 27 : std::string::size_type prevPos = 0;
186 27 : std::string::size_type pos = 0;
187 55 : while ((pos = str.find(',', pos)) != std::string::npos) {
188 29 : if (pos == prevPos)
189 0 : return std::string("--enable parameter is empty");
190 29 : std::string errmsg(parseEnabled(str.substr(prevPos, pos - prevPos), groups));
191 29 : if (!errmsg.empty())
192 1 : return errmsg;
193 28 : ++pos;
194 28 : prevPos = pos;
195 : }
196 26 : if (prevPos >= str.length())
197 0 : return std::string("--enable parameter is empty");
198 52 : return parseEnabled(str.substr(prevPos), groups);
199 : }
200 :
201 227 : auto& severity = std::get<0>(groups);
202 227 : auto& checks = std::get<1>(groups);
203 :
204 227 : if (str == "all") {
205 : // "error" is always enabled and cannot be controlled - so exclude it from "all"
206 4 : SimpleEnableGroup<Severity> newSeverity;
207 4 : newSeverity.fill();
208 4 : newSeverity.disable(Severity::error);
209 4 : severity.enable(newSeverity);
210 4 : checks.enable(Checks::missingInclude);
211 4 : checks.enable(Checks::unusedFunction);
212 223 : } else if (str == "warning") {
213 33 : severity.enable(Severity::warning);
214 190 : } else if (str == "style") {
215 28 : severity.enable(Severity::style);
216 162 : } else if (str == "performance") {
217 29 : severity.enable(Severity::performance);
218 133 : } else if (str == "portability") {
219 33 : severity.enable(Severity::portability);
220 100 : } else if (str == "information") {
221 31 : severity.enable(Severity::information);
222 69 : } else if (str == "unusedFunction") {
223 3 : checks.enable(Checks::unusedFunction);
224 66 : } else if (str == "missingInclude") {
225 60 : checks.enable(Checks::missingInclude);
226 : }
227 : #ifdef CHECK_INTERNAL
228 : else if (str == "internal") {
229 : checks.enable(Checks::internalCheck);
230 : }
231 : #endif
232 : else {
233 : // the actual option is prepending in the applyEnabled() call
234 6 : if (str.empty())
235 2 : return " parameter is empty";
236 8 : return " parameter with the unknown name '" + str + "'";
237 : }
238 :
239 221 : return "";
240 : }
241 :
242 164 : std::string Settings::addEnabled(const std::string &str)
243 : {
244 164 : return applyEnabled(str, true);
245 : }
246 :
247 35 : std::string Settings::removeEnabled(const std::string &str)
248 : {
249 35 : return applyEnabled(str, false);
250 : }
251 :
252 199 : std::string Settings::applyEnabled(const std::string &str, bool enable)
253 : {
254 199 : std::tuple<SimpleEnableGroup<Severity>, SimpleEnableGroup<Checks>> groups;
255 398 : std::string errmsg = parseEnabled(str, groups);
256 199 : if (!errmsg.empty())
257 6 : return (enable ? "--enable" : "--disable") + errmsg;
258 :
259 193 : const auto s = std::get<0>(groups);
260 193 : const auto c = std::get<1>(groups);
261 193 : if (enable) {
262 161 : severity.enable(s);
263 161 : checks.enable(c);
264 : }
265 : else {
266 32 : severity.disable(s);
267 32 : checks.disable(c);
268 : }
269 : // FIXME: hack to make sure "error" is always enabled
270 193 : severity.enable(Severity::error);
271 193 : return errmsg;
272 : }
273 :
274 12501 : bool Settings::isEnabled(const ValueFlow::Value *value, bool inconclusiveCheck) const
275 : {
276 12501 : if (!severity.isEnabled(Severity::warning) && (value->condition || value->defaultArg))
277 0 : return false;
278 12502 : if (!certainty.isEnabled(Certainty::inconclusive) && (inconclusiveCheck || value->isInconclusive()))
279 0 : return false;
280 12502 : return true;
281 : }
282 :
283 25 : void Settings::loadSummaries()
284 : {
285 25 : Summaries::loadReturn(buildDir, summaryReturn);
286 25 : }
287 :
288 4467 : void Settings::setCheckLevel(CheckLevel level)
289 : {
290 4467 : if (level == CheckLevel::normal) {
291 : // Checking should finish in reasonable time.
292 288 : checkLevel = level;
293 288 : performanceValueFlowMaxSubFunctionArgs = 8;
294 288 : performanceValueFlowMaxIfCount = 100;
295 : }
296 4179 : else if (level == CheckLevel::exhaustive) {
297 : // Checking can take a little while. ~ 10 times slower than normal analysis is OK.
298 4178 : checkLevel = CheckLevel::exhaustive;
299 4178 : performanceValueFlowMaxIfCount = -1;
300 4178 : performanceValueFlowMaxSubFunctionArgs = 256;
301 : }
302 4467 : }
303 :
304 : // TODO: auto generate these tables
305 :
306 : static const std::set<std::string> autosarCheckers{
307 : "accessMoved",
308 : "argumentSize",
309 : "arrayIndexOutOfBounds",
310 : "arrayIndexOutOfBoundsCond",
311 : "arrayIndexThenCheck",
312 : "bufferAccessOutOfBounds",
313 : "comparePointers",
314 : "constParameter",
315 : "cstyleCast",
316 : "ctuOneDefinitionViolation",
317 : "doubleFree",
318 : "duplInheritedMember",
319 : "duplicateBreak",
320 : "funcArgNamesDifferent",
321 : "functionConst",
322 : "functionStatic",
323 : "invalidContainer",
324 : "memleak",
325 : "mismatchAllocDealloc",
326 : "missingReturn",
327 : "negativeIndex",
328 : "noExplicitConstructor",
329 : "nullPointer",
330 : "nullPointerArithmetic",
331 : "nullPointerArithmeticRedundantCheck",
332 : "nullPointerDefaultArg",
333 : "nullPointerRedundantCheck",
334 : "objectIndex",
335 : "overlappingWriteFunction",
336 : "overlappingWriteUnion",
337 : "pointerOutOfBounds",
338 : "pointerOutOfBoundsCond",
339 : "preprocessorErrorDirective",
340 : "redundantAssignment",
341 : "redundantInitialization",
342 : "returnDanglingLifetime",
343 : "shiftTooManyBits",
344 : "sizeofSideEffects",
345 : "throwInDestructor",
346 : "throwInNoexceptFunction",
347 : "uninitData",
348 : "uninitMember",
349 : "unreachableCode",
350 : "unreadVariable",
351 : "unsignedLessThanZero",
352 : "unusedFunction",
353 : "unusedStructMember",
354 : "unusedValue",
355 : "unusedVariable",
356 : "useInitializerList",
357 : "variableScope",
358 : "virtualCallInConstructor",
359 : "zerodiv",
360 : "zerodivcond"
361 : };
362 :
363 : static const std::set<std::string> certCCheckers{
364 : "IOWithoutPositioning",
365 : "autoVariables",
366 : "autovarInvalidDeallocation",
367 : "bitwiseOnBoolean",
368 : "comparePointers",
369 : "danglingLifetime",
370 : "deallocret",
371 : "deallocuse",
372 : "doubleFree",
373 : "floatConversionOverflow",
374 : "invalidFunctionArg",
375 : "invalidLengthModifierError",
376 : "invalidLifetime",
377 : "invalidScanfFormatWidth",
378 : "invalidscanf",
379 : "leakReturnValNotUsed",
380 : "leakUnsafeArgAlloc",
381 : "memleak",
382 : "memleakOnRealloc",
383 : "mismatchAllocDealloc",
384 : "missingReturn",
385 : "nullPointer",
386 : "nullPointerArithmetic",
387 : "nullPointerArithmeticRedundantCheck",
388 : "nullPointerDefaultArg",
389 : "nullPointerRedundantCheck",
390 : "preprocessorErrorDirective",
391 : "resourceLeak",
392 : "sizeofCalculation",
393 : "stringLiteralWrite",
394 : "uninitStructMember",
395 : "uninitdata",
396 : "uninitvar",
397 : "unknownEvaluationOrder",
398 : "useClosedFile",
399 : "wrongPrintfScanfArgNum",
400 : "wrongPrintfScanfParameterPositionError"
401 : };
402 :
403 : static const std::set<std::string> certCppCheckers{
404 : "IOWithoutPositioning",
405 : "accessMoved",
406 : "comparePointers",
407 : "containerOutOfBounds",
408 : "ctuOneDefinitionViolation",
409 : "deallocMismatch",
410 : "deallocThrow",
411 : "deallocuse",
412 : "doubleFree",
413 : "eraseDereference",
414 : "exceptThrowInDestructor",
415 : "initializerList",
416 : "invalidContainer",
417 : "lifetime",
418 : "memleak",
419 : "missingReturn",
420 : "nullPointer",
421 : "operatorEqToSelf",
422 : "sizeofCalculation",
423 : "uninitvar",
424 : "virtualCallInConstructor",
425 : "virtualDestructor"
426 : };
427 :
428 : static const std::set<std::string> misrac2012Checkers{
429 : "alwaysFalse",
430 : "alwaysTrue",
431 : "argumentSize",
432 : "autovarInvalidDeallocation",
433 : "bufferAccessOutOfBounds",
434 : "comparePointers",
435 : "compareValueOutOfTypeRangeError",
436 : "constPointer",
437 : "danglingLifetime",
438 : "duplicateBreak",
439 : "error",
440 : "funcArgNamesDifferent",
441 : "incompatibleFileOpen",
442 : "invalidFunctionArg",
443 : "knownConditionTrueFalse",
444 : "leakNoVarFunctionCall",
445 : "leakReturnValNotUsed",
446 : "memleak",
447 : "memleakOnRealloc",
448 : "missingReturn",
449 : "overlappingWriteFunction",
450 : "overlappingWriteUnion",
451 : "pointerOutOfBounds",
452 : "preprocessorErrorDirective",
453 : "redundantAssignInSwitch",
454 : "redundantAssignment",
455 : "redundantCondition",
456 : "resourceLeak",
457 : "shadowVariable",
458 : "sizeofCalculation",
459 : "syntaxError",
460 : "uninitvar",
461 : "unknownEvaluationOrder",
462 : "unreadVariable",
463 : "unusedLabel",
464 : "unusedVariable",
465 : "useClosedFile",
466 : "writeReadOnlyFile"
467 : };
468 :
469 : static const std::set<std::string> misrac2023Checkers{
470 : "alwaysFalse",
471 : "alwaysTrue",
472 : "argumentSize",
473 : "autovarInvalidDeallocation",
474 : "bufferAccessOutOfBounds",
475 : "comparePointers",
476 : "compareValueOutOfTypeRangeError",
477 : "constPointer",
478 : "danglingLifetime",
479 : "duplicateBreak",
480 : "error",
481 : "funcArgNamesDifferent",
482 : "incompatibleFileOpen",
483 : "invalidFunctionArg",
484 : "knownConditionTrueFalse",
485 : "leakNoVarFunctionCall",
486 : "leakReturnValNotUsed",
487 : "memleak",
488 : "memleakOnRealloc",
489 : "missingReturn",
490 : "overlappingWriteFunction",
491 : "overlappingWriteUnion",
492 : "pointerOutOfBounds",
493 : "preprocessorErrorDirective",
494 : "redundantAssignInSwitch",
495 : "redundantAssignment",
496 : "redundantCondition",
497 : "resourceLeak",
498 : "shadowVariable",
499 : "sizeofCalculation",
500 : "syntaxError",
501 : "uninitvar",
502 : "unknownEvaluationOrder",
503 : "unreadVariable",
504 : "unusedLabel",
505 : "unusedVariable",
506 : "useClosedFile",
507 : "writeReadOnlyFile"
508 : };
509 :
510 : static const std::set<std::string> misracpp2008Checkers{
511 : "autoVariables",
512 : "comparePointers",
513 : "constParameter",
514 : "constVariable",
515 : "cstyleCast",
516 : "ctuOneDefinitionViolation",
517 : "danglingLifetime",
518 : "duplInheritedMember",
519 : "duplicateBreak",
520 : "exceptThrowInDestructor",
521 : "funcArgNamesDifferent",
522 : "functionConst",
523 : "functionStatic",
524 : "missingReturn",
525 : "noExplicit",
526 : "overlappingWriteFunction",
527 : "overlappingWriteUnion",
528 : "pointerOutOfBounds",
529 : "preprocessorErrorDirective",
530 : "redundantAssignment",
531 : "redundantInitialization",
532 : "returnReference",
533 : "returnTempReference",
534 : "shadowVariable",
535 : "shiftTooManyBits",
536 : "sizeofSideEffects",
537 : "throwInDestructor",
538 : "uninitDerivedMemberVar",
539 : "uninitDerivedMemberVarPrivate",
540 : "uninitMemberVar",
541 : "uninitMemberVarPrivate",
542 : "uninitStructMember",
543 : "uninitdata",
544 : "uninitvar",
545 : "unknownEvaluationOrder",
546 : "unreachableCode",
547 : "unreadVariable",
548 : "unsignedLessThanZero",
549 : "unusedFunction",
550 : "unusedStructMember",
551 : "unusedVariable",
552 : "varScope",
553 : "variableScope",
554 : "virtualCallInConstructor"
555 : };
556 :
557 147543 : bool Settings::isPremiumEnabled(const char id[]) const
558 : {
559 147539 : if (premiumArgs.find("autosar") != std::string::npos && autosarCheckers.count(id))
560 0 : return true;
561 147563 : if (premiumArgs.find("cert-c-") != std::string::npos && certCCheckers.count(id))
562 0 : return true;
563 147568 : if (premiumArgs.find("cert-c++") != std::string::npos && certCppCheckers.count(id))
564 0 : return true;
565 147567 : if (premiumArgs.find("misra-c-") != std::string::npos && (misrac2012Checkers.count(id) || misrac2023Checkers.count(id)))
566 0 : return true;
567 147571 : if (premiumArgs.find("misra-c++") != std::string::npos && misracpp2008Checkers.count(id))
568 0 : return true;
569 147563 : return false;
570 : }
571 :
572 25 : void Settings::setMisraRuleTexts(const ExecuteCmdFn& executeCommand)
573 : {
574 25 : if (premiumArgs.find("--misra-c-20") != std::string::npos) {
575 0 : const auto it = std::find_if(addonInfos.cbegin(), addonInfos.cend(), [](const AddonInfo& a) {
576 0 : return a.name == "premiumaddon.json";
577 0 : });
578 0 : if (it != addonInfos.cend()) {
579 0 : std::string arg;
580 0 : if (premiumArgs.find("--misra-c-2023") != std::string::npos)
581 0 : arg = "--misra-c-2023-rule-texts";
582 : else
583 0 : arg = "--misra-c-2012-rule-texts";
584 0 : std::string output;
585 0 : executeCommand(it->executable, {std::move(arg)}, "2>&1", output);
586 0 : setMisraRuleTexts(output);
587 : }
588 : }
589 25 : }
590 :
591 1 : void Settings::setMisraRuleTexts(const std::string& data)
592 : {
593 1 : mMisraRuleTexts.clear();
594 2 : std::istringstream istr(data);
595 2 : std::string line;
596 3 : while (std::getline(istr, line)) {
597 2 : std::string::size_type pos = line.find(' ');
598 2 : if (pos == std::string::npos)
599 0 : continue;
600 2 : std::string id = line.substr(0, pos);
601 2 : std::string text = line.substr(pos + 1);
602 2 : if (id.empty() || text.empty())
603 0 : continue;
604 2 : mMisraRuleTexts[id] = std::move(text);
605 : }
606 1 : }
607 :
608 2 : std::string Settings::getMisraRuleText(const std::string& id, const std::string& text) const {
609 2 : if (id.compare(0, 9, "misra-c20") != 0)
610 0 : return text;
611 2 : const auto it = mMisraRuleTexts.find(id.substr(id.rfind('-') + 1));
612 4 : return it != mMisraRuleTexts.end() ? it->second : text;
613 : }
614 :
615 4155 : Settings::ExecutorType Settings::defaultExecutor()
616 : {
617 : static constexpr ExecutorType defaultExecutor =
618 : #if defined(HAS_THREADING_MODEL_FORK)
619 : ExecutorType::Process;
620 : #elif defined(HAS_THREADING_MODEL_THREAD)
621 : ExecutorType::Thread;
622 : #endif
623 4155 : return defaultExecutor;
624 : }
|