From 6c588cc3efe4bf14f7c4d4dd503dceef8b26f16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 28 Jun 2020 21:00:50 +0200 Subject: [PATCH 001/116] Library: Refactoring init attribute --- lib/checkuninitvar.cpp | 31 ++++++++++++++++++------------- lib/library.cpp | 4 +++- lib/library.h | 2 +- test/testlibrary.cpp | 4 +--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp index 03323abfc..e46f94304 100644 --- a/lib/checkuninitvar.cpp +++ b/lib/checkuninitvar.cpp @@ -173,15 +173,18 @@ void CheckUninitVar::checkScope(const Scope* scope, const std::set if (arg.declarationId() && Token::Match(arg.typeStartToken(), "%type% * %name% [,)]")) { // Treat the pointer as initialized until it is assigned by malloc for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { - if (Token::Match(tok, "[;{}] %varid% = %name% (", arg.declarationId()) && - mSettings->library.returnuninitdata.count(tok->strAt(3)) == 1U) { - if (arg.typeStartToken()->strAt(-1) == "struct" || (arg.type() && arg.type()->isStructType())) - checkStruct(tok, arg); - else if (arg.typeStartToken()->isStandardType() || arg.typeStartToken()->isEnumType()) { - Alloc alloc = NO_ALLOC; - const std::map variableValue; - checkScopeForVariable(tok->next(), arg, nullptr, nullptr, &alloc, emptyString, variableValue); - } + if (!Token::Match(tok, "[;{}] %varid% = %name% (", arg.declarationId())) + continue; + const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(tok->tokAt(3)); + if (!allocFunc || allocFunc->initData) + continue; + + if (arg.typeStartToken()->strAt(-1) == "struct" || (arg.type() && arg.type()->isStructType())) + checkStruct(tok, arg); + else if (arg.typeStartToken()->isStandardType() || arg.typeStartToken()->isEnumType()) { + Alloc alloc = NO_ALLOC; + const std::map variableValue; + checkScopeForVariable(tok->next(), arg, nullptr, nullptr, &alloc, emptyString, variableValue); } } } @@ -716,10 +719,12 @@ bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var const Token *rhs = tok->next()->astOperand2(); while (rhs && rhs->isCast()) rhs = rhs->astOperand2() ? rhs->astOperand2() : rhs->astOperand1(); - if (rhs && Token::Match(rhs->previous(), "%name% (") && - mSettings->library.returnuninitdata.count(rhs->previous()->str()) > 0U) { - *alloc = NO_CTOR_CALL; - continue; + if (rhs && Token::Match(rhs->previous(), "%name% (")) { + const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(rhs->astOperand1()); + if (allocFunc && !allocFunc->initData) { + *alloc = NO_CTOR_CALL; + continue; + } } } if (mTokenizer->isCPP() && var.isPointer() && (var.typeStartToken()->isStandardType() || var.typeStartToken()->isEnumType() || (var.type() && var.type()->needInitialization == Type::NeedInitialization::True)) && Token::simpleMatch(tok->next(), "= new")) { diff --git a/lib/library.cpp b/lib/library.cpp index f5192a0ca..5d4da2949 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -194,7 +194,9 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) temp.groupId = allocationId; if (memorynode->Attribute("init", "false")) - returnuninitdata.insert(memorynode->GetText()); + temp.initData = false; + else + temp.initData = true; const char *arg = memorynode->Attribute("arg"); if (arg) diff --git a/lib/library.h b/lib/library.h index f7975faa9..53bdbfe6d 100644 --- a/lib/library.h +++ b/lib/library.h @@ -78,6 +78,7 @@ public: int bufferSizeArg1; int bufferSizeArg2; int reallocArg; + bool initData; }; /** get allocation info for function */ @@ -418,7 +419,6 @@ public: return -1; } - std::set returnuninitdata; std::vector defines; // to provide some library defines std::set smartPointers; diff --git a/test/testlibrary.cpp b/test/testlibrary.cpp index 606149599..d34d4d88f 100644 --- a/test/testlibrary.cpp +++ b/test/testlibrary.cpp @@ -686,11 +686,9 @@ private: ASSERT(library.functions.empty()); const Library::AllocFunc* af = library.getAllocFuncInfo("CreateX"); - ASSERT(af && af->arg == 5); + ASSERT(af && af->arg == 5 && !af->initData); const Library::AllocFunc* df = library.getDeallocFuncInfo("DeleteX"); ASSERT(df && df->arg == 2); - - ASSERT(library.returnuninitdata.find("CreateX") != library.returnuninitdata.cend()); } void resource() const { From ad5e4fef1f0dacb83ec50153b3af12da3efdc5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 28 Jun 2020 21:20:59 +0200 Subject: [PATCH 002/116] Bug hunting; improved handling of 'malloc' in uninit checker --- lib/exprengine.cpp | 12 ++++++++++++ test/testbughuntingchecks.cpp | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index b47b16ea2..65b928f5e 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -1887,6 +1887,18 @@ static ExprEngine::ValuePtr executeFunctionCall(const Token *tok, Data &data) } } + else if (const auto *f = data.settings->library.getAllocFuncInfo(tok->astOperand1())) { + if (!f->initData) { + const std::string name = data.getNewSymbolName(); + auto size = std::make_shared(data.getNewSymbolName(), 1, ~0U); + auto val = std::make_shared(); + auto result = std::make_shared(name, size, val, false, false, false); + call(data.callbacks, tok, result, &data); + data.functionCall(); + return result; + } + } + auto val = getValueRangeFromValueType(tok->valueType(), data); call(data.callbacks, tok, val, &data); data.functionCall(); diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp index 8055033b4..9b1f35c71 100644 --- a/test/testbughuntingchecks.cpp +++ b/test/testbughuntingchecks.cpp @@ -25,21 +25,24 @@ class TestBughuntingChecks : public TestFixture { public: TestBughuntingChecks() : TestFixture("TestBughuntingChecks") { + settings.platform(cppcheck::Platform::Unix64); } private: + Settings settings; + void run() OVERRIDE { #ifdef USE_Z3 + LOAD_LIB_2(settings.library, "std.cfg"); TEST_CASE(uninit); TEST_CASE(uninit_array); TEST_CASE(uninit_function_par); + TEST_CASE(uninit_malloc); TEST_CASE(ctu); #endif } void check(const char code[]) { - Settings settings; - settings.platform(cppcheck::Platform::Unix64); Tokenizer tokenizer(&settings, this); std::istringstream istr(code); tokenizer.tokenize(istr, "test.cpp"); @@ -89,6 +92,11 @@ private: ASSERT_EQUALS("", errout.str()); } + void uninit_malloc() { + check("void foo() { char *p = malloc(10); return *p; }"); + ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that '*p' is initialized\n", errout.str()); + } + void ctu() { check("void init(int &x) {\n" " x = 1;\n" From b5cd96cbda855e0741fbba32e56b0d66e7480bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 28 Jun 2020 21:36:56 +0200 Subject: [PATCH 003/116] ExprEngine; Rename variable --- lib/exprengine.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 65b928f5e..b1d9d81bf 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -1899,10 +1899,10 @@ static ExprEngine::ValuePtr executeFunctionCall(const Token *tok, Data &data) } } - auto val = getValueRangeFromValueType(tok->valueType(), data); - call(data.callbacks, tok, val, &data); + auto result = getValueRangeFromValueType(tok->valueType(), data); + call(data.callbacks, tok, result, &data); data.functionCall(); - return val; + return result; } static ExprEngine::ValuePtr executeArrayIndex(const Token *tok, Data &data) From 5164d87a2e5099c6435f97049dd80bdd9aaa4e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 28 Jun 2020 22:49:51 +0200 Subject: [PATCH 004/116] Bug hunting; Fixed false positives for containers --- lib/bughuntingchecks.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index c97a3172d..c674d50c4 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -185,6 +185,29 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: return; } + // container is not uninitialized + if (tok->valueType() && tok->valueType()->pointer==0 && tok->valueType()->container) + return; + + // container element is not uninitialized + if (tok->str() == "[" && + tok->astOperand1() && + tok->astOperand1()->valueType() && + tok->astOperand1()->valueType()->pointer==0 && + tok->astOperand1()->valueType()->container) { + if (tok->astOperand1()->valueType()->container->stdStringLike) + return; + bool pointerType = false; + for (const Token *typeTok = tok->astOperand1()->valueType()->containerTypeToken; Token::Match(typeTok, "%name%|*|::|<"); typeTok = typeTok->next()) { + if (typeTok->str() == "<" && typeTok->link()) + typeTok = typeTok->link(); + if (typeTok->str() == "*") + pointerType = true; + } + if (!pointerType) + return; + } + // lhs in assignment if (tok->astParent()->str() == "=" && tok == tok->astParent()->astOperand1()) return; From a49d277e0da99f6585c0b94baee1c51afad69496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 29 Jun 2020 13:09:01 +0200 Subject: [PATCH 005/116] Fixed #6471 (FP functionConst - member function modifying member variable after cast (inconclusive)) --- lib/checkclass.cpp | 3 +++ test/testclass.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 049c020bf..7300b8e29 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -1991,6 +1991,9 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, bool& if (tok1->str() == "this" && tok1->previous()->isAssignmentOp()) return false; + // non const pointer cast + if (tok1->valueType() && tok1->valueType()->pointer > 0 && tok1->astParent() && tok1->astParent()->isCast() && !Token::simpleMatch(tok1->astParent(), "( const")) + return false; const Token* lhs = tok1->previous(); if (lhs->str() == "&") { diff --git a/test/testclass.cpp b/test/testclass.cpp index 82d559000..a5db98b02 100644 --- a/test/testclass.cpp +++ b/test/testclass.cpp @@ -170,6 +170,7 @@ private: TEST_CASE(const65); // ticket #8693 TEST_CASE(const66); // ticket #7714 TEST_CASE(const67); // ticket #9193 + TEST_CASE(const68); // ticket #6471 TEST_CASE(const_handleDefaultParameters); TEST_CASE(const_passThisToMemberOfOtherClass); TEST_CASE(assigningPointerToPointerIsNotAConstOperation); @@ -5514,6 +5515,17 @@ private: ASSERT_EQUALS("[test.cpp:8]: (style, inconclusive) Technically the member function 'Test::get' can be const.\n", errout.str()); } + void const68() { // #6471 + checkConst("class MyClass {\n" + " void clear() {\n" + " SVecPtr v = (SVecPtr) m_data;\n" + " v->clear();\n" + " }\n" + " void* m_data;\n" + "};\n"); + ASSERT_EQUALS("", errout.str()); + } + void const_handleDefaultParameters() { checkConst("struct Foo {\n" " void foo1(int i, int j = 0) {\n" From 7c66dabce19215891a69d182c82227017e11b5e6 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Mon, 29 Jun 2020 15:25:32 +0200 Subject: [PATCH 006/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 5d07266bf..093becc74 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -14241,4 +14241,16 @@ + + + + + + + false + + + + + From 7d9220e96cd33451bd142ced03cae9be9bb98442 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Mon, 29 Jun 2020 15:31:03 +0200 Subject: [PATCH 007/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 093becc74..0e4ab863a 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -7387,6 +7387,25 @@ + + + false + + + + + + + + + + + + + + + + false From 07d8cb4f01b1eabbe7db45b0e44be7f43dffbdc8 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jun 2020 11:55:59 -0500 Subject: [PATCH 008/116] Extend scope of afterCondition until end of function --- lib/valueflow.cpp | 5 ++++- test/testnullpointer.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 8b10c6ba2..c0ed8f2ef 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4232,7 +4232,10 @@ struct ValueFlowConditionHandler { // TODO: constValue could be true if there are no assignments in the conditional blocks and // perhaps if there are no && and no || in the condition bool constValue = false; - forward(after, top->scope()->bodyEnd, cond.vartok, values, constValue); + const Scope* scope = top->scope(); + while(scope->nestedIn && scope->type != Scope::eFunction) + scope = scope->nestedIn; + forward(after, scope->bodyEnd, cond.vartok, values, constValue); } } } diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index a6f496127..c27b785cc 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -262,7 +262,7 @@ private: " }\n" " tok->str();\n" "}\n"); - TODO_ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:3]: (warning, inconclusive) Possible null pointer dereference: tok - otherwise it is redundant to check it against null.\n", "", errout.str()); + ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (warning) Either the condition 'tok' is redundant or there is possible null pointer dereference: tok.\n", errout.str()); check("int foo(const Token *tok)\n" "{\n" From a0770f05e185ca146e5bd793954f7e2c2efa2203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 29 Jun 2020 21:01:43 +0200 Subject: [PATCH 009/116] Reuse 'extractForLoopValues' in ValueFlow --- lib/astutils.cpp | 6 +++-- lib/astutils.h | 1 + lib/exprengine.cpp | 3 ++- lib/valueflow.cpp | 59 ++++++++++------------------------------------ 4 files changed, 19 insertions(+), 50 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 712847e46..a4ed885b5 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -438,6 +438,7 @@ const Token* getCondTokFromEnd(const Token* endBlock) bool extractForLoopValues(const Token *forToken, nonneg int * const varid, + bool * const knownInitValue, MathLib::bigint * const initValue, MathLib::bigint * const stepValue, MathLib::bigint * const lastValue) @@ -447,10 +448,11 @@ bool extractForLoopValues(const Token *forToken, const Token *initExpr = forToken->next()->astOperand2()->astOperand1(); const Token *condExpr = forToken->next()->astOperand2()->astOperand2()->astOperand1(); const Token *incExpr = forToken->next()->astOperand2()->astOperand2()->astOperand2(); - if (!initExpr || !initExpr->isBinaryOp() || initExpr->str() != "=" || !Token::Match(initExpr->astOperand1(), "%var%") || !initExpr->astOperand2()->hasKnownIntValue()) + if (!initExpr || !initExpr->isBinaryOp() || initExpr->str() != "=" || !Token::Match(initExpr->astOperand1(), "%var%")) return false; *varid = initExpr->astOperand1()->varId(); - *initValue = initExpr->astOperand2()->getKnownIntValue(); + *knownInitValue = initExpr->astOperand2()->hasKnownIntValue(); + *initValue = (*knownInitValue) ? initExpr->astOperand2()->getKnownIntValue() : 0; if (!Token::Match(condExpr, "<|<=") || !condExpr->isBinaryOp() || condExpr->astOperand1()->varId() != *varid || !condExpr->astOperand2()->hasKnownIntValue()) return false; if (!incExpr || !incExpr->isUnaryOp("++") || incExpr->astOperand1()->varId() != *varid) diff --git a/lib/astutils.h b/lib/astutils.h index 3cd883f4d..b9f88e251 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -113,6 +113,7 @@ const Token* getCondTokFromEnd(const Token* endBlock); */ bool extractForLoopValues(const Token *forToken, nonneg int * const varid, + bool * const knownInitValue, long long * const initValue, long long * const stepValue, long long * const lastValue); diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index b1d9d81bf..7991cebb4 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -2371,8 +2371,9 @@ static std::string execute(const Token *start, const Token *end, Data &data) if (Token::simpleMatch(tok, "for (")) { nonneg int varid; + bool hasKnownInitValue; MathLib::bigint initValue, stepValue, lastValue; - if (extractForLoopValues(tok, &varid, &initValue, &stepValue, &lastValue)) { + if (extractForLoopValues(tok, &varid, &hasKnownInitValue, &initValue, &stepValue, &lastValue) && hasKnownInitValue) { auto loopValues = std::make_shared(data.getNewSymbolName(), initValue, lastValue); data.assignValue(tok, varid, loopValues); tok = tok->linkAt(1); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 8b10c6ba2..0faa10ef5 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4444,44 +4444,6 @@ static void valueFlowInferCondition(TokenList* tokenlist, } } -static bool valueFlowForLoop1(const Token *tok, int * const varid, MathLib::bigint * const num1, MathLib::bigint * const num2, MathLib::bigint * const numAfter) -{ - tok = tok->tokAt(2); - if (!Token::Match(tok, "%type%| %var% =")) - return false; - const Token * const vartok = Token::Match(tok, "%var% =") ? tok : tok->next(); - *varid = vartok->varId(); - tok = vartok->tokAt(2); - const Token * const num1tok = Token::Match(tok, "%num% ;") ? tok : nullptr; - if (num1tok) - *num1 = MathLib::toLongNumber(num1tok->str()); - while (Token::Match(tok, "%name%|%num%|%or%|+|-|*|/|&|[|]|(")) - tok = (tok->str() == "(") ? tok->link()->next() : tok->next(); - if (!tok || tok->str() != ";") - return false; - tok = tok->next(); - const Token *num2tok = nullptr; - if (Token::Match(tok, "%varid% <|<=|!=", vartok->varId())) { - tok = tok->next(); - num2tok = tok->astOperand2(); - if (num2tok && num2tok->str() == "(" && !num2tok->astOperand2()) - num2tok = num2tok->astOperand1(); - if (!Token::Match(num2tok, "%num% ;|%oror%")) // TODO: || enlarges the scope of the condition, so it should not cause FP, but it should no lnger be part of this pattern as soon as valueFlowForLoop2 can handle an unknown RHS of || better - num2tok = nullptr; - } - if (!num2tok) - return false; - *num2 = MathLib::toLongNumber(num2tok->str()) - ((tok->str()=="<=") ? 0 : 1); - *numAfter = *num2 + 1; - if (!num1tok) - *num1 = *num2; - while (tok && tok->str() != ";") - tok = tok->next(); - if (!Token::Match(tok, "; %varid% ++ ) {", vartok->varId()) && !Token::Match(tok, "; ++ %varid% ) {", vartok->varId())) - return false; - return true; -} - static bool valueFlowForLoop2(const Token *tok, ProgramMemory *memory1, ProgramMemory *memory2, @@ -4663,16 +4625,19 @@ static void valueFlowForLoop(TokenList *tokenlist, SymbolDatabase* symboldatabas !Token::simpleMatch(tok->next()->astOperand2()->astOperand2(), ";")) continue; - int varid(0); - MathLib::bigint num1(0), num2(0), numAfter(0); + nonneg int varid; + bool knownInitValue; + MathLib::bigint initValue, stepValue, lastValue; - if (valueFlowForLoop1(tok, &varid, &num1, &num2, &numAfter)) { - if (num1 <= num2) { - valueFlowForLoopSimplify(bodyStart, varid, false, num1, tokenlist, errorLogger, settings); - valueFlowForLoopSimplify(bodyStart, varid, false, num2, tokenlist, errorLogger, settings); - valueFlowForLoopSimplifyAfter(tok, varid, numAfter, tokenlist, errorLogger, settings); - } else - valueFlowForLoopSimplifyAfter(tok, varid, num1, tokenlist, errorLogger, settings); + if (extractForLoopValues(tok, &varid, &knownInitValue, &initValue, &stepValue, &lastValue)) { + const bool executeBody = !knownInitValue || initValue <= lastValue; + if (executeBody) { + valueFlowForLoopSimplify(bodyStart, varid, false, initValue, tokenlist, errorLogger, settings); + if (stepValue == 1) + valueFlowForLoopSimplify(bodyStart, varid, false, lastValue, tokenlist, errorLogger, settings); + } + const MathLib::bigint afterValue = executeBody ? lastValue + stepValue : initValue; + valueFlowForLoopSimplifyAfter(tok, varid, afterValue, tokenlist, errorLogger, settings); } else { ProgramMemory mem1, mem2, memAfter; if (valueFlowForLoop2(tok, &mem1, &mem2, &memAfter)) { From f34ff9325ae00dc2ee82ff9841471edf28274ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 29 Jun 2020 21:53:14 +0200 Subject: [PATCH 010/116] Fixed testrunner --- lib/astutils.cpp | 11 +++++++++++ lib/astutils.h | 1 + lib/exprengine.cpp | 4 ++-- lib/valueflow.cpp | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index a4ed885b5..f1a13f961 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -440,6 +440,7 @@ bool extractForLoopValues(const Token *forToken, nonneg int * const varid, bool * const knownInitValue, MathLib::bigint * const initValue, + bool * const partialCond, MathLib::bigint * const stepValue, MathLib::bigint * const lastValue) { @@ -453,6 +454,16 @@ bool extractForLoopValues(const Token *forToken, *varid = initExpr->astOperand1()->varId(); *knownInitValue = initExpr->astOperand2()->hasKnownIntValue(); *initValue = (*knownInitValue) ? initExpr->astOperand2()->getKnownIntValue() : 0; + *partialCond = Token::Match(condExpr, "%oror%|&&"); + visitAstNodes(condExpr, [varid, &condExpr](const Token *tok) { + if (Token::Match(tok, "%oror%|&&")) + return ChildrenToVisit::op1_and_op2; + if (Token::Match(tok, "<|<=") && tok->isBinaryOp() && tok->astOperand1()->varId() == *varid && tok->astOperand2()->hasKnownIntValue()) { + if (Token::Match(condExpr, "%oror%|&&") || tok->astOperand2()->getKnownIntValue() < condExpr->astOperand2()->getKnownIntValue()) + condExpr = tok; + } + return ChildrenToVisit::none; + }); if (!Token::Match(condExpr, "<|<=") || !condExpr->isBinaryOp() || condExpr->astOperand1()->varId() != *varid || !condExpr->astOperand2()->hasKnownIntValue()) return false; if (!incExpr || !incExpr->isUnaryOp("++") || incExpr->astOperand1()->varId() != *varid) diff --git a/lib/astutils.h b/lib/astutils.h index b9f88e251..a3545d3d7 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -115,6 +115,7 @@ bool extractForLoopValues(const Token *forToken, nonneg int * const varid, bool * const knownInitValue, long long * const initValue, + bool * const partialCond, long long * const stepValue, long long * const lastValue); diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 7991cebb4..0e96a97fb 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -2371,9 +2371,9 @@ static std::string execute(const Token *start, const Token *end, Data &data) if (Token::simpleMatch(tok, "for (")) { nonneg int varid; - bool hasKnownInitValue; + bool hasKnownInitValue, partialCond; MathLib::bigint initValue, stepValue, lastValue; - if (extractForLoopValues(tok, &varid, &hasKnownInitValue, &initValue, &stepValue, &lastValue) && hasKnownInitValue) { + if (extractForLoopValues(tok, &varid, &hasKnownInitValue, &initValue, &partialCond, &stepValue, &lastValue) && hasKnownInitValue && !partialCond) { auto loopValues = std::make_shared(data.getNewSymbolName(), initValue, lastValue); data.assignValue(tok, varid, loopValues); tok = tok->linkAt(1); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 0faa10ef5..60e644c14 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4626,10 +4626,10 @@ static void valueFlowForLoop(TokenList *tokenlist, SymbolDatabase* symboldatabas continue; nonneg int varid; - bool knownInitValue; + bool knownInitValue, partialCond; MathLib::bigint initValue, stepValue, lastValue; - if (extractForLoopValues(tok, &varid, &knownInitValue, &initValue, &stepValue, &lastValue)) { + if (extractForLoopValues(tok, &varid, &knownInitValue, &initValue, &partialCond, &stepValue, &lastValue)) { const bool executeBody = !knownInitValue || initValue <= lastValue; if (executeBody) { valueFlowForLoopSimplify(bodyStart, varid, false, initValue, tokenlist, errorLogger, settings); From 600919f624426f1ffafe8d068d8bd307669c3c24 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jun 2020 15:13:06 -0500 Subject: [PATCH 011/116] Remove redundant conditions --- lib/templatesimplifier.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/templatesimplifier.cpp b/lib/templatesimplifier.cpp index 64697a786..a045cd69d 100644 --- a/lib/templatesimplifier.cpp +++ b/lib/templatesimplifier.cpp @@ -1352,7 +1352,7 @@ bool TemplateSimplifier::getTemplateNamePositionTemplateFunction(const Token *to // skip decltype(...) else if (Token::simpleMatch(tok->next(), "decltype (")) { const Token * end = tok->linkAt(2)->previous(); - while (tok && tok->next() && tok != end) { + while (tok->next() && tok != end) { tok = tok->next(); namepos++; } @@ -1361,7 +1361,7 @@ bool TemplateSimplifier::getTemplateNamePositionTemplateFunction(const Token *to if (closing) { if (closing->strAt(1) == "(" && Tokenizer::isFunctionHead(closing->next(), ";|{|:", true)) return true; - while (tok && tok->next() && tok->next() != closing) { + while (tok->next() && tok->next() != closing) { tok = tok->next(); namepos++; } @@ -1384,7 +1384,7 @@ bool TemplateSimplifier::getTemplateNamePositionTemplateVariable(const Token *to // skip decltype(...) else if (Token::simpleMatch(tok->next(), "decltype (")) { const Token * end = tok->linkAt(2); - while (tok && tok->next() && tok != end) { + while (tok->next() && tok != end) { tok = tok->next(); namepos++; } @@ -1393,7 +1393,7 @@ bool TemplateSimplifier::getTemplateNamePositionTemplateVariable(const Token *to if (closing) { if (Token::Match(closing->next(), "=|;")) return true; - while (tok && tok->next() && tok->next() != closing) { + while (tok->next() && tok->next() != closing) { tok = tok->next(); namepos++; } From a412e3e1f16982585a4b58ca05ddbb1e3b2206b4 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jun 2020 15:33:25 -0500 Subject: [PATCH 012/116] Mark syntax error functions as noreturn --- lib/symboldatabase.cpp | 1 - lib/tokenize.h | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 601a82f38..07af526d9 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -3560,7 +3560,6 @@ void Function::addArguments(const SymbolDatabase *symbolDatabase, const Scope *s typeTok = typeTok->next(); if (Token::Match(typeTok, ",|)")) { // #8333 symbolDatabase->mTokenizer->syntaxError(typeTok); - return; } // skip over qualification while (Token::Match(typeTok, "%type% ::")) diff --git a/lib/tokenize.h b/lib/tokenize.h index da236ecf4..950efb296 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -607,16 +607,16 @@ private: public: /** Syntax error */ - void syntaxError(const Token *tok, const std::string &code = "") const; + [[noreturn]] void syntaxError(const Token *tok, const std::string &code = "") const; /** Syntax error. Unmatched character. */ - void unmatchedToken(const Token *tok) const; + [[noreturn]] void unmatchedToken(const Token *tok) const; /** Syntax error. C++ code in C file. */ - void syntaxErrorC(const Token *tok, const std::string &what) const; + [[noreturn]] void syntaxErrorC(const Token *tok, const std::string &what) const; /** Warn about unknown macro(s), configuration is recommended */ - void unknownMacroError(const Token *tok1) const; + [[noreturn]] void unknownMacroError(const Token *tok1) const; private: From 67e06c18a981a31d8659f1ea46b7cd47d0411779 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jun 2020 15:36:01 -0500 Subject: [PATCH 013/116] Use the already available function scope --- lib/valueflow.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index c0ed8f2ef..158fbf41b 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4232,9 +4232,6 @@ struct ValueFlowConditionHandler { // TODO: constValue could be true if there are no assignments in the conditional blocks and // perhaps if there are no && and no || in the condition bool constValue = false; - const Scope* scope = top->scope(); - while(scope->nestedIn && scope->type != Scope::eFunction) - scope = scope->nestedIn; forward(after, scope->bodyEnd, cond.vartok, values, constValue); } } From c76c03c7111e02aa6e1d9db3e08c5c753956789b Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jun 2020 15:54:51 -0500 Subject: [PATCH 014/116] Replace noreturn with macro --- lib/config.h | 9 +++++++++ lib/tokenize.h | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/config.h b/lib/config.h index 0d549b9ca..4bbb80359 100644 --- a/lib/config.h +++ b/lib/config.h @@ -37,6 +37,15 @@ # define NOEXCEPT #endif +// C++11 noreturn +#if (defined(__GNUC__) && (__GNUC__ >= 5)) \ + || (defined(__clang__) && (defined (__cplusplus)) && (__cplusplus >= 201103L)) \ + || defined(__CPPCHECK__) +# define NORETURN [[noreturn]] +#else +# define NORETURN +#endif + #define REQUIRES(msg, ...) class=typename std::enable_if<__VA_ARGS__::value>::type #include diff --git a/lib/tokenize.h b/lib/tokenize.h index 950efb296..36ea66bd5 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -607,16 +607,16 @@ private: public: /** Syntax error */ - [[noreturn]] void syntaxError(const Token *tok, const std::string &code = "") const; + NORETURN void syntaxError(const Token *tok, const std::string &code = "") const; /** Syntax error. Unmatched character. */ - [[noreturn]] void unmatchedToken(const Token *tok) const; + NORETURN void unmatchedToken(const Token *tok) const; /** Syntax error. C++ code in C file. */ - [[noreturn]] void syntaxErrorC(const Token *tok, const std::string &what) const; + NORETURN void syntaxErrorC(const Token *tok, const std::string &what) const; /** Warn about unknown macro(s), configuration is recommended */ - [[noreturn]] void unknownMacroError(const Token *tok1) const; + NORETURN void unknownMacroError(const Token *tok1) const; private: From 0bd44995aaca9ec034289f844afb6eb4caa4e18f Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Tue, 30 Jun 2020 10:15:44 +0200 Subject: [PATCH 015/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 0e4ab863a..1182322d8 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -6189,6 +6189,7 @@ + @@ -6201,7 +6202,7 @@ - + false From f5d3e6fad17de83f02b0bf39f5943d570da609d3 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Tue, 30 Jun 2020 10:21:52 +0200 Subject: [PATCH 016/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 1182322d8..42372afd1 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -13077,6 +13077,17 @@ + + + + + false + + + + + + From 0f205060638df654170fe1dfe9c24f32c410005e Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Tue, 30 Jun 2020 10:28:09 +0200 Subject: [PATCH 017/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 42372afd1..b48fa5049 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -5139,6 +5139,17 @@ + + + + false + + + + 0: + + + From 0583763cc606cdb7b0389c37361b558881140b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 30 Jun 2020 10:59:57 +0200 Subject: [PATCH 018/116] Fixed #3088 (False positive: Dont report "struct or union member is never used" for structs with __attribute__((packed)) or #pragma pack(push)) --- lib/checkunusedvar.cpp | 12 ++++++++++++ lib/cppcheck.cpp | 31 ++++++++++++++++--------------- lib/preprocessor.h | 3 +++ lib/tokenize.cpp | 4 +++- lib/tokenize.h | 10 ++++++++++ test/testunusedvar.cpp | 16 +++++++++++++++- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 3951b9e87..d54d29f98 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -21,6 +21,7 @@ #include "checkunusedvar.h" #include "astutils.h" +#include "preprocessor.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" @@ -1340,6 +1341,17 @@ void CheckUnusedVar::checkStructMemberUsage() // Packed struct => possibly used by lowlevel code. Struct members might be required by hardware. if (scope.bodyEnd->isAttributePacked()) continue; + if (const Preprocessor *preprocessor = mTokenizer->getPreprocessor()) { + bool isPacked = false; + for (const Directive &d: preprocessor->getDirectives()) { + if (d.str == "#pragma pack(1)" && d.file == mTokenizer->list.getFiles().front() && d.linenr < scope.bodyStart->linenr()) { + isPacked=true; + break; + } + } + if (isPacked) + continue; + } // Bail out if struct/union contains any functions if (!scope.functionList.empty()) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 0b4349eb8..f948e8b78 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -688,16 +688,17 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string continue; } - Tokenizer mTokenizer(&mSettings, this); + Tokenizer tokenizer(&mSettings, this); + tokenizer.setPreprocessor(&preprocessor); if (mSettings.showtime != SHOWTIME_MODES::SHOWTIME_NONE) - mTokenizer.setTimerResults(&s_timerResults); + tokenizer.setTimerResults(&s_timerResults); try { // Create tokens, skip rest of iteration if failed { Timer timer("Tokenizer::createTokens", mSettings.showtime, &s_timerResults); simplecpp::TokenList tokensP = preprocessor.preprocess(tokens1, mCurrentConfig, files, true); - mTokenizer.createTokens(std::move(tokensP)); + tokenizer.createTokens(std::move(tokensP)); } hasValidConfig = true; @@ -708,7 +709,7 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string mErrorLogger.reportOut("Checking " + fixedpath + ": " + mCurrentConfig + "..."); } - if (!mTokenizer.tokens()) + if (!tokenizer.tokens()) continue; // skip rest of iteration if just checking configuration @@ -716,11 +717,11 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string continue; // Check raw tokens - checkRawTokens(mTokenizer); + checkRawTokens(tokenizer); // Simplify tokens into normal form, skip rest of iteration if failed Timer timer2("Tokenizer::simplifyTokens1", mSettings.showtime, &s_timerResults); - bool result = mTokenizer.simplifyTokens1(mCurrentConfig); + bool result = tokenizer.simplifyTokens1(mCurrentConfig); timer2.stop(); if (!result) continue; @@ -733,13 +734,13 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string fdump << " " << std::endl; fdump << " " << std::endl; preprocessor.dump(fdump); - mTokenizer.dump(fdump); + tokenizer.dump(fdump); fdump << "" << std::endl; } // Skip if we already met the same simplified token list if (mSettings.force || mSettings.maxConfigs > 1) { - const unsigned long long checksum = mTokenizer.list.calculateChecksum(); + const unsigned long long checksum = tokenizer.list.calculateChecksum(); if (checksums.find(checksum) != checksums.end()) { if (mSettings.debugwarnings) purgedConfigurationMessage(filename, mCurrentConfig); @@ -749,11 +750,11 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string } // Check normal tokens - checkNormalTokens(mTokenizer); + checkNormalTokens(tokenizer); // Analyze info.. if (!mSettings.buildDir.empty()) - checkUnusedFunctions.parseTokens(mTokenizer, filename.c_str(), &mSettings); + checkUnusedFunctions.parseTokens(tokenizer, filename.c_str(), &mSettings); // simplify more if required, skip rest of iteration if failed if (mSimplify && hasRule("simple")) { @@ -761,13 +762,13 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string // if further simplification fails then skip rest of iteration Timer timer3("Tokenizer::simplifyTokenList2", mSettings.showtime, &s_timerResults); - result = mTokenizer.simplifyTokenList2(); + result = tokenizer.simplifyTokenList2(); timer3.stop(); if (!result) continue; if (!Settings::terminated()) - executeRules("simple", mTokenizer); + executeRules("simple", tokenizer); } } catch (const simplecpp::Output &o) { @@ -779,16 +780,16 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string } catch (const InternalError &e) { std::list locationList; if (e.token) { - ErrorMessage::FileLocation loc(e.token, &mTokenizer.list); + ErrorMessage::FileLocation loc(e.token, &tokenizer.list); locationList.push_back(loc); } else { - ErrorMessage::FileLocation loc(mTokenizer.list.getSourceFilePath(), 0, 0); + ErrorMessage::FileLocation loc(tokenizer.list.getSourceFilePath(), 0, 0); ErrorMessage::FileLocation loc2(filename, 0, 0); locationList.push_back(loc2); locationList.push_back(loc); } ErrorMessage errmsg(locationList, - mTokenizer.list.getSourceFilePath(), + tokenizer.list.getSourceFilePath(), Severity::error, e.errorMessage, e.id, diff --git a/lib/preprocessor.h b/lib/preprocessor.h index 8a0dfd888..71b88dd50 100644 --- a/lib/preprocessor.h +++ b/lib/preprocessor.h @@ -91,6 +91,9 @@ public: void inlineSuppressions(const simplecpp::TokenList &tokens); void setDirectives(const simplecpp::TokenList &tokens); + void setDirectives(const std::list &directives) { + mDirectives = directives; + } /** list of all directives met while preprocessing file */ const std::list &getDirectives() const { diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index b72ba5225..a92301a78 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -157,8 +157,9 @@ Tokenizer::Tokenizer() : mCodeWithTemplates(false), //is there any templates? mTimerResults(nullptr) #ifdef MAXTIME - ,mMaxTime(std::time(0) + MAXTIME) + , mMaxTime(std::time(0) + MAXTIME) #endif + , mPreprocessor(nullptr) { } @@ -175,6 +176,7 @@ Tokenizer::Tokenizer(const Settings *settings, ErrorLogger *errorLogger) : #ifdef MAXTIME ,mMaxTime(std::time(0) + MAXTIME) #endif + , mPreprocessor(nullptr) { // make sure settings are specified assert(mSettings); diff --git a/lib/tokenize.h b/lib/tokenize.h index 36ea66bd5..628628ae6 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -37,6 +37,7 @@ class TimerResults; class Token; class TemplateSimplifier; class ErrorLogger; +class Preprocessor; namespace simplecpp { class TokenList; @@ -560,6 +561,13 @@ public: */ static const Token * isFunctionHead(const Token *tok, const std::string &endsWith, bool cpp); + void setPreprocessor(const Preprocessor *preprocessor) { + mPreprocessor = preprocessor; + } + const Preprocessor *getPreprocessor() const { + return mPreprocessor; + } + private: /** @@ -956,6 +964,8 @@ private: /** Tokenizer maxtime */ const std::time_t mMaxTime; #endif + + const Preprocessor *mPreprocessor; }; /// @} diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index c2cfb1f3b..7450c38fd 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -17,6 +17,7 @@ */ #include "checkunusedvar.h" +#include "preprocessor.h" #include "settings.h" #include "testsuite.h" #include "tokenize.h" @@ -55,6 +56,7 @@ private: TEST_CASE(structmember12); // #7179 - FP unused structmember TEST_CASE(structmember13); // #3088 - __attribute__((packed)) TEST_CASE(structmember14); // #6508 - (struct x){1,2,..} + TEST_CASE(structmember15); // #3088 - #pragma pack(1) TEST_CASE(structmember_sizeof); TEST_CASE(localvar1); @@ -211,12 +213,17 @@ private: TEST_CASE(volatileData); // #9280 } - void checkStructMemberUsage(const char code[]) { + void checkStructMemberUsage(const char code[], const std::list *directives=nullptr) { // Clear the error buffer.. errout.str(""); + Preprocessor preprocessor(settings, nullptr); + if (directives) + preprocessor.setDirectives(*directives); + // Tokenize.. Tokenizer tokenizer(&settings, this); + tokenizer.setPreprocessor(&preprocessor); std::istringstream istr(code); tokenizer.tokenize(istr, "test.cpp"); @@ -473,6 +480,13 @@ private: ASSERT_EQUALS("", errout.str()); } + void structmember15() { // #3088 + std::list directives; + directives.emplace_back("test.cpp", 1, "#pragma pack(1)"); + checkStructMemberUsage("\nstruct Foo { int x; int y; };", &directives); + ASSERT_EQUALS("", errout.str()); + } + void structmember_extern() { // extern struct => no false positive checkStructMemberUsage("extern struct AB\n" From ddd21a260f5567d1c94a805b7430ae4869aeae9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 30 Jun 2020 18:26:24 +0200 Subject: [PATCH 019/116] Fixed #6978 (False positive: unusedLabel shown for labels that are used in some preprocessor configurations) --- lib/checkother.cpp | 37 ++++++++++++++++++++++++------------- lib/checkother.h | 8 +++++--- lib/tokenize.cpp | 17 +++++++++++++++++ lib/tokenize.h | 2 ++ 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index c48e6b735..c3a492fa9 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -2637,6 +2637,7 @@ void CheckOther::checkUnusedLabel() const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { + const bool hasIfdef = mTokenizer->hasIfdef(scope->bodyStart, scope->bodyEnd); for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (!tok->scope()->isExecutable()) tok = tok->scope()->bodyEnd; @@ -2644,25 +2645,35 @@ void CheckOther::checkUnusedLabel() if (Token::Match(tok, "{|}|; %name% :") && tok->strAt(1) != "default") { const std::string tmp("goto " + tok->strAt(1)); if (!Token::findsimplematch(scope->bodyStart->next(), tmp.c_str(), tmp.size(), scope->bodyEnd->previous())) - unusedLabelError(tok->next(), tok->next()->scope()->type == Scope::eSwitch); + unusedLabelError(tok->next(), tok->next()->scope()->type == Scope::eSwitch, hasIfdef); } } } } -void CheckOther::unusedLabelError(const Token* tok, bool inSwitch) +void CheckOther::unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef) { - if (inSwitch) { - if (!tok || mSettings->isEnabled(Settings::WARNING)) - reportError(tok, Severity::warning, "unusedLabelSwitch", - "$symbol:" + (tok ? tok->str() : emptyString) + "\n" - "Label '$symbol' is not used. Should this be a 'case' of the enclosing switch()?", CWE398, false); - } else { - if (!tok || mSettings->isEnabled(Settings::STYLE)) - reportError(tok, Severity::style, "unusedLabel", - "$symbol:" + (tok ? tok->str() : emptyString) + "\n" - "Label '$symbol' is not used.", CWE398, false); - } + if (tok && !mSettings->isEnabled(inSwitch ? Settings::WARNING : Settings::STYLE)) + return; + + std::string id = "unusedLabel"; + if (inSwitch) + id += "Switch"; + if (hasIfdef) + id += "Configuration"; + + std::string msg = "$symbol:" + (tok ? tok->str() : emptyString) + "\nLabel '$symbol' is not used."; + if (hasIfdef) + msg += " There is #if in function body so the label might be used in code that is removed by the preprocessor."; + if (inSwitch) + msg += " Should this be a 'case' of the enclosing switch()?"; + + reportError(tok, + inSwitch ? Severity::warning : Severity::style, + id, + msg, + CWE398, + false); } diff --git a/lib/checkother.h b/lib/checkother.h index 0d1e1a323..4dc418986 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -266,7 +266,7 @@ private: void commaSeparatedReturnError(const Token *tok); void redundantPointerOpError(const Token* tok, const std::string& varname, bool inconclusive); void raceAfterInterlockedDecrementError(const Token* tok); - void unusedLabelError(const Token* tok, bool inSwitch); + void unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef); void unknownEvaluationOrder(const Token* tok); static bool isMovedParameterAllowedForInconclusiveFunction(const Token * tok); void accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive); @@ -331,8 +331,10 @@ private: c.nanInArithmeticExpressionError(nullptr); c.commaSeparatedReturnError(nullptr); c.redundantPointerOpError(nullptr, "varname", false); - c.unusedLabelError(nullptr, true); - c.unusedLabelError(nullptr, false); + c.unusedLabelError(nullptr, false, false); + c.unusedLabelError(nullptr, false, true); + c.unusedLabelError(nullptr, true, false); + c.unusedLabelError(nullptr, true, true); c.unknownEvaluationOrder(nullptr); c.accessMovedError(nullptr, "v", nullptr, false); c.funcArgNamesDifferent("function", 1, nullptr, nullptr); diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index a92301a78..a9fa35bea 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -24,6 +24,7 @@ #include "library.h" #include "mathlib.h" #include "platform.h" +#include "preprocessor.h" #include "settings.h" #include "standards.h" #include "symboldatabase.h" @@ -11773,3 +11774,19 @@ bool Tokenizer::VariableMap::hasVariable(const std::string &varname) const { return mVariableId.find(varname) != mVariableId.end(); } + +bool Tokenizer::hasIfdef(const Token *start, const Token *end) const +{ + if (!mPreprocessor) + return false; + for (const Directive &d: mPreprocessor->getDirectives()) { + if (d.str.compare(0,3,"#if") == 0 && + d.linenr >= start->linenr() && + d.linenr <= end->linenr() && + start->fileIndex() < list.getFiles().size() && + d.file == list.getFiles()[start->fileIndex()]) + return true; + } + return false; +} + diff --git a/lib/tokenize.h b/lib/tokenize.h index 628628ae6..543a6cb74 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -568,6 +568,8 @@ public: return mPreprocessor; } + bool hasIfdef(const Token *start, const Token *end) const; + private: /** From f56a17bf3dc9d545cd6dc69a9de0897b0712be3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 1 Jul 2020 07:48:32 +0200 Subject: [PATCH 020/116] Fixed #8858 (FP: identicalConditionAfterEarlyExit when there is #if) --- lib/checkcondition.cpp | 25 +++++++++++-------------- test/testcondition.cpp | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index ec9b33c39..e15a09a06 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -702,22 +702,19 @@ void CheckCondition::multiCondition2() } } } else { - std::stack tokens2; - tokens2.push(cond2); - while (!tokens2.empty()) { - const Token *secondCondition = tokens2.top(); - tokens2.pop(); - if (!secondCondition) - continue; - if (secondCondition->str() == "||" || secondCondition->str() == "&&") { - tokens2.push(secondCondition->astOperand1()); - tokens2.push(secondCondition->astOperand2()); - } else if ((!cond1->hasKnownIntValue() || !secondCondition->hasKnownIntValue()) && - isSameExpression(mTokenizer->isCPP(), true, cond1, secondCondition, mSettings->library, true, true, &errorPath)) { - if (!isAliased(vars)) + visitAstNodes(cond2, [&](const Token *secondCondition) { + if (secondCondition->str() == "||" || secondCondition->str() == "&&") + return ChildrenToVisit::op1_and_op2; + + if ((!cond1->hasKnownIntValue() || !secondCondition->hasKnownIntValue()) && + isSameExpression(mTokenizer->isCPP(), true, cond1, secondCondition, mSettings->library, true, true, &errorPath)) { + if (!isAliased(vars) && !mTokenizer->hasIfdef(cond1, secondCondition)) { identicalConditionAfterEarlyExitError(cond1, secondCondition, errorPath); + return ChildrenToVisit::done; + } } - } + return ChildrenToVisit::none; + }); } } if (Token::Match(tok, "%name% (") && isVariablesChanged(tok, tok->linkAt(1), true, varsInCond, mSettings, mTokenizer->isCPP())) { diff --git a/test/testcondition.cpp b/test/testcondition.cpp index d02abd8ad..f93c5b386 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -18,6 +18,7 @@ #include "checkcondition.h" #include "library.h" +#include "preprocessor.h" #include "settings.h" #include "testsuite.h" #include "tokenize.h" @@ -136,10 +137,14 @@ private: std::map filedata; simplecpp::preprocess(tokens2, tokens1, files, filedata, simplecpp::DUI()); + Preprocessor preprocessor(settings0, nullptr); + preprocessor.setDirectives(tokens1); + // Tokenizer.. Tokenizer tokenizer(&settings0, this); tokenizer.createTokens(std::move(tokens2)); tokenizer.simplifyTokens1(""); + tokenizer.setPreprocessor(&preprocessor); // Run checks.. CheckCondition checkCondition; @@ -2570,6 +2575,18 @@ private: " int FileIndex; \n" "};\n"); ASSERT_EQUALS("", errout.str()); + + // #8858 - #if + check("short Do() {\n" + " short ret = bar1();\n" + " if ( ret )\n" + " return ret;\n" + "#ifdef FEATURE\n" + " ret = bar2();\n" + "#endif\n" + " return ret;\n" + "}"); + ASSERT_EQUALS("", errout.str()); } void innerConditionModified() { From d2b2bae7bf85f916a3c98fdbb71be61e9285a84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 1 Jul 2020 08:24:52 +0200 Subject: [PATCH 021/116] Fixed #7733 (False positive: scope of the variable can be reduced (variable is used in hidden code)) --- lib/checkother.cpp | 3 +++ test/testother.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index c3a492fa9..d39daaa19 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -882,6 +882,9 @@ void CheckOther::checkVariableScope() if (var->isConst()) continue; + if (mTokenizer->hasIfdef(var->nameToken(), var->scope()->bodyEnd)) + continue; + // reference of range for loop variable.. if (Token::Match(var->nameToken()->previous(), "& %var% = %var% .")) { const Token *otherVarToken = var->nameToken()->tokAt(2); diff --git a/test/testother.cpp b/test/testother.cpp index 3b80943d1..773c20a8a 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -19,6 +19,7 @@ #include "checkother.h" #include "library.h" #include "platform.h" +#include "preprocessor.h" #include "settings.h" #include "standards.h" #include "testsuite.h" @@ -86,6 +87,7 @@ private: TEST_CASE(varScope24); // pointer / reference TEST_CASE(varScope25); // time_t TEST_CASE(varScope26); // range for loop, map + TEST_CASE(varScope27); // #7733 - #if TEST_CASE(oldStylePointerCast); TEST_CASE(invalidPointerCast); @@ -300,10 +302,14 @@ private: std::map filedata; simplecpp::preprocess(tokens2, tokens1, files, filedata, simplecpp::DUI()); + Preprocessor preprocessor(*settings, nullptr); + preprocessor.setDirectives(tokens1); + // Tokenizer.. Tokenizer tokenizer(settings, this); tokenizer.createTokens(std::move(tokens2)); tokenizer.simplifyTokens1(""); + tokenizer.setPreprocessor(&preprocessor); // Check.. CheckOther checkOther(&tokenizer, settings, this); @@ -1229,6 +1235,24 @@ private: ASSERT_EQUALS("", errout.str()); } + void varScope27() { + checkP("void f() {\n" + " int x = 0;\n" + "#ifdef X\n" + "#endif\n" + " if (id == ABC) { return x; }\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + checkP("void f() {\n" + "#ifdef X\n" + "#endif\n" + " int x = 0;\n" + " if (id == ABC) { return x; }\n" + "}"); + ASSERT_EQUALS("[test.cpp:4]: (style) The scope of the variable 'x' can be reduced.\n", errout.str()); + } + void checkOldStylePointerCast(const char code[]) { // Clear the error buffer.. errout.str(""); From 4f191e455e3a4b35fa2a9718a39ae0bde9dae545 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 1 Jul 2020 11:11:47 +0200 Subject: [PATCH 022/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index b48fa5049..0dee41ecd 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -14295,4 +14295,24 @@ + + + false + + + + 0: + + + + 0: + + + + + + + 0: + + From 27d6b9a57d120a282255322da21fd5a3c7d16ac6 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 1 Jul 2020 11:12:22 +0200 Subject: [PATCH 023/116] std.cfg: Formatted comments, there are no functional changes [ci skip] --- cfg/std.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/std.cfg b/cfg/std.cfg index eb9a0f5a7..5840d26d9 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -3755,8 +3755,8 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun 0: - - + + From 0770a6fe6286c4efcbc1d3b409ae05aa201c15e1 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 1 Jul 2020 14:43:17 +0200 Subject: [PATCH 024/116] std.cfg: Added support for std::at_quick_exit() --- cfg/std.cfg | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cfg/std.cfg b/cfg/std.cfg index 5840d26d9..f9c0430a0 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -1475,6 +1475,16 @@ + + + false + + + + + + + From 8ab02ed97aec5e445bc43cc2b1e2ccc47f0064ed Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 1 Jul 2020 15:06:00 +0200 Subject: [PATCH 025/116] std.cfg: Improved bsearch() configuration --- cfg/std.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/cfg/std.cfg b/cfg/std.cfg index f9c0430a0..27866875f 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -4232,6 +4232,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + From cabafca5ae495e7105c989c3d8763bdedfa9b81c Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 1 Jul 2020 16:14:59 +0200 Subject: [PATCH 026/116] windows.cfg: Added some constants from WinUser.h --- cfg/windows.cfg | 141 +++++++++++++++++++++++++++++++++++++++++++++++ test/cfg/std.cpp | 13 +++++ 2 files changed, 154 insertions(+) diff --git a/cfg/windows.cfg b/cfg/windows.cfg index df39e3fde..f50a253e6 100644 --- a/cfg/windows.cfg +++ b/cfg/windows.cfg @@ -12645,6 +12645,147 @@ HFONT CreateFont( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/cfg/std.cpp b/test/cfg/std.cpp index 5019c887d..9247a1e1c 100644 --- a/test/cfg/std.cpp +++ b/test/cfg/std.cpp @@ -2040,6 +2040,19 @@ void uninivar_bsearch(void) (void)std::bsearch(key,base,num,size,(int(*)(const void*,const void*)) strcmp); } +void minsize_bsearch(const void* key, const void* base, + size_t num, size_t size, + int (*compar)(const void*,const void*)) +{ + int Base [3] = {42, 43, 44}; + + (void)std::bsearch(key,Base,2,size,(int(*)(const void*,const void*)) strcmp); + (void)std::bsearch(key,Base,3,size,(int(*)(const void*,const void*)) strcmp); + (void)std::bsearch(key,Base,4,size,(int(*)(const void*,const void*)) strcmp); + + (void)std::bsearch(key,base,2,size,(int(*)(const void*,const void*)) strcmp); +} + void uninitvar_qsort(void) { void *base; From 25055cec626c8b10ca22ae9735384ecfe1f77b06 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Thu, 2 Jul 2020 08:31:27 +0200 Subject: [PATCH 027/116] windows.cfg: Added support for SystemParametersInfo() --- cfg/windows.cfg | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cfg/windows.cfg b/cfg/windows.cfg index f50a253e6..b9cfec2dd 100644 --- a/cfg/windows.cfg +++ b/cfg/windows.cfg @@ -6113,6 +6113,22 @@ HFONT CreateFont( + + + false + + + + + + + + + + + + + From eec622b515649575ef267b9af6e1954a38a5b10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 2 Jul 2020 18:18:30 +0200 Subject: [PATCH 028/116] Bump simplecpp --- externals/simplecpp/simplecpp.cpp | 119 +++++++++++++++++------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 6de395055..900e07c6e 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -1026,15 +1026,15 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) { Token * const tok1 = tok; for (const char *op = "&^|"; *op; op++) { - const std::string* altop; + const std::string* alternativeOp; if (*op == '&') - altop = &BITAND; + alternativeOp = &BITAND; else if (*op == '|') - altop = &BITOR; + alternativeOp = &BITOR; else - altop = &XOR; + alternativeOp = &XOR; for (tok = tok1; tok && tok->op != ')'; tok = tok->next) { - if (tok->op != *op && !isAlternativeBinaryOp(tok, *altop)) + if (tok->op != *op && !isAlternativeBinaryOp(tok, *alternativeOp)) continue; if (!tok->previous || !tok->previous->number) continue; @@ -1472,6 +1472,7 @@ namespace simplecpp { } const Token *appendTokens(TokenList *tokens, + const Location &rawloc, const Token *lpar, const std::map ¯os, const std::set &expandedmacros, @@ -1483,17 +1484,17 @@ namespace simplecpp { while (sameline(lpar, tok)) { if (tok->op == '#' && sameline(tok,tok->next) && tok->next->op == '#' && sameline(tok,tok->next->next)) { // A##B => AB - tok = expandHashHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); + tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { - tok = expandHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); + tok = expandHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else { - if (!expandArg(tokens, tok, tok->location, macros, expandedmacros, parametertokens)) { + if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { bool expanded = false; const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { const Macro &m = it->second; if (!m.functionLike()) { - m.expand(tokens, tok->location, tok, macros, expandedmacros); + m.expand(tokens, rawloc, tok, macros, expandedmacros); expanded = true; } } @@ -1612,13 +1613,19 @@ namespace simplecpp { hashToken = hashToken->next; ++numberOfHash; } - if (numberOfHash == 4) { + if (numberOfHash == 4 && tok->next->location.col + 1 == tok->next->next->location.col) { // # ## # => ## output->push_back(newMacroToken("##", loc, isReplaced(expandedmacros))); tok = hashToken; continue; } + if (numberOfHash >= 2 && tok->location.col + 1 < tok->next->location.col) { + output->push_back(new Token(*tok)); + tok = tok->next; + continue; + } + tok = tok->next; if (tok == endToken) { output->push_back(new Token(*tok->previous)); @@ -1645,6 +1652,41 @@ namespace simplecpp { return functionLike() ? parametertokens2.back()->next : nameTokInst->next; } + const Token *recursiveExpandToken(TokenList *output, TokenList &temp, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { + output->takeTokens(temp); + return tok->next; + } + + if (!sameline(tok, tok->next)) { + output->takeTokens(temp); + return tok->next; + } + + const std::map::const_iterator it = macros.find(temp.cback()->str()); + if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { + output->takeTokens(temp); + return tok->next; + } + + const Macro &calledMacro = it->second; + if (!calledMacro.functionLike()) { + output->takeTokens(temp); + return tok->next; + } + + TokenList temp2(files); + temp2.push_back(new Token(temp.cback()->str(), tok->location)); + + const Token *tok2 = appendTokens(&temp2, loc, tok->next, macros, expandedmacros, parametertokens); + if (!tok2) + return tok->next; + output->takeTokens(temp); + output->deleteToken(output->back()); + calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); + return tok2->next; + } + const Token *expandToken(TokenList *output, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { // Not name.. if (!tok->name) { @@ -1655,63 +1697,36 @@ namespace simplecpp { // Macro parameter.. { TokenList temp(files); - if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) { - if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { - output->takeTokens(temp); - return tok->next; - } - - if (!sameline(tok, tok->next)) { - output->takeTokens(temp); - return tok->next; - } - - const std::map::const_iterator it = macros.find(temp.cback()->str()); - if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output->takeTokens(temp); - return tok->next; - } - - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { - output->takeTokens(temp); - return tok->next; - } - - TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); - - const Token *tok2 = appendTokens(&temp2, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) - return tok->next; - - output->takeTokens(temp); - output->deleteToken(output->back()); - calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); - - return tok2->next; - } + if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens); } // Macro.. const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { + std::set expandedmacros2(expandedmacros); + expandedmacros2.insert(tok->str()); + const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) - return calledMacro.expand(output, loc, tok, macros, expandedmacros); + if (!calledMacro.functionLike()) { + TokenList temp(files); + calledMacro.expand(&temp, loc, tok, macros, expandedmacros); + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); + } if (!sameline(tok, tok->next) || tok->next->op != '(') { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } TokenList tokens(files); tokens.push_back(new Token(*tok)); - const Token *tok2 = appendTokens(&tokens, tok->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, loc, tok->next, macros, expandedmacros, parametertokens); if (!tok2) { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } - calledMacro.expand(output, loc, tokens.cfront(), macros, expandedmacros); - return tok2->next; + TokenList temp(files); + calledMacro.expand(&temp, loc, tokens.cfront(), macros, expandedmacros); + return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros2, parametertokens); } else if (tok->str() == DEFINED) { @@ -1879,7 +1894,7 @@ namespace simplecpp { if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { const std::map::const_iterator it = macros.find(strAB); if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { - const Token *tok2 = appendTokens(&tokens, B->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, loc, B->next, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } From 8ab305d3c9aa5e638d9b44dd8be35e71463f53fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 2 Jul 2020 21:31:23 +0200 Subject: [PATCH 029/116] Revert "Bump simplecpp" This reverts commit eec622b515649575ef267b9af6e1954a38a5b10a. --- externals/simplecpp/simplecpp.cpp | 119 +++++++++++++----------------- 1 file changed, 52 insertions(+), 67 deletions(-) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 900e07c6e..6de395055 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -1026,15 +1026,15 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) { Token * const tok1 = tok; for (const char *op = "&^|"; *op; op++) { - const std::string* alternativeOp; + const std::string* altop; if (*op == '&') - alternativeOp = &BITAND; + altop = &BITAND; else if (*op == '|') - alternativeOp = &BITOR; + altop = &BITOR; else - alternativeOp = &XOR; + altop = &XOR; for (tok = tok1; tok && tok->op != ')'; tok = tok->next) { - if (tok->op != *op && !isAlternativeBinaryOp(tok, *alternativeOp)) + if (tok->op != *op && !isAlternativeBinaryOp(tok, *altop)) continue; if (!tok->previous || !tok->previous->number) continue; @@ -1472,7 +1472,6 @@ namespace simplecpp { } const Token *appendTokens(TokenList *tokens, - const Location &rawloc, const Token *lpar, const std::map ¯os, const std::set &expandedmacros, @@ -1484,17 +1483,17 @@ namespace simplecpp { while (sameline(lpar, tok)) { if (tok->op == '#' && sameline(tok,tok->next) && tok->next->op == '#' && sameline(tok,tok->next->next)) { // A##B => AB - tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); + tok = expandHashHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { - tok = expandHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); + tok = expandHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); } else { - if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { + if (!expandArg(tokens, tok, tok->location, macros, expandedmacros, parametertokens)) { bool expanded = false; const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { const Macro &m = it->second; if (!m.functionLike()) { - m.expand(tokens, rawloc, tok, macros, expandedmacros); + m.expand(tokens, tok->location, tok, macros, expandedmacros); expanded = true; } } @@ -1613,19 +1612,13 @@ namespace simplecpp { hashToken = hashToken->next; ++numberOfHash; } - if (numberOfHash == 4 && tok->next->location.col + 1 == tok->next->next->location.col) { + if (numberOfHash == 4) { // # ## # => ## output->push_back(newMacroToken("##", loc, isReplaced(expandedmacros))); tok = hashToken; continue; } - if (numberOfHash >= 2 && tok->location.col + 1 < tok->next->location.col) { - output->push_back(new Token(*tok)); - tok = tok->next; - continue; - } - tok = tok->next; if (tok == endToken) { output->push_back(new Token(*tok->previous)); @@ -1652,41 +1645,6 @@ namespace simplecpp { return functionLike() ? parametertokens2.back()->next : nameTokInst->next; } - const Token *recursiveExpandToken(TokenList *output, TokenList &temp, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { - if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { - output->takeTokens(temp); - return tok->next; - } - - if (!sameline(tok, tok->next)) { - output->takeTokens(temp); - return tok->next; - } - - const std::map::const_iterator it = macros.find(temp.cback()->str()); - if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output->takeTokens(temp); - return tok->next; - } - - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { - output->takeTokens(temp); - return tok->next; - } - - TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); - - const Token *tok2 = appendTokens(&temp2, loc, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) - return tok->next; - output->takeTokens(temp); - output->deleteToken(output->back()); - calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); - return tok2->next; - } - const Token *expandToken(TokenList *output, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { // Not name.. if (!tok->name) { @@ -1697,36 +1655,63 @@ namespace simplecpp { // Macro parameter.. { TokenList temp(files); - if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) - return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens); + if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) { + if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { + output->takeTokens(temp); + return tok->next; + } + + if (!sameline(tok, tok->next)) { + output->takeTokens(temp); + return tok->next; + } + + const std::map::const_iterator it = macros.find(temp.cback()->str()); + if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { + output->takeTokens(temp); + return tok->next; + } + + const Macro &calledMacro = it->second; + if (!calledMacro.functionLike()) { + output->takeTokens(temp); + return tok->next; + } + + TokenList temp2(files); + temp2.push_back(new Token(temp.cback()->str(), tok->location)); + + const Token *tok2 = appendTokens(&temp2, tok->next, macros, expandedmacros, parametertokens); + if (!tok2) + return tok->next; + + output->takeTokens(temp); + output->deleteToken(output->back()); + calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); + + return tok2->next; + } } // Macro.. const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { - std::set expandedmacros2(expandedmacros); - expandedmacros2.insert(tok->str()); - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { - TokenList temp(files); - calledMacro.expand(&temp, loc, tok, macros, expandedmacros); - return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); - } + if (!calledMacro.functionLike()) + return calledMacro.expand(output, loc, tok, macros, expandedmacros); if (!sameline(tok, tok->next) || tok->next->op != '(') { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } TokenList tokens(files); tokens.push_back(new Token(*tok)); - const Token *tok2 = appendTokens(&tokens, loc, tok->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, tok->next, macros, expandedmacros, parametertokens); if (!tok2) { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } - TokenList temp(files); - calledMacro.expand(&temp, loc, tokens.cfront(), macros, expandedmacros); - return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros2, parametertokens); + calledMacro.expand(output, loc, tokens.cfront(), macros, expandedmacros); + return tok2->next; } else if (tok->str() == DEFINED) { @@ -1894,7 +1879,7 @@ namespace simplecpp { if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { const std::map::const_iterator it = macros.find(strAB); if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { - const Token *tok2 = appendTokens(&tokens, loc, B->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, B->next, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } From d606eb5ed907b393015737308bf39e1b5642e931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 3 Jul 2020 11:42:58 +0200 Subject: [PATCH 030/116] Bump simplecpp --- externals/simplecpp/simplecpp.cpp | 123 +++++++++++++++++------------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 6de395055..c78bc8746 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -1026,15 +1026,15 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) { Token * const tok1 = tok; for (const char *op = "&^|"; *op; op++) { - const std::string* altop; + const std::string* alternativeOp; if (*op == '&') - altop = &BITAND; + alternativeOp = &BITAND; else if (*op == '|') - altop = &BITOR; + alternativeOp = &BITOR; else - altop = &XOR; + alternativeOp = &XOR; for (tok = tok1; tok && tok->op != ')'; tok = tok->next) { - if (tok->op != *op && !isAlternativeBinaryOp(tok, *altop)) + if (tok->op != *op && !isAlternativeBinaryOp(tok, *alternativeOp)) continue; if (!tok->previous || !tok->previous->number) continue; @@ -1472,7 +1472,8 @@ namespace simplecpp { } const Token *appendTokens(TokenList *tokens, - const Token *lpar, + const Location &rawloc, + const Token * const lpar, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { @@ -1483,17 +1484,17 @@ namespace simplecpp { while (sameline(lpar, tok)) { if (tok->op == '#' && sameline(tok,tok->next) && tok->next->op == '#' && sameline(tok,tok->next->next)) { // A##B => AB - tok = expandHashHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); + tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { - tok = expandHash(tokens, tok->location, tok, macros, expandedmacros, parametertokens); + tok = expandHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else { - if (!expandArg(tokens, tok, tok->location, macros, expandedmacros, parametertokens)) { + if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { bool expanded = false; const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { const Macro &m = it->second; if (!m.functionLike()) { - m.expand(tokens, tok->location, tok, macros, expandedmacros); + m.expand(tokens, rawloc, tok, macros, expandedmacros); expanded = true; } } @@ -1511,6 +1512,8 @@ namespace simplecpp { tok = tok->next; } } + for (Token *tok2 = tokens->front(); tok2; tok2 = tok2->next) + tok2->location = lpar->location; return sameline(lpar,tok) ? tok : NULL; } @@ -1612,13 +1615,19 @@ namespace simplecpp { hashToken = hashToken->next; ++numberOfHash; } - if (numberOfHash == 4) { + if (numberOfHash == 4 && tok->next->location.col + 1 == tok->next->next->location.col) { // # ## # => ## output->push_back(newMacroToken("##", loc, isReplaced(expandedmacros))); tok = hashToken; continue; } + if (numberOfHash >= 2 && tok->location.col + 1 < tok->next->location.col) { + output->push_back(new Token(*tok)); + tok = tok->next; + continue; + } + tok = tok->next; if (tok == endToken) { output->push_back(new Token(*tok->previous)); @@ -1645,6 +1654,41 @@ namespace simplecpp { return functionLike() ? parametertokens2.back()->next : nameTokInst->next; } + const Token *recursiveExpandToken(TokenList *output, TokenList &temp, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { + output->takeTokens(temp); + return tok->next; + } + + if (!sameline(tok, tok->next)) { + output->takeTokens(temp); + return tok->next; + } + + const std::map::const_iterator it = macros.find(temp.cback()->str()); + if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { + output->takeTokens(temp); + return tok->next; + } + + const Macro &calledMacro = it->second; + if (!calledMacro.functionLike()) { + output->takeTokens(temp); + return tok->next; + } + + TokenList temp2(files); + temp2.push_back(new Token(temp.cback()->str(), tok->location)); + + const Token *tok2 = appendTokens(&temp2, loc, tok->next, macros, expandedmacros, parametertokens); + if (!tok2) + return tok->next; + output->takeTokens(temp); + output->deleteToken(output->back()); + calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); + return tok2->next; + } + const Token *expandToken(TokenList *output, const Location &loc, const Token *tok, const std::map ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { // Not name.. if (!tok->name) { @@ -1655,63 +1699,36 @@ namespace simplecpp { // Macro parameter.. { TokenList temp(files); - if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) { - if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { - output->takeTokens(temp); - return tok->next; - } - - if (!sameline(tok, tok->next)) { - output->takeTokens(temp); - return tok->next; - } - - const std::map::const_iterator it = macros.find(temp.cback()->str()); - if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output->takeTokens(temp); - return tok->next; - } - - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { - output->takeTokens(temp); - return tok->next; - } - - TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); - - const Token *tok2 = appendTokens(&temp2, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) - return tok->next; - - output->takeTokens(temp); - output->deleteToken(output->back()); - calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); - - return tok2->next; - } + if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens); } // Macro.. const std::map::const_iterator it = macros.find(tok->str()); if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { + std::set expandedmacros2(expandedmacros); + expandedmacros2.insert(tok->str()); + const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) - return calledMacro.expand(output, loc, tok, macros, expandedmacros); + if (!calledMacro.functionLike()) { + TokenList temp(files); + calledMacro.expand(&temp, loc, tok, macros, expandedmacros); + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); + } if (!sameline(tok, tok->next) || tok->next->op != '(') { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } TokenList tokens(files); tokens.push_back(new Token(*tok)); - const Token *tok2 = appendTokens(&tokens, tok->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, loc, tok->next, macros, expandedmacros, parametertokens); if (!tok2) { output->push_back(newMacroToken(tok->str(), loc, true)); return tok->next; } - calledMacro.expand(output, loc, tokens.cfront(), macros, expandedmacros); - return tok2->next; + TokenList temp(files); + calledMacro.expand(&temp, loc, tokens.cfront(), macros, expandedmacros); + return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros2, parametertokens); } else if (tok->str() == DEFINED) { @@ -1879,7 +1896,7 @@ namespace simplecpp { if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { const std::map::const_iterator it = macros.find(strAB); if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { - const Token *tok2 = appendTokens(&tokens, B->next, macros, expandedmacros, parametertokens); + const Token *tok2 = appendTokens(&tokens, loc, B->next, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } From 921997c9e90909fd82b38034bada578eaba64349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 3 Jul 2020 17:50:56 +0200 Subject: [PATCH 031/116] itc.py; false negative is fixed --- test/bug-hunting/itc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/bug-hunting/itc.py b/test/bug-hunting/itc.py index df9c79abb..bd77e1780 100644 --- a/test/bug-hunting/itc.py +++ b/test/bug-hunting/itc.py @@ -38,8 +38,6 @@ def get_error_lines(filename): linenr = 176 elif linenr == 241: linenr = 242 # warn about usage - elif linenr == 266: - continue # no warning should be written ret.append(linenr) return ret From 686a6c7862975a0bc4cac20dee79ca662875bc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 3 Jul 2020 17:54:55 +0200 Subject: [PATCH 032/116] Fixed compiler warning --- lib/astutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index f1a13f961..bbf0a64a4 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1306,7 +1306,7 @@ const Token * getTokenArgumentFunction(const Token * tok, int& argn) return tok; } -const Variable* getArgumentVar(const Token* tok, int argnr) +static const Variable* getArgumentVar(const Token* tok, int argnr) { if (!tok) return nullptr; From 14df79a53f21cc5e59d7b79599ca72615e05fba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 3 Jul 2020 19:23:29 +0200 Subject: [PATCH 033/116] Travis: Use json.tool instead of jsonlint as there was some installation issues --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 104f94c51..1552c56a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,6 @@ before_install: - travis_retry python3 -m pip install --user pexpect # imported by tools/ci.py - travis_retry python3 -m pip install --user requests # imported by tools/pr.py - travis_retry python3 -m pip install --user pygments - - travis_retry sudo python3 -m pip install demjson # installs jsonlint => sudo required - travis_retry python3 -m pip install --user natsort - cp externals/z3_version_old.h externals/z3_version.h # because travis z3 version is old @@ -183,7 +182,7 @@ matrix: - make -s -j2 CXXFLAGS=-funsigned-char testrunner - ./testrunner TestSymbolDatabase # check .json files - - find . -name '*.json' -not -path '*/\.*' | xargs jsonlint -s + - find . -name '*.json' | xargs -n 1 python3 -m json.tool > /dev/null # build fuzz client - make -s -j2 CXXFLAGS="-fsanitize=address" -C oss-fuzz fuzz-client From 5cb3aacf5a72febbd6fcd0ecb782dd9016404578 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Sat, 4 Jul 2020 22:33:34 +0200 Subject: [PATCH 034/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 0dee41ecd..e4cef2575 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -9919,7 +9919,8 @@ - + + false From 24299544d4c043218a231974163be41ce6ae0f62 Mon Sep 17 00:00:00 2001 From: anoy Date: Mon, 6 Jul 2020 07:10:20 +0200 Subject: [PATCH 035/116] qt.cfg: added namespace macros --- cfg/qt.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cfg/qt.cfg b/cfg/qt.cfg index bcfa237fd..e18a3bea7 100644 --- a/cfg/qt.cfg +++ b/cfg/qt.cfg @@ -5047,11 +5047,14 @@ + + + From d5345052ab84d4910b09b5795b8f7be29b915e3d Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Tue, 7 Jul 2020 21:36:14 +0200 Subject: [PATCH 036/116] Fix #9793 (false positive, memleak with lambda) Skip scopes with lambdas (similar to how checkleakautovar does). In order to fix this when the lambda is inside a for loop, make hasInlineOrLambdaFunction() recursive. This should be what all existing users want. --- lib/checkmemoryleak.cpp | 2 ++ lib/symboldatabase.cpp | 2 ++ test/testmemleak.cpp | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/lib/checkmemoryleak.cpp b/lib/checkmemoryleak.cpp index a4dc0ee4d..43c0664e2 100644 --- a/lib/checkmemoryleak.cpp +++ b/lib/checkmemoryleak.cpp @@ -764,6 +764,8 @@ void CheckMemoryLeakStructMember::check() continue; if (var->typeEndToken()->isStandardType()) continue; + if (var->scope()->hasInlineOrLambdaFunction()) + continue; checkStructVariable(var); } } diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 07af526d9..0dcceb21d 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -4447,6 +4447,8 @@ bool Scope::hasInlineOrLambdaFunction() const // Lambda function if (s->type == Scope::eLambda) return true; + if (s->hasInlineOrLambdaFunction()) + return true; } return false; } diff --git a/test/testmemleak.cpp b/test/testmemleak.cpp index f77661372..b70b35921 100644 --- a/test/testmemleak.cpp +++ b/test/testmemleak.cpp @@ -1677,6 +1677,8 @@ private: TEST_CASE(varid_2); // #5315: Analysis confused by ((variable).attribute) notation TEST_CASE(customAllocation); + + TEST_CASE(lambdaInForLoop); // #9793 } void err() { @@ -2062,6 +2064,22 @@ private: "}", false); ASSERT_EQUALS("[test.c:7]: (error) Memory leak: abc.a\n", errout.str()); } + + void lambdaInForLoop() { // #9793 + check( + "struct S { int * p{nullptr}; };\n" + "int main()\n" + "{\n" + " S s;\n" + " s.p = new int[10];\n" + " for (int i = 0; i < 10; ++i) {\n" + " s.p[i] = []() { return 1; }();\n" + " }\n" + " delete[] s.p;\n" + " return 0;\n" + "}", true); + ASSERT_EQUALS("", errout.str()); + } }; REGISTER_TEST(TestMemleakStructMember) From 4996ec190ecf27a4bf018eb0dcd12e2a51fd550e Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Tue, 7 Jul 2020 23:46:24 +0200 Subject: [PATCH 037/116] Fix #9652 (fp memleak with function call with cast) When the first argument was (void *)(1), at the start of the second iteration, arg was pointing to the "1", which caused problems for nextArgument(), which saw the ")" as the next token and returned nullptr, signalling that there are no more arguments. Instead, save the first token in the argument, which makes nextArgument() do the right thing. --- lib/checkleakautovar.cpp | 3 ++- test/testleakautovar.cpp | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 7400f39b4..551e475ce 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -856,7 +856,8 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin } int argNr = 1; - for (const Token *arg = tokFirstArg; arg; arg = arg->nextArgument()) { + for (const Token *funcArg = tokFirstArg; funcArg; funcArg = funcArg->nextArgument()) { + const Token* arg = funcArg; if (mTokenizer->isCPP() && arg->str() == "new") { arg = arg->next(); if (Token::simpleMatch(arg, "( std :: nothrow )")) diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 28a6fa532..7aea76b06 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -184,6 +184,8 @@ private: TEST_CASE(smartPtrInContainer); // #8262 TEST_CASE(recursiveCountLimit); // #5872 #6157 #9097 + + TEST_CASE(functionCallCastConfig); // #9652 } void check(const char code[], bool cpp = false) { @@ -202,6 +204,22 @@ private: c.runChecks(&tokenizer, &settings, this); } + void check(const char code[], Settings & settings) { + // Clear the error buffer.. + errout.str(""); + + // Tokenize.. + Tokenizer tokenizer(&settings, this); + std::istringstream istr(code); + tokenizer.tokenize(istr, "test.cpp"); + + // Check for leaks.. + CheckLeakAutoVar c; + settings.checkLibrary = true; + settings.addEnabled("information"); + c.runChecks(&tokenizer, &settings, this); + } + void checkP(const char code[], bool cpp = false) { // Clear the error buffer.. errout.str(""); @@ -2023,6 +2041,31 @@ private: "}")); } + void functionCallCastConfig() { // #9652 + Settings settingsFunctionCall = settings; + + const char xmldata[] = "\n" + "\n" + " \n" + " false\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + tinyxml2::XMLDocument doc; + doc.Parse(xmldata, sizeof(xmldata)); + settingsFunctionCall.library.load(doc); + check("void test_func()\n" + "{\n" + " char * buf = malloc(4);\n" + " free_func((void *)(1), buf);\n" + "}", settingsFunctionCall); + ASSERT_EQUALS("[test.cpp:5]: (information) --check-library: Function free_func() should have / configuration\n", errout.str()); + } }; REGISTER_TEST(TestLeakAutoVar) From d973a5f8bca03a3735498b28ed8fc04afe1ea9a8 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 8 Jul 2020 13:24:07 +0200 Subject: [PATCH 038/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index e4cef2575..08184475b 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -5539,6 +5539,18 @@ + + + false + + + + + 0: + + + + false From 9b98ee4be5bd70b0b2c27fab2c55623a128da603 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 8 Jul 2020 13:25:16 +0200 Subject: [PATCH 039/116] wxwidgets.cfg: Added missing cont attribute --- cfg/wxwidgets.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 08184475b..c444d153e 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -5544,6 +5544,7 @@ false + 0: From 12033a33dc6c1ff9d09f9a67cac2890aee552ea3 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 8 Jul 2020 13:35:47 +0200 Subject: [PATCH 040/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index c444d153e..6bdb96c43 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -11203,6 +11203,21 @@ + + + + false + + + + + + + + + + From 1e679cc5d1a9a24f886f621ef946f7f91c526681 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Thu, 9 Jul 2020 21:13:54 +0200 Subject: [PATCH 041/116] Fix #9635 (FP: Memory leak with comma operator in if-statement) When checking for comparisons in if-statements, if there are comma operators in the if-statement, skip until after the last comma. --- lib/checkleakautovar.cpp | 6 +++++- test/testleakautovar.cpp | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 551e475ce..0991bfdf0 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -465,7 +465,11 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, // Recursively scan variable comparisons in condition std::stack tokens; - tokens.push(tok->next()->astOperand2()); + // Skip expressions before commas + const Token * astOperand2AfterCommas = tok->next()->astOperand2(); + while (Token::simpleMatch(astOperand2AfterCommas, ",")) + astOperand2AfterCommas = astOperand2AfterCommas->astOperand2(); + tokens.push(astOperand2AfterCommas); while (!tokens.empty()) { const Token *tok3 = tokens.top(); tokens.pop(); diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 7aea76b06..90d5d14ec 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -134,6 +134,7 @@ private: TEST_CASE(ifelse13); // #8392 TEST_CASE(ifelse14); // #9130 - if (x == (char*)NULL) TEST_CASE(ifelse15); // #9206 - if (global_ptr = malloc(1)) + TEST_CASE(ifelse16); // #9635 - if (p = malloc(4), p == NULL) // switch TEST_CASE(switch1); @@ -1484,6 +1485,26 @@ private: ASSERT_EQUALS("", errout.str()); } + void ifelse16() { // #9635 + check("void f(void) {\n" + " char *p;\n" + " if(p = malloc(4), p == NULL)\n" + " return;\n" + " free(p);\n" + " return;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void f(void) {\n" + " char *p, q;\n" + " if(p = malloc(4), q = 1, p == NULL)\n" + " return;\n" + " free(p);\n" + " return;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + void switch1() { check("void f() {\n" " char *p = 0;\n" From 5fa3d5304bbe4c506cbc93e42bdfd91ad26504f1 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Fri, 10 Jul 2020 08:20:38 +0200 Subject: [PATCH 042/116] Run dmake (#2705) --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 71651e37e..327915764 100644 --- a/Makefile +++ b/Makefile @@ -484,7 +484,7 @@ $(libcppdir)/checkuninitvar.o: lib/checkuninitvar.cpp lib/astutils.h lib/check.h $(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml/tinyxml2.h lib/astutils.h lib/check.h lib/checkunusedfunctions.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/checkunusedfunctions.o $(libcppdir)/checkunusedfunctions.cpp -$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp lib/astutils.h lib/check.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h +$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/check.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/checkunusedvar.o $(libcppdir)/checkunusedvar.cpp $(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/astutils.h lib/check.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h @@ -556,7 +556,7 @@ $(libcppdir)/timer.o: lib/timer.cpp lib/config.h lib/timer.h $(libcppdir)/token.o: lib/token.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/token.o $(libcppdir)/token.cpp -$(libcppdir)/tokenize.o: lib/tokenize.cpp lib/check.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h +$(libcppdir)/tokenize.o: lib/tokenize.cpp externals/simplecpp/simplecpp.h lib/check.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/tokenize.o $(libcppdir)/tokenize.cpp $(libcppdir)/tokenlist.o: lib/tokenlist.cpp externals/simplecpp/simplecpp.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h @@ -622,7 +622,7 @@ test/testclass.o: test/testclass.cpp externals/tinyxml/tinyxml2.h lib/check.h li test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlineparser.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h test/redirect.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testcmdlineparser.o test/testcmdlineparser.cpp -test/testcondition.o: test/testcondition.cpp externals/simplecpp/simplecpp.h externals/tinyxml/tinyxml2.h lib/check.h lib/checkcondition.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h +test/testcondition.o: test/testcondition.cpp externals/simplecpp/simplecpp.h externals/tinyxml/tinyxml2.h lib/check.h lib/checkcondition.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testcondition.o test/testcondition.cpp test/testconstructors.o: test/testconstructors.cpp lib/check.h lib/checkclass.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h @@ -679,7 +679,7 @@ test/testnullpointer.o: test/testnullpointer.cpp externals/simplecpp/simplecpp.h test/testoptions.o: test/testoptions.cpp lib/config.h lib/errorlogger.h lib/errortypes.h lib/suppressions.h test/options.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testoptions.o test/testoptions.cpp -test/testother.o: test/testother.cpp externals/simplecpp/simplecpp.h externals/tinyxml/tinyxml2.h lib/check.h lib/checkother.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h +test/testother.o: test/testother.cpp externals/simplecpp/simplecpp.h externals/tinyxml/tinyxml2.h lib/check.h lib/checkother.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testother.o test/testother.cpp test/testpath.o: test/testpath.cpp lib/config.h lib/errorlogger.h lib/errortypes.h lib/path.h lib/suppressions.h test/testsuite.h @@ -760,7 +760,7 @@ test/testunusedfunctions.o: test/testunusedfunctions.cpp lib/check.h lib/checkun test/testunusedprivfunc.o: test/testunusedprivfunc.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkclass.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testunusedprivfunc.o test/testunusedprivfunc.cpp -test/testunusedvar.o: test/testunusedvar.cpp lib/check.h lib/checkunusedvar.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h +test/testunusedvar.o: test/testunusedvar.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkunusedvar.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testunusedvar.o test/testunusedvar.cpp test/testutils.o: test/testutils.cpp lib/config.h lib/errorlogger.h lib/errortypes.h lib/suppressions.h lib/utils.h test/testsuite.h From 27841d6b811a9a7537efe943af23f99f5afb68ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 10 Jul 2020 19:24:45 +0200 Subject: [PATCH 043/116] Fixed #9795 (False positive: Local lock is not ineffective, mutex is locked in thread also.) --- lib/checkstl.cpp | 29 +-------------------------- lib/checkstl.h | 2 -- test/teststl.cpp | 51 ------------------------------------------------ 3 files changed, 1 insertion(+), 81 deletions(-) diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index ebeb7f44f..58706e2ae 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -2394,25 +2394,12 @@ void CheckStl::useStlAlgorithm() } } -static bool isMutex(const Variable* var) -{ - const Token* tok = Token::typeDecl(var->nameToken()).first; - return Token::Match(tok, "std :: mutex|recursive_mutex|timed_mutex|recursive_timed_mutex|shared_mutex"); -} - static bool isLockGuard(const Variable* var) { const Token* tok = Token::typeDecl(var->nameToken()).first; return Token::Match(tok, "std :: lock_guard|unique_lock|scoped_lock"); } -static bool isLocalMutex(const Variable* var, const Scope* scope) -{ - if (!var) - return false; - return !var->isReference() && !var->isRValueReference() && !var->isStatic() && var->scope() == scope; -} - void CheckStl::globalLockGuardError(const Token* tok) { reportError(tok, Severity::warning, @@ -2420,13 +2407,6 @@ void CheckStl::globalLockGuardError(const Token* tok) "Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.", CWE833, false); } -void CheckStl::localMutexError(const Token* tok) -{ - reportError(tok, Severity::warning, - "localMutex", - "The lock is ineffective because the mutex is locked at the same scope as the mutex itself.", CWE667, false); -} - void CheckStl::checkMutexes() { for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -2434,19 +2414,12 @@ void CheckStl::checkMutexes() const Variable* var = tok->variable(); if (!var) continue; - if (Token::Match(tok, "%var% . lock ( )")) { - if (!isMutex(var)) - continue; - if (isLocalMutex(var, tok->scope())) - localMutexError(tok); - } else if (Token::Match(tok, "%var% (|{ %var% )|}|,")) { + if (Token::Match(tok, "%var% (|{ %var% )|}|,")) { if (!isLockGuard(var)) continue; const Variable* mvar = tok->tokAt(2)->variable(); if (var->isStatic() || var->isGlobal()) globalLockGuardError(tok); - else if (isLocalMutex(mvar, tok->scope())) - localMutexError(tok); } } } diff --git a/lib/checkstl.h b/lib/checkstl.h index 9dd5e07d5..a0ae7d1e5 100644 --- a/lib/checkstl.h +++ b/lib/checkstl.h @@ -233,7 +233,6 @@ private: void useStlAlgorithmError(const Token *tok, const std::string &algoName); void globalLockGuardError(const Token *tok); - void localMutexError(const Token *tok); void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const OVERRIDE { ErrorPath errorPath; @@ -272,7 +271,6 @@ private: c.readingEmptyStlContainerError(nullptr); c.useStlAlgorithmError(nullptr, ""); c.globalLockGuardError(nullptr); - c.localMutexError(nullptr); } static std::string myName() { diff --git a/test/teststl.cpp b/test/teststl.cpp index 240e20709..e9e929a92 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -4445,28 +4445,6 @@ private: true); ASSERT_EQUALS("", errout.str()); - check("void f() {\n" - " std::mutex m;\n" - " std::lock_guard g(m);\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); - - check("void f() {\n" - " std::mutex m;\n" - " std::unique_lock g(m);\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); - - check("void f() {\n" - " std::mutex m;\n" - " std::unique_lock g(m, std::defer_lock);\n" - " std::lock(g);\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); - check("void g();\n" "void f() {\n" " static std::mutex m;\n" @@ -4477,16 +4455,6 @@ private: true); ASSERT_EQUALS("", errout.str()); - check("void g();\n" - "void f() {\n" - " std::mutex m;\n" - " m.lock();\n" - " g();\n" - " m.unlock();\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); - check("class A {\n" " std::mutex m;\n" " void f() {\n" @@ -4535,25 +4503,6 @@ private: "}\n", true); ASSERT_EQUALS("", errout.str()); - - check("std::mutex& h();\n" - "void f() {\n" - " auto m = h();\n" - " std::lock_guard g(m);\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); - - check("void g();\n" - "std::mutex& h();\n" - "void f() {\n" - " auto m = h();\n" - " m.lock();\n" - " g();\n" - " m.unlock();\n" - "}\n", - true); - ASSERT_EQUALS("[test.cpp:5]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); } }; From d8e7e9176b08261d749b2b76a957fa0a5e83adad Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Fri, 10 Jul 2020 23:33:43 +0200 Subject: [PATCH 044/116] Fix FN with known condition and sizeof cppcheck behaved differently if sizeof was to the left or right of the comparison. In order to fix this, we cannot break the while loop until all operands have been processed. --- lib/checkcondition.cpp | 5 +++-- test/testcondition.cpp | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index e15a09a06..4a5de6828 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -1457,6 +1457,7 @@ void CheckCondition::alwaysTrueFalse() // don't warn when condition checks sizeof result bool hasSizeof = false; + bool hasNonNumber = false; tokens.push(tok); while (!tokens.empty()) { const Token *tok2 = tokens.top(); @@ -1473,9 +1474,9 @@ void CheckCondition::alwaysTrueFalse() tokens.push(tok2->astOperand1()); tokens.push(tok2->astOperand2()); } else - break; + hasNonNumber = true; } - if (tokens.empty() && hasSizeof) + if (!hasNonNumber && hasSizeof) continue; alwaysTrueFalseError(tok, &tok->values().front()); diff --git a/test/testcondition.cpp b/test/testcondition.cpp index f93c5b386..a9a62f7ba 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -2987,14 +2987,17 @@ private: // Avoid FP for sizeof condition check("void f() {\n" " if (sizeof(char) != 123) {}\n" + " if (123 != sizeof(char)) {}\n" "}"); ASSERT_EQUALS("", errout.str()); check("void f() {\n" " int x = 123;\n" " if (sizeof(char) != x) {}\n" + " if (x != sizeof(char)) {}\n" "}"); - ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'sizeof(char)!=x' is always true\n", errout.str()); + ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'sizeof(char)!=x' is always true\n" + "[test.cpp:4]: (style) Condition 'x!=sizeof(char)' is always true\n", errout.str()); // Don't warn in assertions. Condition is often 'always true' by intention. // If platform,defines,etc cause an 'always false' assertion then that is not very dangerous neither From 61ccf888b31f84da6fc7495b2cdf1845e6c23b00 Mon Sep 17 00:00:00 2001 From: Ken-Patrick Lehrmann Date: Sat, 11 Jul 2020 13:42:57 +0200 Subject: [PATCH 045/116] Fix some false positives when the same expression at different places does not have the same value Typically with ``` int F(int *f); void F2(int *a, int *b) { int c = *a; F(a); // modifies *a if (b && c != *a) {} } ``` we would get the following FP: ``` [test.cpp:3] -> [test.cpp:5]: (style) The comparison 'c != *a' is always false because 'c' and '*a' represent the same value.\n ``` I guess it boils down to isSameExpression only checking that the expression is the same (in the above case, "*a" and "*a" are indeed the same), but there's not real check on the values. So the patch here is a bit hackish, and we still have false negatives in cases with dereferenced pointers. --- lib/checkother.cpp | 4 +++- test/testother.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index d39daaa19..96ed875ee 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -2014,7 +2014,9 @@ void CheckOther::checkDuplicateExpression() if (tok->isOp() && tok->astOperand1() && !Token::Match(tok, "+|*|<<|>>|+=|*=|<<=|>>=")) { if (Token::Match(tok, "==|!=|-") && astIsFloat(tok->astOperand1(), true)) continue; - const bool followVar = !isConstVarExpression(tok) || Token::Match(tok, "%comp%|%oror%|&&"); + const bool pointerDereference = (tok->astOperand1() && tok->astOperand1()->isUnaryOp("*")) || + (tok->astOperand2() && tok->astOperand2()->isUnaryOp("*")); + const bool followVar = (!isConstVarExpression(tok) || Token::Match(tok, "%comp%|%oror%|&&")) && !pointerDereference; if (isSameExpression(mTokenizer->isCPP(), true, tok->astOperand1(), diff --git a/test/testother.cpp b/test/testother.cpp index 773c20a8a..b6463cbac 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -243,6 +243,8 @@ private: TEST_CASE(unusedVariableValueTemplate); // #8994 TEST_CASE(moduloOfOne); + + TEST_CASE(sameExpressionPointers); } void check(const char code[], const char *filename = nullptr, bool experimental = false, bool inconclusive = true, bool runSimpleChecks=true, bool verbose=false, Settings* settings = nullptr) { @@ -5484,6 +5486,24 @@ private: ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (style) The comparison 'val < 0' is always false.\n" "[test.cpp:2] -> [test.cpp:4]: (style) The comparison 'val > 0' is always false.\n", errout.str()); + check("void f() {\n" + " int val = 0;\n" + " int *p = &val;n" + " val = 1;\n" + " if (*p < 0) continue;\n" + " if ((*p > 0)) {}\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + + check("void f() {\n" + " int val = 0;\n" + " int *p = &val;n" + " if (*p < 0) continue;\n" + " if ((*p > 0)) {}\n" + "}\n"); + TODO_ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (style) The comparison '*p < 0' is always false.\n" + "[test.cpp:2] -> [test.cpp:4]: (style) The comparison '*p > 0' is always false.\n", "", errout.str()); + check("void f() {\n" " int val = 0;\n" " if (val < 0) {\n" @@ -8701,6 +8721,15 @@ private: ASSERT_EQUALS("", errout.str()); } + void sameExpressionPointers() { + check("int f(int *i);\n" + "void g(int *a, int *b) {\n" + " int c = *a;\n" + " f(a);\n" + " if (b && c != *a) {}\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } }; REGISTER_TEST(TestOther) From caabe56f14ad1daf2fa8eadeb61629d3a1d1c939 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 12 Jul 2020 21:31:53 -0500 Subject: [PATCH 046/116] Handle FPs: mutexes being locked at different scopes --- lib/checkstl.cpp | 9 +++++++++ test/teststl.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index ebeb7f44f..15bf0ac70 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -2430,19 +2430,28 @@ void CheckStl::localMutexError(const Token* tok) void CheckStl::checkMutexes() { for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { + std::set checkedVars; for (const Token *tok = function->bodyStart; tok != function->bodyEnd; tok = tok->next()) { + if (!Token::Match(tok, "%var%")) + continue; const Variable* var = tok->variable(); if (!var) continue; if (Token::Match(tok, "%var% . lock ( )")) { if (!isMutex(var)) continue; + if (!checkedVars.insert(var->declarationId()).second) + continue; if (isLocalMutex(var, tok->scope())) localMutexError(tok); } else if (Token::Match(tok, "%var% (|{ %var% )|}|,")) { if (!isLockGuard(var)) continue; const Variable* mvar = tok->tokAt(2)->variable(); + if (!mvar) + continue; + if (!checkedVars.insert(mvar->declarationId()).second) + continue; if (var->isStatic() || var->isGlobal()) globalLockGuardError(tok); else if (isLocalMutex(mvar, tok->scope())) diff --git a/test/teststl.cpp b/test/teststl.cpp index 240e20709..931fdc73f 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -4554,6 +4554,36 @@ private: "}\n", true); ASSERT_EQUALS("[test.cpp:5]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + + check("void foo();\n" + "void bar();\n" + "void f() {\n" + " std::mutex m;\n" + " std::thread t([&m](){\n" + " m.lock();\n" + " foo();\n" + " m.unlock();\n" + " });\n" + " m.lock();\n" + " bar();\n" + " m.unlock();\n" + "}\n", + true); + ASSERT_EQUALS("", errout.str()); + + check("void foo();\n" + "void bar();\n" + "void f() {\n" + " std::mutex m;\n" + " std::thread t([&m](){\n" + " std::unique_lock g{m};\n" + " foo();\n" + " });\n" + " std::unique_lock g{m};\n" + " bar();\n" + "}\n", + true); + ASSERT_EQUALS("", errout.str()); } }; From 0009b4c8af16f500073d7a9ed88aba78534bca05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2020 11:18:51 +0200 Subject: [PATCH 047/116] test/bug-hunting/cve.py: Add --slow argument to check slow packages --- test/bug-hunting/cve.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/test/bug-hunting/cve.py b/test/bug-hunting/cve.py index 18c524358..b2fa0f032 100644 --- a/test/bug-hunting/cve.py +++ b/test/bug-hunting/cve.py @@ -1,6 +1,7 @@ # Test if --bug-hunting works using cve tests import glob +import logging import os import re import shutil @@ -14,26 +15,32 @@ else: CPPCHECK_PATH = '../../cppcheck' TEST_SUITE = 'cve' +slow = '--slow' in sys.argv + +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s', datefmt='%H:%M:%S') + def test(test_folder): - print(test_folder) + logging.info(test_folder) cmd_file = os.path.join(test_folder, 'cmd.txt') expected_file = os.path.join(test_folder, 'expected.txt') - cmd = [CPPCHECK_PATH, + cmd = ['nice', + CPPCHECK_PATH, '-D__GNUC__', '--bug-hunting', '--inconclusive', '--platform=unix64', '--template={file}:{line}:{id}', - '-rp=' + test_folder, - test_folder] + '-rp=' + test_folder] if os.path.isfile(cmd_file): for line in open(cmd_file, 'rt'): if len(line) > 1: cmd.append(line.strip()) + cmd.append(test_folder) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) comm = p.communicate() stdout = comm[0].decode(encoding='utf-8', errors='ignore') @@ -49,10 +56,20 @@ def test(test_folder): print(stderr) sys.exit(1) -if len(sys.argv) > 1: +if (slow is False) and len(sys.argv) > 1: test(sys.argv[1]) sys.exit(0) +SLOW = [] + for test_folder in sorted(glob.glob(TEST_SUITE + '/CVE*')): + if slow is False: + check = False + for s in SLOW: + if s in test_folder: + check = True + if check is True: + logging.info('skipping %s', test_folder) + continue test(test_folder) From 4465d033f6602b55436f255cab24f2fe28517d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2020 11:43:11 +0200 Subject: [PATCH 048/116] Bug hunting; When passing uninitialized data to unknown function it is inconclusive if that would be a problem for the function or not --- lib/bughuntingchecks.cpp | 28 ++++++++++++++++++++-------- test/testbughuntingchecks.cpp | 3 ++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index c674d50c4..87ca9ee34 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -254,6 +254,7 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: } // Uninitialized function argument + bool inconclusive = false; if (Token::Match(tok->astParent(), "[,(]")) { const Token *parent = tok->astParent(); int count = 0; @@ -271,10 +272,16 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: const Variable *argvar = parent->astOperand1()->function()->getArgumentVar(count); if (argvar && argvar->isReference() && !argvar->isConst()) return; - if (uninitData && argvar && !argvar->isConst()) - return; - if (!uninitStructMember.empty() && dataBase->isC() && argvar && !argvar->isConst()) - return; + if (uninitData && argvar && !argvar->isConst()) { + if (parent->astOperand1()->function()->hasBody()) + return; + inconclusive = true; + } + if (!uninitStructMember.empty() && dataBase->isC() && argvar && !argvar->isConst()) { + if (parent->astOperand1()->function()->hasBody()) + return; + inconclusive = true; + } } else if (uninitData) { if (dataBase->settings->library.getFunction(parent->astOperand1())) return; @@ -285,6 +292,9 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: return; } + if (inconclusive && !dataBase->settings->inconclusive) + return; + // Avoid FP for array declaration const Token *parent = tok->astParent(); while (parent && parent->str() == "[") @@ -292,13 +302,15 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (!parent) return; + const std::string inconclusiveMessage(inconclusive ? ". It is inconclusive if there would be a problem in the function call." : ""); + if (!uninitStructMember.empty()) { dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingUninitStructMember", - "Cannot determine that '" + tok->expressionString() + "." + uninitStructMember + "' is initialized", + "Cannot determine that '" + tok->expressionString() + "." + uninitStructMember + "' is initialized" + inconclusiveMessage, CWE_USE_OF_UNINITIALIZED_VARIABLE, - false, + inconclusive, value.type == ExprEngine::ValueType::BailoutValue); return; } @@ -310,9 +322,9 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingUninit", - "Cannot determine that '" + uninitexpr + "' is initialized", + "Cannot determine that '" + uninitexpr + "' is initialized" + inconclusiveMessage, CWE_USE_OF_UNINITIALIZED_VARIABLE, - false, + inconclusive, value.type == ExprEngine::ValueType::BailoutValue); } diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp index 9b1f35c71..bfb62e985 100644 --- a/test/testbughuntingchecks.cpp +++ b/test/testbughuntingchecks.cpp @@ -33,6 +33,7 @@ private: void run() OVERRIDE { #ifdef USE_Z3 + settings.inconclusive = true; LOAD_LIB_2(settings.library, "std.cfg"); TEST_CASE(uninit); TEST_CASE(uninit_array); @@ -86,7 +87,7 @@ private: check("char foo(char id[]);\n" "void bar() { char data[10]; foo(data); }"); - ASSERT_EQUALS("", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error, inconclusive) Cannot determine that 'data[0]' is initialized. It is inconclusive if there would be a problem in the function call.\n", errout.str()); check("void foo(int *p) { if (p) *p=0; }"); ASSERT_EQUALS("", errout.str()); From 9ff8adcc40c618df5f0561fb52f1aa53850f9874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2020 12:31:59 +0200 Subject: [PATCH 049/116] GUI: Remove message box when saving results even though there are no results, to prevent problems with autosave --- gui/resultsview.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index eae38141d..1dc0a294c 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -171,13 +171,6 @@ void ResultsView::updateFromOldReport(const QString &filename) const void ResultsView::save(const QString &filename, Report::Type type) const { - if (!hasResults()) { - QMessageBox msgBox; - msgBox.setText(tr("No errors found, nothing to save.")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.exec(); - } - Report *report = nullptr; switch (type) { From 450bdfedf3fe0773fd666bfd91226211d8fb8d47 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Jul 2020 12:40:01 -0500 Subject: [PATCH 050/116] Fix FP of duplicateCondition when modifying the this variable --- lib/astutils.cpp | 31 ++++++++++++++++++++++++++- lib/astutils.h | 6 ++++++ lib/checkcondition.cpp | 6 ++++++ test/testcondition.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 712847e46..7d636c829 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -539,7 +539,10 @@ bool exprDependsOnThis(const Token* expr, nonneg int depth) // calling nonstatic method? if (Token::Match(expr->previous(), "!!:: %name% (") && expr->function() && expr->function()->nestedIn && expr->function()->nestedIn->isClassOrStruct()) { // is it a method of this? - const Scope *nestedIn = expr->scope()->functionOf; + const Scope *fScope = expr->scope(); + while (!fScope->functionOf && fScope->nestedIn) + fScope = fScope->nestedIn; + const Scope *nestedIn = fScope->functionOf; if (nestedIn && nestedIn->function) nestedIn = nestedIn->function->token->scope(); while (nestedIn && nestedIn != expr->function()->nestedIn) { @@ -1562,6 +1565,32 @@ bool isVariablesChanged(const Token* start, return false; } +bool isThisChanged(const Token* start, + const Token* end, + int indirect, + const Settings* settings, + bool cpp) +{ + for (const Token* tok = start; tok != end; tok = tok->next()) { + if (!exprDependsOnThis(tok)) + continue; + if (Token::Match(tok->previous(), "%name% (")) { + if (tok->previous()->function()) { + if (!tok->previous()->function()->isConst()) + return true; + else + continue; + } else if (!tok->previous()->isKeyword()){ + return true; + } + + } + if (isVariableChanged(tok, indirect, settings, cpp)) + return true; + } + return false; +} + int numberOfArguments(const Token *start) { int arguments=0; diff --git a/lib/astutils.h b/lib/astutils.h index 3cd883f4d..20e402d42 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -192,6 +192,12 @@ bool isVariablesChanged(const Token* start, const Settings* settings, bool cpp); +bool isThisChanged(const Token* start, + const Token* end, + int indirect, + const Settings* settings, + bool cpp); + const Token* findVariableChanged(const Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20); Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20); diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index ec9b33c39..17e3e026e 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -453,6 +453,12 @@ void CheckCondition::duplicateCondition() bool modified = false; visitAstNodes(cond1, [&](const Token *tok3) { + if (exprDependsOnThis(tok3)) { + if (isThisChanged(scope.classDef->next(), cond2, false, mSettings, mTokenizer->isCPP())) { + modified = true; + return ChildrenToVisit::done; + } + } if (tok3->varId() > 0 && isVariableChanged(scope.classDef->next(), cond2, tok3->varId(), false, mSettings, mTokenizer->isCPP())) { modified = true; diff --git a/test/testcondition.cpp b/test/testcondition.cpp index d02abd8ad..05c6ba239 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -3668,6 +3668,54 @@ private: " if (a.b) {}\n" "}\n"); ASSERT_EQUALS("", errout.str()); + + check("struct A {\n" + " int a;\n" + " void b() const {\n" + " return a == 1;\n" + " }\n" + " void c();\n" + " void d() {\n" + " if(b()) {\n" + " c();\n" + " }\n" + " if (b()) {\n" + " a = 3;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + + check("struct A {\n" + " int a;\n" + " void b() const {\n" + " return a == 1;\n" + " }\n" + " void d() {\n" + " if(b()) {\n" + " a = 2;\n" + " }\n" + " if (b()) {\n" + " a = 3;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + + check("struct A {\n" + " int a;\n" + " void b() const {\n" + " return a == 1;\n" + " }\n" + " void d() {\n" + " if(b()) {\n" + " }\n" + " if (b()) {\n" + " a = 3;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:9]: (style) The if condition is the same as the previous if condition\n", errout.str()); } void checkInvalidTestForOverflow() { From 36b9e545ac26d3b8c40bd1548f54ed5ca67e84f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2020 20:23:44 +0200 Subject: [PATCH 051/116] Bug hunting; more bailout warnings in uninit check --- lib/bughuntingchecks.cpp | 31 ++++++++++++++++++++----------- lib/exprengine.h | 2 -- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 87ca9ee34..39062cfac 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -87,7 +87,7 @@ static void divByZero(const Token *tok, const ExprEngine::Value &value, ExprEngi return; if (tok->isImpossibleIntValue(0)) return; - if (value.isUninit()) + if (value.isUninit() && value.type != ExprEngine::ValueType::BailoutValue) return; float f = getKnownFloatValue(tok, 0.0f); if (f > 0.0f || f < 0.0f) @@ -216,22 +216,16 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (value.type == ExprEngine::ValueType::BailoutValue) { if (tok->hasKnownValue()) return; - if (tok->function()) - return; - if (Token::Match(tok, "<<|>>|,")) - // Only warn about the operands + if (!tok->variable()) + // FIXME return; + // lhs for scope operator if (Token::Match(tok, "%name% ::")) return; if (tok->astParent()->str() == "::" && tok == tok->astParent()->astOperand1()) return; - if (tok->str() == "(") - // cast: result is not uninitialized if expression is initialized - // function: does not return a uninitialized value - return; - // Containers are not uninitialized std::vector tokens{tok, tok->astOperand1(), tok->astOperand2()}; if (Token::Match(tok->previous(), ". %name%")) @@ -242,15 +236,30 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: } const Variable *var = tok->variable(); + if (var && var->nameToken() == tok) + return; + if (var && !var->isLocal()) + return; // FIXME if (var && !var->isPointer()) { if (!var->isLocal() || var->isStatic()) return; } - if (var && (Token::Match(var->nameToken(), "%name% =") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId()))) + if (var && (Token::Match(var->nameToken(), "%name% [=:]") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId()))) return; if (var && var->nameToken() == tok) return; + // Are there unconditional assignment? + if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId())) + return; + for (const Token *prev = tok->previous(); prev; prev = prev->previous()) { + if (!precedes(var->nameToken(), prev)) + break; + if (prev->str() == "}") + prev = prev->link(); + if (Token::Match(prev, "%varid% =", tok->varId())) + return; + } } // Uninitialized function argument diff --git a/lib/exprengine.h b/lib/exprengine.h index cff8d536f..46966d610 100644 --- a/lib/exprengine.h +++ b/lib/exprengine.h @@ -317,11 +317,9 @@ namespace ExprEngine { bool isEqual(DataBase * /*dataBase*/, int /*value*/) const OVERRIDE { return true; } - /* FIXME: This is too noisy bool isUninit() const OVERRIDE { return true; } - */ }; typedef std::function Callback; From cb221e970d4da79f5ea6088cc46baad19e018032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2020 20:42:53 +0200 Subject: [PATCH 052/116] Bug hunting: Add test case for CVE-2019-12977. Passing uninitialized struct to function --- test/bug-hunting/cve/CVE-2019-12977/cmd.txt | 2 + .../cve/CVE-2019-12977/expected.txt | 1 + test/bug-hunting/cve/CVE-2019-12977/jp2.c | 1106 +++++++++++++++++ 3 files changed, 1109 insertions(+) create mode 100644 test/bug-hunting/cve/CVE-2019-12977/cmd.txt create mode 100644 test/bug-hunting/cve/CVE-2019-12977/expected.txt create mode 100644 test/bug-hunting/cve/CVE-2019-12977/jp2.c diff --git a/test/bug-hunting/cve/CVE-2019-12977/cmd.txt b/test/bug-hunting/cve/CVE-2019-12977/cmd.txt new file mode 100644 index 000000000..7000b0c5f --- /dev/null +++ b/test/bug-hunting/cve/CVE-2019-12977/cmd.txt @@ -0,0 +1,2 @@ +-DMAGICKCORE_LIBOPENJP2_DELEGATE + diff --git a/test/bug-hunting/cve/CVE-2019-12977/expected.txt b/test/bug-hunting/cve/CVE-2019-12977/expected.txt new file mode 100644 index 000000000..4e0e618ff --- /dev/null +++ b/test/bug-hunting/cve/CVE-2019-12977/expected.txt @@ -0,0 +1 @@ +jp2.c:865:bughuntingUninit diff --git a/test/bug-hunting/cve/CVE-2019-12977/jp2.c b/test/bug-hunting/cve/CVE-2019-12977/jp2.c new file mode 100644 index 000000000..d4d68ab51 --- /dev/null +++ b/test/bug-hunting/cve/CVE-2019-12977/jp2.c @@ -0,0 +1,1106 @@ +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% JJJ PPPP 222 % +% J P P 2 2 % +% J PPPP 22 % +% J J P 2 % +% JJ P 22222 % +% % +% % +% Read/Write JPEG-2000 Image Format % +% % +% Cristy % +% Nathan Brown % +% June 2001 % +% % +% % +% Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization % +% dedicated to making software imaging solutions freely available. % +% % +% You may not use this file except in compliance with the License. You may % +% obtain a copy of the License at % +% % +% https://imagemagick.org/script/license.php % +% % +% Unless required by applicable law or agreed to in writing, software % +% distributed under the License is distributed on an "AS IS" BASIS, % +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % +% See the License for the specific language governing permissions and % +% limitations under the License. % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% +*/ + +/* + Include declarations. +*/ +#include "MagickCore/studio.h" +#include "MagickCore/artifact.h" +#include "MagickCore/attribute.h" +#include "MagickCore/blob.h" +#include "MagickCore/blob-private.h" +#include "MagickCore/cache.h" +#include "MagickCore/colorspace.h" +#include "MagickCore/colorspace-private.h" +#include "MagickCore/color.h" +#include "MagickCore/color-private.h" +#include "MagickCore/exception.h" +#include "MagickCore/exception-private.h" +#include "MagickCore/image.h" +#include "MagickCore/image-private.h" +#include "MagickCore/list.h" +#include "MagickCore/magick.h" +#include "MagickCore/memory_.h" +#include "MagickCore/monitor.h" +#include "MagickCore/monitor-private.h" +#include "MagickCore/option.h" +#include "MagickCore/pixel-accessor.h" +#include "MagickCore/profile.h" +#include "MagickCore/property.h" +#include "MagickCore/quantum-private.h" +#include "MagickCore/resource_.h" +#include "MagickCore/semaphore.h" +#include "MagickCore/static.h" +#include "MagickCore/statistic.h" +#include "MagickCore/string_.h" +#include "MagickCore/string-private.h" +#include "MagickCore/module.h" +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) +#include +#endif + +/* + Forward declarations. +*/ +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) +static MagickBooleanType + WriteJP2Image(const ImageInfo *,Image *,ExceptionInfo *); +#endif + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% I s J 2 K % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% IsJ2K() returns MagickTrue if the image format type, identified by the +% magick string, is J2K. +% +% The format of the IsJ2K method is: +% +% MagickBooleanType IsJ2K(const unsigned char *magick,const size_t length) +% +% A description of each parameter follows: +% +% o magick: compare image format pattern against these bytes. +% +% o length: Specifies the length of the magick string. +% +*/ +static MagickBooleanType IsJ2K(const unsigned char *magick,const size_t length) +{ + if (length < 4) + return(MagickFalse); + if (memcmp(magick,"\xff\x4f\xff\x51",4) == 0) + return(MagickTrue); + return(MagickFalse); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% I s J P 2 % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% IsJP2() returns MagickTrue if the image format type, identified by the +% magick string, is JP2. +% +% The format of the IsJP2 method is: +% +% MagickBooleanType IsJP2(const unsigned char *magick,const size_t length) +% +% A description of each parameter follows: +% +% o magick: compare image format pattern against these bytes. +% +% o length: Specifies the length of the magick string. +% +*/ +static MagickBooleanType IsJP2(const unsigned char *magick,const size_t length) +{ + if (length < 4) + return(MagickFalse); + if (memcmp(magick,"\x0d\x0a\x87\x0a",4) == 0) + return(MagickTrue); + if (length < 12) + return(MagickFalse); + if (memcmp(magick,"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a",12) == 0) + return(MagickTrue); + return(MagickFalse); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% R e a d J P 2 I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ReadJP2Image() reads a JPEG 2000 Image file (JP2) or JPEG 2000 +% codestream (JPC) image file and returns it. It allocates the memory +% necessary for the new Image structure and returns a pointer to the new +% image or set of images. +% +% JP2 support is originally written by Nathan Brown, nathanbrown@letu.edu. +% +% The format of the ReadJP2Image method is: +% +% Image *ReadJP2Image(const ImageInfo *image_info, +% ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image_info: the image info. +% +% o exception: return any errors or warnings in this structure. +% +*/ +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) +static void JP2ErrorHandler(const char *message,void *client_data) +{ + ExceptionInfo + *exception; + + exception=(ExceptionInfo *) client_data; + (void) ThrowMagickException(exception,GetMagickModule(),CoderError, + message,"`%s'","OpenJP2"); +} + +static OPJ_SIZE_T JP2ReadHandler(void *buffer,OPJ_SIZE_T length,void *context) +{ + Image + *image; + + ssize_t + count; + + image=(Image *) context; + count=ReadBlob(image,(ssize_t) length,(unsigned char *) buffer); + if (count == 0) + return((OPJ_SIZE_T) -1); + return((OPJ_SIZE_T) count); +} + +static OPJ_BOOL JP2SeekHandler(OPJ_OFF_T offset,void *context) +{ + Image + *image; + + image=(Image *) context; + return(SeekBlob(image,offset,SEEK_SET) < 0 ? OPJ_FALSE : OPJ_TRUE); +} + +static OPJ_OFF_T JP2SkipHandler(OPJ_OFF_T offset,void *context) +{ + Image + *image; + + image=(Image *) context; + return(SeekBlob(image,offset,SEEK_CUR) < 0 ? -1 : offset); +} + +static void JP2WarningHandler(const char *message,void *client_data) +{ + ExceptionInfo + *exception; + + exception=(ExceptionInfo *) client_data; + (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning, + message,"`%s'","OpenJP2"); +} + +static OPJ_SIZE_T JP2WriteHandler(void *buffer,OPJ_SIZE_T length,void *context) +{ + Image + *image; + + ssize_t + count; + + image=(Image *) context; + count=WriteBlob(image,(ssize_t) length,(unsigned char *) buffer); + return((OPJ_SIZE_T) count); +} + +static Image *ReadJP2Image(const ImageInfo *image_info,ExceptionInfo *exception) +{ + const char + *option; + + Image + *image; + + int + jp2_status; + + MagickBooleanType + status; + + opj_codec_t + *jp2_codec; + + opj_dparameters_t + parameters; + + opj_image_t + *jp2_image; + + opj_stream_t + *jp2_stream; + + register ssize_t + i; + + ssize_t + y; + + unsigned char + sans[4]; + + /* + Open image file. + */ + assert(image_info != (const ImageInfo *) NULL); + assert(image_info->signature == MagickCoreSignature); + if (image_info->debug != MagickFalse) + (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", + image_info->filename); + assert(exception != (ExceptionInfo *) NULL); + assert(exception->signature == MagickCoreSignature); + image=AcquireImage(image_info,exception); + status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); + if (status == MagickFalse) + { + image=DestroyImageList(image); + return((Image *) NULL); + } + /* + Initialize JP2 codec. + */ + if (ReadBlob(image,4,sans) != 4) + { + image=DestroyImageList(image); + return((Image *) NULL); + } + (void) SeekBlob(image,SEEK_SET,0); + if (LocaleCompare(image_info->magick,"JPT") == 0) + jp2_codec=opj_create_decompress(OPJ_CODEC_JPT); + else + if (IsJ2K(sans,4) != MagickFalse) + jp2_codec=opj_create_decompress(OPJ_CODEC_J2K); + else + jp2_codec=opj_create_decompress(OPJ_CODEC_JP2); + opj_set_warning_handler(jp2_codec,JP2WarningHandler,exception); + opj_set_error_handler(jp2_codec,JP2ErrorHandler,exception); + opj_set_default_decoder_parameters(¶meters); + option=GetImageOption(image_info,"jp2:reduce-factor"); + if (option != (const char *) NULL) + parameters.cp_reduce=StringToInteger(option); + option=GetImageOption(image_info,"jp2:quality-layers"); + if (option != (const char *) NULL) + parameters.cp_layer=StringToInteger(option); + if (opj_setup_decoder(jp2_codec,¶meters) == 0) + { + opj_destroy_codec(jp2_codec); + ThrowReaderException(DelegateError,"UnableToManageJP2Stream"); + } + jp2_stream=opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE,1); + opj_stream_set_read_function(jp2_stream,JP2ReadHandler); + opj_stream_set_write_function(jp2_stream,JP2WriteHandler); + opj_stream_set_seek_function(jp2_stream,JP2SeekHandler); + opj_stream_set_skip_function(jp2_stream,JP2SkipHandler); + opj_stream_set_user_data(jp2_stream,image,NULL); + opj_stream_set_user_data_length(jp2_stream,GetBlobSize(image)); + if (opj_read_header(jp2_stream,jp2_codec,&jp2_image) == 0) + { + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); + } + jp2_status=OPJ_TRUE; + if (image->ping == MagickFalse) + { + if ((image->columns != 0) && (image->rows != 0)) + /* + Extract an area from the image. + */ + jp2_status=opj_set_decode_area(jp2_codec,jp2_image, + (OPJ_INT32) image->extract_info.x,(OPJ_INT32) image->extract_info.y, + (OPJ_INT32) (image->extract_info.x+(ssize_t) image->columns), + (OPJ_INT32) (image->extract_info.y+(ssize_t) image->rows)); + else + jp2_status=opj_set_decode_area(jp2_codec,jp2_image,0,0, + jp2_image->comps[0].w,jp2_image->comps[0].h); + if (jp2_status == OPJ_FALSE) + { + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); + } + } + if ((AcquireMagickResource(WidthResource,(size_t) jp2_image->comps[0].w) == MagickFalse) || + (AcquireMagickResource(HeightResource,(size_t) jp2_image->comps[0].h) == MagickFalse)) + { + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); + } + if ((image_info->number_scenes != 0) && (image_info->scene != 0)) + jp2_status=opj_get_decoded_tile(jp2_codec,jp2_stream,jp2_image, + (unsigned int) image_info->scene-1); + else + if (image->ping == MagickFalse) + { + jp2_status=opj_decode(jp2_codec,jp2_stream,jp2_image); + if (jp2_status != OPJ_FALSE) + jp2_status=opj_end_decompress(jp2_codec,jp2_stream); + } + if (jp2_status == OPJ_FALSE) + { + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); + } + opj_stream_destroy(jp2_stream); + for (i=0; i < (ssize_t) jp2_image->numcomps; i++) + { + if ((jp2_image->comps[0].dx == 0) || (jp2_image->comps[0].dy == 0) || + (jp2_image->comps[0].prec != jp2_image->comps[i].prec) || + (jp2_image->comps[0].sgnd != jp2_image->comps[i].sgnd) || + ((image->ping == MagickFalse) && (jp2_image->comps[i].data == NULL))) + { + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowReaderException(CoderError,"IrregularChannelGeometryNotSupported") + } + } + /* + Convert JP2 image. + */ + image->columns=(size_t) jp2_image->comps[0].w; + image->rows=(size_t) jp2_image->comps[0].h; + image->depth=jp2_image->comps[0].prec; + image->compression=JPEG2000Compression; + if (jp2_image->numcomps == 1) + SetImageColorspace(image,GRAYColorspace,exception); + else + if (jp2_image->color_space == 2) + { + SetImageColorspace(image,GRAYColorspace,exception); + if (jp2_image->numcomps > 1) + image->alpha_trait=BlendPixelTrait; + } + else + if (jp2_image->color_space == 3) + SetImageColorspace(image,Rec601YCbCrColorspace,exception); + if (jp2_image->numcomps > 3) + image->alpha_trait=BlendPixelTrait; + if (jp2_image->icc_profile_buf != (unsigned char *) NULL) + { + StringInfo + *profile; + + profile=BlobToStringInfo(jp2_image->icc_profile_buf, + jp2_image->icc_profile_len); + if (profile != (StringInfo *) NULL) + { + SetImageProfile(image,"icc",profile,exception); + profile=DestroyStringInfo(profile); + } + } + if (image->ping != MagickFalse) + { + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + return(GetFirstImageInList(image)); + } + status=SetImageExtent(image,image->columns,image->rows,exception); + if (status == MagickFalse) + { + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + return(DestroyImageList(image)); + } + for (y=0; y < (ssize_t) image->rows; y++) + { + register Quantum + *magick_restrict q; + + register ssize_t + x; + + q=GetAuthenticPixels(image,0,y,image->columns,1,exception); + if (q == (Quantum *) NULL) + break; + for (x=0; x < (ssize_t) image->columns; x++) + { + for (i=0; i < (ssize_t) jp2_image->numcomps; i++) + { + double + pixel, + scale; + + scale=QuantumRange/(double) ((1UL << jp2_image->comps[i].prec)-1); + pixel=scale*(jp2_image->comps[i].data[y/jp2_image->comps[i].dy* + image->columns/jp2_image->comps[i].dx+x/jp2_image->comps[i].dx]+ + (jp2_image->comps[i].sgnd ? 1UL << (jp2_image->comps[i].prec-1) : 0)); + switch (i) + { + case 0: + { + if (jp2_image->numcomps == 1) + { + SetPixelGray(image,ClampToQuantum(pixel),q); + SetPixelAlpha(image,OpaqueAlpha,q); + break; + } + SetPixelRed(image,ClampToQuantum(pixel),q); + SetPixelGreen(image,ClampToQuantum(pixel),q); + SetPixelBlue(image,ClampToQuantum(pixel),q); + SetPixelAlpha(image,OpaqueAlpha,q); + break; + } + case 1: + { + if (jp2_image->numcomps == 2) + { + SetPixelAlpha(image,ClampToQuantum(pixel),q); + break; + } + SetPixelGreen(image,ClampToQuantum(pixel),q); + break; + } + case 2: + { + SetPixelBlue(image,ClampToQuantum(pixel),q); + break; + } + case 3: + { + SetPixelAlpha(image,ClampToQuantum(pixel),q); + break; + } + } + } + q+=GetPixelChannels(image); + } + if (SyncAuthenticPixels(image,exception) == MagickFalse) + break; + status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y, + image->rows); + if (status == MagickFalse) + break; + } + /* + Free resources. + */ + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + (void) CloseBlob(image); + if ((image_info->number_scenes != 0) && (image_info->scene != 0)) + AppendImageToList(&image,CloneImage(image,0,0,MagickTrue,exception)); + return(GetFirstImageInList(image)); +} +#endif + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% R e g i s t e r J P 2 I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% RegisterJP2Image() adds attributes for the JP2 image format to the list of +% supported formats. The attributes include the image format tag, a method +% method to read and/or write the format, whether the format supports the +% saving of more than one frame to the same file or blob, whether the format +% supports native in-memory I/O, and a brief description of the format. +% +% The format of the RegisterJP2Image method is: +% +% size_t RegisterJP2Image(void) +% +*/ +ModuleExport size_t RegisterJP2Image(void) +{ + char + version[MagickPathExtent]; + + MagickInfo + *entry; + + *version='\0'; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + (void) FormatLocaleString(version,MagickPathExtent,"%s",opj_version()); +#endif + entry=AcquireMagickInfo("JP2","JP2","JPEG-2000 File Format Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJP2; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + entry=AcquireMagickInfo("JP2","J2C","JPEG-2000 Code Stream Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJ2K; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + entry=AcquireMagickInfo("JP2","J2K","JPEG-2000 Code Stream Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJ2K; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + entry=AcquireMagickInfo("JP2","JPM","JPEG-2000 File Format Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJP2; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + entry=AcquireMagickInfo("JP2","JPT","JPEG-2000 File Format Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJP2; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + entry=AcquireMagickInfo("JP2","JPC","JPEG-2000 Code Stream Syntax"); + if (*version != '\0') + entry->version=ConstantString(version); + entry->mime_type=ConstantString("image/jp2"); + entry->magick=(IsImageFormatHandler *) IsJP2; + entry->flags^=CoderAdjoinFlag; + entry->flags|=CoderDecoderSeekableStreamFlag; + entry->flags|=CoderEncoderSeekableStreamFlag; +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) + entry->decoder=(DecodeImageHandler *) ReadJP2Image; + entry->encoder=(EncodeImageHandler *) WriteJP2Image; +#endif + (void) RegisterMagickInfo(entry); + return(MagickImageCoderSignature); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% U n r e g i s t e r J P 2 I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% UnregisterJP2Image() removes format registrations made by the JP2 module +% from the list of supported formats. +% +% The format of the UnregisterJP2Image method is: +% +% UnregisterJP2Image(void) +% +*/ +ModuleExport void UnregisterJP2Image(void) +{ + (void) UnregisterMagickInfo("JPC"); + (void) UnregisterMagickInfo("JPT"); + (void) UnregisterMagickInfo("JPM"); + (void) UnregisterMagickInfo("JP2"); + (void) UnregisterMagickInfo("J2K"); +} + +#if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% W r i t e J P 2 I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% WriteJP2Image() writes an image in the JPEG 2000 image format. +% +% JP2 support originally written by Nathan Brown, nathanbrown@letu.edu +% +% The format of the WriteJP2Image method is: +% +% MagickBooleanType WriteJP2Image(const ImageInfo *image_info,Image *image, +% ExceptionInfo *exception) +% +% A description of each parameter follows. +% +% o image_info: the image info. +% +% o image: The image. +% +*/ + +static void CinemaProfileCompliance(const opj_image_t *jp2_image, + opj_cparameters_t *parameters) +{ + /* + Digital Cinema 4K profile compliant codestream. + */ + parameters->tile_size_on=OPJ_FALSE; + parameters->cp_tdx=1; + parameters->cp_tdy=1; + parameters->tp_flag='C'; + parameters->tp_on=1; + parameters->cp_tx0=0; + parameters->cp_ty0=0; + parameters->image_offset_x0=0; + parameters->image_offset_y0=0; + parameters->cblockw_init=32; + parameters->cblockh_init=32; + parameters->csty|=0x01; + parameters->prog_order=OPJ_CPRL; + parameters->roi_compno=(-1); + parameters->subsampling_dx=1; + parameters->subsampling_dy=1; + parameters->irreversible=1; + if ((jp2_image->comps[0].w == 2048) || (jp2_image->comps[0].h == 1080)) + { + /* + Digital Cinema 2K. + */ + parameters->cp_cinema=OPJ_CINEMA2K_24; + parameters->cp_rsiz=OPJ_CINEMA2K; + parameters->max_comp_size=1041666; + if (parameters->numresolution > 6) + parameters->numresolution=6; + + } + if ((jp2_image->comps[0].w == 4096) || (jp2_image->comps[0].h == 2160)) + { + /* + Digital Cinema 4K. + */ + parameters->cp_cinema=OPJ_CINEMA4K_24; + parameters->cp_rsiz=OPJ_CINEMA4K; + parameters->max_comp_size=1041666; + if (parameters->numresolution < 1) + parameters->numresolution=1; + if (parameters->numresolution > 7) + parameters->numresolution=7; + parameters->numpocs=2; + parameters->POC[0].tile=1; + parameters->POC[0].resno0=0; + parameters->POC[0].compno0=0; + parameters->POC[0].layno1=1; + parameters->POC[0].resno1=parameters->numresolution-1; + parameters->POC[0].compno1=3; + parameters->POC[0].prg1=OPJ_CPRL; + parameters->POC[1].tile=1; + parameters->POC[1].resno0=parameters->numresolution-1; + parameters->POC[1].compno0=0; + parameters->POC[1].layno1=1; + parameters->POC[1].resno1=parameters->numresolution; + parameters->POC[1].compno1=3; + parameters->POC[1].prg1=OPJ_CPRL; + } + parameters->tcp_numlayers=1; + parameters->tcp_rates[0]=((float) (jp2_image->numcomps*jp2_image->comps[0].w* + jp2_image->comps[0].h*jp2_image->comps[0].prec))/(parameters->max_comp_size* + 8*jp2_image->comps[0].dx*jp2_image->comps[0].dy); + parameters->cp_disto_alloc=1; +} + +static MagickBooleanType WriteJP2Image(const ImageInfo *image_info,Image *image, + ExceptionInfo *exception) +{ + const char + *option, + *property; + + int + jp2_status; + + MagickBooleanType + status; + + opj_codec_t + *jp2_codec; + + OPJ_COLOR_SPACE + jp2_colorspace; + + opj_cparameters_t + parameters; + + opj_image_cmptparm_t + jp2_info[5]; + + opj_image_t + *jp2_image; + + opj_stream_t + *jp2_stream; + + register ssize_t + i; + + ssize_t + y; + + unsigned int + channels; + + /* + Open image file. + */ + assert(image_info != (const ImageInfo *) NULL); + assert(image_info->signature == MagickCoreSignature); + assert(image != (Image *) NULL); + assert(image->signature == MagickCoreSignature); + if (image->debug != MagickFalse) + (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); + assert(exception != (ExceptionInfo *) NULL); + assert(exception->signature == MagickCoreSignature); + status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); + if (status == MagickFalse) + return(status); + /* + Initialize JPEG 2000 API. + */ + opj_set_default_encoder_parameters(¶meters); + for (i=1; i < 6; i++) + if (((size_t) (1UL << (i+2)) > image->columns) && + ((size_t) (1UL << (i+2)) > image->rows)) + break; + parameters.numresolution=i; + option=GetImageOption(image_info,"jp2:number-resolutions"); + if (option != (const char *) NULL) + parameters.numresolution=StringToInteger(option); + parameters.tcp_numlayers=1; + parameters.tcp_rates[0]=0; /* lossless */ + parameters.cp_disto_alloc=1; + if ((image_info->quality != 0) && (image_info->quality != 100)) + { + parameters.tcp_distoratio[0]=(double) image_info->quality; + parameters.cp_fixed_quality=OPJ_TRUE; + } + if (image_info->extract != (char *) NULL) + { + RectangleInfo + geometry; + + int + flags; + + /* + Set tile size. + */ + flags=ParseAbsoluteGeometry(image_info->extract,&geometry); + parameters.cp_tdx=(int) geometry.width; + parameters.cp_tdy=(int) geometry.width; + if ((flags & HeightValue) != 0) + parameters.cp_tdy=(int) geometry.height; + if ((flags & XValue) != 0) + parameters.cp_tx0=geometry.x; + if ((flags & YValue) != 0) + parameters.cp_ty0=geometry.y; + parameters.tile_size_on=OPJ_TRUE; + } + option=GetImageOption(image_info,"jp2:quality"); + if (option != (const char *) NULL) + { + register const char + *p; + + /* + Set quality PSNR. + */ + p=option; + for (i=0; sscanf(p,"%f",¶meters.tcp_distoratio[i]) == 1; i++) + { + if (i > 100) + break; + while ((*p != '\0') && (*p != ',')) + p++; + if (*p == '\0') + break; + p++; + } + parameters.tcp_numlayers=i+1; + parameters.cp_fixed_quality=OPJ_TRUE; + } + option=GetImageOption(image_info,"jp2:progression-order"); + if (option != (const char *) NULL) + { + if (LocaleCompare(option,"LRCP") == 0) + parameters.prog_order=OPJ_LRCP; + if (LocaleCompare(option,"RLCP") == 0) + parameters.prog_order=OPJ_RLCP; + if (LocaleCompare(option,"RPCL") == 0) + parameters.prog_order=OPJ_RPCL; + if (LocaleCompare(option,"PCRL") == 0) + parameters.prog_order=OPJ_PCRL; + if (LocaleCompare(option,"CPRL") == 0) + parameters.prog_order=OPJ_CPRL; + } + option=GetImageOption(image_info,"jp2:rate"); + if (option != (const char *) NULL) + { + register const char + *p; + + /* + Set compression rate. + */ + p=option; + for (i=0; sscanf(p,"%f",¶meters.tcp_rates[i]) == 1; i++) + { + if (i >= 100) + break; + while ((*p != '\0') && (*p != ',')) + p++; + if (*p == '\0') + break; + p++; + } + parameters.tcp_numlayers=i+1; + parameters.cp_disto_alloc=OPJ_TRUE; + } + if (image_info->sampling_factor != (const char *) NULL) + (void) sscanf(image_info->sampling_factor,"%d,%d", + ¶meters.subsampling_dx,¶meters.subsampling_dy); + property=GetImageProperty(image,"comment",exception); + if (property != (const char *) NULL) + parameters.cp_comment=(char *) property; + channels=3; + jp2_colorspace=OPJ_CLRSPC_SRGB; + if (image->colorspace == YUVColorspace) + { + jp2_colorspace=OPJ_CLRSPC_SYCC; + parameters.subsampling_dx=2; + } + else + { + if (IsGrayColorspace(image->colorspace) != MagickFalse) + { + channels=1; + jp2_colorspace=OPJ_CLRSPC_GRAY; + } + else + (void) TransformImageColorspace(image,sRGBColorspace,exception); + if (image->alpha_trait != UndefinedPixelTrait) + channels++; + } + parameters.tcp_mct=channels == 3 ? 1 : 0; + memset(jp2_info,0,sizeof(jp2_info)); + for (i=0; i < (ssize_t) channels; i++) + { + jp2_info[i].prec=(OPJ_UINT32) image->depth; + jp2_info[i].bpp=(OPJ_UINT32) image->depth; + if ((image->depth == 1) && + ((LocaleCompare(image_info->magick,"JPT") == 0) || + (LocaleCompare(image_info->magick,"JP2") == 0))) + { + jp2_info[i].prec++; /* OpenJPEG returns exception for depth @ 1 */ + jp2_info[i].bpp++; + } + jp2_info[i].sgnd=0; + jp2_info[i].dx=parameters.subsampling_dx; + jp2_info[i].dy=parameters.subsampling_dy; + jp2_info[i].w=(OPJ_UINT32) image->columns; + jp2_info[i].h=(OPJ_UINT32) image->rows; + } + jp2_image=opj_image_create((OPJ_UINT32) channels,jp2_info,jp2_colorspace); + if (jp2_image == (opj_image_t *) NULL) + ThrowWriterException(DelegateError,"UnableToEncodeImageFile"); + jp2_image->x0=parameters.image_offset_x0; + jp2_image->y0=parameters.image_offset_y0; + jp2_image->x1=(unsigned int) (2*parameters.image_offset_x0+(image->columns-1)* + parameters.subsampling_dx+1); + jp2_image->y1=(unsigned int) (2*parameters.image_offset_y0+(image->rows-1)* + parameters.subsampling_dx+1); + if ((image->depth == 12) && + ((image->columns == 2048) || (image->rows == 1080) || + (image->columns == 4096) || (image->rows == 2160))) + CinemaProfileCompliance(jp2_image,¶meters); + if (channels == 4) + jp2_image->comps[3].alpha=1; + else + if ((channels == 2) && (jp2_colorspace == OPJ_CLRSPC_GRAY)) + jp2_image->comps[1].alpha=1; + /* + Convert to JP2 pixels. + */ + for (y=0; y < (ssize_t) image->rows; y++) + { + register const Quantum + *p; + + ssize_t + x; + + p=GetVirtualPixels(image,0,y,image->columns,1,exception); + if (p == (const Quantum *) NULL) + break; + for (x=0; x < (ssize_t) image->columns; x++) + { + for (i=0; i < (ssize_t) channels; i++) + { + double + scale; + + register int + *q; + + scale=(double) ((1UL << jp2_image->comps[i].prec)-1)/QuantumRange; + q=jp2_image->comps[i].data+(y/jp2_image->comps[i].dy* + image->columns/jp2_image->comps[i].dx+x/jp2_image->comps[i].dx); + switch (i) + { + case 0: + { + if (jp2_colorspace == OPJ_CLRSPC_GRAY) + { + *q=(int) (scale*GetPixelGray(image,p)); + break; + } + *q=(int) (scale*GetPixelRed(image,p)); + break; + } + case 1: + { + if (jp2_colorspace == OPJ_CLRSPC_GRAY) + { + *q=(int) (scale*GetPixelAlpha(image,p)); + break; + } + *q=(int) (scale*GetPixelGreen(image,p)); + break; + } + case 2: + { + *q=(int) (scale*GetPixelBlue(image,p)); + break; + } + case 3: + { + *q=(int) (scale*GetPixelAlpha(image,p)); + break; + } + } + } + p+=GetPixelChannels(image); + } + status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y, + image->rows); + if (status == MagickFalse) + break; + } + if (LocaleCompare(image_info->magick,"JPT") == 0) + jp2_codec=opj_create_compress(OPJ_CODEC_JPT); + else + if (LocaleCompare(image_info->magick,"J2K") == 0) + jp2_codec=opj_create_compress(OPJ_CODEC_J2K); + else + jp2_codec=opj_create_compress(OPJ_CODEC_JP2); + opj_set_warning_handler(jp2_codec,JP2WarningHandler,exception); + opj_set_error_handler(jp2_codec,JP2ErrorHandler,exception); + opj_setup_encoder(jp2_codec,¶meters,jp2_image); + jp2_stream=opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE,OPJ_FALSE); + if (jp2_stream == (opj_stream_t *) NULL) + { + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowWriterException(DelegateError,"UnableToEncodeImageFile"); + } + opj_stream_set_read_function(jp2_stream,JP2ReadHandler); + opj_stream_set_write_function(jp2_stream,JP2WriteHandler); + opj_stream_set_seek_function(jp2_stream,JP2SeekHandler); + opj_stream_set_skip_function(jp2_stream,JP2SkipHandler); + opj_stream_set_user_data(jp2_stream,image,NULL); + jp2_status=opj_start_compress(jp2_codec,jp2_image,jp2_stream); + if ((jp2_status == 0) || (opj_encode(jp2_codec,jp2_stream) == 0) || + (opj_end_compress(jp2_codec,jp2_stream) == 0)) + { + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + ThrowWriterException(DelegateError,"UnableToEncodeImageFile"); + } + /* + Free resources. + */ + opj_stream_destroy(jp2_stream); + opj_destroy_codec(jp2_codec); + opj_image_destroy(jp2_image); + (void) CloseBlob(image); + return(MagickTrue); +} +#endif From 519f2a537a58aafffee030facfd965acf77635d5 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Jul 2020 13:55:45 -0500 Subject: [PATCH 053/116] Format --- lib/astutils.cpp | 13 ++++--------- lib/astutils.h | 6 +----- lib/checkcondition.cpp | 2 +- test/testcondition.cpp | 3 ++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 7d636c829..293ad1dcc 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -539,10 +539,10 @@ bool exprDependsOnThis(const Token* expr, nonneg int depth) // calling nonstatic method? if (Token::Match(expr->previous(), "!!:: %name% (") && expr->function() && expr->function()->nestedIn && expr->function()->nestedIn->isClassOrStruct()) { // is it a method of this? - const Scope *fScope = expr->scope(); + const Scope* fScope = expr->scope(); while (!fScope->functionOf && fScope->nestedIn) fScope = fScope->nestedIn; - const Scope *nestedIn = fScope->functionOf; + const Scope* nestedIn = fScope->functionOf; if (nestedIn && nestedIn->function) nestedIn = nestedIn->function->token->scope(); while (nestedIn && nestedIn != expr->function()->nestedIn) { @@ -1565,11 +1565,7 @@ bool isVariablesChanged(const Token* start, return false; } -bool isThisChanged(const Token* start, - const Token* end, - int indirect, - const Settings* settings, - bool cpp) +bool isThisChanged(const Token* start, const Token* end, int indirect, const Settings* settings, bool cpp) { for (const Token* tok = start; tok != end; tok = tok->next()) { if (!exprDependsOnThis(tok)) @@ -1580,10 +1576,9 @@ bool isThisChanged(const Token* start, return true; else continue; - } else if (!tok->previous()->isKeyword()){ + } else if (!tok->previous()->isKeyword()) { return true; } - } if (isVariableChanged(tok, indirect, settings, cpp)) return true; diff --git a/lib/astutils.h b/lib/astutils.h index 20e402d42..cb453482c 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -192,11 +192,7 @@ bool isVariablesChanged(const Token* start, const Settings* settings, bool cpp); -bool isThisChanged(const Token* start, - const Token* end, - int indirect, - const Settings* settings, - bool cpp); +bool isThisChanged(const Token* start, const Token* end, int indirect, const Settings* settings, bool cpp); const Token* findVariableChanged(const Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20); Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20); diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index 17e3e026e..ca086d5b1 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -452,7 +452,7 @@ void CheckCondition::duplicateCondition() continue; bool modified = false; - visitAstNodes(cond1, [&](const Token *tok3) { + visitAstNodes(cond1, [&](const Token* tok3) { if (exprDependsOnThis(tok3)) { if (isThisChanged(scope.classDef->next(), cond2, false, mSettings, mTokenizer->isCPP())) { modified = true; diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 05c6ba239..7519a17e7 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -3715,7 +3715,8 @@ private: " }\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:9]: (style) The if condition is the same as the previous if condition\n", errout.str()); + ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:9]: (style) The if condition is the same as the previous if condition\n", + errout.str()); } void checkInvalidTestForOverflow() { From 6030ab72ab4651cd0bc5279e2b4941ddc04c4499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 08:12:40 +0200 Subject: [PATCH 054/116] Bug hunting; Avoid some bailout false positives in uninit checker --- lib/bughuntingchecks.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 39062cfac..85c598062 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -226,6 +226,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (tok->astParent()->str() == "::" && tok == tok->astParent()->astOperand1()) return; + // Object allocated on the stack + if (tok->valueType() && tok->valueType()->pointer == 0 && Token::Match(tok, "%var% .")) + return; + // Containers are not uninitialized std::vector tokens{tok, tok->astOperand1(), tok->astOperand2()}; if (Token::Match(tok->previous(), ". %name%")) From fe324aea4998b411c0e450654c66d54717683e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 10:25:00 +0200 Subject: [PATCH 055/116] Bug hunting; Detect internal error and throw exception --- lib/exprengine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 0e96a97fb..4a2d66808 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -716,6 +716,8 @@ namespace { const std::string c = line.substr(pos, end-pos); pos = end; d.constraints.push_back(c); + } else { + throw ExprEngineException(nullptr, "Internal Error: Data::parsestr(), line:" + line); } } importData->push_back(d); From 176826a1f5dd26b47f8a166a4cea9edf806cd604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 11:15:26 +0200 Subject: [PATCH 056/116] Bug hunting; Avoid false positives --- lib/bughuntingchecks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 85c598062..46879ec04 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -227,7 +227,7 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: return; // Object allocated on the stack - if (tok->valueType() && tok->valueType()->pointer == 0 && Token::Match(tok, "%var% .")) + if (Token::Match(tok, "%var% .") && tok->next()->originalName() != "->") return; // Containers are not uninitialized From 8d6fd4769b60950decda14cfda4f585502ed2c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 11:22:42 +0200 Subject: [PATCH 057/116] Bug hunting; Terminating analysis --- lib/exprengine.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 4a2d66808..388f4149c 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -154,6 +154,7 @@ namespace { const Token *tok; const std::string what; }; + struct TerminateExpression {}; } static std::string str(ExprEngine::ValuePtr val) @@ -2153,6 +2154,9 @@ static ExprEngine::ValuePtr executeStringLiteral(const Token *tok, Data &data) static ExprEngine::ValuePtr executeExpression1(const Token *tok, Data &data) { + if (data.settings->terminated()) + throw TerminateExpression(); + if (tok->str() == "return") return executeReturn(tok, data); @@ -2489,6 +2493,8 @@ void ExprEngine::executeAllFunctions(ErrorLogger *errorLogger, const Tokenizer * // FIXME.. there should not be exceptions std::string functionName = functionScope->function->name(); std::cout << "Verify: Aborted analysis of function '" << functionName << "': " << e.what() << std::endl; + } catch (const TerminateExpression &) { + break; } } } From a6d70b9022da74bc5350d6f4645910023ead1674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 16:54:00 +0200 Subject: [PATCH 058/116] Code cleanup, __temp__ files are not needed anymore --- lib/analyzerinfo.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 460d9cf16..1d6af8537 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -59,12 +59,6 @@ void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std:: const std::string afile = getFilename(fs.filename); fout << afile << ".a" << (++fileCount[afile]) << ":" << fs.cfg << ":" << Path::simplifyPath(Path::fromNativeSeparators(fs.filename)) << std::endl; } - - std::ofstream fc(buildDir + "/__temp__.c"); - fc << "int x;\n"; - - std::ofstream fcpp(buildDir + "/__temp__.cpp"); - fcpp << "int x;\n"; } void AnalyzerInformation::close() From 62702a6816bf21671c86d7af6465e8db982f2982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 18:19:03 +0200 Subject: [PATCH 059/116] GUI: Use const argument --- gui/resultstree.cpp | 2 +- gui/resultstree.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 435412780..c3e3d4b22 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -1133,7 +1133,7 @@ void ResultsTree::saveResults(Report *report) const report->writeFooter(); } -void ResultsTree::saveErrors(Report *report, QStandardItem *fileItem) const +void ResultsTree::saveErrors(Report *report, const QStandardItem *fileItem) const { if (!fileItem) { return; diff --git a/gui/resultstree.h b/gui/resultstree.h index 71c237bb5..55b5b709e 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -333,7 +333,7 @@ protected: * @param report Report that errors are saved to * @param fileItem Item whose errors to save */ - void saveErrors(Report *report, QStandardItem *fileItem) const; + void saveErrors(Report *report, const QStandardItem *fileItem) const; /** * @brief Convert a severity string to a icon filename From 4373404238a785bcb5b73d96a82afd0111086cfa Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Jul 2020 13:04:59 -0500 Subject: [PATCH 060/116] Revert "Fixed #9795 (False positive: Local lock is not ineffective, mutex is locked in thread also.)" This reverts commit 27841d6b811a9a7537efe943af23f99f5afb68ce. --- lib/checkstl.cpp | 29 ++++++++++++++++++++++++++- lib/checkstl.h | 2 ++ test/teststl.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index 58706e2ae..ebeb7f44f 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -2394,12 +2394,25 @@ void CheckStl::useStlAlgorithm() } } +static bool isMutex(const Variable* var) +{ + const Token* tok = Token::typeDecl(var->nameToken()).first; + return Token::Match(tok, "std :: mutex|recursive_mutex|timed_mutex|recursive_timed_mutex|shared_mutex"); +} + static bool isLockGuard(const Variable* var) { const Token* tok = Token::typeDecl(var->nameToken()).first; return Token::Match(tok, "std :: lock_guard|unique_lock|scoped_lock"); } +static bool isLocalMutex(const Variable* var, const Scope* scope) +{ + if (!var) + return false; + return !var->isReference() && !var->isRValueReference() && !var->isStatic() && var->scope() == scope; +} + void CheckStl::globalLockGuardError(const Token* tok) { reportError(tok, Severity::warning, @@ -2407,6 +2420,13 @@ void CheckStl::globalLockGuardError(const Token* tok) "Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.", CWE833, false); } +void CheckStl::localMutexError(const Token* tok) +{ + reportError(tok, Severity::warning, + "localMutex", + "The lock is ineffective because the mutex is locked at the same scope as the mutex itself.", CWE667, false); +} + void CheckStl::checkMutexes() { for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -2414,12 +2434,19 @@ void CheckStl::checkMutexes() const Variable* var = tok->variable(); if (!var) continue; - if (Token::Match(tok, "%var% (|{ %var% )|}|,")) { + if (Token::Match(tok, "%var% . lock ( )")) { + if (!isMutex(var)) + continue; + if (isLocalMutex(var, tok->scope())) + localMutexError(tok); + } else if (Token::Match(tok, "%var% (|{ %var% )|}|,")) { if (!isLockGuard(var)) continue; const Variable* mvar = tok->tokAt(2)->variable(); if (var->isStatic() || var->isGlobal()) globalLockGuardError(tok); + else if (isLocalMutex(mvar, tok->scope())) + localMutexError(tok); } } } diff --git a/lib/checkstl.h b/lib/checkstl.h index a0ae7d1e5..9dd5e07d5 100644 --- a/lib/checkstl.h +++ b/lib/checkstl.h @@ -233,6 +233,7 @@ private: void useStlAlgorithmError(const Token *tok, const std::string &algoName); void globalLockGuardError(const Token *tok); + void localMutexError(const Token *tok); void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const OVERRIDE { ErrorPath errorPath; @@ -271,6 +272,7 @@ private: c.readingEmptyStlContainerError(nullptr); c.useStlAlgorithmError(nullptr, ""); c.globalLockGuardError(nullptr); + c.localMutexError(nullptr); } static std::string myName() { diff --git a/test/teststl.cpp b/test/teststl.cpp index e9e929a92..240e20709 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -4445,6 +4445,28 @@ private: true); ASSERT_EQUALS("", errout.str()); + check("void f() {\n" + " std::mutex m;\n" + " std::lock_guard g(m);\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + + check("void f() {\n" + " std::mutex m;\n" + " std::unique_lock g(m);\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + + check("void f() {\n" + " std::mutex m;\n" + " std::unique_lock g(m, std::defer_lock);\n" + " std::lock(g);\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + check("void g();\n" "void f() {\n" " static std::mutex m;\n" @@ -4455,6 +4477,16 @@ private: true); ASSERT_EQUALS("", errout.str()); + check("void g();\n" + "void f() {\n" + " std::mutex m;\n" + " m.lock();\n" + " g();\n" + " m.unlock();\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + check("class A {\n" " std::mutex m;\n" " void f() {\n" @@ -4503,6 +4535,25 @@ private: "}\n", true); ASSERT_EQUALS("", errout.str()); + + check("std::mutex& h();\n" + "void f() {\n" + " auto m = h();\n" + " std::lock_guard g(m);\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); + + check("void g();\n" + "std::mutex& h();\n" + "void f() {\n" + " auto m = h();\n" + " m.lock();\n" + " g();\n" + " m.unlock();\n" + "}\n", + true); + ASSERT_EQUALS("[test.cpp:5]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str()); } }; From af0db3cc2176079fc752402a083ee09604cef4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 14 Jul 2020 22:30:42 +0200 Subject: [PATCH 061/116] Add cppcheck-id for warnings. To start with it's a simple id that changes when file is changed. --- lib/errorlogger.cpp | 46 +++++++++++++++++++++++++++------------- lib/errorlogger.h | 3 +++ test/testerrorlogger.cpp | 2 ++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 97f8f5ab3..b3d25952b 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -32,6 +32,7 @@ #include #include #include +#include // std::hash InternalError::InternalError(const Token *tok, const std::string &errorMsg, Type type) : token(tok), errorMessage(errorMsg), type(type) @@ -58,8 +59,15 @@ InternalError::InternalError(const Token *tok, const std::string &errorMsg, Type } } +static std::size_t calculateCppcheckId(const TokenList *tokenList, const std::string &msg) +{ + if (!tokenList) + return 0; + return std::hash {}(msg + "\n" + tokenList->front()->stringifyList(false, true, false, false, false)); +} + ErrorMessage::ErrorMessage() - : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false) + : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false), cppcheckId(0) { } @@ -70,7 +78,8 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std:: incomplete(false), severity(severity), // severity for this error message cwe(0U), - inconclusive(inconclusive) + inconclusive(inconclusive), + cppcheckId(0) { // set the summary and verbose messages setmsg(msg); @@ -85,14 +94,15 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std:: incomplete(false), severity(severity), // severity for this error message cwe(cwe.id), - inconclusive(inconclusive) + inconclusive(inconclusive), + cppcheckId(0) { // set the summary and verbose messages setmsg(msg); } ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity::SeverityType severity, const std::string& id, const std::string& msg, bool inconclusive) - : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive) + : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive), cppcheckId(0) { // Format callstack for (std::list::const_iterator it = callstack.begin(); it != callstack.end(); ++it) { @@ -126,6 +136,8 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token file0 = list->getFiles()[0]; setmsg(msg); + + cppcheckId = calculateCppcheckId(list, toString(false)); } ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenList, Severity::SeverityType severity, const char id[], const std::string &msg, const CWE &cwe, bool inconclusive) @@ -145,6 +157,8 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis file0 = tokenList->getFiles()[0]; setmsg(msg); + + cppcheckId = calculateCppcheckId(tokenList, toString(false)); } ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) @@ -173,6 +187,9 @@ ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) attr = errmsg->Attribute("verbose"); mVerboseMessage = attr ? attr : ""; + attr = errmsg->Attribute("cppcheck-id"); + std::istringstream(attr ? attr : "0") >> cppcheckId; + for (const tinyxml2::XMLElement *e = errmsg->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(),"location")==0) { const char *strfile = e->Attribute("file"); @@ -236,6 +253,7 @@ std::string ErrorMessage::serialize() const oss << id.length() << " " << id; oss << Severity::toString(severity).length() << " " << Severity::toString(severity); oss << MathLib::toString(cwe.id).length() << " " << MathLib::toString(cwe.id); + oss << MathLib::toString(cppcheckId).length() << " " << MathLib::toString(cppcheckId); if (inconclusive) { const std::string text("inconclusive"); oss << text.length() << " " << text; @@ -262,9 +280,9 @@ bool ErrorMessage::deserialize(const std::string &data) inconclusive = false; callStack.clear(); std::istringstream iss(data); - std::array results; + std::array results; std::size_t elem = 0; - while (iss.good()) { + while (iss.good() && elem < 6) { unsigned int len = 0; if (!(iss >> len)) return false; @@ -282,19 +300,17 @@ bool ErrorMessage::deserialize(const std::string &data) } results[elem++] = temp; - if (elem == 5) - break; } - if (elem != 5) + if (elem != 6) throw InternalError(nullptr, "Internal Error: Deserialization of error message failed"); id = results[0]; severity = Severity::fromString(results[1]); - std::istringstream scwe(results[2]); - scwe >> cwe.id; - mShortMessage = results[3]; - mVerboseMessage = results[4]; + std::istringstream(results[2]) >> cwe.id; + std::istringstream(results[3]) >> cppcheckId; + mShortMessage = results[4]; + mVerboseMessage = results[5]; unsigned int stackSize = 0; if (!(iss >> stackSize)) @@ -347,8 +363,6 @@ bool ErrorMessage::deserialize(const std::string &data) std::string ErrorMessage::getXMLHeader() { - // xml_version 1 is the default xml format - tinyxml2::XMLPrinter printer; // standard xml header @@ -403,6 +417,8 @@ std::string ErrorMessage::toXML() const printer.PushAttribute("verbose", fixInvalidChars(mVerboseMessage).c_str()); if (cwe.id) printer.PushAttribute("cwe", cwe.id); + if (cppcheckId) + printer.PushAttribute("cppcheck-id", MathLib::toString(cppcheckId).c_str()); if (inconclusive) printer.PushAttribute("inconclusive", "true"); diff --git a/lib/errorlogger.h b/lib/errorlogger.h index ea03eecce..76d168be5 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -196,6 +196,9 @@ public: CWE cwe; bool inconclusive; + /** Warning ID */ + std::size_t cppcheckId; + /** set short and verbose messages */ void setmsg(const std::string &msg); diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 3fb718371..f827f8723 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -252,6 +252,7 @@ private: ASSERT_EQUALS("7 errorId" "5 error" "1 0" + "1 0" "12 inconclusive" "17 Programming error" "17 Programming error" @@ -278,6 +279,7 @@ private: ASSERT_EQUALS("7 errorId" "5 error" "1 0" + "1 0" "33 Illegal character in \"foo\\001bar\"" "33 Illegal character in \"foo\\001bar\"" "0 ", msg.serialize()); From 260c53ba6f73e7395e11c6232aa78f6a1eb51639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 15 Jul 2020 07:59:05 +0200 Subject: [PATCH 062/116] GUI: Write cppcheck-id in xml report --- gui/erroritem.cpp | 9 +++++++-- gui/erroritem.h | 4 +++- gui/resultstree.cpp | 4 ++++ gui/resultsview.cpp | 2 +- gui/xmlreportv2.cpp | 7 ++++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp index acf420204..cccc25911 100644 --- a/gui/erroritem.cpp +++ b/gui/erroritem.cpp @@ -37,6 +37,7 @@ ErrorItem::ErrorItem() , incomplete(false) , inconclusive(false) , cwe(-1) + , cppcheckId(0) { } @@ -50,6 +51,7 @@ ErrorItem::ErrorItem(const ErrorMessage &errmsg) , summary(QString::fromStdString(errmsg.shortMessage())) , message(QString::fromStdString(errmsg.verboseMessage())) , cwe(errmsg.cwe.id) + , cppcheckId(errmsg.cppcheckId) , symbolNames(QString::fromStdString(errmsg.symbolNames())) { for (std::list::const_iterator loc = errmsg.callStack.begin(); @@ -70,7 +72,7 @@ QString ErrorItem::tool() const return "cppcheck"; } -QString ErrorItem::ToString() const +QString ErrorItem::toString() const { QString str = errorPath.back().file + " - " + errorId + " - "; if (inconclusive) @@ -86,7 +88,10 @@ QString ErrorItem::ToString() const bool ErrorItem::sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2) { - // TODO: Implement some better CID calculation + if (errorItem1.cppcheckId || errorItem2.cppcheckId) + return errorItem1.cppcheckId == errorItem2.cppcheckId; + + // fallback return errorItem1.errorId == errorItem2.errorId && errorItem1.errorPath == errorItem2.errorPath && errorItem1.file0 == errorItem2.file0 && diff --git a/gui/erroritem.h b/gui/erroritem.h index 96ed90e7d..7bcb9a74f 100644 --- a/gui/erroritem.h +++ b/gui/erroritem.h @@ -76,7 +76,7 @@ public: * @brief Convert error item to string. * @return Error item as string. */ - QString ToString() const; + QString toString() const; QString tool() const; QString file0; @@ -88,6 +88,7 @@ public: QString summary; QString message; int cwe; + unsigned long long cppcheckId; QList errorPath; QString symbolNames; @@ -114,6 +115,7 @@ public: QString errorId; bool incomplete; int cwe; + unsigned long long cppcheckId; bool inconclusive; Severity::SeverityType severity; QString summary; diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index c3e3d4b22..cbb1e3b93 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -168,6 +168,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) line.errorId = item.errorId; line.incomplete = item.incomplete; line.cwe = item.cwe; + line.cppcheckId = item.cppcheckId; line.inconclusive = item.inconclusive; line.summary = item.summary; line.message = item.message; @@ -197,6 +198,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) data["id"] = item.errorId; data["incomplete"] = item.incomplete; data["cwe"] = item.cwe; + data["cppcheckId"] = item.cppcheckId; data["inconclusive"] = item.inconclusive; data["file0"] = stripPath(item.file0, true); data["function"] = item.function; @@ -232,6 +234,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) child_data["id"] = line.errorId; child_data["incomplete"] = line.incomplete; child_data["cwe"] = line.cwe; + child_data["cppcheckId"] = line.cppcheckId; child_data["inconclusive"] = line.inconclusive; child_item->setData(QVariant(child_data)); } @@ -1221,6 +1224,7 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con item->errorId = data["id"].toString(); item->incomplete = data["incomplete"].toBool(); item->cwe = data["cwe"].toInt(); + item->cppcheckId = data["cppcheckId"].toULongLong(); item->inconclusive = data["inconclusive"].toBool(); item->file0 = data["file0"].toString(); item->sinceDate = data["sinceDate"].toString(); diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index 1dc0a294c..fcc88d73b 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -448,7 +448,7 @@ void ResultsView::log(const QString &str) void ResultsView::debugError(const ErrorItem &item) { - mUI.mListLog->addItem(item.ToString()); + mUI.mListLog->addItem(item.toString()); } void ResultsView::bughuntingReportLine(const QString& line) diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp index f70053de5..5fac01c9e 100644 --- a/gui/xmlreportv2.cpp +++ b/gui/xmlreportv2.cpp @@ -34,6 +34,7 @@ static const QString ErrorElementName = "error"; static const QString ErrorsElementName = "errors"; static const QString LocationElementName = "location"; static const QString CWEAttribute = "cwe"; +static const QString CppcheckIdAttribute = "cppcheck-id"; static const QString SinceDateAttribute = "sinceDate"; static const QString TagsAttribute = "tag"; static const QString FilenameAttribute = "file"; @@ -122,6 +123,8 @@ void XmlReportV2::writeError(const ErrorItem &error) mXmlWriter->writeAttribute(InconclusiveAttribute, "true"); if (error.cwe > 0) mXmlWriter->writeAttribute(CWEAttribute, QString::number(error.cwe)); + if (error.cppcheckId > 0) + mXmlWriter->writeAttribute(CppcheckIdAttribute, QString::number(error.cppcheckId)); if (!error.sinceDate.isEmpty()) mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate); if (!error.tags.isEmpty()) @@ -214,7 +217,9 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader) if (attribs.hasAttribute(QString(), InconclusiveAttribute)) item.inconclusive = true; if (attribs.hasAttribute(QString(), CWEAttribute)) - item.cwe = attribs.value(QString(), CWEAttribute).toString().toInt(); + item.cwe = attribs.value(QString(), CWEAttribute).toInt(); + if (attribs.hasAttribute(QString(), CppcheckIdAttribute)) + item.cppcheckId = attribs.value(QString(), CWEAttribute).toULongLong(); if (attribs.hasAttribute(QString(), SinceDateAttribute)) item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString(); if (attribs.hasAttribute(QString(), TagsAttribute)) From 6ab4f39f523f3e55a4e001a3e3468a8b2c549d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 15 Jul 2020 13:03:07 +0200 Subject: [PATCH 063/116] GUI: Suppress cppcheck-id --- gui/mainwindow.cpp | 3 +++ gui/projectfile.cpp | 15 +++++++++++++++ gui/projectfile.h | 14 ++++++++++++++ gui/resultstree.cpp | 27 +++++++++++++++++++++++++++ gui/resultstree.h | 3 +++ gui/xmlreportv2.cpp | 2 +- lib/errorlogger.cpp | 1 + lib/suppressions.cpp | 10 ++++++++-- lib/suppressions.h | 9 +++++++-- 9 files changed, 79 insertions(+), 5 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 0a78fc653..e721b605c 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -341,6 +341,7 @@ void MainWindow::loadSettings() if (inf.exists() && inf.isReadable()) { setPath(SETTINGS_LAST_PROJECT_PATH, projectFile); mProjectFile = new ProjectFile(this); + mProjectFile->setActiveProject(); mProjectFile->read(projectFile); loadLastResults(); QDir::setCurrent(inf.absolutePath()); @@ -1508,6 +1509,7 @@ void MainWindow::loadProjectFile(const QString &filePath) mUI.mActionEditProjectFile->setEnabled(true); delete mProjectFile; mProjectFile = new ProjectFile(filePath, this); + mProjectFile->setActiveProject(); updateContractsTab(); if (!loadLastResults()) analyzeProject(mProjectFile); @@ -1630,6 +1632,7 @@ void MainWindow::newProjectFile() delete mProjectFile; mProjectFile = new ProjectFile(this); + mProjectFile->setActiveProject(); mProjectFile->setFilename(filepath); mProjectFile->setBuildDir(filename.left(filename.indexOf(".")) + "-cppcheck-build-dir"); diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 01bdd28fa..fce386ee8 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -28,6 +28,8 @@ #include "settings.h" +ProjectFile *ProjectFile::mActiveProject; + ProjectFile::ProjectFile(QObject *parent) : QObject(parent) { @@ -603,6 +605,8 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader) suppression.lineNumber = reader.attributes().value(QString(),"lineNumber").toInt(); if (reader.attributes().hasAttribute(QString(),"symbolName")) suppression.symbolName = reader.attributes().value(QString(),"symbolName").toString().toStdString(); + if (reader.attributes().hasAttribute(QString(),"cppcheck-id")) + suppression.cppcheckId = reader.attributes().value(QString(),"cppcheck-id").toULongLong(); type = reader.readNext(); if (type == QXmlStreamReader::Characters) { suppression.errorId = reader.text().toString().toStdString(); @@ -873,6 +877,8 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeAttribute("lineNumber", QString::number(suppression.lineNumber)); if (!suppression.symbolName.empty()) xmlWriter.writeAttribute("symbolName", QString::fromStdString(suppression.symbolName)); + if (suppression.cppcheckId > 0) + xmlWriter.writeAttribute("cppcheck-id", QString::number(suppression.cppcheckId)); if (!suppression.errorId.empty()) xmlWriter.writeCharacters(QString::fromStdString(suppression.errorId)); xmlWriter.writeEndElement(); @@ -1022,3 +1028,12 @@ QString ProjectFile::getAddonFilePath(QString filesDir, const QString &addon) return QString(); } +void ProjectFile::suppressCppcheckId(std::size_t cppcheckId) +{ + if (cppcheckId > 0) { + Suppressions::Suppression s; + s.cppcheckId = cppcheckId; + mSuppressions.append(s); + write(); + } +} diff --git a/gui/projectfile.h b/gui/projectfile.h index 18772dcd8..fc1effc00 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -44,6 +44,19 @@ class ProjectFile : public QObject { public: explicit ProjectFile(QObject *parent = nullptr); explicit ProjectFile(const QString &filename, QObject *parent = nullptr); + ~ProjectFile() { + if (this == mActiveProject) mActiveProject = nullptr; + } + + static ProjectFile* getActiveProject() { + return mActiveProject; + } + void setActiveProject() { + mActiveProject = this; + } + + /** Suppress warning with Cppcheck-ID */ + void suppressCppcheckId(std::size_t cppcheckId); /** * @brief Read the project file. @@ -551,6 +564,7 @@ private: QStringList mCheckUnknownFunctionReturn; + static ProjectFile *mActiveProject; }; /// @} #endif // PROJECT_FILE_H diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index cbb1e3b93..7233a4d15 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -43,6 +43,7 @@ #include "resultstree.h" #include "report.h" #include "application.h" +#include "projectfile.h" #include "showtypes.h" #include "threadhandler.h" #include "path.h" @@ -638,6 +639,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) QAction *copy = new QAction(tr("Copy"), &menu); QAction *hide = new QAction(tr("Hide"), &menu); QAction *hideallid = new QAction(tr("Hide all with id"), &menu); + QAction *suppressCppcheckID = new QAction(tr("Suppress cppcheck-id"), &menu); QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu); if (multipleSelection) { @@ -655,6 +657,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); menu.addAction(hide); menu.addAction(hideallid); + menu.addAction(suppressCppcheckID); if (!bughunting) { QAction *suppress = new QAction(tr("Suppress selected id(s)"), &menu); menu.addAction(suppress); @@ -667,6 +670,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) connect(copy, SIGNAL(triggered()), this, SLOT(copy())); connect(hide, SIGNAL(triggered()), this, SLOT(hideResult())); connect(hideallid, SIGNAL(triggered()), this, SLOT(hideAllIdResult())); + connect(suppressCppcheckID, &QAction::triggered, this, &ResultsTree::suppressCppcheckID); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); if (!mTags.isEmpty()) { @@ -1031,12 +1035,35 @@ void ResultsTree::suppressSelectedIds() j++; } } + if (file->rowCount() == 0) + mModel.removeRow(file->row()); } emit suppressIds(selectedIds.toList()); } +void ResultsTree::suppressCppcheckID() +{ + if (!mSelectionModel) + return; + ProjectFile *projectFile = ProjectFile::getActiveProject(); + foreach (QModelIndex index, mSelectionModel->selectedRows()) { + QStandardItem *item = mModel.itemFromIndex(index); + if (!item->parent()) + continue; + while (item->parent()->parent()) + item = item->parent(); + const QVariantMap data = item->data().toMap(); + if (projectFile && data.contains("cppcheckId")) + projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong()); + QStandardItem *fileItem = item->parent(); + fileItem->removeRow(item->row()); + if (fileItem->rowCount() == 0) + mModel.removeRow(fileItem->row()); + } +} + void ResultsTree::openContainingFolder() { QString filePath = getFilePath(mContextItem, true); diff --git a/gui/resultstree.h b/gui/resultstree.h index 55b5b709e..349ea855f 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -285,6 +285,9 @@ protected slots: /** Slot for context menu item to suppress all messages with the current message id */ void suppressSelectedIds(); + /** Slot for context menu item to suppress message with cppcheck ID */ + void suppressCppcheckID(); + /** * @brief Slot for context menu item to open the folder containing the current file. */ diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp index 5fac01c9e..00fd096e8 100644 --- a/gui/xmlreportv2.cpp +++ b/gui/xmlreportv2.cpp @@ -219,7 +219,7 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader) if (attribs.hasAttribute(QString(), CWEAttribute)) item.cwe = attribs.value(QString(), CWEAttribute).toInt(); if (attribs.hasAttribute(QString(), CppcheckIdAttribute)) - item.cppcheckId = attribs.value(QString(), CWEAttribute).toULongLong(); + item.cppcheckId = attribs.value(QString(), CppcheckIdAttribute).toULongLong(); if (attribs.hasAttribute(QString(), SinceDateAttribute)) item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString(); if (attribs.hasAttribute(QString(), TagsAttribute)) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index b3d25952b..06bfbde76 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -235,6 +235,7 @@ void ErrorMessage::setmsg(const std::string &msg) Suppressions::ErrorMessage ErrorMessage::toSuppressionsErrorMessage() const { Suppressions::ErrorMessage ret; + ret.cppcheckId = cppcheckId; ret.errorId = id; if (!callStack.empty()) { ret.setFileName(callStack.back().getfile(false)); diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index 0f307b834..93bc71531 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -204,9 +204,9 @@ std::string Suppressions::addSuppressionLine(const std::string &line) std::string Suppressions::addSuppression(const Suppressions::Suppression &suppression) { // Check that errorId is valid.. - if (suppression.errorId.empty()) { + if (suppression.errorId.empty() && suppression.cppcheckId == 0) return "Failed to add suppression. No id."; - } + if (suppression.errorId != "*") { for (std::string::size_type pos = 0; pos < suppression.errorId.length(); ++pos) { if (suppression.errorId[pos] < 0 || !isAcceptedErrorIdChar(suppression.errorId[pos])) { @@ -271,6 +271,8 @@ bool Suppressions::Suppression::parseComment(std::string comment, std::string *e bool Suppressions::Suppression::isSuppressed(const Suppressions::ErrorMessage &errmsg) const { + if (cppcheckId > 0 && cppcheckId != errmsg.cppcheckId) + return false; if (!errorId.empty() && !matchglob(errorId, errmsg.errorId)) return false; if (!fileName.empty() && !matchglob(fileName, errmsg.getFileName())) @@ -371,6 +373,8 @@ std::list Suppressions::getUnmatchedLocalSuppressions for (const Suppression &s : mSuppressions) { if (s.matched) continue; + if (s.cppcheckId > 0) + continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; if (file.empty() || !s.isLocal() || s.fileName != file) @@ -386,6 +390,8 @@ std::list Suppressions::getUnmatchedGlobalSuppression for (const Suppression &s : mSuppressions) { if (s.matched) continue; + if (s.cppcheckId > 0) + continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; if (s.isLocal()) diff --git a/lib/suppressions.h b/lib/suppressions.h index 014e897df..5c9873e37 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -35,6 +35,7 @@ class CPPCHECKLIB Suppressions { public: struct CPPCHECKLIB ErrorMessage { + std::size_t cppcheckId; std::string errorId; void setFileName(const std::string &s); const std::string &getFileName() const { @@ -48,17 +49,18 @@ public: }; struct CPPCHECKLIB Suppression { - Suppression() : lineNumber(NO_LINE), matched(false) {} + Suppression() : lineNumber(NO_LINE), cppcheckId(0), matched(false) {} Suppression(const Suppression &other) { *this = other; } - Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), matched(false) {} + Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), cppcheckId(0), matched(false) {} Suppression & operator=(const Suppression &other) { errorId = other.errorId; fileName = other.fileName; lineNumber = other.lineNumber; symbolName = other.symbolName; + cppcheckId = other.cppcheckId; matched = other.matched; return *this; } @@ -72,6 +74,8 @@ public: return fileName < other.fileName; if (symbolName != other.symbolName) return symbolName < other.symbolName; + if (cppcheckId != other.cppcheckId) + return cppcheckId < other.cppcheckId; return false; } @@ -96,6 +100,7 @@ public: std::string fileName; int lineNumber; std::string symbolName; + std::size_t cppcheckId; bool matched; enum { NO_LINE = -1 }; From 60399b9321e37d39ec095e8c983f91a86adc275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 15 Jul 2020 13:06:17 +0200 Subject: [PATCH 064/116] cppcheck-errors.rng: Update xml schema, cppcheck-id attribute was added --- cppcheck-errors.rng | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cppcheck-errors.rng b/cppcheck-errors.rng index 57ce4224c..6446d98ce 100644 --- a/cppcheck-errors.rng +++ b/cppcheck-errors.rng @@ -24,6 +24,13 @@ + + + + 1 + + + From 4f83b3618c7c9012ffe61816e975537fa2852941 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Wed, 15 Jul 2020 13:37:01 +0200 Subject: [PATCH 065/116] wxwidgets.cfg: Added support for more interfaces --- cfg/wxwidgets.cfg | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg index 6bdb96c43..ef4014114 100644 --- a/cfg/wxwidgets.cfg +++ b/cfg/wxwidgets.cfg @@ -13428,6 +13428,13 @@ wxItemKind kind = wxITEM_NORMAL) --> + + + false + + + + false From bdb08232d7ab6c7713e89c55d1e8e8caaaaccb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 15 Jul 2020 20:55:04 +0200 Subject: [PATCH 066/116] GUI: Fix suppression of multiple cppcheck ids --- gui/resultstree.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 7233a4d15..a9202bde0 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -1047,17 +1047,24 @@ void ResultsTree::suppressCppcheckID() { if (!mSelectionModel) return; - ProjectFile *projectFile = ProjectFile::getActiveProject(); + + // Extract selected warnings + QSet selectedWarnings; foreach (QModelIndex index, mSelectionModel->selectedRows()) { QStandardItem *item = mModel.itemFromIndex(index); if (!item->parent()) continue; while (item->parent()->parent()) item = item->parent(); + selectedWarnings.insert(item); + } + + ProjectFile *projectFile = ProjectFile::getActiveProject(); + for (QStandardItem *item: selectedWarnings) { + QStandardItem *fileItem = item->parent(); const QVariantMap data = item->data().toMap(); if (projectFile && data.contains("cppcheckId")) projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong()); - QStandardItem *fileItem = item->parent(); fileItem->removeRow(item->row()); if (fileItem->rowCount() == 0) mModel.removeRow(fileItem->row()); From a23d1ca6925ebe07bb30b4d421d69f21b4d430c2 Mon Sep 17 00:00:00 2001 From: Simon Large Date: Wed, 15 Jul 2020 21:49:25 +0200 Subject: [PATCH 067/116] manual.md: Reformatting lists --- man/manual.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/man/manual.md b/man/manual.md index 63fbe9df0..98a2c126d 100644 --- a/man/manual.md +++ b/man/manual.md @@ -175,8 +175,9 @@ As you can see Cppcheck has instantiated `a` until `a<101>` was reached and then it bails out. To limit template recursion you can; - * add template specialisation - * configure cppcheck (in the GUI project file dialog) + +- add template specialisation +- configure cppcheck (in the GUI project file dialog) Example code with template specialisation: @@ -348,14 +349,15 @@ Use `--std` on the command line to specify a C/C++ standard. Cppcheck assumes that the code is compatible with the latest C/C++ standard but you can override this. The available options are: - * c89: C code is C89 compatible - * c99: C code is C99 compatible - * c11: C code is C11 compatible (default) - * c++03: C++ code is C++03 compatible - * c++11: C++ code is C++11 compatible - * c++14: C++ code is C++14 compatible - * c++17: C++ code is C++17 compatible - * c++20: C++ code is C++20 compatible (default) + +- c89: C code is C89 compatible +- c99: C code is C99 compatible +- c11: C code is C11 compatible (default) +- c++03: C++ code is C++03 compatible +- c++11: C++ code is C++11 compatible +- c++14: C++ code is C++14 compatible +- c++17: C++ code is C++17 compatible +- c++20: C++ code is C++20 compatible (default) # Suppressions @@ -873,10 +875,11 @@ This analysis is "soundy"; it should diagnose most bugs reported in CVEs and fro You have to expect false alarms. However Cppcheck tries to limit false alarms. The purpose of the data flow analysis is to limit false alarms. Some possible use cases; - * you are writing new code and want to ensure it is safe. - * you are reviewing code and want to get hints about possible UB. - * you need extra help troubleshooting a weird bug. - * you want to check if a release candidate is safe. + +- you are writing new code and want to ensure it is safe. +- you are reviewing code and want to get hints about possible UB. +- you need extra help troubleshooting a weird bug. +- you want to check if a release candidate is safe. The intention is that this will be used primarily in the GUI. @@ -923,8 +926,9 @@ Cppcheck will warn: ## Adding a contract in the GUI There are two ways: - * Open the "Contracts" tab at the bottom of the screen. Find the function in the listbox and double click on it. - * Right click on a warning and click on "Edit contract.." in the popup menu. This popup menu item is only available if the warning is not inconclusive. + +- Open the "Contracts" tab at the bottom of the screen. Find the function in the listbox and double click on it. +- Right click on a warning and click on "Edit contract.." in the popup menu. This popup menu item is only available if the warning is not inconclusive. ## Incomplete analysis From dac72beb9e4bd818ef41b47b37c24a9b97388eeb Mon Sep 17 00:00:00 2001 From: Simon Large Date: Wed, 15 Jul 2020 21:56:27 +0200 Subject: [PATCH 068/116] manual: add css style sheet --- man/manual.css | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 man/manual.css diff --git a/man/manual.css b/man/manual.css new file mode 100644 index 000000000..06c29597c --- /dev/null +++ b/man/manual.css @@ -0,0 +1,86 @@ +/* stylelint-disable */ +html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none} +/* stylelint-enable */ + +html { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} + +body { + background-color: #fff; + color: #333; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + max-width: 1012px; + margin: 20px auto; + border: 1px solid #eaecef; + padding: 20px 100px; + overflow-x: hidden; +} + +a { + color: #0366d6; + text-decoration: none; + +} + +a:hover, a:focus { + text-decoration: underline; + +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.25; + margin-top: 24px; + margin-bottom: 16px; +} + +h1 { + font-size: 2em; +} + +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.25em; +} + +h4, h5, h6 { + font-size: 1.1em; +} + +h1, h2 { + padding-bottom: 0.3em; + border-bottom: 1px solid #eaecef; +} + +pre { + padding: 16px; + background-color: #f6f8fa; + border-radius: 6px; + line-height: 1.45; + font-size: 85%; + margin-top: 0; + margin-bottom: 16px; + overflow: auto; +} + +code { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + background-color: #f6f8fa; + font-size: 100%; + word-break: normal; + white-space: pre; +} + +ol, ul { + padding-left: 2em; + margin-top: 0; + margin-bottom: 16px; +} From c02b39a3baca1cc3da6e7cb54764788223ec318c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 15 Jul 2020 21:57:49 +0200 Subject: [PATCH 069/116] Use manual.css in manual --- man/buildman.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/buildman.sh b/man/buildman.sh index d7371c03a..f1ff96f31 100755 --- a/man/buildman.sh +++ b/man/buildman.sh @@ -2,8 +2,8 @@ # To install required tools in debian: # sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-latex-extra -pandoc manual.md -o manual.pdf -s --number-sections --toc -pandoc manual.md -o manual.html -s --number-sections --toc +pandoc manual.md -o manual.pdf -s --number-sections --toc --css manual.css +pandoc manual.md -o manual.html -s --number-sections --toc --css manual.css pandoc reference-cfg-format.md -o reference-cfg-format.pdf -s --number-sections --toc pandoc reference-cfg-format.md -o reference-cfg-format.html -s --number-sections --toc From 118e9eb3e2783b1d9682a91247f9c86d83ad14bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 15:27:07 +0200 Subject: [PATCH 070/116] Better handling of CppcheckID suppressions --- lib/importproject.cpp | 22 +++++++++++++++++----- lib/suppressions.cpp | 10 +++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 384ad4682..727c6704f 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1033,7 +1033,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti const std::string &path = mPath; std::list paths; - std::list suppressions; + std::list suppressions; Settings temp; guiProject.analyzeAllVsConfigs.clear(); @@ -1072,8 +1072,20 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib); else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0) guiProject.libraries = readXmlStringList(node, "", CppcheckXml::LibraryElementName, nullptr); - else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0) - suppressions = readXmlStringList(node, "", CppcheckXml::SuppressionElementName, nullptr); + else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0) { + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { + if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0) + continue; + auto read = [](const char *s, const char *def) { return s ? s : def; }; + Suppressions::Suppression s; + s.errorId = read(child->GetText(), ""); + s.fileName = read(child->Attribute("fileName"), ""); + s.lineNumber = child->IntAttribute("lineNumber", Suppressions::Suppression::NO_LINE); + s.symbolName = read(child->Attribute("symbolName"), ""); + std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId; + suppressions.push_back(s); + } + } else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0) guiProject.checkVsConfigs = readXmlStringList(node, "", CppcheckXml::VSConfigurationName, nullptr); else if (strcmp(node->Name(), CppcheckXml::PlatformElementName) == 0) @@ -1130,8 +1142,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti for (const std::string &p : paths) guiProject.pathNames.push_back(p); - for (const std::string &supp : suppressions) - settings->nomsg.addSuppressionLine(supp); + for (const Suppressions::Suppression &supp : suppressions) + settings->nomsg.addSuppression(supp); settings->checkHeaders = temp.checkHeaders; settings->checkUnusedTemplates = temp.checkUnusedTemplates; settings->maxCtuDepth = temp.maxCtuDepth; diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index 93bc71531..e436a7413 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -98,8 +98,10 @@ std::string Suppressions::parseXmlFile(const char *filename) s.lineNumber = std::atoi(text); else if (std::strcmp(e2->Name(), "symbolName") == 0) s.symbolName = text; + else if (*text && std::strcmp(e2->Name(), "cppcheckId") == 0) + std::istringstream(text) >> s.cppcheckId; else - return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ///"; + return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ////"; } const std::string err = addSuppression(s); @@ -317,6 +319,8 @@ std::string Suppressions::Suppression::getText() const ret += " lineNumber=" + MathLib::toString(lineNumber); if (!symbolName.empty()) ret += " symbolName=" + symbolName; + if (cppcheckId > 0) + ret += " cppcheckId=" + MathLib::toString(cppcheckId); if (ret.compare(0,1," ")==0) return ret.substr(1); return ret; @@ -360,13 +364,13 @@ void Suppressions::dump(std::ostream & out) const out << " lineNumber=\"" << suppression.lineNumber << '"'; if (!suppression.symbolName.empty()) out << " symbolName=\"" << ErrorLogger::toxml(suppression.symbolName) << '\"'; + if (suppression.cppcheckId > 0) + out << " cppcheckId=\"" << suppression.cppcheckId << '\"'; out << " />" << std::endl; } out << " " << std::endl; } -#include - std::list Suppressions::getUnmatchedLocalSuppressions(const std::string &file, const bool unusedFunctionChecking) const { std::list result; From 9edbec8594c0d841d488552f35b4b80062abefb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 16:36:11 +0200 Subject: [PATCH 071/116] astyle formatting --- lib/importproject.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 727c6704f..ad498d21d 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1076,7 +1076,9 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0) continue; - auto read = [](const char *s, const char *def) { return s ? s : def; }; + auto read = [](const char *s, const char *def) { + return s ? s : def; + }; Suppressions::Suppression s; s.errorId = read(child->GetText(), ""); s.fileName = read(child->Attribute("fileName"), ""); @@ -1085,8 +1087,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId; suppressions.push_back(s); } - } - else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0) + } else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0) guiProject.checkVsConfigs = readXmlStringList(node, "", CppcheckXml::VSConfigurationName, nullptr); else if (strcmp(node->Name(), CppcheckXml::PlatformElementName) == 0) guiProject.platform = node->GetText(); From bec78a096004dd5f0f5936dbf46d3621616e5c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 16:36:55 +0200 Subject: [PATCH 072/116] GUI: Adding suppression with cppcheckId; include all relevant fields --- gui/projectfile.cpp | 15 +++++---------- gui/projectfile.h | 6 +++--- gui/resultstree.cpp | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index fce386ee8..60872dac3 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -719,6 +719,11 @@ void ProjectFile::setSuppressions(const QList &suppre mSuppressions = suppressions; } +void ProjectFile::addSuppression(const Suppressions::Suppression &suppression) +{ + mSuppressions.append(suppression); +} + void ProjectFile::setAddons(const QStringList &addons) { mAddons = addons; @@ -1027,13 +1032,3 @@ QString ProjectFile::getAddonFilePath(QString filesDir, const QString &addon) return QString(); } - -void ProjectFile::suppressCppcheckId(std::size_t cppcheckId) -{ - if (cppcheckId > 0) { - Suppressions::Suppression s; - s.cppcheckId = cppcheckId; - mSuppressions.append(s); - write(); - } -} diff --git a/gui/projectfile.h b/gui/projectfile.h index fc1effc00..1a92f994e 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -55,9 +55,6 @@ public: mActiveProject = this; } - /** Suppress warning with Cppcheck-ID */ - void suppressCppcheckId(std::size_t cppcheckId); - /** * @brief Read the project file. * @param filename Filename (can be also given to constructor). @@ -312,6 +309,9 @@ public: */ void setSuppressions(const QList &suppressions); + /** Add suppression */ + void addSuppression(const Suppressions::Suppression &suppression); + /** * @brief Set list of addons. * @param addons List of addons. diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index a9202bde0..27274c176 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -1059,16 +1059,27 @@ void ResultsTree::suppressCppcheckID() selectedWarnings.insert(item); } + bool changed = false; ProjectFile *projectFile = ProjectFile::getActiveProject(); for (QStandardItem *item: selectedWarnings) { QStandardItem *fileItem = item->parent(); const QVariantMap data = item->data().toMap(); - if (projectFile && data.contains("cppcheckId")) - projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong()); + if (projectFile && data.contains("cppcheckId")) { + Suppressions::Suppression suppression; + suppression.cppcheckId = data["cppcheckId"].toULongLong(); + suppression.errorId = data["id"].toString().toStdString(); + suppression.fileName = data["file"].toString().toStdString(); + suppression.lineNumber = data["line"].toInt(); + projectFile->addSuppression(suppression); + changed = true; + } fileItem->removeRow(item->row()); if (fileItem->rowCount() == 0) mModel.removeRow(fileItem->row()); } + + if (changed) + projectFile->write(); } void ResultsTree::openContainingFolder() From 0632f864491bd1c39ae70479d00c095a84e3375d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 16:38:22 +0200 Subject: [PATCH 073/116] Bug hunting; avoid bailout false positives when constructor is called --- lib/bughuntingchecks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 46879ec04..454a50bc5 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -248,7 +248,7 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (!var->isLocal() || var->isStatic()) return; } - if (var && (Token::Match(var->nameToken(), "%name% [=:]") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId()))) + if (var && (Token::Match(var->nameToken(), "%name% [=:({)]") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId()))) return; if (var && var->nameToken() == tok) return; From b249d9be313dec9fcb7fe58f0dfb1413a39fb7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 20:19:36 +0200 Subject: [PATCH 074/116] GUI: Refactor tags --- gui/mainwindow.cpp | 4 ---- gui/resultstree.cpp | 9 +++++---- gui/resultstree.h | 6 ------ gui/resultsview.h | 4 ---- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index e721b605c..eab66d82f 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1524,8 +1524,6 @@ QString MainWindow::getLastResults() const bool MainWindow::loadLastResults() { - if (mProjectFile) - mUI.mResults->setTags(mProjectFile->getTags()); const QString &lastResults = getLastResults(); if (lastResults.isEmpty()) return false; @@ -1548,7 +1546,6 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const bool check QDir::setCurrent(inf.absolutePath()); mThread->setAddonsAndTools(projectFile->getAddonsAndTools()); - mUI.mResults->setTags(projectFile->getTags()); // If the root path is not given or is not "current dir", use project // file's location directory as root path @@ -1652,7 +1649,6 @@ void MainWindow::closeProjectFile() delete mProjectFile; mProjectFile = nullptr; mUI.mResults->clear(true); - mUI.mResults->setTags(QStringList()); enableProjectActions(false); enableProjectOpenActions(true); formatAndSetTitle(); diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 27274c176..48e481b4f 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -50,8 +50,8 @@ #include "xmlreportv2.h" // These must match column headers given in ResultsTree::translate() -static const unsigned int COLUMN_SINCE_DATE = 6; -static const unsigned int COLUMN_TAGS = 7; +static const int COLUMN_SINCE_DATE = 6; +static const int COLUMN_TAGS = 7; static QString getFunction(QStandardItem *item) { @@ -673,7 +673,8 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) connect(suppressCppcheckID, &QAction::triggered, this, &ResultsTree::suppressCppcheckID); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); - if (!mTags.isEmpty()) { + const ProjectFile *currentProject = ProjectFile::getActiveProject(); + if (currentProject && !currentProject->getTags().isEmpty()) { menu.addSeparator(); QMenu *tagMenu = menu.addMenu(tr("Tag")); { @@ -684,7 +685,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) }); } - foreach (const QString tagstr, mTags) { + foreach (const QString tagstr, currentProject->getTags()) { QAction *action = new QAction(tagstr, tagMenu); tagMenu->addAction(action); connect(action, &QAction::triggered, [=]() { diff --git a/gui/resultstree.h b/gui/resultstree.h index 349ea855f..cc7dacd35 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -52,10 +52,6 @@ public: virtual ~ResultsTree(); void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler); - void setTags(const QStringList &tags) { - mTags = tags; - } - /** * @brief Add a new item to the tree * @@ -532,8 +528,6 @@ private: /** @brief Convert GUI error item into data error item */ void readErrorItem(const QStandardItem *error, ErrorItem *item) const; - QStringList mTags; - QStringList mHiddenMessageId; QItemSelectionModel *mSelectionModel; diff --git a/gui/resultsview.h b/gui/resultsview.h index bd623b836..15f98e9cb 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -50,10 +50,6 @@ public: virtual ~ResultsView(); ResultsView &operator=(const ResultsView &) = delete; - void setTags(const QStringList &tags) { - mUI.mTree->setTags(tags); - } - void setAddedContracts(const QStringList &addedContracts); /** From 9af288e1dde6647ee9fea0a393d54e719007f343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 16 Jul 2020 23:03:54 +0200 Subject: [PATCH 075/116] Fixed #9724 (GUI: The tags do not work anymore) --- gui/mainwindow.cpp | 8 -- gui/mainwindow.h | 3 - gui/projectfile.cpp | 76 ++++++++++++++++- gui/projectfile.h | 19 ++++- gui/resultstree.cpp | 203 ++++++++++++++++++++++++-------------------- gui/resultsview.cpp | 1 - gui/resultsview.h | 5 -- lib/importproject.h | 4 + 8 files changed, 210 insertions(+), 109 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index eab66d82f..d98ea4b6e 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -141,7 +141,6 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI.mResults, &ResultsView::gotResults, this, &MainWindow::resultsAdded); connect(mUI.mResults, &ResultsView::resultsHidden, mUI.mActionShowHidden, &QAction::setEnabled); connect(mUI.mResults, &ResultsView::checkSelected, this, &MainWindow::performSelectedFilesCheck); - connect(mUI.mResults, &ResultsView::tagged, this, &MainWindow::tagged); connect(mUI.mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds); connect(mUI.mResults, &ResultsView::editFunctionContract, this, &MainWindow::editFunctionContract); connect(mUI.mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu); @@ -1810,13 +1809,6 @@ void MainWindow::selectPlatform() } } -void MainWindow::tagged() -{ - const QString &lastResults = getLastResults(); - if (!lastResults.isEmpty()) - mUI.mResults->save(lastResults, Report::XMLV2); -} - void MainWindow::suppressIds(QStringList ids) { if (!mProjectFile) diff --git a/gui/mainwindow.h b/gui/mainwindow.h index d5ccf2b1c..2ea082d83 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -221,9 +221,6 @@ protected slots: /** @brief Selects the platform as analyzed platform. */ void selectPlatform(); - /** Some results were tagged */ - void tagged(); - /** Suppress error ids */ void suppressIds(QStringList ids); diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 60872dac3..8745cf345 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -72,6 +72,8 @@ void ProjectFile::clear() mCheckUnknownFunctionReturn.clear(); safeChecks.clear(); mVsConfigurations.clear(); + mTags.clear(); + mWarningTags.clear(); } bool ProjectFile::read(const QString &filename) @@ -188,6 +190,9 @@ bool ProjectFile::read(const QString &filename) if (xmlReader.name() == CppcheckXml::TagsElementName) readStringList(mTags, xmlReader, CppcheckXml::TagElementName); + if (xmlReader.name() == CppcheckXml::TagWarningsElementName) + readTagWarnings(xmlReader, xmlReader.attributes().value(QString(), CppcheckXml::TagAttributeName).toString()); + if (xmlReader.name() == CppcheckXml::MaxCtuDepthElementName) mMaxCtuDepth = readInt(xmlReader, mMaxCtuDepth); @@ -636,6 +641,41 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader) } +void ProjectFile::readTagWarnings(QXmlStreamReader &reader, QString tag) +{ + QXmlStreamReader::TokenType type; + do { + type = reader.readNext(); + switch (type) { + case QXmlStreamReader::StartElement: + // Read library-elements + if (reader.name().toString() == CppcheckXml::WarningElementName) { + std::size_t cppcheckId = reader.attributes().value(QString(), CppcheckXml::CppcheckIdAttributeName).toULongLong(); + mWarningTags[cppcheckId] = tag; + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name().toString() != CppcheckXml::WarningElementName) + return; + break; + + // Not handled + case QXmlStreamReader::NoToken: + case QXmlStreamReader::Invalid: + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::EndDocument: + case QXmlStreamReader::Characters: + case QXmlStreamReader::Comment: + case QXmlStreamReader::DTD: + case QXmlStreamReader::EntityReference: + case QXmlStreamReader::ProcessingInstruction: + break; + } + } while (true); +} + + void ProjectFile::readStringList(QStringList &stringlist, QXmlStreamReader &reader, const char elementname[]) { QXmlStreamReader::TokenType type; @@ -734,6 +774,20 @@ void ProjectFile::setVSConfigurations(const QStringList &vsConfigs) mVsConfigurations = vsConfigs; } +void ProjectFile::setWarningTags(std::size_t cppcheckId, QString tag) +{ + if (tag.isEmpty()) + mWarningTags.erase(cppcheckId); + else if (cppcheckId > 0) + mWarningTags[cppcheckId] = tag; +} + +QString ProjectFile::getWarningTags(std::size_t cppcheckId) const +{ + auto it = mWarningTags.find(cppcheckId); + return (it != mWarningTags.end()) ? it->second : QString(); +} + bool ProjectFile::write(const QString &filename) { if (!filename.isEmpty()) @@ -883,7 +937,7 @@ bool ProjectFile::write(const QString &filename) if (!suppression.symbolName.empty()) xmlWriter.writeAttribute("symbolName", QString::fromStdString(suppression.symbolName)); if (suppression.cppcheckId > 0) - xmlWriter.writeAttribute("cppcheck-id", QString::number(suppression.cppcheckId)); + xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(suppression.cppcheckId)); if (!suppression.errorId.empty()) xmlWriter.writeCharacters(QString::fromStdString(suppression.errorId)); xmlWriter.writeEndElement(); @@ -914,6 +968,26 @@ bool ProjectFile::write(const QString &filename) CppcheckXml::ToolElementName); writeStringList(xmlWriter, mTags, CppcheckXml::TagsElementName, CppcheckXml::TagElementName); + if (!mWarningTags.empty()) { + QStringList tags; + for (const auto wt: mWarningTags) { + if (!tags.contains(wt.second)) + tags.append(wt.second); + } + for (const QString &tag: tags) { + xmlWriter.writeStartElement(CppcheckXml::TagWarningsElementName); + xmlWriter.writeAttribute(CppcheckXml::TagAttributeName, tag); + QStringList warnings; + for (const auto wt: mWarningTags) { + if (wt.second == tag) { + xmlWriter.writeStartElement(CppcheckXml::WarningElementName); + xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(wt.first)); + xmlWriter.writeEndElement(); + } + } + xmlWriter.writeEndElement(); + } + } xmlWriter.writeEndDocument(); file.close(); diff --git a/gui/projectfile.h b/gui/projectfile.h index 1a92f994e..1d372ed35 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -331,6 +331,12 @@ public: mTags = tags; } + /** Set tags for a warning */ + void setWarningTags(std::size_t cppcheckId, QString tags); + + /** Get tags for a warning */ + QString getWarningTags(std::size_t cppcheckId) const; + /** * @brief Write project file (to disk). * @param filename Filename to use. @@ -437,6 +443,12 @@ protected: */ void readSuppressions(QXmlStreamReader &reader); + /** + * @brief Read tag warnings, what warnings are tagged with a specific tag + * @param reader XML stream reader. + */ + void readTagWarnings(QXmlStreamReader &reader, QString tag); + /** * @brief Read string list * @param stringlist destination string list @@ -552,10 +564,15 @@ private: bool mClangTidy; /** - * @brief Warning tags + * @brief Tags */ QStringList mTags; + /** + * @brief Warning tags + */ + std::map mWarningTags; + /** Max CTU depth */ int mMaxCtuDepth; diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 48e481b4f..392fc082e 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -49,6 +49,23 @@ #include "path.h" #include "xmlreportv2.h" +static const char COLUMN[] = "column"; +static const char CPPCHECKID[] = "cppcheckId"; +static const char CWE[] = "cwe"; +static const char ERRORID[] = "id"; +static const char FILENAME[] = "file"; +static const char FILE0[] = "file0"; +static const char FUNCTION[] = "function"; +static const char HIDE[] = "hide"; +static const char INCOMPLETE[] = "incomplete"; +static const char INCONCLUSIVE[] = "inconclusive"; +static const char LINE[] = "line"; +static const char MESSAGE[] = "message"; +static const char SEVERITY[] = "severity"; +static const char SINCEDATE[] = "sinceDate"; +static const char SUMMARY[] = "summary"; +static const char TAGS[] = "tags"; + // These must match column headers given in ResultsTree::translate() static const int COLUMN_SINCE_DATE = 6; static const int COLUMN_TAGS = 7; @@ -175,7 +192,9 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) line.message = item.message; line.severity = item.severity; line.sinceDate = item.sinceDate; - line.tags = item.tags; + if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) { + line.tags = activeProject->getWarningTags(item.cppcheckId); + } //Create the base item for the error and ensure it has a proper //file item as a parent QStandardItem* fileItem = ensureFileItem(loc.file, item.file0, hide); @@ -190,22 +209,22 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) //Add user data to that item QMap data; - data["severity"] = ShowTypes::SeverityToShowType(item.severity); - data["summary"] = item.summary; - data["message"] = item.message; - data["file"] = loc.file; - data["line"] = loc.line; - data["column"] = loc.column; - data["id"] = item.errorId; - data["incomplete"] = item.incomplete; - data["cwe"] = item.cwe; - data["cppcheckId"] = item.cppcheckId; - data["inconclusive"] = item.inconclusive; - data["file0"] = stripPath(item.file0, true); - data["function"] = item.function; - data["sinceDate"] = item.sinceDate; - data["tags"] = item.tags; - data["hide"] = hide; + data[SEVERITY] = ShowTypes::SeverityToShowType(item.severity); + data[SUMMARY] = item.summary; + data[MESSAGE] = item.message; + data[FILENAME] = loc.file; + data[LINE] = loc.line; + data[COLUMN] = loc.column; + data[ERRORID] = item.errorId; + data[INCOMPLETE] = item.incomplete; + data[CWE] = item.cwe; + data[CPPCHECKID] = item.cppcheckId; + data[INCONCLUSIVE] = item.inconclusive; + data[FILE0] = stripPath(item.file0, true); + data[FUNCTION] = item.function; + data[SINCEDATE] = item.sinceDate; + data[TAGS] = line.tags; + data[HIDE] = hide; stditem->setData(QVariant(data)); //Add backtrace files as children @@ -226,17 +245,17 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) // Add user data to that item QMap child_data; - child_data["severity"] = ShowTypes::SeverityToShowType(line.severity); - child_data["summary"] = line.summary; - child_data["message"] = line.message; - child_data["file"] = e.file; - child_data["line"] = e.line; - child_data["column"] = e.column; - child_data["id"] = line.errorId; - child_data["incomplete"] = line.incomplete; - child_data["cwe"] = line.cwe; - child_data["cppcheckId"] = line.cppcheckId; - child_data["inconclusive"] = line.inconclusive; + child_data[SEVERITY] = ShowTypes::SeverityToShowType(line.severity); + child_data[SUMMARY] = line.summary; + child_data[MESSAGE] = line.message; + child_data[FILENAME] = e.file; + child_data[LINE] = e.line; + child_data[COLUMN] = e.column; + child_data[ERRORID] = line.errorId; + child_data[INCOMPLETE] = line.incomplete; + child_data[CWE] = line.cwe; + child_data[CPPCHECKID] = line.cppcheckId; + child_data[INCONCLUSIVE] = line.inconclusive; child_item->setData(QVariant(child_data)); } } @@ -363,8 +382,8 @@ void ResultsTree::clear(const QString &filename) continue; QVariantMap data = fileItem->data().toMap(); - if (stripped == data["file"].toString() || - filename == data["file0"].toString()) { + if (stripped == data[FILENAME].toString() || + filename == data[FILE0].toString()) { mModel.removeRow(i); break; } @@ -380,7 +399,7 @@ void ResultsTree::clearRecheckFile(const QString &filename) QString actualfile((!mCheckPath.isEmpty() && filename.startsWith(mCheckPath)) ? filename.mid(mCheckPath.length() + 1) : filename); QVariantMap data = fileItem->data().toMap(); - QString storedfile = data["file"].toString(); + QString storedfile = data[FILENAME].toString(); storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile); if (actualfile == storedfile) { mModel.removeRow(i); @@ -450,7 +469,7 @@ void ResultsTree::showHiddenResults() continue; QVariantMap data = fileItem->data().toMap(); - data["hide"] = false; + data[HIDE] = false; fileItem->setData(QVariant(data)); int errorcount = fileItem->rowCount(); @@ -458,7 +477,7 @@ void ResultsTree::showHiddenResults() QStandardItem *child = fileItem->child(j, 0); if (child) { data = child->data().toMap(); - data["hide"] = false; + data[HIDE] = false; child->setData(QVariant(data)); } } @@ -500,21 +519,21 @@ void ResultsTree::refreshTree() QVariantMap data = userdata.toMap(); //Check if this error should be hidden - bool hide = (data["hide"].toBool() || !mShowSeverities.isShown(ShowTypes::VariantToShowType(data["severity"]))); + bool hide = (data[HIDE].toBool() || !mShowSeverities.isShown(ShowTypes::VariantToShowType(data[SEVERITY]))); //If specified, filter on summary, message, filename, and id if (!hide && !mFilter.isEmpty()) { - if (!data["summary"].toString().contains(mFilter, Qt::CaseInsensitive) && - !data["message"].toString().contains(mFilter, Qt::CaseInsensitive) && - !data["file"].toString().contains(mFilter, Qt::CaseInsensitive) && - !data["id"].toString().contains(mFilter, Qt::CaseInsensitive)) { + if (!data[SUMMARY].toString().contains(mFilter, Qt::CaseInsensitive) && + !data[MESSAGE].toString().contains(mFilter, Qt::CaseInsensitive) && + !data[FILENAME].toString().contains(mFilter, Qt::CaseInsensitive) && + !data[ERRORID].toString().contains(mFilter, Qt::CaseInsensitive)) { hide = true; } } // Tool filter if (!hide) { - if (data["id"].toString().startsWith("clang")) + if (data[ERRORID].toString().startsWith("clang")) hide = !mShowClang; else hide = !mShowCppcheck; @@ -561,8 +580,8 @@ QStandardItem *ResultsTree::ensureFileItem(const QString &fullpath, const QStrin //Add user data to that item QMap data; - data["file"] = fullpath; - data["file0"] = file0; + data[FILENAME] = fullpath; + data[FILE0] = file0; item->setData(QVariant(data)); mModel.appendRow(item); @@ -753,7 +772,7 @@ void ResultsTree::startApplication(QStandardItem *target, int application) QVariantMap data = target->data().toMap(); //Replace (file) with filename - QString file = data["file"].toString(); + QString file = data[FILENAME].toString(); file = QDir::toNativeSeparators(file); #ifdef Q_OS_WIN file.replace(QString("\\"), QString("\\\\")); @@ -789,11 +808,11 @@ void ResultsTree::startApplication(QStandardItem *target, int application) QString params = app.getParameters(); params.replace("(file)", file, Qt::CaseInsensitive); - QVariant line = data["line"]; + QVariant line = data[LINE]; params.replace("(line)", QString("%1").arg(line.toInt()), Qt::CaseInsensitive); - params.replace("(message)", data["message"].toString(), Qt::CaseInsensitive); - params.replace("(severity)", data["severity"].toString(), Qt::CaseInsensitive); + params.replace("(message)", data[MESSAGE].toString(), Qt::CaseInsensitive); + params.replace("(severity)", data[SEVERITY].toString(), Qt::CaseInsensitive); QString program = app.getPath(); @@ -889,14 +908,14 @@ void ResultsTree::copy() QVariantMap data = item->data().toMap(); if (!data.contains("id")) continue; - QString inconclusive = data["inconclusive"].toBool() ? ",inconclusive" : ""; - text += '[' + data["file"].toString() + ':' + QString::number(data["line"].toInt()) + QString inconclusive = data[INCONCLUSIVE].toBool() ? ",inconclusive" : ""; + text += '[' + data[FILENAME].toString() + ':' + QString::number(data[LINE].toInt()) + "] (" - + QString::fromStdString(Severity::toString(ShowTypes::ShowTypeToSeverity((ShowTypes::ShowType)data["severity"].toInt()))) + inconclusive + + QString::fromStdString(Severity::toString(ShowTypes::ShowTypeToSeverity((ShowTypes::ShowType)data[SEVERITY].toInt()))) + inconclusive + ") " - + data["message"].toString() + + data[MESSAGE].toString() + " [" - + data["id"].toString() + + data[ERRORID].toString() + "]\n"; } @@ -914,7 +933,7 @@ void ResultsTree::hideResult() QStandardItem *item = mModel.itemFromIndex(index); //Set the "hide" flag for this item QVariantMap data = item->data().toMap(); - data["hide"] = true; + data[HIDE] = true; item->setData(QVariant(data)); refreshTree(); @@ -934,7 +953,7 @@ void ResultsTree::recheckSelectedFiles() while (item->parent()) item = item->parent(); QVariantMap data = item->data().toMap(); - QString currentFile = data["file"].toString(); + QString currentFile = data[FILENAME].toString(); if (!currentFile.isEmpty()) { QString fileNameWithCheckPath; QFileInfo curfileInfo(currentFile); @@ -948,8 +967,8 @@ void ResultsTree::recheckSelectedFiles() return; } if (Path::isHeader(currentFile.toStdString())) { - if (!data["file0"].toString().isEmpty() && !selectedItems.contains(data["file0"].toString())) { - selectedItems<<((!mCheckPath.isEmpty() && (data["file0"].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data["file0"].toString()) : data["file0"].toString()); + if (!data[FILE0].toString().isEmpty() && !selectedItems.contains(data[FILE0].toString())) { + selectedItems<<((!mCheckPath.isEmpty() && (data[FILE0].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data[FILE0].toString()) : data[FILE0].toString()); if (!selectedItems.contains(fileNameWithCheckPath)) selectedItems<parent()->child(mContextItem->row(), 0); QVariantMap data = mContextItem->data().toMap(); - QString messageId = data["id"].toString(); + QString messageId = data[ERRORID].toString(); mHiddenMessageId.append(messageId); @@ -994,8 +1013,8 @@ void ResultsTree::hideAllIdResult() } QVariantMap userdata = child->data().toMap(); - if (userdata["id"].toString() == messageId) { - userdata["hide"] = true; + if (userdata[ERRORID].toString() == messageId) { + userdata[HIDE] = true; child->setData(QVariant(userdata)); } } @@ -1021,7 +1040,7 @@ void ResultsTree::suppressSelectedIds() QVariantMap data = item->data().toMap(); if (!data.contains("id")) continue; - selectedIds << data["id"].toString(); + selectedIds << data[ERRORID].toString(); } // delete all errors with selected message Ids @@ -1030,7 +1049,7 @@ void ResultsTree::suppressSelectedIds() for (int j = 0; j < file->rowCount();) { QStandardItem *errorItem = file->child(j, 0); QVariantMap userdata = errorItem->data().toMap(); - if (selectedIds.contains(userdata["id"].toString())) { + if (selectedIds.contains(userdata[ERRORID].toString())) { file->removeRow(j); } else { j++; @@ -1067,10 +1086,10 @@ void ResultsTree::suppressCppcheckID() const QVariantMap data = item->data().toMap(); if (projectFile && data.contains("cppcheckId")) { Suppressions::Suppression suppression; - suppression.cppcheckId = data["cppcheckId"].toULongLong(); - suppression.errorId = data["id"].toString().toStdString(); - suppression.fileName = data["file"].toString().toStdString(); - suppression.lineNumber = data["line"].toInt(); + suppression.cppcheckId = data[CPPCHECKID].toULongLong(); + suppression.errorId = data[ERRORID].toString().toStdString(); + suppression.fileName = data[FILENAME].toString().toStdString(); + suppression.lineNumber = data[LINE].toInt(); projectFile->addSuppression(suppression); changed = true; } @@ -1102,18 +1121,22 @@ void ResultsTree::tagSelectedItems(const QString &tag) if (!mSelectionModel) return; bool isTagged = false; + ProjectFile *currentProject = ProjectFile::getActiveProject(); foreach (QModelIndex index, mSelectionModel->selectedRows()) { QStandardItem *item = mModel.itemFromIndex(index); QVariantMap data = item->data().toMap(); if (data.contains("tags")) { - data["tags"] = tag; + data[TAGS] = tag; item->setData(QVariant(data)); item->parent()->child(index.row(), COLUMN_TAGS)->setText(tag); - isTagged = true; + if (currentProject && data.contains(CPPCHECKID)) { + isTagged = true; + currentProject->setWarningTags(data[CPPCHECKID].toULongLong(), tag); + } } } if (isTagged) - emit tagged(); + currentProject->write(); } void ResultsTree::context(int application) @@ -1137,7 +1160,7 @@ QString ResultsTree::getFilePath(QStandardItem *target, bool fullPath) QString pathStr; //Replace (file) with filename - QString file = data["file"].toString(); + QString file = data[FILENAME].toString(); pathStr = QDir::toNativeSeparators(file); if (!fullPath) { QFileInfo fi(pathStr); @@ -1237,12 +1260,12 @@ void ResultsTree::updateFromOldReport(const QString &filename) // New error .. set the "sinceDate" property if (oldErrorIndex >= 0 && !oldErrors[oldErrorIndex].sinceDate.isEmpty()) { - data["sinceDate"] = oldErrors[oldErrorIndex].sinceDate; + data[SINCEDATE] = oldErrors[oldErrorIndex].sinceDate; error->setData(data); fileItem->child(j, COLUMN_SINCE_DATE)->setText(oldErrors[oldErrorIndex].sinceDate); - } else if (oldErrorIndex < 0 || data["sinceDate"].toString().isEmpty()) { + } else if (oldErrorIndex < 0 || data[SINCEDATE].toString().isEmpty()) { const QString sinceDate = QDate::currentDate().toString(Qt::SystemLocaleShortDate); - data["sinceDate"] = sinceDate; + data[SINCEDATE] = sinceDate; error->setData(data); fileItem->child(j, COLUMN_SINCE_DATE)->setText(sinceDate); if (oldErrorIndex < 0) @@ -1253,7 +1276,7 @@ void ResultsTree::updateFromOldReport(const QString &filename) continue; const ErrorItem &oldErrorItem = oldErrors[oldErrorIndex]; - data["tags"] = oldErrorItem.tags; + data[TAGS] = oldErrorItem.tags; error->setData(data); } } @@ -1264,23 +1287,23 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con // Get error's user data QVariantMap data = error->data().toMap(); - item->severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(data["severity"])); - item->summary = data["summary"].toString(); - item->message = data["message"].toString(); - item->errorId = data["id"].toString(); - item->incomplete = data["incomplete"].toBool(); - item->cwe = data["cwe"].toInt(); - item->cppcheckId = data["cppcheckId"].toULongLong(); - item->inconclusive = data["inconclusive"].toBool(); - item->file0 = data["file0"].toString(); - item->sinceDate = data["sinceDate"].toString(); - item->tags = data["tags"].toString(); + item->severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(data[SEVERITY])); + item->summary = data[SUMMARY].toString(); + item->message = data[MESSAGE].toString(); + item->errorId = data[ERRORID].toString(); + item->incomplete = data[INCOMPLETE].toBool(); + item->cwe = data[CWE].toInt(); + item->cppcheckId = data[CPPCHECKID].toULongLong(); + item->inconclusive = data[INCONCLUSIVE].toBool(); + item->file0 = data[FILE0].toString(); + item->sinceDate = data[SINCEDATE].toString(); + item->tags = data[TAGS].toString(); if (error->rowCount() == 0) { QErrorPathItem e; - e.file = stripPath(data["file"].toString(), true); - e.line = data["line"].toUInt(); - e.info = data["message"].toString(); + e.file = stripPath(data[FILENAME].toString(), true); + e.line = data[LINE].toInt(); + e.info = data[MESSAGE].toString(); item->errorPath << e; } @@ -1292,9 +1315,9 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con QVariantMap child_data = child_userdata.toMap(); QErrorPathItem e; - e.file = stripPath(child_data["file"].toString(), true); - e.line = child_data["line"].toUInt(); - e.info = child_data["message"].toString(); + e.file = stripPath(child_data[FILENAME].toString(), true); + e.line = child_data[LINE].toUInt(); + e.info = child_data[MESSAGE].toString(); item->errorPath << e; } } @@ -1362,7 +1385,7 @@ void ResultsTree::refreshFilePaths(QStandardItem *item) QVariantMap data = userdata.toMap(); //Get list of files - QString file = data["file"].toString(); + QString file = data[FILENAME].toString(); //Update this error's text error->setText(stripPath(file, false)); @@ -1382,7 +1405,7 @@ void ResultsTree::refreshFilePaths(QStandardItem *item) QVariantMap child_data = child_userdata.toMap(); //Get list of files - QString child_files = child_data["file"].toString(); + QString child_files = child_data[FILENAME].toString(); //Update file's path child->setText(stripPath(child_files, false)); } diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index fcc88d73b..e5cd359fa 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -53,7 +53,6 @@ ResultsView::ResultsView(QWidget * parent) : connect(mUI.mTree, &ResultsTree::resultsHidden, this, &ResultsView::resultsHidden); connect(mUI.mTree, &ResultsTree::checkSelected, this, &ResultsView::checkSelected); connect(mUI.mTree, &ResultsTree::treeSelectionChanged, this, &ResultsView::updateDetails); - connect(mUI.mTree, &ResultsTree::tagged, this, &ResultsView::tagged); connect(mUI.mTree, &ResultsTree::suppressIds, this, &ResultsView::suppressIds); connect(mUI.mTree, &ResultsTree::editFunctionContract, this, &ResultsView::editFunctionContract); connect(this, &ResultsView::showResults, mUI.mTree, &ResultsTree::showResults); diff --git a/gui/resultsview.h b/gui/resultsview.h index 15f98e9cb..2332b429f 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -218,11 +218,6 @@ signals: */ void checkSelected(QStringList selectedFilesList); - /** - * Some results have been tagged - */ - void tagged(); - /** Suppress Ids */ void suppressIds(QStringList ids); diff --git a/lib/importproject.h b/lib/importproject.h index 3676c3b8c..3f4e73ef4 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -161,6 +161,10 @@ namespace CppcheckXml { const char ToolsElementName[] = "tools"; const char TagsElementName[] = "tags"; const char TagElementName[] = "tag"; + const char TagWarningsElementName[] = "tag-warnings"; + const char TagAttributeName[] = "tag"; + const char WarningElementName[] = "warning"; + const char CppcheckIdAttributeName[] = "cppcheck-id"; const char CheckHeadersElementName[] = "check-headers"; const char CheckUnusedTemplatesElementName[] = "check-unused-templates"; const char MaxCtuDepthElementName[] = "max-ctu-depth"; From 592637af612eb7f7985e7eb35b95d42c2fa71ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 17 Jul 2020 09:05:38 +0200 Subject: [PATCH 076/116] Bug hunting; Avoid uninit false positives with simple analysis --- lib/bughuntingchecks.cpp | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 454a50bc5..cb93bb5c0 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -157,6 +157,39 @@ static void integerOverflow(const Token *tok, const ExprEngine::Value &value, Ex } #endif +/** check if variable is unconditionally assigned */ +static bool isVariableAssigned(const Variable *var, const Token *tok, const Token *scopeStart=nullptr) +{ + const Token * const start = scopeStart && precedes(var->nameToken(), scopeStart) ? scopeStart : var->nameToken(); + + for (const Token *prev = tok->previous(); prev; prev = prev->previous()) { + if (!precedes(start, prev)) + break; + + if (prev->str() == "}") { + if (Token::simpleMatch(prev->link()->tokAt(-2), "} else {")) { + const Token *elseEnd = prev; + const Token *elseStart = prev->link(); + const Token *ifEnd = elseStart->tokAt(-2); + const Token *ifStart = ifEnd->link(); + if (isVariableAssigned(var, ifEnd, ifStart) && isVariableAssigned(var, elseEnd, elseStart)) { + return true; + } + } + prev = prev->link(); + } + if (scopeStart && Token::Match(prev, "return|throw|continue|break")) + return true; + if (Token::Match(prev, "%varid% =", var->declarationId())) + return true; + + // bailout; if variable is used previously that is checked first + if (!scopeStart && prev->varId() == var->declarationId()) + return true; + } + return false; +} + static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase) { if (!tok->astParent()) @@ -256,16 +289,11 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: // Are there unconditional assignment? if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId())) return; - for (const Token *prev = tok->previous(); prev; prev = prev->previous()) { - if (!precedes(var->nameToken(), prev)) - break; - if (prev->str() == "}") - prev = prev->link(); - if (Token::Match(prev, "%varid% =", tok->varId())) - return; - } } + if (tok->variable() && isVariableAssigned(tok->variable(), tok)) + return; + // Uninitialized function argument bool inconclusive = false; if (Token::Match(tok->astParent(), "[,(]")) { From f2bd603bd308effc5dfa0f4d4dd28d7b00f4dfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 17 Jul 2020 11:02:46 +0200 Subject: [PATCH 077/116] Bug hunting; Fix TestBughuntingChecks --- lib/bughuntingchecks.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index cb93bb5c0..2162a8d05 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -180,12 +180,18 @@ static bool isVariableAssigned(const Variable *var, const Token *tok, const Toke } if (scopeStart && Token::Match(prev, "return|throw|continue|break")) return true; - if (Token::Match(prev, "%varid% =", var->declarationId())) - return true; - - // bailout; if variable is used previously that is checked first - if (!scopeStart && prev->varId() == var->declarationId()) - return true; + if (Token::Match(prev, "%varid% =", var->declarationId())) { + bool usedInRhs = false; + visitAstNodes(prev->next()->astOperand2(), [&usedInRhs, var](const Token *tok) { + if (tok->varId() == var->declarationId()) { + usedInRhs = true; + return ChildrenToVisit::done; + } + return ChildrenToVisit::op1_and_op2; + }); + if (!usedInRhs) + return true; + } } return false; } From 7a4e6daecde8ea2dbc783e85d6b9266675705464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 17 Jul 2020 11:26:03 +0200 Subject: [PATCH 078/116] Fix import GUI project problem --- lib/importproject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index ad498d21d..62ff618ca 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1074,7 +1074,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti guiProject.libraries = readXmlStringList(node, "", CppcheckXml::LibraryElementName, nullptr); else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0) { for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { - if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0) + if (strcmp(child->Name(), CppcheckXml::SuppressionElementName) != 0) continue; auto read = [](const char *s, const char *def) { return s ? s : def; @@ -1082,6 +1082,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti Suppressions::Suppression s; s.errorId = read(child->GetText(), ""); s.fileName = read(child->Attribute("fileName"), ""); + if (!s.fileName.empty()) + s.fileName = joinRelativePath(path, s.fileName); s.lineNumber = child->IntAttribute("lineNumber", Suppressions::Suppression::NO_LINE); s.symbolName = read(child->Attribute("symbolName"), ""); std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId; From 2713474f56a11bbd59e01e7f271ce0901e87856d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 17 Jul 2020 12:25:06 +0200 Subject: [PATCH 079/116] Fixed Cppcheck warning --- gui/projectfile.cpp | 2 +- gui/projectfile.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 8745cf345..45035aec1 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -641,7 +641,7 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader) } -void ProjectFile::readTagWarnings(QXmlStreamReader &reader, QString tag) +void ProjectFile::readTagWarnings(QXmlStreamReader &reader, const QString &tag) { QXmlStreamReader::TokenType type; do { diff --git a/gui/projectfile.h b/gui/projectfile.h index 1d372ed35..388c03379 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -447,7 +447,7 @@ protected: * @brief Read tag warnings, what warnings are tagged with a specific tag * @param reader XML stream reader. */ - void readTagWarnings(QXmlStreamReader &reader, QString tag); + void readTagWarnings(QXmlStreamReader &reader, const QString &tag); /** * @brief Read string list From 58638d775769acfb50778b87dfc69c9e6a3d8bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 17 Jul 2020 13:20:31 +0200 Subject: [PATCH 080/116] Bug hunting; Fix itc.py test --- lib/bughuntingchecks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 2162a8d05..3ba3643a6 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -295,10 +295,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: // Are there unconditional assignment? if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId())) return; - } - if (tok->variable() && isVariableAssigned(tok->variable(), tok)) - return; + if (tok->variable() && isVariableAssigned(tok->variable(), tok)) + return; + } // Uninitialized function argument bool inconclusive = false; From 382f21a5c926368dadb4be769c4dd238b83421cb Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Sat, 18 Jul 2020 07:02:12 +0300 Subject: [PATCH 081/116] Fixed crash on garbage code: comparisson with an empty second operand This will fix #9774. --- lib/tokenize.cpp | 17 ++++++++++++----- test/testgarbage.cpp | 2 +- test/testtokenize.cpp | 5 ++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index a9fa35bea..196653206 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9624,11 +9624,18 @@ void Tokenizer::findGarbageCode() const if (match1 && match2) syntaxError(tok); } - if (Token::Match(tok, "%or%|%oror%|~|^|!|%comp%|+|-|/|% )|]|}")) { - if (isC()) - syntaxError(tok, tok->str() + tok->next()->str()); - if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator")) - syntaxError(tok, tok->str() + " " + tok->next()->str()); + if (Token::Match(tok, "%or%|%oror%|~|^|!|%comp%|+|-|/|%")) { + std::string code = ""; + if (Token::Match(tok->next(), ")|]|}")) + code = tok->str() + tok->next()->str(); + if (Token::simpleMatch(tok->next(), "( )")) + code = tok->str() + "()"; + if (!code.empty()) { + if (isC()) + syntaxError(tok, code); + if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator")) + syntaxError(tok, code); + } } if (Token::Match(tok, "%num%|%bool%|%char%|%str% %num%|%bool%|%char%|%str%") && !Token::Match(tok, "%str% %str%")) syntaxError(tok); diff --git a/test/testgarbage.cpp b/test/testgarbage.cpp index 48c1df944..814d2ac21 100644 --- a/test/testgarbage.cpp +++ b/test/testgarbage.cpp @@ -1402,7 +1402,7 @@ private: void garbageCode164() { //7234 - checkCode("class d{k p;}(){d::d():B<()}"); + ASSERT_THROW(checkCode("class d{k p;}(){d::d():B<()}"), InternalError); } void garbageCode165() { diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index 361c396a8..4d2705d9e 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -8087,7 +8087,7 @@ private: ASSERT_THROW(tokenizeAndStringify("void foo() { for_chain( if (!done) done = 1); }"), InternalError); ASSERT_THROW(tokenizeAndStringify("void foo() { for_chain( a, b, if (!done) done = 1); }"), InternalError); - ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { if (retval==){} }"), InternalError, "syntax error: == )"); + ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { if (retval==){} }"), InternalError, "syntax error: ==)"); // after (expr) ASSERT_NO_THROW(tokenizeAndStringify("void f() { switch (a) int b; }")); @@ -8108,6 +8108,9 @@ private: // Ticket #9664 ASSERT_NO_THROW(tokenizeAndStringify("S s = { .x { 2 }, .y[0] { 3 } };")); + + ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a==()); }"), InternalError, "syntax error: ==()"); + ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a+()); }"), InternalError, "syntax error: +()"); } From 6bc13080eec83491e31f6cdc1f7f4d66733830fc Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Sat, 18 Jul 2020 07:07:20 +0300 Subject: [PATCH 082/116] Simplify condition --- lib/tokenize.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 196653206..06884c75a 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9631,9 +9631,7 @@ void Tokenizer::findGarbageCode() const if (Token::simpleMatch(tok->next(), "( )")) code = tok->str() + "()"; if (!code.empty()) { - if (isC()) - syntaxError(tok, code); - if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator")) + if (isC() || (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator"))) syntaxError(tok, code); } } From 744b3631861d510abde174dee88c83359b178dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 08:23:04 +0200 Subject: [PATCH 083/116] GUI; Tweaks. Add tooltips, change texts. --- gui/projectfiledialog.cpp | 5 +-- gui/projectfiledialog.ui | 39 ++++++++++++++++++---- gui/resultsview.ui | 69 +++++++++++++++------------------------ 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 4eb1c403d..52ce8c61e 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -208,6 +208,7 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent) connect(mUI.mListSuppressions, &QListWidget::doubleClicked, this, &ProjectFileDialog::editSuppression); connect(mUI.mBtnBrowseMisraFile, &QPushButton::clicked, this, &ProjectFileDialog::browseMisraFile); connect(mUI.mChkAllVsConfigs, &QCheckBox::clicked, this, &ProjectFileDialog::checkAllVSConfigs); + connect(mUI.mBtnNormalAnalysis, &QCheckBox::toggled, mUI.mBtnSafeClasses, &QCheckBox::setEnabled); loadFromProjectFile(projectFile); } @@ -266,7 +267,7 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) else mUI.mBtnCppcheckParser->setChecked(true); mUI.mBtnSafeClasses->setChecked(projectFile->safeChecks.classes); - mUI.mBugHunting->setChecked(projectFile->bugHunting); + mUI.mBtnBugHunting->setChecked(projectFile->bugHunting); setExcludedPaths(projectFile->getExcludedPaths()); setLibraries(projectFile->getLibraries()); const QString platform = projectFile->getPlatform(); @@ -371,7 +372,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const projectFile->setLibraries(getLibraries()); projectFile->clangParser = mUI.mBtnClangParser->isChecked(); projectFile->safeChecks.classes = mUI.mBtnSafeClasses->isChecked(); - projectFile->bugHunting = mUI.mBugHunting->isChecked(); + projectFile->bugHunting = mUI.mBtnBugHunting->isChecked(); if (mUI.mComboBoxPlatform->currentText().endsWith(".xml")) projectFile->setPlatform(mUI.mComboBoxPlatform->currentText()); else { diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index 1cf6d5d15..d809dd894 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -420,7 +420,11 @@ - + + + This is a workfolder that Cppcheck will use for various purposes. + + @@ -461,18 +465,31 @@ - Check that code is safe + Analysis - + - Bug hunting -- Detect all bugs. Generates mostly noise. + Normal analysis -- Avoid false positives. + + + true + + + + + + + Bug hunting -- Generates mostly noise. The goal is to be "soundy" and detect most bugs. + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. + Check that each class has a safe public interface @@ -605,7 +622,11 @@ - + + + Filepaths in warnings will be relative to this path + + @@ -617,7 +638,11 @@ - + + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. + + @@ -625,7 +650,7 @@ - Exclude source files in paths + Exclude source files diff --git a/gui/resultsview.ui b/gui/resultsview.ui index 584c8a9ea..cfe8b57d5 100644 --- a/gui/resultsview.ui +++ b/gui/resultsview.ui @@ -159,49 +159,34 @@ - - - Qt::Vertical + + + Configured contracts: + + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + Missing contracts: + + + + + + + QAbstractItemView::NoEditTriggers + + + true - - - - - - Configured contracts: - - - - - - - QAbstractItemView::NoEditTriggers - - - - - - - - - - - Missing contracts: - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - - - From 23008e7bc0cae23b93269f782a4c7a15772e3c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 08:56:31 +0200 Subject: [PATCH 084/116] GUI: Add button in ProjectFileDialog to exclude a file --- gui/projectfiledialog.cpp | 20 ++++++++++++++++---- gui/projectfiledialog.h | 7 ++++++- gui/projectfiledialog.ui | 9 ++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 52ce8c61e..70452845a 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -199,6 +199,7 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent) connect(mUI.mBtnEditInclude, &QPushButton::clicked, this, &ProjectFileDialog::editIncludeDir); connect(mUI.mBtnRemoveInclude, &QPushButton::clicked, this, &ProjectFileDialog::removeIncludeDir); connect(mUI.mBtnAddIgnorePath, SIGNAL(clicked()), this, SLOT(addExcludePath())); + connect(mUI.mBtnAddIgnoreFile, SIGNAL(clicked()), this, SLOT(addExcludeFile())); connect(mUI.mBtnEditIgnorePath, &QPushButton::clicked, this, &ProjectFileDialog::editExcludePath); connect(mUI.mBtnRemoveIgnorePath, &QPushButton::clicked, this, &ProjectFileDialog::removeExcludePath); connect(mUI.mBtnIncludeUp, &QPushButton::clicked, this, &ProjectFileDialog::moveIncludePathUp); @@ -718,9 +719,17 @@ void ProjectFileDialog::editIncludeDir() void ProjectFileDialog::addExcludePath() { - QString dir = getExistingDirectory(tr("Select directory to ignore"), true); - if (!dir.isEmpty()) - addExcludePath(dir); + addExcludePath(getExistingDirectory(tr("Select directory to ignore"), true)); +} + +void ProjectFileDialog::addExcludeFile() +{ + const QFileInfo inf(mProjectFile->getFilename()); + const QDir &dir = inf.absoluteDir(); + QMap filters; + filters[tr("Source files")] = "*.c *.cpp"; + filters[tr("All files")] = "*.*"; + addExcludePath(QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters))); } void ProjectFileDialog::editExcludePath() @@ -803,7 +812,10 @@ int ProjectFileDialog::getSuppressionIndex(const QString &shortText) const void ProjectFileDialog::browseMisraFile() { - const QString fileName = QFileDialog::getOpenFileName(this, tr("Select MISRA rule texts file"), QDir::homePath(), tr("Misra rule texts file (%1)").arg("*.txt")); + const QString fileName = QFileDialog::getOpenFileName(this, + tr("Select MISRA rule texts file"), + QDir::homePath(), + tr("Misra rule texts file (%1)").arg("*.txt")); if (!fileName.isEmpty()) { QSettings settings; mUI.mEditMisraFile->setText(fileName); diff --git a/gui/projectfiledialog.h b/gui/projectfiledialog.h index 30237a370..fef320a4f 100644 --- a/gui/projectfiledialog.h +++ b/gui/projectfiledialog.h @@ -215,10 +215,15 @@ protected slots: void editIncludeDir(); /** - * @brief Add new path to exclude. + * @brief Add new path to exclude list. */ void addExcludePath(); + /** + * @brief Add new file to exclude list. + */ + void addExcludeFile(); + /** * @brief Edit excluded path in the list. */ diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index d809dd894..4ba8af1aa 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -661,7 +661,14 @@ - Add... + Add folder... + + + + + + + Add file... From 9ec5ae2929dcded279c3c9d73330c201fc90d031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 15:27:25 +0200 Subject: [PATCH 085/116] GUI: Started writing a manual --- man/gui-manual.md | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 man/gui-manual.md diff --git a/man/gui-manual.md b/man/gui-manual.md new file mode 100644 index 000000000..d4ccee5ac --- /dev/null +++ b/man/gui-manual.md @@ -0,0 +1,183 @@ +--- +title: Cppcheck GUI manual +subtitle: Version 2.1.99 +author: Cppcheck team +lang: en +documentclass: report +--- + + +# Standalone analysis + +It is possible to quickly analyze files. Open the `Analyze` menu and click on either `Files...` or `Directory...`. + +It is recommended that you create a project for analysis. A properly configured project will give you better analysis. + +# Project + +## Creating project + +Open the `File` menu and click on `New project...`. + +## Project options + +The `Project file` dialog contains 4 tabs: + - Paths and defines; paths to check and basic preprocessor settings. + - Types and Functions; configuration of platform and 3rd party libraries + - Analysis; analysis options + - Warning options; formatting warnings, suppressing warnings, etc + - Addons; extra analysis with addons + +### Paths and defines + +It is recommended to import a project file. + +#### Import project + +Project to import. Cppcheck will get: + * what files to check + * preprocessor defines + * preprocessor include paths + * language standard if set + +#### Paths (If you do not import project) + +What paths to check. + +#### Defines (If you do not import project) + +Cppcheck automatically checks the code with different preprocessor configurations. + + #ifdef A + code1 + #endif + #ifdef B + code2 + #endif + +Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected. + +If you want to configure that A will always be defined in Cppcheck analysis you can do that here. + +Defines are separated by semicolon. So you can for instance write: + + A;B=3;C + +#### Undefines (If you do not import project) + + +Cppcheck automatically checks the code with different preprocessor configurations. + + #ifdef A + code1 + #endif + #ifdef B + code2 + #endif + +Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected. + +If you want to configure that A is never defined in Cppcheck analysis you can do that here. + +Undefines are separated by semicolon. So you can for instance write: + + A;C + +#### Include paths (If you do not import project) + +Specify include paths. + +### Types and Functions + +Cppcheck uses `Platform` setting to determine size of short/int/long/pointer/etc. + +Check the libraries that you use in the `Libraries` listbox. + +### Analysis + +#### Cppcheck build dir + +This is a work-folder that Cppcheck uses. Each Cppcheck project should have a separate build dir. It is used for: + * whole program analysis + * debug output + * faster analysis (if a source file has changed check it, if source file is not changed then reuse old results) + * statistics + +#### Parser + +It is in general recommended to use Cppcheck parser. However you can choose to use Clang parser; Clang will be executed with a command line flag that tells it to dump its AST and Cppcheck will read that AST and convert it into a corresponding Cppcheck AST and use that. + +#### Analysis + +Configure what kind of analysis you want. + +The `Normal analysis` is recommended for most use cases. Especially if you use Cppcheck in CI. + +The `Bug hunting` can be used if you really want to find a bug in your code and can invest time looking at bad results and providing extra configuration. + +#### Limit analysis + +You can turn off checking of headers. That could be interesting if Cppcheck is very slow. But normally, you should check the code in headers. + +It is possible to check the code in unused templates. However the Cppcheck AST will be incomplete/wrong. The recommendation is that you do not check unused templates to avoid wrong warnings. The templates will be checked properly when you do use them. + +Max CTU depth: How deep should the whole program analysis be. The risk with a "too high" value is that Cppcheck will be slow. + +Max recursion in template instantiation: Max recursion when Cppcheck instantiates templates. The risk with a "too high" value is that Cppcheck will be slow and can require much memory. + + +### Warning options + +#### Root path + +The root path for warnings. Cppcheck will strip away this part of the path from warnings. For instance if there is a warning in `../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then the path for the warning will be `bar/file.cpp`. + +#### Warning Tags + +Tags allow you to manually categorize warnings. + +#### Exclude source files + +Excluded source files will not be analyzed by Cppcheck + +#### Suppressions + +List of suppressions. These warnings will not be shown. + +### Addons + +Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in year 2038. Check that the code does not use such timers. + +Thread safety - Check that the code is thread safe + +Cert - Ensure that the Cert coding standard is followed + +Misra - Ensure that the Misra coding standard is followed. Please note you need to have a textfile with the misra rule texts to get proper warning messages. Cppcheck is not legally allowed to distribute the misra rule texts. + +Clang-tidy - Run Clang-tidy + + +# Preferences + +TODO + + +# Looking at results + +When you have run the analysis it is time to look at the results. + +If you click on a warning then the corresponding code will be shown in the "Warning details" at the bottom. + +You can right click warnings to get options. The difference of "hiding" a warning and "suppressing" a warning is that the suppression is permanent and hiding the warning is only temporary. + + +# Tagging warnings + +You can manually categorize warnings. + +You choose the names of the categories yourself in the project file dialog. + +If tag names are configured then when you look at results you can right click on a warning and tag it. + + + From 2b0505930722af59c0070d093f59cec3d3245f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 17:10:39 +0200 Subject: [PATCH 086/116] GUI; Tweaked button texts --- gui/projectfiledialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index 4ba8af1aa..31884ce91 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -661,14 +661,14 @@ - Add folder... + Exclude folder... - Add file... + Exclude file... From 46d997cd71db27754e4079266ea0cfa13d2023b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 17:20:45 +0200 Subject: [PATCH 087/116] GUI; Remove unused signal ResultTree::tagged --- gui/resultstree.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gui/resultstree.h b/gui/resultstree.h index cc7dacd35..5f2557826 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -202,11 +202,6 @@ signals: */ void treeSelectionChanged(const QModelIndex ¤t); - /** - * Selected item(s) has been tagged - */ - void tagged(); - /** Suppress Ids */ void suppressIds(QStringList ids); From 7cb65b7f674ee9d04a03e6e6a56d0b33eae23f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 18:14:55 +0200 Subject: [PATCH 088/116] GUI: Speedup code editor when selecting another warning in the same file --- gui/codeeditor.cpp | 13 +++++++++++++ gui/codeeditor.h | 24 ++++++++++++++++++++++++ gui/resultstree.cpp | 3 +++ gui/resultsview.cpp | 32 +++++++++++++++++++------------- gui/resultsview.h | 3 +++ lib/bughuntingchecks.cpp | 7 +++++-- 6 files changed, 67 insertions(+), 15 deletions(-) diff --git a/gui/codeeditor.cpp b/gui/codeeditor.cpp index a44e351c1..e7d6c6f07 100644 --- a/gui/codeeditor.cpp +++ b/gui/codeeditor.cpp @@ -257,6 +257,19 @@ void CodeEditor::setError(const QString &code, int errorLine, const QStringList highlightErrorLine(); } +void CodeEditor::setError(int errorLine, const QStringList &symbols) +{ + mHighlighter->setSymbols(symbols); + + mErrorPosition = getPos(toPlainText(), errorLine); + QTextCursor tc = textCursor(); + tc.setPosition(mErrorPosition); + setTextCursor(tc); + centerCursor(); + + highlightErrorLine(); +} + int CodeEditor::lineNumberAreaWidth() { int digits = 1; diff --git a/gui/codeeditor.h b/gui/codeeditor.h index 09fe523d0..3ed01d6b0 100644 --- a/gui/codeeditor.h +++ b/gui/codeeditor.h @@ -77,6 +77,29 @@ public: */ void setError(const QString &code, int errorLine, const QStringList &symbols); + /** + * Goto another error in existing source file + * \param errorLine line number + * \param symbols the related symbols, these are marked + */ + void setError(int errorLine, const QStringList &symbols); + + void setFileName(const QString &fileName) + { + mFileName = fileName; + } + + QString getFileName() const + { + return mFileName; + } + + void clear() + { + mFileName.clear(); + setPlainText(QString()); + } + protected: void resizeEvent(QResizeEvent *event) override; @@ -93,6 +116,7 @@ private: Highlighter *mHighlighter; CodeEditorStyle *mWidgetStyle; int mErrorPosition; + QString mFileName; }; diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 392fc082e..191735c39 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -63,6 +63,7 @@ static const char LINE[] = "line"; static const char MESSAGE[] = "message"; static const char SEVERITY[] = "severity"; static const char SINCEDATE[] = "sinceDate"; +static const char SYMBOLNAMES[] = "symbolNames"; static const char SUMMARY[] = "summary"; static const char TAGS[] = "tags"; @@ -223,6 +224,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) data[FILE0] = stripPath(item.file0, true); data[FUNCTION] = item.function; data[SINCEDATE] = item.sinceDate; + data[SYMBOLNAMES] = item.symbolNames; data[TAGS] = line.tags; data[HIDE] = hide; stditem->setData(QVariant(data)); @@ -256,6 +258,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) child_data[CWE] = line.cwe; child_data[CPPCHECKID] = line.cppcheckId; child_data[INCONCLUSIVE] = line.inconclusive; + child_data[SYMBOLNAMES] = item.symbolNames; child_item->setData(QVariant(child_data)); } } diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index e5cd359fa..35a5b5d96 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -385,9 +385,8 @@ void ResultsView::updateDetails(const QModelIndex &index) QStandardItemModel *model = qobject_cast(mUI.mTree->model()); QStandardItem *item = model->itemFromIndex(index); - mUI.mCode->setPlainText(QString()); - if (!item) { + mUI.mCode->clear(); mUI.mDetails->setText(QString()); return; } @@ -400,6 +399,7 @@ void ResultsView::updateDetails(const QModelIndex &index) // If there is no severity data then it is a parent item without summary and message if (!data.contains("severity")) { + mUI.mCode->clear(); mUI.mDetails->setText(QString()); return; } @@ -425,19 +425,25 @@ void ResultsView::updateDetails(const QModelIndex &index) if (!QFileInfo(filepath).exists() && QFileInfo(mUI.mTree->getCheckDirectory() + '/' + filepath).exists()) filepath = mUI.mTree->getCheckDirectory() + '/' + filepath; - QFile file(filepath); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QStringList symbols; - QRegularExpression re(".*: ([A-Za-z_][A-Za-z0-9_]*)$"); - const QString errorMessage = data["message"].toString(); - QRegularExpressionMatch match = re.match(errorMessage); - if (match.hasMatch()) { - symbols << match.captured(1); - } + QStringList symbols; + if (data.contains("symbolNames")) + symbols = data["symbolNames"].toString().split("\n"); - QTextStream in(&file); - mUI.mCode->setError(in.readAll(), lineNumber, symbols); + if (filepath == mUI.mCode->getFileName()) + { + mUI.mCode->setError(lineNumber, symbols); + return; } + + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + mUI.mCode->clear(); + return; + } + + QTextStream in(&file); + mUI.mCode->setError(in.readAll(), lineNumber, symbols); + mUI.mCode->setFileName(filepath); } void ResultsView::log(const QString &str) diff --git a/gui/resultsview.h b/gui/resultsview.h index 2332b429f..44871a266 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -359,6 +359,9 @@ private slots: void on_mListLog_customContextMenuRequested(const QPoint &pos); private: QSet mContracts; + + /** Current file shown in the code editor */ + QString mCurrentFileName; }; /// @} #endif // RESULTSVIEW_H diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 3ba3643a6..82c7bdde9 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -352,10 +352,11 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: const std::string inconclusiveMessage(inconclusive ? ". It is inconclusive if there would be a problem in the function call." : ""); if (!uninitStructMember.empty()) { + const std::string symbol = tok->expressionString() + "." + uninitStructMember; dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingUninitStructMember", - "Cannot determine that '" + tok->expressionString() + "." + uninitStructMember + "' is initialized" + inconclusiveMessage, + "$symbol:" + symbol + "\nCannot determine that '$symbol' is initialized" + inconclusiveMessage, CWE_USE_OF_UNINITIALIZED_VARIABLE, inconclusive, value.type == ExprEngine::ValueType::BailoutValue); @@ -366,10 +367,12 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (uninitData) uninitexpr += "[0]"; + const std::string symbol = (tok->varId() > 0) ? ("$symbol:" + tok->str() + "\n") : std::string(); + dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingUninit", - "Cannot determine that '" + uninitexpr + "' is initialized" + inconclusiveMessage, + symbol + "Cannot determine that '" + uninitexpr + "' is initialized" + inconclusiveMessage, CWE_USE_OF_UNINITIALIZED_VARIABLE, inconclusive, value.type == ExprEngine::ValueType::BailoutValue); From e9281babc498e7fd3bcd19a9f418637a9a399634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 18 Jul 2020 18:54:21 +0200 Subject: [PATCH 089/116] Bug hunting; avoid false positives for structs/classes with constructors --- lib/exprengine.cpp | 11 +++++++++-- test/testbughuntingchecks.cpp | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 388f4149c..f0ad91118 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -2567,8 +2567,15 @@ static ExprEngine::ValuePtr createVariableValue(const Variable &var, Data &data) data.addConstraints(value, var.nameToken()); return value; } - if (valueType->type == ValueType::Type::RECORD) - return createStructVal(valueType->typeScope, var.isLocal() && !var.isStatic(), data); + if (valueType->type == ValueType::Type::RECORD) { + bool init = true; + if (var.isLocal() && !var.isStatic()) { + init = valueType->typeScope && + valueType->typeScope->definedType && + valueType->typeScope->definedType->needInitialization != Type::NeedInitialization::False; + } + return createStructVal(valueType->typeScope, init, data); + } if (valueType->smartPointerType) { auto structValue = createStructVal(valueType->smartPointerType->classScope, var.isLocal() && !var.isStatic(), data); auto size = std::make_shared(data.getNewSymbolName(), 1, ~0UL); diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp index bfb62e985..392991b31 100644 --- a/test/testbughuntingchecks.cpp +++ b/test/testbughuntingchecks.cpp @@ -39,6 +39,7 @@ private: TEST_CASE(uninit_array); TEST_CASE(uninit_function_par); TEST_CASE(uninit_malloc); + TEST_CASE(uninit_struct); TEST_CASE(ctu); #endif } @@ -98,6 +99,17 @@ private: ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that '*p' is initialized\n", errout.str()); } + void uninit_struct() { + // Assume that constructors initialize all members + // TODO whole program analysis + check("struct Data { Data(); int x; }\n" + "void foo() {\n" + " Data data;\n" + " x = data.x;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + void ctu() { check("void init(int &x) {\n" " x = 1;\n" From 3723c708fc051c74269a56b2d338087f5db9db97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 08:07:10 +0200 Subject: [PATCH 090/116] ImportProject; Do not fail loading a GUI project that has warning tags --- lib/importproject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 62ff618ca..3f81338d6 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1130,7 +1130,9 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti else return false; } - } else + } else if (strcmp(node->Name(), CppcheckXml::TagWarningsElementName) == 0) + ; // TODO + else return false; } settings->basePaths = temp.basePaths; From 011c5e97f98e9d2a5db36297fd85f3a5591b1b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 08:56:14 +0200 Subject: [PATCH 091/116] gui-manual: Preferences --- man/gui-manual.md | 133 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 110 insertions(+), 23 deletions(-) diff --git a/man/gui-manual.md b/man/gui-manual.md index d4ccee5ac..a94b0c664 100644 --- a/man/gui-manual.md +++ b/man/gui-manual.md @@ -9,9 +9,11 @@ documentclass: report # Standalone analysis -It is possible to quickly analyze files. Open the `Analyze` menu and click on either `Files...` or `Directory...`. +It is possible to quickly analyze files. Open the `Analyze` menu and click on +either `Files...` or `Directory...`. -It is recommended that you create a project for analysis. A properly configured project will give you better analysis. +It is recommended that you create a project for analysis. A properly configured +project will give you better analysis. # Project @@ -55,9 +57,11 @@ Cppcheck automatically checks the code with different preprocessor configuration code2 #endif -Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected. +Cppcheck will automatically perform analysis both when A is defined and B is +defined. So any bugs in both code1 and code2 will be detected. -If you want to configure that A will always be defined in Cppcheck analysis you can do that here. +If you want to configure that A will always be defined in Cppcheck analysis you +can do that here. Defines are separated by semicolon. So you can for instance write: @@ -75,9 +79,11 @@ Cppcheck automatically checks the code with different preprocessor configuration code2 #endif -Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected. +Cppcheck will automatically perform analysis both when A is defined and B is +defined. So any bugs in both code1 and code2 will be detected. -If you want to configure that A is never defined in Cppcheck analysis you can do that here. +If you want to configure that A is never defined in Cppcheck analysis you can +do that here. Undefines are separated by semicolon. So you can for instance write: @@ -97,40 +103,57 @@ Check the libraries that you use in the `Libraries` listbox. #### Cppcheck build dir -This is a work-folder that Cppcheck uses. Each Cppcheck project should have a separate build dir. It is used for: +This is a work-folder that Cppcheck uses. Each Cppcheck project should have a +separate build dir. It is used for: * whole program analysis * debug output - * faster analysis (if a source file has changed check it, if source file is not changed then reuse old results) + * faster analysis (if a source file has changed check it, if source file is + not changed then reuse old results) * statistics #### Parser -It is in general recommended to use Cppcheck parser. However you can choose to use Clang parser; Clang will be executed with a command line flag that tells it to dump its AST and Cppcheck will read that AST and convert it into a corresponding Cppcheck AST and use that. +It is in general recommended to use Cppcheck parser. However you can choose to +use Clang parser; Clang will be executed with a command line flag that tells it +to dump its AST and Cppcheck will read that AST and convert it into a +corresponding Cppcheck AST and use that. #### Analysis Configure what kind of analysis you want. -The `Normal analysis` is recommended for most use cases. Especially if you use Cppcheck in CI. +The `Normal analysis` is recommended for most use cases. Especially if you use +Cppcheck in CI. -The `Bug hunting` can be used if you really want to find a bug in your code and can invest time looking at bad results and providing extra configuration. +The `Bug hunting` can be used if you really want to find a bug in your code +and can invest time looking at bad results and providing extra configuration. #### Limit analysis -You can turn off checking of headers. That could be interesting if Cppcheck is very slow. But normally, you should check the code in headers. +You can turn off checking of headers. That could be interesting if Cppcheck is +very slow. But normally, you should check the code in headers. -It is possible to check the code in unused templates. However the Cppcheck AST will be incomplete/wrong. The recommendation is that you do not check unused templates to avoid wrong warnings. The templates will be checked properly when you do use them. +It is possible to check the code in unused templates. However the Cppcheck AST +will be incomplete/wrong. The recommendation is that you do not check unused +templates to avoid wrong warnings. The templates will be checked properly when +you do use them. -Max CTU depth: How deep should the whole program analysis be. The risk with a "too high" value is that Cppcheck will be slow. +Max CTU depth: How deep should the whole program analysis be. The risk with a +"too high" value is that Cppcheck will be slow. -Max recursion in template instantiation: Max recursion when Cppcheck instantiates templates. The risk with a "too high" value is that Cppcheck will be slow and can require much memory. +Max recursion in template instantiation: Max recursion when Cppcheck +instantiates templates. The risk with a "too high" value is that Cppcheck will +be slow and can require much memory. ### Warning options #### Root path -The root path for warnings. Cppcheck will strip away this part of the path from warnings. For instance if there is a warning in `../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then the path for the warning will be `bar/file.cpp`. +The root path for warnings. Cppcheck will strip away this part of the path +from warnings. For instance if there is a warning in +`../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then +the path for the warning will be `bar/file.cpp`. #### Warning Tags @@ -146,38 +169,102 @@ List of suppressions. These warnings will not be shown. ### Addons -Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in year 2038. Check that the code does not use such timers. +Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in +year 2038. Check that the code does not use such timers. Thread safety - Check that the code is thread safe Cert - Ensure that the Cert coding standard is followed -Misra - Ensure that the Misra coding standard is followed. Please note you need to have a textfile with the misra rule texts to get proper warning messages. Cppcheck is not legally allowed to distribute the misra rule texts. +Misra - Ensure that the Misra coding standard is followed. Please note you +need to have a textfile with the misra rule texts to get proper warning +messages. Cppcheck is not legally allowed to distribute the misra rule texts. Clang-tidy - Run Clang-tidy # Preferences -TODO +`Number of threads`: Number of threads to use in analysis. Each thread checks +its own source file. + +`Force checking of all #ifdef configurations`: Cppcheck try to check all code +and will therefore guess different preprocessor configurations. The maximum +number of configurations that is checked is 14 by default. + +`Show full path of files`: Show the full paths in the results. + +`Show "No errors found" message when no errors found`: If you want to get a +message box about this. + +`Display error id column "Id"`: Show error id in results + +`Enable inline suppressions`: You can suppress warnings with comments. See the +Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information about +those. + +`Check for inconclusive errors also`: When full analysis of the code can not +determine if there should be a warning or not, it is inconclusive. Normally +Cppcheck does not warn then. + +`Show statistics on check completion`: Show statistics in a window when +analysis finish. + +`Show internal warnings in log`: Internal warnings (for debugging) is shown +in the `Analysis log`. + +`Applications`: Configure external editor to open from context menu when you +right click on a warning. + +`Save all errors when creating report`: If hidden warnings should be saved or +not. + +`Save full path to files in report`: If you use `Root path` the warnings on the +screen will not have the full path. + +`Language`: Configure language to use for GUI. + +`Python binary`: To be able to execute addons, Cppcheck needs to know where +python is. Unless you configure something, Cppcheck will try to execute python +in your PATH. + +`Misra rule texts`: Only needed if you want to use the Misra addon. Cppcheck is +not legally allowed to distribute the Misra rule texts and these must be +provided by users. The Misra rule texts are proprietary. An example rule text +file can be found here: https://github.com/danmar/cppcheck/blob/main/addons/test/misra/misra2012_rules_dummy_ascii.txt + +`Clang path`: The path to `clang` binary. If no path is provided then system +PATH is used. + +`Visual studio headers`: If you want to use the Visual Studio headers in the +analysis you can provide the path(s) here. Hint: Open a visual studio command +prompt and type `SET INCLUDE`. Then copy/paste the paths. + +`Code editor style`: The visual theme to use for the code editor that is used +when you investigate results. + # Looking at results When you have run the analysis it is time to look at the results. -If you click on a warning then the corresponding code will be shown in the "Warning details" at the bottom. +If you click on a warning then the corresponding code will be shown in the +"Warning details" at the bottom. -You can right click warnings to get options. The difference of "hiding" a warning and "suppressing" a warning is that the suppression is permanent and hiding the warning is only temporary. +You can right click warnings to get options. The difference of "hiding" a +warning and "suppressing" a warning is that the suppression is permanent and +hiding the warning is only temporary. # Tagging warnings You can manually categorize warnings. -You choose the names of the categories yourself in the project file dialog. +You choose the names of the categories yourself in the project file dialog. -If tag names are configured then when you look at results you can right click on a warning and tag it. +If tag names are configured then when you look at results you can right click +on a warning and tag it. From 7e65b561f0e47d03b856cbd7c65a57d72c6a337e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 11:10:38 +0200 Subject: [PATCH 092/116] AST: Fix ast for 'for ((..initexpr..);;)' --- lib/tokenlist.cpp | 12 ++++++++++++ test/testtokenize.cpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index d923ca7b3..e12983f8e 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -1387,6 +1387,18 @@ static Token * createAstAtToken(Token *tok, bool cpp) init1 = tok2; AST_state state1(cpp); compileExpression(tok2, state1); + if (init1->str() == "(") { + for (Token *tok3 = init1; tok3 != tok3->link(); tok3 = tok3->next()) { + if (tok3->astParent()) { + while (tok3->astParent()) + tok3 = tok3->astParent(); + init1 = tok3; + break; + } + if (!Token::Match(tok3, "%op%|(|[")) + init1 = tok3; + } + } } else { while (tok2 && tok2 != endPar && tok2->str() != ";") { if (tok2->str() == "<" && tok2->link()) { diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index 4d2705d9e..63438b58e 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -7677,6 +7677,8 @@ private: ASSERT_EQUALS("pf.pf.12,(&&", testAst("((p.f) && (p.f)(1,2))")); + ASSERT_EQUALS("forresdirGetFirst.file&_T(,(=;;(", testAst("for ((res = dir.GetFirst(&file, _T("")));;) {}")); + // problems with: if (x[y]==z) ASSERT_EQUALS("ifa(0[1==(", testAst("if(a()[0]==1){}")); ASSERT_EQUALS("ifbuff0[&(*1==(", testAst("if (*((DWORD*)&buff[0])==1){}")); From 5df9cd90a61b8adaf33e9ee1ce73c2ed85dcf059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 11:10:53 +0200 Subject: [PATCH 093/116] astyle formatting [ci skip] --- gui/codeeditor.h | 9 +++------ gui/resultsview.cpp | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gui/codeeditor.h b/gui/codeeditor.h index 3ed01d6b0..479125c0a 100644 --- a/gui/codeeditor.h +++ b/gui/codeeditor.h @@ -84,18 +84,15 @@ public: */ void setError(int errorLine, const QStringList &symbols); - void setFileName(const QString &fileName) - { + void setFileName(const QString &fileName) { mFileName = fileName; } - QString getFileName() const - { + QString getFileName() const { return mFileName; } - void clear() - { + void clear() { mFileName.clear(); setPlainText(QString()); } diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index 35a5b5d96..b3beb24b7 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -429,8 +429,7 @@ void ResultsView::updateDetails(const QModelIndex &index) if (data.contains("symbolNames")) symbols = data["symbolNames"].toString().split("\n"); - if (filepath == mUI.mCode->getFileName()) - { + if (filepath == mUI.mCode->getFileName()) { mUI.mCode->setError(lineNumber, symbols); return; } From 41a846d8a714b50410dbca5e03d690e5540cc274 Mon Sep 17 00:00:00 2001 From: "Richard A. Smith" Date: Thu, 16 Jul 2020 16:29:17 -0300 Subject: [PATCH 094/116] misra.py: Squelch duplicate violation messages When checking multiple files if the same violation is encountered from an included file then the violation is both displayed and counted in the error summary. Track each violation by location and error number and if the violation has been previously encountered then do not report it again. --- addons/misra.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/addons/misra.py b/addons/misra.py index 5763ce668..6e8b1602f 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -1059,6 +1059,8 @@ class MisraChecker: self.severity = None + self.existing_violations = set() + def __repr__(self): attrs = ["settings", "verify_expected", "verify_actual", "violations", "ruleTexts", "suppressedRules", "dumpfileSuppressions", @@ -2726,11 +2728,17 @@ class MisraChecker: if self.severity: cppcheck_severity = self.severity - cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity) + this_violation = '{}-{}-{}-{}'.format(location.file, location.linenr, location.column, ruleNum) - if misra_severity not in self.violations: - self.violations[misra_severity] = [] - self.violations[misra_severity].append('misra-' + errorId) + # If this is new violation then record it and show it. If not then + # skip it since it has already been displayed. + if not this_violation in self.existing_violations: + self.existing_violations.add(this_violation) + cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity) + + if misra_severity not in self.violations: + self.violations[misra_severity] = [] + self.violations[misra_severity].append('misra-' + errorId) def loadRuleTexts(self, filename): num1 = 0 From fe0081496c95526ec9491bb043ed7a81e8f6953e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 16:27:56 +0200 Subject: [PATCH 095/116] Bug hunting; Avoid bailout uninit FP, arrays --- lib/bughuntingchecks.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index 82c7bdde9..eb38858e1 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -296,6 +296,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId())) return; + // Arrays are allocated on the stack + if (var && Token::Match(tok, "%var% [") && var->isArray()) + return; + if (tok->variable() && isVariableAssigned(tok->variable(), tok)) return; } From 4a76dbb632320c1c387c7327a7e06613622eedf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 16:54:44 +0200 Subject: [PATCH 096/116] Bug hunting; Avoid bailout uninit FP, stream object --- lib/bughuntingchecks.cpp | 4 ++++ lib/exprengine.cpp | 4 ++++ test/testbughuntingchecks.cpp | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index eb38858e1..afb55c5f7 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -269,6 +269,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine: if (Token::Match(tok, "%var% .") && tok->next()->originalName() != "->") return; + // Assume that stream object is initialized + if (Token::Match(tok->previous(), "[;{}] %var% <<|>>") && !tok->next()->astParent()) + return; + // Containers are not uninitialized std::vector tokens{tok, tok->astOperand1(), tok->astOperand2()}; if (Token::Match(tok->previous(), ". %name%")) diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index f0ad91118..59fc71ffa 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -2241,6 +2241,10 @@ static std::string execute(const Token *start, const Token *end, Data &data) if (Token::Match(tok, "[;{}]")) data.trackProgramState(tok); + if (Token::simpleMatch(tok, "__CPPCHECK_BAILOUT__ ;")) + // This is intended for testing + throw ExprEngineException(tok, "__CPPCHECK_BAILOUT__"); + if (Token::simpleMatch(tok, "while (") && (tok->linkAt(1), ") ;") && tok->next()->astOperand1()->hasKnownIntValue() && tok->next()->astOperand1()->getKnownIntValue() == 0) { tok = tok->tokAt(4); continue; diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp index 392991b31..0e2d7ccc1 100644 --- a/test/testbughuntingchecks.cpp +++ b/test/testbughuntingchecks.cpp @@ -40,6 +40,7 @@ private: TEST_CASE(uninit_function_par); TEST_CASE(uninit_malloc); TEST_CASE(uninit_struct); + TEST_CASE(uninit_bailout); TEST_CASE(ctu); #endif } @@ -110,6 +111,22 @@ private: ASSERT_EQUALS("", errout.str()); } + void uninit_bailout() { + check("void foo() {\n" + " __CPPCHECK_BAILOUT__;\n" + " int values[5];\n" + " values[i] = 123;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void foo() {\n" + " __CPPCHECK_BAILOUT__;\n" + " std::ostringstream comm;\n" + " comm << 123;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + void ctu() { check("void init(int &x) {\n" " x = 1;\n" From 0a84a1fad7186e5a850ee9509ea93bc6261737ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 19 Jul 2020 21:40:17 +0200 Subject: [PATCH 097/116] GUI: Add help files that replace the gui-manual.md --- gui/help/investigating-warnings.html | 22 +++ gui/help/preferences.html | 74 ++++++++ gui/help/projectfiledialog.html | 181 ++++++++++++++++++ gui/help/standalone-analysis.html | 17 ++ gui/help/tagging.html | 22 +++ man/gui-manual.md | 270 --------------------------- 6 files changed, 316 insertions(+), 270 deletions(-) create mode 100644 gui/help/investigating-warnings.html create mode 100644 gui/help/preferences.html create mode 100644 gui/help/projectfiledialog.html create mode 100644 gui/help/standalone-analysis.html create mode 100644 gui/help/tagging.html delete mode 100644 man/gui-manual.md diff --git a/gui/help/investigating-warnings.html b/gui/help/investigating-warnings.html new file mode 100644 index 000000000..5eec66bd0 --- /dev/null +++ b/gui/help/investigating-warnings.html @@ -0,0 +1,22 @@ + + +Investigating warnings + + + +

Investigating warnings

+ +

When you have run the analysis it is time to look at the results.

+ +

If you click on a warning then the corresponding code will be shown in the +"Warning details" at the bottom.

+ +

You can right click warnings to get options. The difference of +"hiding" a warning and "suppressing" a warning is that +the suppression is permanent and hiding the warning is only temporary. When +suppressing warning(s), that is saved in the project file. + + + + + diff --git a/gui/help/preferences.html b/gui/help/preferences.html new file mode 100644 index 000000000..31cce6593 --- /dev/null +++ b/gui/help/preferences.html @@ -0,0 +1,74 @@ + + +Preferences + + + +

Preferences

+ +

Number of threads
Number of threads to use in analysis. Each +thread checks its own source file.

+ +

Force checking of all #ifdef configurations
Cppcheck try to check +all code and will therefore guess different preprocessor configurations. The +maximum number of configurations that is checked is 14 by default.

+ +

Show full path of files
Show the full paths in the results.

+ +

Show "No errors found" message when no errors found
+If you want to get a message box about this.

+ +

Display error id column "Id"
Show error id in results

+ +

Enable inline suppressions
You can suppress warnings with comments. +See the Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information +about inline suppressions.

+ +

Check for inconclusive errors also
When full analysis of the code +can not determine if there should be a warning or not, it is inconclusive. +Normally Cppcheck does not warn then.

+ +

Show statistics on check completion
Show statistics in a window +when analysis finish.

+ +

Show internal warnings in log
Internal warnings (for debugging) is +shown in the Analysis log.

+ +

Applications
Configure external editor to open from context menu +when you right click on a warning.

+ +

Save all errors when creating report
If hidden warnings should be +saved or not.

+ +

Save full path to files in report
If you use Root path the +warnings on the screen will not have the full path.

+ +

Language
Configure language to use for GUI.

+ +

Python binary
To be able to execute addons, Cppcheck needs to know +where python is. Unless you configure something, Cppcheck will try to execute +python in your PATH.

+ +

Misra rule texts
Only needed if you want to use the Misra addon. +Cppcheck is not legally allowed to distribute the Misra rule texts and these +must be provided by users. The Misra rule texts are proprietary. An example +rule text file: +

Appendix A Summary of guidelines
+Rule 1.1
+Text of rule 1.1
+Rule 1.2
+Text of rule 1.2
+

+ +

Clang path
The path to clang binary. If no path is provided +then system PATH is used.

+ +

Visual studio headers
If you want to use the Visual Studio headers +in the analysis you can provide the path(s) here. Hint: Open a visual studio +command prompt and type SET INCLUDE. Then copy/paste the paths.

+ +

Code editor style
The visual theme to use for the code editor that +is used when you investigate results.

+ + + diff --git a/gui/help/projectfiledialog.html b/gui/help/projectfiledialog.html new file mode 100644 index 000000000..5e4ef7bc5 --- /dev/null +++ b/gui/help/projectfiledialog.html @@ -0,0 +1,181 @@ + + +Project File Dialog + + + +

Project File Dialog

+ +

The Project file dialog contains 4 tabs:

+
    +
  • Paths and defines; paths to check and basic preprocessor settings. +
  • Types and Functions; configuration of platform and 3rd party libraries +
  • Analysis; analysis options +
  • Warning options; formatting warnings, suppressing warnings, etc +
  • Addons; extra analysis with addons +
+ +

Paths and defines

+ +

It is recommended to import a project file.

+ +

Import project

+ +Project to import. Cppcheck will get: +
    +
  • What files to check +
  • Preprocessor defines +
  • Preprocessor include paths +
  • Language standard if set +
+ +

Paths (If you do not import project)

+ +

What paths to check.

+ +

Defines (If you do not import project)

+ +

Cppcheck automatically checks the code with different preprocessor +configurations.

+ +
#ifdef A
+code1
+#endif
+#ifdef B
+code2
+#endif
+ +

Cppcheck will automatically perform analysis both when A is defined and B is +defined. So any bugs in both code1 and code2 will be detected.

+ +

If you want to configure that A will always be defined in Cppcheck analysis +you can do that here.

+ +

Defines are separated by semicolon. So you can for instance write:

+ +
A;B=3;C
+ +

Undefines (If you do not import project)

+ +

Cppcheck automatically checks the code with different preprocessor +configurations.

+ +
#ifdef A
+code1
+#endif
+#ifdef B
+code2
+#endif
+ +

Cppcheck will automatically perform analysis both when A is defined and B is +defined. So any bugs in both code1 and code2 will be detected.

+ +

If you want to configure that A is never defined in Cppcheck analysis you +can do that here.

+ +

Undefines are separated by semicolon. So you can for instance write:

+ +
A;C
+ +

Include paths (If you do not import project)

+ +

Specify include paths.

+ +

Types and Functions

+ +

Cppcheck uses the Platform setting to determine size of +short/int/long/pointer/etc.

+ +

Check the libraries that you use in the Libraries listbox.

+ +

Analysis

+ +

Cppcheck build dir

+ +

This is a work-folder that Cppcheck uses. Each Cppcheck project should have +a separate build dir. It is used for:

+
    +
  • whole program analysis +
  • debug output +
  • faster analysis (if a source file has changed check it, if source file is + not changed then reuse old results) +
  • statistics +
+ +

Parser

+ +

It is in general recommended to use Cppcheck parser. However you can choose +to use Clang parser; Clang will be executed with a command line flag that tells +it to dump its AST and Cppcheck will read that AST and convert it into a +corresponding Cppcheck AST and use that.

+ +

Analysis

+ +

Configure what kind of analysis you want.

+ +

The Normal analysis is recommended for most use cases. Especially if +you use Cppcheck in CI.

+ +

The Bug hunting can be used if you really want to find a bug in your +code and can invest time looking at bad results and providing extra +configuration.

+ +

Limit analysis

+ +

You can turn off checking of headers. That could be interesting if Cppcheck +is very slow. But normally, you should check the code in headers.

+ +

It is possible to check the code in unused templates. However the Cppcheck +AST will be incomplete/wrong. The recommendation is that you do not check +unused templates to avoid wrong warnings. The templates will be checked +properly when you do use them.

+ +

Max CTU depth: How deep should the whole program analysis be. The risk with +a "too high" value is that Cppcheck will be slow.

+ +

Max recursion in template instantiation: Max recursion when Cppcheck +instantiates templates. The risk with a "too high" value is that +Cppcheck will be slow and can require much memory.

+ + +

Warning options

+ +

Root path

+ +

The root path for warnings. Cppcheck will strip away this part of the path +from warnings. For instance if there is a warning in +

../myproject/foo/bar/file.cpp
and the root path is +
../myproject/foo
then the path for the warning will be +
bar/file.cpp
.

+ +

Warning Tags

+ +

Tags allow you to manually categorize warnings.

+ +

Exclude source files

+ +

Excluded source files will not be analyzed by Cppcheck.

+ +

Suppressions

+ +

List of suppressions. These warnings will not be shown.

+ +

Addons

+ +

Y2038
32-bit timers that count number of seconds since 1970 will +overflow in year 2038. Check that the code does not use such timers.

+ +

Thread safety
Check that the code is thread safe

+ +

Cert
Ensure that the Cert coding standard is followed

+ +

Misra
Ensure that the Misra coding standard is followed. Please +note you need to have a textfile with the misra rule texts to get proper +warning messages. Cppcheck is not legally allowed to distribute the misra +rule texts.

+ +

Clang-tidy
Run Clang-tidy

+ + + + diff --git a/gui/help/standalone-analysis.html b/gui/help/standalone-analysis.html new file mode 100644 index 000000000..ad7fae5fe --- /dev/null +++ b/gui/help/standalone-analysis.html @@ -0,0 +1,17 @@ + + + +Standalone analysis + + + +

Standalone analysis

+ +

It is possible to quickly analyze files. Open the Analyze menu and click on +either Files... or Directory....

+ +

It is recommended that you create a project for analysis. A properly configured +project will give you better analysis.

+ + + diff --git a/gui/help/tagging.html b/gui/help/tagging.html new file mode 100644 index 000000000..7ac4e7ee8 --- /dev/null +++ b/gui/help/tagging.html @@ -0,0 +1,22 @@ + + +Tagging warnings + + + +

Tagging warnings

+ +

You can manually categorize warnings.

+ +

You choose the names of the categories yourself in the project file +dialog.

+ +

If tag names are configured, then you can tag the warnings by +right-clicking on them and selecting the proper tag in the +context menu.

+ +

Tags are saved in the project file and will be permanent.

+ + + + diff --git a/man/gui-manual.md b/man/gui-manual.md deleted file mode 100644 index a94b0c664..000000000 --- a/man/gui-manual.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -title: Cppcheck GUI manual -subtitle: Version 2.1.99 -author: Cppcheck team -lang: en -documentclass: report ---- - - -# Standalone analysis - -It is possible to quickly analyze files. Open the `Analyze` menu and click on -either `Files...` or `Directory...`. - -It is recommended that you create a project for analysis. A properly configured -project will give you better analysis. - -# Project - -## Creating project - -Open the `File` menu and click on `New project...`. - -## Project options - -The `Project file` dialog contains 4 tabs: - - Paths and defines; paths to check and basic preprocessor settings. - - Types and Functions; configuration of platform and 3rd party libraries - - Analysis; analysis options - - Warning options; formatting warnings, suppressing warnings, etc - - Addons; extra analysis with addons - -### Paths and defines - -It is recommended to import a project file. - -#### Import project - -Project to import. Cppcheck will get: - * what files to check - * preprocessor defines - * preprocessor include paths - * language standard if set - -#### Paths (If you do not import project) - -What paths to check. - -#### Defines (If you do not import project) - -Cppcheck automatically checks the code with different preprocessor configurations. - - #ifdef A - code1 - #endif - #ifdef B - code2 - #endif - -Cppcheck will automatically perform analysis both when A is defined and B is -defined. So any bugs in both code1 and code2 will be detected. - -If you want to configure that A will always be defined in Cppcheck analysis you -can do that here. - -Defines are separated by semicolon. So you can for instance write: - - A;B=3;C - -#### Undefines (If you do not import project) - - -Cppcheck automatically checks the code with different preprocessor configurations. - - #ifdef A - code1 - #endif - #ifdef B - code2 - #endif - -Cppcheck will automatically perform analysis both when A is defined and B is -defined. So any bugs in both code1 and code2 will be detected. - -If you want to configure that A is never defined in Cppcheck analysis you can -do that here. - -Undefines are separated by semicolon. So you can for instance write: - - A;C - -#### Include paths (If you do not import project) - -Specify include paths. - -### Types and Functions - -Cppcheck uses `Platform` setting to determine size of short/int/long/pointer/etc. - -Check the libraries that you use in the `Libraries` listbox. - -### Analysis - -#### Cppcheck build dir - -This is a work-folder that Cppcheck uses. Each Cppcheck project should have a -separate build dir. It is used for: - * whole program analysis - * debug output - * faster analysis (if a source file has changed check it, if source file is - not changed then reuse old results) - * statistics - -#### Parser - -It is in general recommended to use Cppcheck parser. However you can choose to -use Clang parser; Clang will be executed with a command line flag that tells it -to dump its AST and Cppcheck will read that AST and convert it into a -corresponding Cppcheck AST and use that. - -#### Analysis - -Configure what kind of analysis you want. - -The `Normal analysis` is recommended for most use cases. Especially if you use -Cppcheck in CI. - -The `Bug hunting` can be used if you really want to find a bug in your code -and can invest time looking at bad results and providing extra configuration. - -#### Limit analysis - -You can turn off checking of headers. That could be interesting if Cppcheck is -very slow. But normally, you should check the code in headers. - -It is possible to check the code in unused templates. However the Cppcheck AST -will be incomplete/wrong. The recommendation is that you do not check unused -templates to avoid wrong warnings. The templates will be checked properly when -you do use them. - -Max CTU depth: How deep should the whole program analysis be. The risk with a -"too high" value is that Cppcheck will be slow. - -Max recursion in template instantiation: Max recursion when Cppcheck -instantiates templates. The risk with a "too high" value is that Cppcheck will -be slow and can require much memory. - - -### Warning options - -#### Root path - -The root path for warnings. Cppcheck will strip away this part of the path -from warnings. For instance if there is a warning in -`../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then -the path for the warning will be `bar/file.cpp`. - -#### Warning Tags - -Tags allow you to manually categorize warnings. - -#### Exclude source files - -Excluded source files will not be analyzed by Cppcheck - -#### Suppressions - -List of suppressions. These warnings will not be shown. - -### Addons - -Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in -year 2038. Check that the code does not use such timers. - -Thread safety - Check that the code is thread safe - -Cert - Ensure that the Cert coding standard is followed - -Misra - Ensure that the Misra coding standard is followed. Please note you -need to have a textfile with the misra rule texts to get proper warning -messages. Cppcheck is not legally allowed to distribute the misra rule texts. - -Clang-tidy - Run Clang-tidy - - -# Preferences - -`Number of threads`: Number of threads to use in analysis. Each thread checks -its own source file. - -`Force checking of all #ifdef configurations`: Cppcheck try to check all code -and will therefore guess different preprocessor configurations. The maximum -number of configurations that is checked is 14 by default. - -`Show full path of files`: Show the full paths in the results. - -`Show "No errors found" message when no errors found`: If you want to get a -message box about this. - -`Display error id column "Id"`: Show error id in results - -`Enable inline suppressions`: You can suppress warnings with comments. See the -Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information about -those. - -`Check for inconclusive errors also`: When full analysis of the code can not -determine if there should be a warning or not, it is inconclusive. Normally -Cppcheck does not warn then. - -`Show statistics on check completion`: Show statistics in a window when -analysis finish. - -`Show internal warnings in log`: Internal warnings (for debugging) is shown -in the `Analysis log`. - -`Applications`: Configure external editor to open from context menu when you -right click on a warning. - -`Save all errors when creating report`: If hidden warnings should be saved or -not. - -`Save full path to files in report`: If you use `Root path` the warnings on the -screen will not have the full path. - -`Language`: Configure language to use for GUI. - -`Python binary`: To be able to execute addons, Cppcheck needs to know where -python is. Unless you configure something, Cppcheck will try to execute python -in your PATH. - -`Misra rule texts`: Only needed if you want to use the Misra addon. Cppcheck is -not legally allowed to distribute the Misra rule texts and these must be -provided by users. The Misra rule texts are proprietary. An example rule text -file can be found here: https://github.com/danmar/cppcheck/blob/main/addons/test/misra/misra2012_rules_dummy_ascii.txt - -`Clang path`: The path to `clang` binary. If no path is provided then system -PATH is used. - -`Visual studio headers`: If you want to use the Visual Studio headers in the -analysis you can provide the path(s) here. Hint: Open a visual studio command -prompt and type `SET INCLUDE`. Then copy/paste the paths. - -`Code editor style`: The visual theme to use for the code editor that is used -when you investigate results. - - - -# Looking at results - -When you have run the analysis it is time to look at the results. - -If you click on a warning then the corresponding code will be shown in the -"Warning details" at the bottom. - -You can right click warnings to get options. The difference of "hiding" a -warning and "suppressing" a warning is that the suppression is permanent and -hiding the warning is only temporary. - - -# Tagging warnings - -You can manually categorize warnings. - -You choose the names of the categories yourself in the project file dialog. - -If tag names are configured then when you look at results you can right click -on a warning and tag it. - - - From 2ace2b006a533e1ad75b4e491296101e27d7c2ce Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Thu, 9 Jul 2020 22:40:52 +0200 Subject: [PATCH 098/116] Refactor: Use visitAstNodes in checkleakautovar --- lib/checkleakautovar.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 0991bfdf0..a6c681814 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -463,27 +463,21 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, VarInfo varInfo1(*varInfo); // VarInfo for if code VarInfo varInfo2(*varInfo); // VarInfo for else code - // Recursively scan variable comparisons in condition - std::stack tokens; // Skip expressions before commas const Token * astOperand2AfterCommas = tok->next()->astOperand2(); while (Token::simpleMatch(astOperand2AfterCommas, ",")) astOperand2AfterCommas = astOperand2AfterCommas->astOperand2(); - tokens.push(astOperand2AfterCommas); - while (!tokens.empty()) { - const Token *tok3 = tokens.top(); - tokens.pop(); + + // Recursively scan variable comparisons in condition + visitAstNodes(astOperand2AfterCommas, [&](const Token *tok3) { if (!tok3) - continue; + return ChildrenToVisit::none; if (tok3->str() == "&&" || tok3->str() == "||") { // FIXME: handle && ! || better - tokens.push(tok3->astOperand1()); - tokens.push(tok3->astOperand2()); - continue; + return ChildrenToVisit::op1_and_op2; } if (tok3->str() == "(" && Token::Match(tok3->astOperand1(), "UNLIKELY|LIKELY")) { - tokens.push(tok3->astOperand2()); - continue; + return ChildrenToVisit::op2; } else if (tok3->str() == "(" && Token::Match(tok3->previous(), "%name%")) { const std::vector params = getArguments(tok3->previous()); for (const Token *par : params) { @@ -500,7 +494,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, varInfo2.erase(vartok->varId()); } } - continue; + return ChildrenToVisit::none; } const Token *vartok = nullptr; @@ -517,7 +511,8 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, } else if (astIsVariableComparison(tok3, "==", "-1", &vartok)) { varInfo1.erase(vartok->varId()); } - } + return ChildrenToVisit::none; + }); checkScope(closingParenthesis->next(), &varInfo1, notzero, recursiveCount); closingParenthesis = closingParenthesis->linkAt(1); From 82fe6193fa6a36e5411208550b42cd9d278f7a25 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Thu, 9 Jul 2020 23:08:04 +0200 Subject: [PATCH 099/116] Refactor: Use visitAstNodes in checkstring --- lib/checkstring.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/checkstring.cpp b/lib/checkstring.cpp index a87d5cd0c..576be7a1b 100644 --- a/lib/checkstring.cpp +++ b/lib/checkstring.cpp @@ -355,37 +355,32 @@ void CheckString::overlappingStrcmp() continue; std::list equals0; std::list notEquals0; - std::stack tokens; - tokens.push(tok); - while (!tokens.empty()) { - const Token * const t = tokens.top(); - tokens.pop(); + visitAstNodes(tok, [&](const Token * t) { if (!t) - continue; + return ChildrenToVisit::none; if (t->str() == "||") { - tokens.push(t->astOperand1()); - tokens.push(t->astOperand2()); - continue; + return ChildrenToVisit::op1_and_op2; } if (t->str() == "==") { if (Token::simpleMatch(t->astOperand1(), "(") && Token::simpleMatch(t->astOperand2(), "0")) equals0.push_back(t->astOperand1()); else if (Token::simpleMatch(t->astOperand2(), "(") && Token::simpleMatch(t->astOperand1(), "0")) equals0.push_back(t->astOperand2()); - continue; + return ChildrenToVisit::none; } if (t->str() == "!=") { if (Token::simpleMatch(t->astOperand1(), "(") && Token::simpleMatch(t->astOperand2(), "0")) notEquals0.push_back(t->astOperand1()); else if (Token::simpleMatch(t->astOperand2(), "(") && Token::simpleMatch(t->astOperand1(), "0")) notEquals0.push_back(t->astOperand2()); - continue; + return ChildrenToVisit::none; } if (t->str() == "!" && Token::simpleMatch(t->astOperand1(), "(")) equals0.push_back(t->astOperand1()); else if (t->str() == "(") notEquals0.push_back(t); - } + return ChildrenToVisit::none; + }); for (const Token *eq0 : equals0) { for (const Token * ne0 : notEquals0) { From 9ced26a7a112bf1d50adb6fcd41db5a7ec77640c Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Fri, 10 Jul 2020 00:50:57 +0200 Subject: [PATCH 100/116] Refactor: Use visitAstNodes in checkcondition --- lib/checkcondition.cpp | 46 ++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index 22f166646..97dad288e 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -688,16 +688,11 @@ void CheckCondition::multiCondition2() ErrorPath errorPath; if (type == MULTICONDITIONTYPE::INNER) { - std::stack tokens1; - tokens1.push(cond1); - while (!tokens1.empty()) { - const Token *firstCondition = tokens1.top(); - tokens1.pop(); + visitAstNodes(cond1, [&](const Token* firstCondition) { if (!firstCondition) - continue; + return ChildrenToVisit::none; if (firstCondition->str() == "&&") { - tokens1.push(firstCondition->astOperand1()); - tokens1.push(firstCondition->astOperand2()); + return ChildrenToVisit::op1_and_op2; } else if (!firstCondition->hasKnownIntValue()) { if (!isReturnVar && isOppositeCond(false, mTokenizer->isCPP(), firstCondition, cond2, mSettings->library, true, true, &errorPath)) { if (!isAliased(vars)) @@ -706,7 +701,8 @@ void CheckCondition::multiCondition2() identicalInnerConditionError(firstCondition, cond2, errorPath); } } - } + return ChildrenToVisit::none; + }); } else { visitAstNodes(cond2, [&](const Token *secondCondition) { if (secondCondition->str() == "||" || secondCondition->str() == "&&") @@ -1436,20 +1432,15 @@ void CheckCondition::alwaysTrueFalse() // Don't warn when there are expanded macros.. bool isExpandedMacro = false; - std::stack tokens; - tokens.push(tok); - while (!tokens.empty()) { - const Token *tok2 = tokens.top(); - tokens.pop(); + visitAstNodes(tok, [&](const Token * tok2) { if (!tok2) - continue; - tokens.push(tok2->astOperand1()); - tokens.push(tok2->astOperand2()); + return ChildrenToVisit::none; if (tok2->isExpandedMacro()) { isExpandedMacro = true; - break; + return ChildrenToVisit::done; } - } + return ChildrenToVisit::op1_and_op2; + }); if (isExpandedMacro) continue; for (const Token *parent = tok; parent; parent = parent->astParent()) { @@ -1464,24 +1455,21 @@ void CheckCondition::alwaysTrueFalse() // don't warn when condition checks sizeof result bool hasSizeof = false; bool hasNonNumber = false; - tokens.push(tok); - while (!tokens.empty()) { - const Token *tok2 = tokens.top(); - tokens.pop(); + visitAstNodes(tok, [&](const Token * tok2) { if (!tok2) - continue; + return ChildrenToVisit::none; if (tok2->isNumber()) - continue; + return ChildrenToVisit::none; if (Token::simpleMatch(tok2->previous(), "sizeof (")) { hasSizeof = true; - continue; + return ChildrenToVisit::none; } if (tok2->isComparisonOp() || tok2->isArithmeticalOp()) { - tokens.push(tok2->astOperand1()); - tokens.push(tok2->astOperand2()); + return ChildrenToVisit::op1_and_op2; } else hasNonNumber = true; - } + return ChildrenToVisit::none; + }); if (!hasNonNumber && hasSizeof) continue; From ed36856451b0a77ea0d5c0a66f4f5ab3377238db Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 11 Jul 2020 00:02:28 +0200 Subject: [PATCH 101/116] Refactor: Use visitAstNodes in checkuninitvar --- lib/checkuninitvar.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp index e46f94304..c1b9d6454 100644 --- a/lib/checkuninitvar.cpp +++ b/lib/checkuninitvar.cpp @@ -881,22 +881,17 @@ bool CheckUninitVar::checkLoopBody(const Token *tok, const Variable& var, const usetok = tok; else if (tok->strAt(1) == "=") { bool varIsUsedInRhs = false; - std::stack tokens; - tokens.push(tok->next()->astOperand2()); - while (!tokens.empty()) { - const Token *t = tokens.top(); - tokens.pop(); + visitAstNodes(tok->next()->astOperand2(), [&](const Token * t) { if (!t) - continue; + return ChildrenToVisit::none; if (t->varId() == var.declarationId()) { varIsUsedInRhs = true; - break; + return ChildrenToVisit::done; } if (Token::simpleMatch(t->previous(),"sizeof (")) - continue; - tokens.push(t->astOperand1()); - tokens.push(t->astOperand2()); - } + return ChildrenToVisit::none; + return ChildrenToVisit::op1_and_op2; + }); if (!varIsUsedInRhs) return true; } else { From 7973fd843c46c3b2a7e1ff7fb4955facb727e46d Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Mon, 20 Jul 2020 11:15:18 +0200 Subject: [PATCH 102/116] Refactor: Simplify checkSignConversion The loop only checks astoperand1 and astoperand2. Simplify the condition to loop over these instead of using a stack. Also, add a testcase for when astoperand2 is negative. --- lib/checktype.cpp | 8 ++------ test/testtype.cpp | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 6ac2709b6..e46a8142d 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -239,12 +239,8 @@ void CheckType::checkSignConversion() continue; // Check if an operand can be negative.. - std::stack tokens; - tokens.push(tok->astOperand1()); - tokens.push(tok->astOperand2()); - while (!tokens.empty()) { - const Token *tok1 = tokens.top(); - tokens.pop(); + const Token * astOperands[] = { tok->astOperand1(), tok->astOperand2() }; + for (const Token * tok1 : astOperands) { if (!tok1) continue; const ValueFlow::Value *negativeValue = tok1->getValueLE(-1,mSettings); diff --git a/test/testtype.cpp b/test/testtype.cpp index e5ea6f2bd..4a121a09a 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -252,6 +252,9 @@ private: check("x = -4 * (unsigned)y;"); ASSERT_EQUALS("[test.cpp:1]: (warning) Expression '-4' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.\n", errout.str()); + check("x = (unsigned)y * -4;"); + ASSERT_EQUALS("[test.cpp:1]: (warning) Expression '-4' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.\n", errout.str()); + check("unsigned int dostuff(int x) {\n" // x is signed " if (x==0) {}\n" " return (x-1)*sizeof(int);\n" From 09241030c38e435d046e69e04ae0cd1878cc1db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 11:59:28 +0200 Subject: [PATCH 103/116] GUI: Add online help --- gui/gui.pro | 4 ++ gui/help/index.html | 52 ++++++++++++++++++ gui/help/online-help.qhp | 22 ++++++-- gui/helpdialog.cpp | 52 ++++++++++++++++++ gui/helpdialog.h | 36 +++++++++++++ gui/helpdialog.ui | 110 +++++++++++++++++++++++++++++++++++++++ gui/mainwindow.cpp | 12 +++-- 7 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 gui/help/index.html create mode 100644 gui/helpdialog.cpp create mode 100644 gui/helpdialog.h create mode 100644 gui/helpdialog.ui diff --git a/gui/gui.pro b/gui/gui.pro index 7052e4b90..b8d53034d 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -10,6 +10,7 @@ INCLUDEPATH += . \ ../externals/z3/include QT += widgets QT += printsupport +QT += help contains(LINKCORE, [yY][eE][sS]) { LIBS += -l../bin/cppcheck-core @@ -61,6 +62,7 @@ FORMS = about.ui \ application.ui \ file.ui \ functioncontractdialog.ui \ + helpdialog.ui \ mainwindow.ui \ projectfiledialog.ui \ resultsview.ui \ @@ -110,6 +112,7 @@ HEADERS += aboutdialog.h \ filelist.h \ fileviewdialog.h \ functioncontractdialog.h \ + helpdialog.h \ mainwindow.h \ platforms.h \ printablereport.h \ @@ -150,6 +153,7 @@ SOURCES += aboutdialog.cpp \ filelist.cpp \ fileviewdialog.cpp \ functioncontractdialog.cpp \ + helpdialog.cpp \ main.cpp \ mainwindow.cpp\ platforms.cpp \ diff --git a/gui/help/index.html b/gui/help/index.html new file mode 100644 index 000000000..b466002dc --- /dev/null +++ b/gui/help/index.html @@ -0,0 +1,52 @@ + + +Cppcheck GUI + + + +

Cppcheck GUI

+ +

With the Cppcheck GUI you can analyze your code.

+ +

Quick walk through

+ +

Step 1: Create a project.

+ +

Create a new project.

+ +

In the Paths and Defines tab, it is recommended that you import your project file at the top.

+ +

In the Types and Functions tab, try to activate all 3rd party libraries you use (windows, posix, ...).

+ +

In the Analysis tab, leave the default settings to start with.

+ +

In the Warnings options tab, leave the default settings to start with.

+ +

In the Addons tab, leave the default settings to start with.

+ + +

Step 2: Analyze code.

+ +

When the project file has been created, the analysis will start automatically.

+ +

While analysis is performed in the background, you can investigate the warnings.

+ + +

Step 3: Investigate warnings.

+ +

In the toolbar you choose what types of warnings you want to see (error/warning/style/performance/portability/information).

+ +

All warnings are shown in a list. If you select a warning in the list, then details about that warning is shown below the list.

+ +

If you right click on warning(s) then you get a context menu.

+ + +

Step 4: Configuration.

+ +

It is possible to improve configuration to get better analysis. The automatic assumptions are conservative and through configuration those automatic assumptions can be avoided.

+ +

TODO: library, contracts

+ + + + diff --git a/gui/help/online-help.qhp b/gui/help/online-help.qhp index 16ab79981..4bbe8074d 100644 --- a/gui/help/online-help.qhp +++ b/gui/help/online-help.qhp @@ -4,11 +4,25 @@ doc -
+
+
+
+
+
+
+
- + + + + - manual.html + index.html + investigating-warnings.html + preferences.html + projectfiledialog.html + standalone-analysis.html + tagging.html - + diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp new file mode 100644 index 000000000..920671834 --- /dev/null +++ b/gui/helpdialog.cpp @@ -0,0 +1,52 @@ +#include "helpdialog.h" +#include "ui_helpdialog.h" + +#include +#include +#include + +void HelpBrowser::setHelpEngine(QHelpEngine *helpEngine) +{ + mHelpEngine = helpEngine; +} + +QVariant HelpBrowser::loadResource(int type, const QUrl &name){ + if (name.scheme() == "qthelp") { + QString url(name.toString()); + while (url.indexOf("/./") > 0) + url.remove(url.indexOf("/./"), 2); + return QVariant(mHelpEngine->fileData(QUrl(url))); + } + return QTextBrowser::loadResource(type, name); +} + +HelpDialog::HelpDialog(QWidget *parent) : + QDialog(parent), + mUi(new Ui::HelpDialog) +{ + mUi->setupUi(this); + + mHelpEngine = new QHelpEngine(QApplication::applicationDirPath() + "/online-help.qhc"); + mHelpEngine->setupData(); + + mUi->contents->addWidget(mHelpEngine->contentWidget()); + mUi->index->addWidget(mHelpEngine->indexWidget()); + + mUi->textBrowser->setHelpEngine(mHelpEngine); + + mUi->textBrowser->setSource(QUrl("qthelp://cppcheck.sourceforge.net/doc/index.html")); + connect(mHelpEngine->contentWidget(), + SIGNAL(linkActivated(QUrl)), + mUi->textBrowser, + SLOT(setSource(QUrl))); + + connect(mHelpEngine->indexWidget(), + SIGNAL(linkActivated(QUrl, QString)), + mUi->textBrowser, + SLOT(setSource(QUrl))); +} + +HelpDialog::~HelpDialog() +{ + delete mUi; +} diff --git a/gui/helpdialog.h b/gui/helpdialog.h new file mode 100644 index 000000000..36e1e7954 --- /dev/null +++ b/gui/helpdialog.h @@ -0,0 +1,36 @@ +#ifndef HELPDIALOG_H +#define HELPDIALOG_H + +#include +#include + +namespace Ui { +class HelpDialog; +} + +class QHelpEngine; + +class HelpBrowser : public QTextBrowser +{ +public: + HelpBrowser(QWidget* parent = 0) : QTextBrowser(parent), mHelpEngine(nullptr) {} + void setHelpEngine(QHelpEngine *helpEngine); + QVariant loadResource (int type, const QUrl& name); +private: + QHelpEngine* mHelpEngine; +}; + +class HelpDialog : public QDialog +{ + Q_OBJECT + +public: + explicit HelpDialog(QWidget *parent = nullptr); + ~HelpDialog(); + +private: + Ui::HelpDialog *mUi; + QHelpEngine* mHelpEngine; +}; + +#endif // HELPDIALOG_H diff --git a/gui/helpdialog.ui b/gui/helpdialog.ui new file mode 100644 index 000000000..bc5ccc706 --- /dev/null +++ b/gui/helpdialog.ui @@ -0,0 +1,110 @@ + + + HelpDialog + + + + 0 + 0 + 635 + 446 + + + + Cppcheck GUI help + + + + + + Qt::Horizontal + + + + + 200 + 16777215 + + + + 0 + + + + Contents + + + + + + + + + + Index + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + HelpBrowser + QTextBrowser +
helpdialog.h
+
+
+ + + + buttonBox + accepted() + HelpDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + HelpDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index d98ea4b6e..5a1e8189f 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -34,20 +34,21 @@ #include "applicationlist.h" #include "aboutdialog.h" #include "common.h" -#include "threadhandler.h" +#include "filelist.h" #include "fileviewdialog.h" #include "functioncontractdialog.h" +#include "helpdialog.h" +#include "librarydialog.h" #include "projectfile.h" #include "projectfiledialog.h" #include "report.h" #include "scratchpad.h" +#include "showtypes.h" #include "statsdialog.h" #include "settingsdialog.h" +#include "threadhandler.h" #include "threadresult.h" #include "translationhandler.h" -#include "filelist.h" -#include "showtypes.h" -#include "librarydialog.h" static const QString OnlineHelpURL("http://cppcheck.net/manual.html"); static const QString compile_commands_json("compile_commands.json"); @@ -1465,7 +1466,8 @@ void MainWindow::openHelpContents() void MainWindow::openOnlineHelp() { - QDesktopServices::openUrl(QUrl(OnlineHelpURL)); + HelpDialog *helpDialog = new HelpDialog; + helpDialog->showMaximized(); } void MainWindow::openProjectFile() From 89ba12d9b38dece6396b92e94cc905672027010d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 12:04:51 +0200 Subject: [PATCH 104/116] Remove old build.bat file --- build.bat | 72 ------------------------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 build.bat diff --git a/build.bat b/build.bat deleted file mode 100644 index 9c075594e..000000000 --- a/build.bat +++ /dev/null @@ -1,72 +0,0 @@ -@echo off -REM A simple script to build different cppcheck targets from project root -REM folder. This script can be run from VS prompt or Qt prompt. -REM -REM Usage: build [release|debug] -REM where is any of cppcheck/gui/tests/all -REM release or debug is the configuration -REM all-target builds both cppcheck and gui. -REM -REM Run the command before build.bat to enable rules using pcre: -REM set HAVE_RULES=yes -REM -REM TODO: -REM - run tests too - -pushd %~dp0 - -if "%1" == "" goto help - -REM Qt prompt sets QMAKESPEC -if "%QMAKESPEC%" == "" ( -REM parse qmakespec to see if it's some msvc - if "%QMAKESPEC:~6,4%" == "msvc" ( - set MAKE=nmake - ) else ( - set MAKE=mingw32-make - ) -) else ( - set MAKE=nmake -) - -if "%2" == "" set TARGET=release -if "%2" == "debug" set TARGET=debug -if "%2" == "release" set TARGET=release - -if "%1" == "all" goto cppcheck -if "%1" == "cppcheck" goto cppcheck -if "%1" == "gui" goto gui -if "%1" == "tests" goto tests -goto help - -:cppcheck -pushd cli -qmake -config %TARGET% HAVE_RULES=%HAVE_RULES% -%MAKE% -popd -if "%1" == "all" goto gui -goto end - -:gui -pushd gui -qmake -config %TARGET% HAVE_RULES=%HAVE_RULES% -%MAKE% -lupdate -no-obsolete gui.pro -lrelease gui.pro -popd -goto end - -:tests -pushd test -qmake -config %TARGET% HAVE_RULES=%HAVE_RULES% -%MAKE% -popd -goto end - -:help -echo Syntax: build ^ [debug^|release] -echo where ^ is any of cppcheck/gui/tests/all -echo debug or release define used configuration -echo all- target builds both cppcheck and gui. - -:end From 65742b9779135e7ddcf94040368ae7418df60fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 12:43:08 +0200 Subject: [PATCH 105/116] gui.pro: build help --- gui/gui.pro | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gui/gui.pro b/gui/gui.pro index b8d53034d..6fe367f7e 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -12,6 +12,13 @@ QT += widgets QT += printsupport QT += help +# Build online help +onlinehelp.target = online-help.qhc +#onlinehelp.depends = $$PWD/help/online-help.qhp +onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc +QMAKE_EXTRA_TARGETS += onlinehelp +PRE_TARGETDEPS += online-help.qhc + contains(LINKCORE, [yY][eE][sS]) { LIBS += -l../bin/cppcheck-core DEFINES += CPPCHECKLIB_IMPORT From bdb7db0fd5a69e2c6636792ea429fc54ea1749b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 19:04:47 +0200 Subject: [PATCH 106/116] Travis: Try to install qt help --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1552c56a5..b901ba75d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ env: before_install: # install needed deps - travis_retry sudo apt-get update -qq - - travis_retry sudo apt-get install -qq python3-pip qt5-default qt5-qmake qtbase5-dev qtcreator libxml2-utils libpcre3 gdb unzip wx-common xmlstarlet python3-dev liblua5.3-dev libcurl3 libcairo2-dev libsigc++-2.0-dev tidy libopencv-dev libz3-dev + - travis_retry sudo apt-get install -qq python3-pip qt5-default qt5-qmake qtbase5-dev qtcreator qttools5-dev qttools5-dev-tools libxml2-utils libpcre3 gdb unzip wx-common xmlstarlet python3-dev liblua5.3-dev libcurl3 libcairo2-dev libsigc++-2.0-dev tidy libopencv-dev libz3-dev # Python 2 modules - travis_retry python2 -m pip install --user pytest==4.6.4 - travis_retry python2 -m pip install --user pylint From 1c39bed5b0ec5b761d4d5d72812bd7cf2bd065ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 19:18:06 +0200 Subject: [PATCH 107/116] buildhelp.bat: remove old script --- gui/help/buildhelp.bat | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 gui/help/buildhelp.bat diff --git a/gui/help/buildhelp.bat b/gui/help/buildhelp.bat deleted file mode 100644 index b0cfbcc0d..000000000 --- a/gui/help/buildhelp.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -pushd %~dp0 - -if exist online-help.qhc del online-help.qhc -if exist online-help.qch del online-help.qch - -qcollectiongenerator online-help.qhcp -o online-help.qhc - -popd From d7f9dc25cd1cf3b5470bb014f2d07f8262a76153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 19:19:37 +0200 Subject: [PATCH 108/116] Quick fix for Travis problems. Skip building gui online-help. --- gui/gui.pro | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gui/gui.pro b/gui/gui.pro index 6fe367f7e..3fd8663fb 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -13,11 +13,10 @@ QT += printsupport QT += help # Build online help -onlinehelp.target = online-help.qhc -#onlinehelp.depends = $$PWD/help/online-help.qhp -onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc -QMAKE_EXTRA_TARGETS += onlinehelp -PRE_TARGETDEPS += online-help.qhc +#onlinehelp.target = online-help.qhc +#onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc +#QMAKE_EXTRA_TARGETS += onlinehelp +#PRE_TARGETDEPS += online-help.qhc contains(LINKCORE, [yY][eE][sS]) { LIBS += -l../bin/cppcheck-core From 5fca5830c53da2d68d9a5ed10eb15d26345c8565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 21:41:46 +0200 Subject: [PATCH 109/116] astyle formatting [ci skip] --- gui/helpdialog.cpp | 3 ++- gui/helpdialog.h | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp index 920671834..68b306509 100644 --- a/gui/helpdialog.cpp +++ b/gui/helpdialog.cpp @@ -10,7 +10,8 @@ void HelpBrowser::setHelpEngine(QHelpEngine *helpEngine) mHelpEngine = helpEngine; } -QVariant HelpBrowser::loadResource(int type, const QUrl &name){ +QVariant HelpBrowser::loadResource(int type, const QUrl &name) +{ if (name.scheme() == "qthelp") { QString url(name.toString()); while (url.indexOf("/./") > 0) diff --git a/gui/helpdialog.h b/gui/helpdialog.h index 36e1e7954..e37784437 100644 --- a/gui/helpdialog.h +++ b/gui/helpdialog.h @@ -5,23 +5,21 @@ #include namespace Ui { -class HelpDialog; + class HelpDialog; } class QHelpEngine; -class HelpBrowser : public QTextBrowser -{ +class HelpBrowser : public QTextBrowser { public: HelpBrowser(QWidget* parent = 0) : QTextBrowser(parent), mHelpEngine(nullptr) {} void setHelpEngine(QHelpEngine *helpEngine); - QVariant loadResource (int type, const QUrl& name); + QVariant loadResource(int type, const QUrl& name); private: QHelpEngine* mHelpEngine; }; -class HelpDialog : public QDialog -{ +class HelpDialog : public QDialog { Q_OBJECT public: From a68d9e75ef4024c03a4673524033afc6d8529795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 20 Jul 2020 22:06:07 +0200 Subject: [PATCH 110/116] GUI: In ProjectFileDialog; updated choice of vs configurations --- gui/projectfiledialog.cpp | 64 ++++++++++++++++++++++++--------------- gui/projectfiledialog.h | 4 +-- gui/projectfiledialog.ui | 12 +------- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 70452845a..c084ac658 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -62,6 +62,8 @@ static const int numberOfBuiltinPlatforms = sizeof(builtinPlatforms) / sizeof(bu QStringList ProjectFileDialog::getProjectConfigs(const QString &fileName) { + if (!fileName.endsWith(".sln") && !fileName.endsWith(".vcxproj")) + return QStringList(); QStringList ret; ImportProject importer; Settings projSettings; @@ -244,8 +246,12 @@ static void updateAddonCheckBox(QCheckBox *cb, const ProjectFile *projectFile, c void ProjectFileDialog::checkAllVSConfigs() { - if (mUI.mChkAllVsConfigs->isChecked()) - mUI.mListVsConfigs->selectAll(); + if (mUI.mChkAllVsConfigs->isChecked()) { + for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) { + QListWidgetItem *item = mUI.mListVsConfigs->item(row); + item->setCheckState(Qt::Checked); + } + } mUI.mListVsConfigs->setEnabled(!mUI.mChkAllVsConfigs->isChecked()); } @@ -259,6 +265,14 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) setCheckPaths(projectFile->getCheckPaths()); setImportProject(projectFile->getImportProject()); mUI.mChkAllVsConfigs->setChecked(projectFile->getAnalyzeAllVsConfigs()); + setProjectConfigurations(getProjectConfigs(mUI.mEditImportProject->text())); + for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) { + QListWidgetItem *item = mUI.mListVsConfigs->item(row); + if (projectFile->getAnalyzeAllVsConfigs() || projectFile->getVsConfigurations().contains(item->text())) + item->setCheckState(Qt::Checked); + else + item->setCheckState(Qt::Unchecked); + } mUI.mCheckHeaders->setChecked(projectFile->getCheckHeaders()); mUI.mCheckUnusedTemplates->setChecked(projectFile->getCheckUnusedTemplates()); mUI.mMaxCtuDepth->setValue(projectFile->getMaxCtuDepth()); @@ -342,17 +356,6 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) } mUI.mEditTags->setText(projectFile->getTags().join(';')); updatePathsAndDefines(); - if (mUI.mEditImportProject->text().endsWith(".sln") || mUI.mEditImportProject->text().endsWith(".vcxproj")) { - setVsConfigurations(getProjectConfigs(mUI.mEditImportProject->text())); - foreach (const QString &cfg, projectFile->getVsConfigurations()) { - QList items = mUI.mListVsConfigs->findItems(cfg, Qt::MatchFlag::MatchExactly); - items[0]->setSelected(true); - } - } else { - mUI.mListVsConfigs->clear(); - mUI.mListVsConfigs->setEnabled(false); - } - } void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const @@ -361,6 +364,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const projectFile->setBuildDir(getBuildDir()); projectFile->setImportProject(getImportProject()); projectFile->setAnalyzeAllVsConfigs(mUI.mChkAllVsConfigs->isChecked()); + projectFile->setVSConfigurations(getProjectConfigurations()); projectFile->setCheckHeaders(mUI.mCheckHeaders->isChecked()); projectFile->setCheckUnusedTemplates(mUI.mCheckUnusedTemplates->isChecked()); projectFile->setMaxCtuDepth(mUI.mMaxCtuDepth->value()); @@ -414,7 +418,6 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const projectFile->setClangAnalyzer(mUI.mToolClangAnalyzer->isChecked()); projectFile->setClangTidy(mUI.mToolClangTidy->isChecked()); projectFile->setTags(mUI.mEditTags->text().split(";", QString::SkipEmptyParts)); - projectFile->setVSConfigurations(getVsConfigurations()); } void ProjectFileDialog::ok() @@ -460,6 +463,7 @@ void ProjectFileDialog::updatePathsAndDefines() { const QString &fileName = mUI.mEditImportProject->text(); bool importProject = !fileName.isEmpty(); + bool hasConfigs = fileName.endsWith(".sln") || fileName.endsWith(".vcxproj"); mUI.mBtnClearImportProject->setEnabled(importProject); mUI.mListCheckPaths->setEnabled(!importProject); mUI.mListIncludeDirs->setEnabled(!importProject); @@ -473,9 +477,9 @@ void ProjectFileDialog::updatePathsAndDefines() mUI.mBtnRemoveInclude->setEnabled(!importProject); mUI.mBtnIncludeUp->setEnabled(!importProject); mUI.mBtnIncludeDown->setEnabled(!importProject); - mUI.mChkAllVsConfigs->setEnabled(fileName.endsWith(".sln") || fileName.endsWith(".vcxproj")); - mUI.mListVsConfigs->setEnabled(fileName.endsWith(".sln") || fileName.endsWith(".vcxproj")); - if (!mUI.mListVsConfigs->isEnabled()) + mUI.mChkAllVsConfigs->setEnabled(hasConfigs); + mUI.mListVsConfigs->setEnabled(hasConfigs && !mUI.mChkAllVsConfigs->isChecked()); + if (!hasConfigs) mUI.mListVsConfigs->clear(); } @@ -499,24 +503,34 @@ void ProjectFileDialog::browseImportProject() if (!fileName.isEmpty()) { mUI.mEditImportProject->setText(dir.relativeFilePath(fileName)); updatePathsAndDefines(); - setVsConfigurations(getProjectConfigs(fileName)); - mUI.mListVsConfigs->selectAll(); + setProjectConfigurations(getProjectConfigs(fileName)); + for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) { + QListWidgetItem *item = mUI.mListVsConfigs->item(row); + item->setCheckState(Qt::Checked); + } } } -QStringList ProjectFileDialog::getVsConfigurations() const +QStringList ProjectFileDialog::getProjectConfigurations() const { QStringList configs; - foreach (QListWidgetItem *item, mUI.mListVsConfigs->selectedItems()) - configs << item->text(); - + for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) { + QListWidgetItem *item = mUI.mListVsConfigs->item(row); + if (item->checkState() == Qt::Checked) + configs << item->text(); + } return configs; } -void ProjectFileDialog::setVsConfigurations(const QStringList &configs) +void ProjectFileDialog::setProjectConfigurations(const QStringList &configs) { mUI.mListVsConfigs->clear(); - mUI.mListVsConfigs->addItems(configs); + mUI.mListVsConfigs->setEnabled(!configs.isEmpty() && !mUI.mChkAllVsConfigs->isChecked()); + foreach (const QString &cfg, configs) { + QListWidgetItem* item = new QListWidgetItem(cfg, mUI.mListVsConfigs); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); // set checkable flag + item->setCheckState(Qt::Unchecked); + } } QString ProjectFileDialog::getImportProject() const diff --git a/gui/projectfiledialog.h b/gui/projectfiledialog.h index fef320a4f..d7ad84d24 100644 --- a/gui/projectfiledialog.h +++ b/gui/projectfiledialog.h @@ -58,8 +58,8 @@ private: */ QString getRootPath() const; - QStringList getVsConfigurations() const; - void setVsConfigurations(const QStringList &configs); + QStringList getProjectConfigurations() const; + void setProjectConfigurations(const QStringList &configs); QString getImportProject() const; diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index 31884ce91..879ca9261 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -130,17 +130,7 @@ - - - - 16777215 - 100 - - - - QAbstractItemView::MultiSelection - - + From ae0ad171520fcfc63df2108d5bf94fdfa3cc4260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 10:30:50 +0200 Subject: [PATCH 111/116] GUI: quick walk through in online-help --- gui/help/images/walkthrough-analysis.png | Bin 0 -> 142639 bytes .../images/walkthrough-import-project.png | Bin 0 -> 12437 bytes gui/help/images/walkthrough-library.png | Bin 0 -> 4743 bytes gui/help/images/walkthrough-new-project.png | Bin 0 -> 10712 bytes .../images/walkthrough-toolbar-severities.png | Bin 0 -> 6693 bytes gui/help/images/walkthrough-warning-menu.png | Bin 0 -> 10339 bytes gui/help/index.html | 39 ---------- gui/help/online-help.qhp | 8 ++ gui/help/walkthrough.html | 69 ++++++++++++++++++ gui/helpdialog.ui | 45 +----------- 10 files changed, 78 insertions(+), 83 deletions(-) create mode 100644 gui/help/images/walkthrough-analysis.png create mode 100644 gui/help/images/walkthrough-import-project.png create mode 100644 gui/help/images/walkthrough-library.png create mode 100644 gui/help/images/walkthrough-new-project.png create mode 100644 gui/help/images/walkthrough-toolbar-severities.png create mode 100644 gui/help/images/walkthrough-warning-menu.png create mode 100644 gui/help/walkthrough.html diff --git a/gui/help/images/walkthrough-analysis.png b/gui/help/images/walkthrough-analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..94efb2890d80f5d4a1ea5e1c0d127b81b5228b76 GIT binary patch literal 142639 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⪙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
=5(Nqv$y7dBI~Ow1OxlN=m} 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 literal 0 HcmV?d00001 diff --git a/gui/help/images/walkthrough-toolbar-severities.png b/gui/help/images/walkthrough-toolbar-severities.png new file mode 100644 index 0000000000000000000000000000000000000000..f06e3aa41cf97272c39ad85d14a877402bce42b8 GIT binary patch literal 6693 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

KWg|6F321>6xgS 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°78eG&7%Hcj?EJwd8|qQnA_LXCN$-;E-B7e!TtJJ>o5w z!xYIK%;nvW$B#AnjPq3ueotO>tyT``7{~jn5=a1CcvS6yR_&*k^5s&0sX}I?JjNQiFJY3VHG%N4I2lXM7J&(E1|sPr z1_9Wf9$5%=3TZcf3!{16&~xG2zhoiQ-)(@~8t}WJrx@(MfczO@$cmvsweRm=1r*d+ zUu#2W2rA0!1Xn;xdbZ<-{@5H&-J;5X_k`pHTrMCEEXXiDet!0H3MoSw(`~efVF<%L zXU~e8j1j29QNvB?W-YuPqx0E)0fxR)-2bCB{cA#a$1-Tp6vk zsN!c^%o(^T36O%Huc*->NhKu|`rXhG-~94i3AmCI(P6IFz&r*m4N(%{{2hd@xkw?< zk%%IjWK4g!P=R0TJGPhZU*L;guh#f|<}Oppc@Yeexnvno=(;~uV91=JLyGmL2p(4` zMgG%%*s-?{PyD$*L-Q)h+8TWV^k;#xCr7}o%-UM066<394zRl%Z@KQirX3*;nkGbb zJ=&1TCWsq^6$Vq@?u)0KIhe(a@y-=C9{O#iYx`z|Zs~H`%?XfA9W3bbpXJgv=WW48 zx({%7bk#Gg^Lsp`nk>vF%npTji7h?(l6~iOi^t!2YNeXGPfPrMiy>bR_F8b1YM8}6HNQ5xaV@i8AK zo)%W@iRrcyTE4%UldFx@SOP13R$KS_PG5fPEr0nusN*u=mBFqx`H&)t-EBrQLk`52 z!pR?a-Z3ih9^1x}v89wVt2pHae#^@LHM)Xa*mXXwpxbtwzrm2XWbH96&N3QiA4Y0sb27{rFh;pGlcbT`VYQ08GMjn$(sztggTm0i70 zAdVgNsHhb!_YjHMG1r!mUx>z&wE99Tpz<4!6Lc|w{fGrM=mer)V0Zi{;n z(E&@`TqOs{;-EC4FV9!Say>0Hx z(;uNQm9Z&T58}$ip{hR7h;H+J%_1lk<(7y+xpHnJ`$;$VD)WTm7I)ib$CZ}81Jl;& z`9S5;px7NJDzO3bx@{)xwAtD?Y^?%K(%U&{IO0KVGPt=5K>0$SarnwpNx5jVLyki$ zbA`j$r}R^WxyPh%mC(fCAZ9Ld*@M*?edf9QT8YR z`9j0ge^lV=V`0T5k^GZyV#2Rn@EIMTY!tIe5eG(!>fm@vn6zN|n^>|&qU{wloGfHl zJRKBDfQs|bX!8PZ-`#M|T*vPX@AlbarqY3qfiP`URgTi49O3$&{#(14M=!JNx38%m z!buBSbZ^zPjXK|oHX(%|@1pu5ET;jHWiIG-XiyG4Uc%K%0jZNUVb7le=8eUTy_b>( z7t@;Zy}6rVp#!SIxE+a=a*AV;Tf8sXX&*nGR<7((FfytI_IF~}wZ4c`q>YFs7~*{t zAxuAuzPHMTU-g#|7lwsJAkx(%ExS!_9*u+e(kgtKqbE89Tl#fg&@ z$jBBZ?%hD^Kk@oH2U&_I`GGRI*~AFOXa_{<4Ny$ERhjZFm|&JRrqt%VikjFTwEZ0& zE$-{?+g(<3R2<@DiwyHsrF>CCb$&H4ksJ-54HQ{!O^OH4+C0OAL$Z~|8rRcE>Hezw zDgBOfDAX^lqLnKr6!vtM{{$t{$Y^Oth-Q-ESC5BmyFI09jNn9OzGq=DPS}D)&1E{OpHPE)%H-R7UC*g#ga-%p2N+8r6h#v(Hhn zg1@eu3N6c892#PDkkK`zX1P36Ahq=@ss!l7IFYl(BLKEMD^&zbA3X#5MJM;vG5%as74 zZ@$Qy<4*_dLuun;^Ypv7;cYhEhQlB5$bUb z{+ZUl>fQ6FBC)rrL^)$x^ABHXQP*1DvH3KocOq*v9cEMmY;UH_)hH*cID~1{`tlwZ zY_RBs!XVlIWheD}^mNfA|$~ zXG)aR&7Kx$mwFjQU^0n*1AlG;Ht9QFWe<;1e(WgoMeJkEh7SRwRN};H@#H2q@9vGr%QY1oA%aBVkq;s}#FozYiO`g~&WevW3<%8b0Anz;YHxomalCr? zNK+qf!8{`vf!VPr8ZLZiGHQT2lDm;HZGaj37PPxKUJu4{wN~?WN$5}1*Rex4@p;sa zcprQ=D@ZI1V$CCv1N%>J3w0CTe|pNZI5wsGT__oaON_7}qZpMzA@Q#XvtmDcIWffl zj3o5dDg#yR5Gv0x?t)N$I^{DKSr^V5n&z4pk3z$g2FX+3erz(!rrVMVZ8A84-Ta=O zW9XH0LsKLrV^UB-D#XWDo_qG{Gjv!-xiRxZe^!JhMY!g^An<0KgG>w`i!?{XuNAuU zFfcv6MgOYRX(<3C5zr)KVY~y(cpLE(C*3|qfGU?{DU}yOymV0b{!J(<#&=76Yv&O$M!&m=^(Hh& zwgx^<6#?-Ii=j;PQoJGDR=aiqgJ_~Clxz?|i|#6~qhH9-g!utVFEbqYawgv-QlzY$ z{`N|dxLBaPjL>7KaKajN+}`X`qwq|Cl}*MkA*EZ4cN1eD2|?fsa?#_xT2X2QC`$~* zw8V%IG-8A*jNxwq;7H~jiel7cotH<0(Y%t|D)1SK1{xE|3oY=Niguir@Uh4JsCN}+ zClb-DCqoEczGb3t;0D6_T`+=LQ&df~D7~Q3uQ-6>k^U1+D&jAW>U$(4EbM=00UjdV zp82>xnKR`Z!I@a20hLFl9&tU&VplRgC|y0LSIBdCOv{ zcLz0yt0lM!{cdy+(}!rzN168-SufDDF0ul2w>B4_lJ{~b(;+X@ZDXLcteUvB+Lp{c zjVVw5gt+g~?1mfj$bYC|ze;t~HB%dO<>}N*Ufr53qdJs0Pks8H!jU37QYYW0A*5Ft zTYXX8jdFvCBfdWp+%q=oUy{g7z$>m^)+8NL7pGOi3}+z>18|fpy5>yQc>pV(!BDv^b&8W!~*@*zMZYE&!}$q)b{v! z>r_Rs0^0{xy-cARXF`-eHf8)dk97}&#rwC%b6FuQ8Lyq!b<66*Z!Z^}n)EJ#6A0MD zl|ZcG>hwfRa=2b1ml`hFd-C{jM~zfrN|r$qdZt}xzzoI7=pb}w@VUWmufLK>pkYpX zwF@!_zt7A`?ju$R*4X%SH->x6vAT4(=>4}a>I0YgfNZ)%cSt~vXg~)gk4@6M#n#$p zywG~skY-?lGy@fPczCwO1p3TcI>R<2;3v*zrjtZQFvNYphPl~?ii%B$)n==Q!&uzxgnyG zs`VDD)7LkGF)tCx0ccBiF8A!*^4M?DW$#~`wsKNX^(_9r;EX2 zxeDEaK1wnk?A&B8SlWCrft03w#o54S^7zG6_+67livT)zdBd$~&Gk9!3@qUAxRg{7{v!(fDNS;6c#!O#Ad)^Na;H67XsBrXg5L8RWW zXX!9z63z_^-}MaBG~(~+)>q#90UOThWwa*Q8y`DQ-}|>Yuf4uu_YM%na|BMQeDu3I z1Y^r03Yk5=ThEW`=seW!t}ENrG`cdn2WK!46e?Am&xa*AD-ros`w7UVTRZP)rLB<1*H96u zl#KS+$Qo%7P#4~xIPV(Vyc5p6h==ZK_5K`y#Tem9>H23{fNA2mJxr!1t#k)t#+F; zAf%O1Xl+uqX@D+b5`du!lHCnyDzQwBGLke&(={gx)i-#(2gK4vI$wM*Mg+GfoDNj9 zdQD7!j|&M9T0)GR^VM~N9!m&&6CeRZ6}x~LvBW15Ulp=ydCiuk6WV&>81q2m_Y;W! z2FIBpH)xd3SeNiv*I)w&hn21`3|Gxh;2P-UY<}dh;hstsk=6@j!pqh3(r%|2l4NBI zX-QSSnedaCp_39bpWd1Ha(I5_+wwU|8{rcdRV%6BY^e(aBPd_bQW%M6&&Dw+ zhcU_Pj|p*4vnAVv`z0w_09&1>+&Mp)$A^Ms)SpeCo>DOx_u-D@1DVJE!UDK};BL*# zdJ*1&oAD#M;&~sZzD89;=0*S^^{)K#u~t>!MJ0I?9&)zX3j5EFRErn&CY|!VIVuBb)B!Y^zR0iF#G>BQ#jj~x kUGz9xrjdm*-u*~0iYV(4hBFKRrv{Pa-Y84ey*3H`AJv7KX#fBK literal 0 HcmV?d00001 diff --git a/gui/help/index.html b/gui/help/index.html index b466002dc..57a466b0c 100644 --- a/gui/help/index.html +++ b/gui/help/index.html @@ -8,45 +8,6 @@

With the Cppcheck GUI you can analyze your code.

-

Quick walk through

- -

Step 1: Create a project.

- -

Create a new project.

- -

In the Paths and Defines tab, it is recommended that you import your project file at the top.

- -

In the Types and Functions tab, try to activate all 3rd party libraries you use (windows, posix, ...).

- -

In the Analysis tab, leave the default settings to start with.

- -

In the Warnings options tab, leave the default settings to start with.

- -

In the Addons tab, leave the default settings to start with.

- - -

Step 2: Analyze code.

- -

When the project file has been created, the analysis will start automatically.

- -

While analysis is performed in the background, you can investigate the warnings.

- - -

Step 3: Investigate warnings.

- -

In the toolbar you choose what types of warnings you want to see (error/warning/style/performance/portability/information).

- -

All warnings are shown in a list. If you select a warning in the list, then details about that warning is shown below the list.

- -

If you right click on warning(s) then you get a context menu.

- - -

Step 4: Configuration.

- -

It is possible to improve configuration to get better analysis. The automatic assumptions are conservative and through configuration those automatic assumptions can be avoided.

- -

TODO: library, contracts

- diff --git a/gui/help/online-help.qhp b/gui/help/online-help.qhp index 4bbe8074d..2af58d079 100644 --- a/gui/help/online-help.qhp +++ b/gui/help/online-help.qhp @@ -10,6 +10,7 @@
+
@@ -23,6 +24,13 @@ projectfiledialog.html standalone-analysis.html tagging.html + walkthrough.html + images/walkthrough-analysis.png + images/walkthrough-library.png + images/walkthrough-toolbar-severities.png + images/walkthrough-import-project.png + images/walkthrough-new-project.png + images/walkthrough-warning-menu.png diff --git a/gui/help/walkthrough.html b/gui/help/walkthrough.html new file mode 100644 index 000000000..9f27ce05f --- /dev/null +++ b/gui/help/walkthrough.html @@ -0,0 +1,69 @@ + + +Walk through + + + +

Quick walk through

+ +This is a quick and short walk through to get you started. + +

Step 1: Create a project.

+ +

Create a new project:

+ +
+ +

In the Paths and Defines tab, it is recommended that you import your +project file at the top.

+ +
+ +

In the Types and Functions tab, try to activate all 3rd party +libraries you use (windows, posix, ...).

+ +
+ +

In the Analysis tab, leave the default settings to start with.

+ +

In the Warnings options tab, leave the default settings to start +with.

+ +

In the Addons tab, leave the default settings to start with.

+ + +

Step 2: Analyze code.

+ +

When the project file has been created, the analysis will start +automatically.

+ +

While analysis is performed in the background, you can investigate the +results.

+ +
+ + +

Step 3: Investigate warnings.

+ +

In the toolbar you choose what types of warnings you want to see +(error/warning/style/performance/portability/information).

+ +
+ + +

All warnings are shown in a list. If you select a warning in the list, then +details about that warning is shown.

+ +

If you right click on warning(s) then you get a context menu.

+ +
+ +

The difference of "Hide" and "Suppress" is that +suppressions are saved in the project file. The suppressed warnings will not be +shown again unless you remove the suppression. When you hide a warning then +they will be temporarily hidden; the next time you analyze your code these +warnings will be shown again.

+ + + + diff --git a/gui/helpdialog.ui b/gui/helpdialog.ui index bc5ccc706..0fb74c6a1 100644 --- a/gui/helpdialog.ui +++ b/gui/helpdialog.ui @@ -53,16 +53,6 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - @@ -73,38 +63,5 @@ - - - buttonBox - accepted() - HelpDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - HelpDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + From 4ab04db53c7d2cf6083f78a324762c55d5bed145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 11:27:03 +0200 Subject: [PATCH 112/116] Rename 'cppcheckID' to 'hash' --- cppcheck-errors.rng | 2 +- gui/erroritem.cpp | 8 ++++---- gui/erroritem.h | 4 ++-- gui/projectfile.cpp | 26 +++++++++++++------------- gui/projectfile.h | 4 ++-- gui/resultstree.cpp | 28 ++++++++++++++-------------- gui/resultstree.h | 4 ++-- gui/xmlreportv2.cpp | 10 +++++----- lib/errorlogger.cpp | 28 ++++++++++++++-------------- lib/errorlogger.h | 4 ++-- lib/importproject.cpp | 2 +- lib/importproject.h | 2 +- lib/suppressions.cpp | 24 ++++++++++++------------ lib/suppressions.h | 14 +++++++------- 14 files changed, 80 insertions(+), 80 deletions(-) diff --git a/cppcheck-errors.rng b/cppcheck-errors.rng index 6446d98ce..a2a5168b0 100644 --- a/cppcheck-errors.rng +++ b/cppcheck-errors.rng @@ -25,7 +25,7 @@ - + 1 diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp index cccc25911..89a4fd7f9 100644 --- a/gui/erroritem.cpp +++ b/gui/erroritem.cpp @@ -37,7 +37,7 @@ ErrorItem::ErrorItem() , incomplete(false) , inconclusive(false) , cwe(-1) - , cppcheckId(0) + , hash(0) { } @@ -51,7 +51,7 @@ ErrorItem::ErrorItem(const ErrorMessage &errmsg) , summary(QString::fromStdString(errmsg.shortMessage())) , message(QString::fromStdString(errmsg.verboseMessage())) , cwe(errmsg.cwe.id) - , cppcheckId(errmsg.cppcheckId) + , hash(errmsg.hash) , symbolNames(QString::fromStdString(errmsg.symbolNames())) { for (std::list::const_iterator loc = errmsg.callStack.begin(); @@ -88,8 +88,8 @@ QString ErrorItem::toString() const bool ErrorItem::sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2) { - if (errorItem1.cppcheckId || errorItem2.cppcheckId) - return errorItem1.cppcheckId == errorItem2.cppcheckId; + if (errorItem1.hash || errorItem2.hash) + return errorItem1.hash == errorItem2.hash; // fallback return errorItem1.errorId == errorItem2.errorId && diff --git a/gui/erroritem.h b/gui/erroritem.h index 7bcb9a74f..0ab90938a 100644 --- a/gui/erroritem.h +++ b/gui/erroritem.h @@ -88,7 +88,7 @@ public: QString summary; QString message; int cwe; - unsigned long long cppcheckId; + unsigned long long hash; QList errorPath; QString symbolNames; @@ -115,7 +115,7 @@ public: QString errorId; bool incomplete; int cwe; - unsigned long long cppcheckId; + unsigned long long hash; bool inconclusive; Severity::SeverityType severity; QString summary; diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 45035aec1..ef93e78b3 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -610,8 +610,8 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader) suppression.lineNumber = reader.attributes().value(QString(),"lineNumber").toInt(); if (reader.attributes().hasAttribute(QString(),"symbolName")) suppression.symbolName = reader.attributes().value(QString(),"symbolName").toString().toStdString(); - if (reader.attributes().hasAttribute(QString(),"cppcheck-id")) - suppression.cppcheckId = reader.attributes().value(QString(),"cppcheck-id").toULongLong(); + if (reader.attributes().hasAttribute(QString(),"hash")) + suppression.hash = reader.attributes().value(QString(),"hash").toULongLong(); type = reader.readNext(); if (type == QXmlStreamReader::Characters) { suppression.errorId = reader.text().toString().toStdString(); @@ -650,8 +650,8 @@ void ProjectFile::readTagWarnings(QXmlStreamReader &reader, const QString &tag) case QXmlStreamReader::StartElement: // Read library-elements if (reader.name().toString() == CppcheckXml::WarningElementName) { - std::size_t cppcheckId = reader.attributes().value(QString(), CppcheckXml::CppcheckIdAttributeName).toULongLong(); - mWarningTags[cppcheckId] = tag; + std::size_t hash = reader.attributes().value(QString(), CppcheckXml::HashAttributeName).toULongLong(); + mWarningTags[hash] = tag; } break; @@ -774,17 +774,17 @@ void ProjectFile::setVSConfigurations(const QStringList &vsConfigs) mVsConfigurations = vsConfigs; } -void ProjectFile::setWarningTags(std::size_t cppcheckId, QString tag) +void ProjectFile::setWarningTags(std::size_t hash, QString tag) { if (tag.isEmpty()) - mWarningTags.erase(cppcheckId); - else if (cppcheckId > 0) - mWarningTags[cppcheckId] = tag; + mWarningTags.erase(hash); + else if (hash > 0) + mWarningTags[hash] = tag; } -QString ProjectFile::getWarningTags(std::size_t cppcheckId) const +QString ProjectFile::getWarningTags(std::size_t hash) const { - auto it = mWarningTags.find(cppcheckId); + auto it = mWarningTags.find(hash); return (it != mWarningTags.end()) ? it->second : QString(); } @@ -936,8 +936,8 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeAttribute("lineNumber", QString::number(suppression.lineNumber)); if (!suppression.symbolName.empty()) xmlWriter.writeAttribute("symbolName", QString::fromStdString(suppression.symbolName)); - if (suppression.cppcheckId > 0) - xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(suppression.cppcheckId)); + if (suppression.hash > 0) + xmlWriter.writeAttribute(CppcheckXml::HashAttributeName, QString::number(suppression.hash)); if (!suppression.errorId.empty()) xmlWriter.writeCharacters(QString::fromStdString(suppression.errorId)); xmlWriter.writeEndElement(); @@ -981,7 +981,7 @@ bool ProjectFile::write(const QString &filename) for (const auto wt: mWarningTags) { if (wt.second == tag) { xmlWriter.writeStartElement(CppcheckXml::WarningElementName); - xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(wt.first)); + xmlWriter.writeAttribute(CppcheckXml::HashAttributeName, QString::number(wt.first)); xmlWriter.writeEndElement(); } } diff --git a/gui/projectfile.h b/gui/projectfile.h index 388c03379..9f10ee651 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -332,10 +332,10 @@ public: } /** Set tags for a warning */ - void setWarningTags(std::size_t cppcheckId, QString tags); + void setWarningTags(std::size_t hash, QString tags); /** Get tags for a warning */ - QString getWarningTags(std::size_t cppcheckId) const; + QString getWarningTags(std::size_t hash) const; /** * @brief Write project file (to disk). diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 191735c39..ba10a94ed 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -50,12 +50,12 @@ #include "xmlreportv2.h" static const char COLUMN[] = "column"; -static const char CPPCHECKID[] = "cppcheckId"; static const char CWE[] = "cwe"; static const char ERRORID[] = "id"; static const char FILENAME[] = "file"; static const char FILE0[] = "file0"; static const char FUNCTION[] = "function"; +static const char HASH[] = "hash"; static const char HIDE[] = "hide"; static const char INCOMPLETE[] = "incomplete"; static const char INCONCLUSIVE[] = "inconclusive"; @@ -187,14 +187,14 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) line.errorId = item.errorId; line.incomplete = item.incomplete; line.cwe = item.cwe; - line.cppcheckId = item.cppcheckId; + line.hash = item.hash; line.inconclusive = item.inconclusive; line.summary = item.summary; line.message = item.message; line.severity = item.severity; line.sinceDate = item.sinceDate; if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) { - line.tags = activeProject->getWarningTags(item.cppcheckId); + line.tags = activeProject->getWarningTags(item.hash); } //Create the base item for the error and ensure it has a proper //file item as a parent @@ -219,7 +219,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) data[ERRORID] = item.errorId; data[INCOMPLETE] = item.incomplete; data[CWE] = item.cwe; - data[CPPCHECKID] = item.cppcheckId; + data[HASH] = item.hash; data[INCONCLUSIVE] = item.inconclusive; data[FILE0] = stripPath(item.file0, true); data[FUNCTION] = item.function; @@ -256,7 +256,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) child_data[ERRORID] = line.errorId; child_data[INCOMPLETE] = line.incomplete; child_data[CWE] = line.cwe; - child_data[CPPCHECKID] = line.cppcheckId; + child_data[HASH] = line.hash; child_data[INCONCLUSIVE] = line.inconclusive; child_data[SYMBOLNAMES] = item.symbolNames; child_item->setData(QVariant(child_data)); @@ -661,7 +661,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) QAction *copy = new QAction(tr("Copy"), &menu); QAction *hide = new QAction(tr("Hide"), &menu); QAction *hideallid = new QAction(tr("Hide all with id"), &menu); - QAction *suppressCppcheckID = new QAction(tr("Suppress cppcheck-id"), &menu); + QAction *suppresshash = new QAction(tr("Suppress hash"), &menu); QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu); if (multipleSelection) { @@ -679,7 +679,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); menu.addAction(hide); menu.addAction(hideallid); - menu.addAction(suppressCppcheckID); + menu.addAction(suppresshash); if (!bughunting) { QAction *suppress = new QAction(tr("Suppress selected id(s)"), &menu); menu.addAction(suppress); @@ -692,7 +692,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) connect(copy, SIGNAL(triggered()), this, SLOT(copy())); connect(hide, SIGNAL(triggered()), this, SLOT(hideResult())); connect(hideallid, SIGNAL(triggered()), this, SLOT(hideAllIdResult())); - connect(suppressCppcheckID, &QAction::triggered, this, &ResultsTree::suppressCppcheckID); + connect(suppresshash, &QAction::triggered, this, &ResultsTree::suppressHash); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); const ProjectFile *currentProject = ProjectFile::getActiveProject(); @@ -1066,7 +1066,7 @@ void ResultsTree::suppressSelectedIds() emit suppressIds(selectedIds.toList()); } -void ResultsTree::suppressCppcheckID() +void ResultsTree::suppressHash() { if (!mSelectionModel) return; @@ -1087,9 +1087,9 @@ void ResultsTree::suppressCppcheckID() for (QStandardItem *item: selectedWarnings) { QStandardItem *fileItem = item->parent(); const QVariantMap data = item->data().toMap(); - if (projectFile && data.contains("cppcheckId")) { + if (projectFile && data.contains(HASH)) { Suppressions::Suppression suppression; - suppression.cppcheckId = data[CPPCHECKID].toULongLong(); + suppression.hash = data[HASH].toULongLong(); suppression.errorId = data[ERRORID].toString().toStdString(); suppression.fileName = data[FILENAME].toString().toStdString(); suppression.lineNumber = data[LINE].toInt(); @@ -1132,9 +1132,9 @@ void ResultsTree::tagSelectedItems(const QString &tag) data[TAGS] = tag; item->setData(QVariant(data)); item->parent()->child(index.row(), COLUMN_TAGS)->setText(tag); - if (currentProject && data.contains(CPPCHECKID)) { + if (currentProject && data.contains(HASH)) { isTagged = true; - currentProject->setWarningTags(data[CPPCHECKID].toULongLong(), tag); + currentProject->setWarningTags(data[HASH].toULongLong(), tag); } } } @@ -1296,7 +1296,7 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con item->errorId = data[ERRORID].toString(); item->incomplete = data[INCOMPLETE].toBool(); item->cwe = data[CWE].toInt(); - item->cppcheckId = data[CPPCHECKID].toULongLong(); + item->hash = data[HASH].toULongLong(); item->inconclusive = data[INCONCLUSIVE].toBool(); item->file0 = data[FILE0].toString(); item->sinceDate = data[SINCEDATE].toString(); diff --git a/gui/resultstree.h b/gui/resultstree.h index 5f2557826..f35169a42 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -276,8 +276,8 @@ protected slots: /** Slot for context menu item to suppress all messages with the current message id */ void suppressSelectedIds(); - /** Slot for context menu item to suppress message with cppcheck ID */ - void suppressCppcheckID(); + /** Slot for context menu item to suppress message with hash */ + void suppressHash(); /** * @brief Slot for context menu item to open the folder containing the current file. diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp index 00fd096e8..18e3b9156 100644 --- a/gui/xmlreportv2.cpp +++ b/gui/xmlreportv2.cpp @@ -34,7 +34,7 @@ static const QString ErrorElementName = "error"; static const QString ErrorsElementName = "errors"; static const QString LocationElementName = "location"; static const QString CWEAttribute = "cwe"; -static const QString CppcheckIdAttribute = "cppcheck-id"; +static const QString HashAttribute = "hash"; static const QString SinceDateAttribute = "sinceDate"; static const QString TagsAttribute = "tag"; static const QString FilenameAttribute = "file"; @@ -123,8 +123,8 @@ void XmlReportV2::writeError(const ErrorItem &error) mXmlWriter->writeAttribute(InconclusiveAttribute, "true"); if (error.cwe > 0) mXmlWriter->writeAttribute(CWEAttribute, QString::number(error.cwe)); - if (error.cppcheckId > 0) - mXmlWriter->writeAttribute(CppcheckIdAttribute, QString::number(error.cppcheckId)); + if (error.hash > 0) + mXmlWriter->writeAttribute(HashAttribute, QString::number(error.hash)); if (!error.sinceDate.isEmpty()) mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate); if (!error.tags.isEmpty()) @@ -218,8 +218,8 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader) item.inconclusive = true; if (attribs.hasAttribute(QString(), CWEAttribute)) item.cwe = attribs.value(QString(), CWEAttribute).toInt(); - if (attribs.hasAttribute(QString(), CppcheckIdAttribute)) - item.cppcheckId = attribs.value(QString(), CppcheckIdAttribute).toULongLong(); + if (attribs.hasAttribute(QString(), HashAttribute)) + item.hash = attribs.value(QString(), HashAttribute).toULongLong(); if (attribs.hasAttribute(QString(), SinceDateAttribute)) item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString(); if (attribs.hasAttribute(QString(), TagsAttribute)) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 06bfbde76..9451441c4 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -59,7 +59,7 @@ InternalError::InternalError(const Token *tok, const std::string &errorMsg, Type } } -static std::size_t calculateCppcheckId(const TokenList *tokenList, const std::string &msg) +static std::size_t calculateWarningHash(const TokenList *tokenList, const std::string &msg) { if (!tokenList) return 0; @@ -67,7 +67,7 @@ static std::size_t calculateCppcheckId(const TokenList *tokenList, const std::st } ErrorMessage::ErrorMessage() - : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false), cppcheckId(0) + : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false), hash(0) { } @@ -79,7 +79,7 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std:: severity(severity), // severity for this error message cwe(0U), inconclusive(inconclusive), - cppcheckId(0) + hash(0) { // set the summary and verbose messages setmsg(msg); @@ -95,14 +95,14 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std:: severity(severity), // severity for this error message cwe(cwe.id), inconclusive(inconclusive), - cppcheckId(0) + hash(0) { // set the summary and verbose messages setmsg(msg); } ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity::SeverityType severity, const std::string& id, const std::string& msg, bool inconclusive) - : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive), cppcheckId(0) + : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive), hash(0) { // Format callstack for (std::list::const_iterator it = callstack.begin(); it != callstack.end(); ++it) { @@ -137,7 +137,7 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token setmsg(msg); - cppcheckId = calculateCppcheckId(list, toString(false)); + hash = calculateWarningHash(list, toString(false)); } ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenList, Severity::SeverityType severity, const char id[], const std::string &msg, const CWE &cwe, bool inconclusive) @@ -158,7 +158,7 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis setmsg(msg); - cppcheckId = calculateCppcheckId(tokenList, toString(false)); + hash = calculateWarningHash(tokenList, toString(false)); } ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) @@ -187,8 +187,8 @@ ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) attr = errmsg->Attribute("verbose"); mVerboseMessage = attr ? attr : ""; - attr = errmsg->Attribute("cppcheck-id"); - std::istringstream(attr ? attr : "0") >> cppcheckId; + attr = errmsg->Attribute("hash"); + std::istringstream(attr ? attr : "0") >> hash; for (const tinyxml2::XMLElement *e = errmsg->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(),"location")==0) { @@ -235,7 +235,7 @@ void ErrorMessage::setmsg(const std::string &msg) Suppressions::ErrorMessage ErrorMessage::toSuppressionsErrorMessage() const { Suppressions::ErrorMessage ret; - ret.cppcheckId = cppcheckId; + ret.hash = hash; ret.errorId = id; if (!callStack.empty()) { ret.setFileName(callStack.back().getfile(false)); @@ -254,7 +254,7 @@ std::string ErrorMessage::serialize() const oss << id.length() << " " << id; oss << Severity::toString(severity).length() << " " << Severity::toString(severity); oss << MathLib::toString(cwe.id).length() << " " << MathLib::toString(cwe.id); - oss << MathLib::toString(cppcheckId).length() << " " << MathLib::toString(cppcheckId); + oss << MathLib::toString(hash).length() << " " << MathLib::toString(hash); if (inconclusive) { const std::string text("inconclusive"); oss << text.length() << " " << text; @@ -309,7 +309,7 @@ bool ErrorMessage::deserialize(const std::string &data) id = results[0]; severity = Severity::fromString(results[1]); std::istringstream(results[2]) >> cwe.id; - std::istringstream(results[3]) >> cppcheckId; + std::istringstream(results[3]) >> hash; mShortMessage = results[4]; mVerboseMessage = results[5]; @@ -418,8 +418,8 @@ std::string ErrorMessage::toXML() const printer.PushAttribute("verbose", fixInvalidChars(mVerboseMessage).c_str()); if (cwe.id) printer.PushAttribute("cwe", cwe.id); - if (cppcheckId) - printer.PushAttribute("cppcheck-id", MathLib::toString(cppcheckId).c_str()); + if (hash) + printer.PushAttribute("hash", MathLib::toString(hash).c_str()); if (inconclusive) printer.PushAttribute("inconclusive", "true"); diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 76d168be5..17c503c4b 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -196,8 +196,8 @@ public: CWE cwe; bool inconclusive; - /** Warning ID */ - std::size_t cppcheckId; + /** Warning hash */ + std::size_t hash; /** set short and verbose messages */ void setmsg(const std::string &msg); diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 3f81338d6..bcf272ea4 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1086,7 +1086,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti s.fileName = joinRelativePath(path, s.fileName); s.lineNumber = child->IntAttribute("lineNumber", Suppressions::Suppression::NO_LINE); s.symbolName = read(child->Attribute("symbolName"), ""); - std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId; + std::istringstream(read(child->Attribute("hash"), "0")) >> s.hash; suppressions.push_back(s); } } else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0) diff --git a/lib/importproject.h b/lib/importproject.h index 3f4e73ef4..e39ec8624 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -164,7 +164,7 @@ namespace CppcheckXml { const char TagWarningsElementName[] = "tag-warnings"; const char TagAttributeName[] = "tag"; const char WarningElementName[] = "warning"; - const char CppcheckIdAttributeName[] = "cppcheck-id"; + const char HashAttributeName[] = "hash"; const char CheckHeadersElementName[] = "check-headers"; const char CheckUnusedTemplatesElementName[] = "check-unused-templates"; const char MaxCtuDepthElementName[] = "max-ctu-depth"; diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index e436a7413..c5fb02126 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -85,7 +85,7 @@ std::string Suppressions::parseXmlFile(const char *filename) const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); for (const tinyxml2::XMLElement * e = rootnode->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "suppress") != 0) - return "Invalid suppression xml file format, expected element but got a <" + std::string(e->Name()) + '>'; + return "Invalid suppression xml file format, expected element but got a \"" + std::string(e->Name()) + '\"'; Suppression s; for (const tinyxml2::XMLElement * e2 = e->FirstChildElement(); e2; e2 = e2->NextSiblingElement()) { @@ -98,10 +98,10 @@ std::string Suppressions::parseXmlFile(const char *filename) s.lineNumber = std::atoi(text); else if (std::strcmp(e2->Name(), "symbolName") == 0) s.symbolName = text; - else if (*text && std::strcmp(e2->Name(), "cppcheckId") == 0) - std::istringstream(text) >> s.cppcheckId; + else if (*text && std::strcmp(e2->Name(), "hash") == 0) + std::istringstream(text) >> s.hash; else - return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ////"; + return "Unknown suppression element \"" + std::string(e2->Name()) + "\", expected id/fileName/lineNumber/symbolName/hash"; } const std::string err = addSuppression(s); @@ -206,7 +206,7 @@ std::string Suppressions::addSuppressionLine(const std::string &line) std::string Suppressions::addSuppression(const Suppressions::Suppression &suppression) { // Check that errorId is valid.. - if (suppression.errorId.empty() && suppression.cppcheckId == 0) + if (suppression.errorId.empty() && suppression.hash == 0) return "Failed to add suppression. No id."; if (suppression.errorId != "*") { @@ -273,7 +273,7 @@ bool Suppressions::Suppression::parseComment(std::string comment, std::string *e bool Suppressions::Suppression::isSuppressed(const Suppressions::ErrorMessage &errmsg) const { - if (cppcheckId > 0 && cppcheckId != errmsg.cppcheckId) + if (hash > 0 && hash != errmsg.hash) return false; if (!errorId.empty() && !matchglob(errorId, errmsg.errorId)) return false; @@ -319,8 +319,8 @@ std::string Suppressions::Suppression::getText() const ret += " lineNumber=" + MathLib::toString(lineNumber); if (!symbolName.empty()) ret += " symbolName=" + symbolName; - if (cppcheckId > 0) - ret += " cppcheckId=" + MathLib::toString(cppcheckId); + if (hash > 0) + ret += " hash=" + MathLib::toString(hash); if (ret.compare(0,1," ")==0) return ret.substr(1); return ret; @@ -364,8 +364,8 @@ void Suppressions::dump(std::ostream & out) const out << " lineNumber=\"" << suppression.lineNumber << '"'; if (!suppression.symbolName.empty()) out << " symbolName=\"" << ErrorLogger::toxml(suppression.symbolName) << '\"'; - if (suppression.cppcheckId > 0) - out << " cppcheckId=\"" << suppression.cppcheckId << '\"'; + if (suppression.hash > 0) + out << " hash=\"" << suppression.hash << '\"'; out << " />" << std::endl; } out << " " << std::endl; @@ -377,7 +377,7 @@ std::list Suppressions::getUnmatchedLocalSuppressions for (const Suppression &s : mSuppressions) { if (s.matched) continue; - if (s.cppcheckId > 0) + if (s.hash > 0) continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; @@ -394,7 +394,7 @@ std::list Suppressions::getUnmatchedGlobalSuppression for (const Suppression &s : mSuppressions) { if (s.matched) continue; - if (s.cppcheckId > 0) + if (s.hash > 0) continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; diff --git a/lib/suppressions.h b/lib/suppressions.h index 5c9873e37..a34207106 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -35,7 +35,7 @@ class CPPCHECKLIB Suppressions { public: struct CPPCHECKLIB ErrorMessage { - std::size_t cppcheckId; + std::size_t hash; std::string errorId; void setFileName(const std::string &s); const std::string &getFileName() const { @@ -49,18 +49,18 @@ public: }; struct CPPCHECKLIB Suppression { - Suppression() : lineNumber(NO_LINE), cppcheckId(0), matched(false) {} + Suppression() : lineNumber(NO_LINE), hash(0), matched(false) {} Suppression(const Suppression &other) { *this = other; } - Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), cppcheckId(0), matched(false) {} + Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), hash(0), matched(false) {} Suppression & operator=(const Suppression &other) { errorId = other.errorId; fileName = other.fileName; lineNumber = other.lineNumber; symbolName = other.symbolName; - cppcheckId = other.cppcheckId; + hash = other.hash; matched = other.matched; return *this; } @@ -74,8 +74,8 @@ public: return fileName < other.fileName; if (symbolName != other.symbolName) return symbolName < other.symbolName; - if (cppcheckId != other.cppcheckId) - return cppcheckId < other.cppcheckId; + if (hash != other.hash) + return hash < other.hash; return false; } @@ -100,7 +100,7 @@ public: std::string fileName; int lineNumber; std::string symbolName; - std::size_t cppcheckId; + std::size_t hash; bool matched; enum { NO_LINE = -1 }; From 453cd93faee782d15cd866ebadd976f2b0087903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 15:59:48 +0200 Subject: [PATCH 113/116] update warning hash calculation --- lib/errorlogger.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 9451441c4..d35e7edf9 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -137,16 +137,21 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token setmsg(msg); - hash = calculateWarningHash(list, toString(false)); + std::ostringstream hashWarning; + for (const Token *tok: callstack) + hashWarning << std::hex << (tok ? tok->index() : 0) << " "; + hashWarning << mShortMessage; + + hash = calculateWarningHash(list, hashWarning.str()); } ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenList, Severity::SeverityType severity, const char id[], const std::string &msg, const CWE &cwe, bool inconclusive) : id(id), incomplete(false), severity(severity), cwe(cwe.id), inconclusive(inconclusive) { // Format callstack - for (ErrorPath::const_iterator it = errorPath.begin(); it != errorPath.end(); ++it) { - const Token *tok = it->first; - const std::string &info = it->second; + for (const ErrorPathItem& e: errorPath) { + const Token *tok = e.first; + const std::string &info = e.second; // --errorlist can provide null values here if (tok) @@ -158,7 +163,12 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis setmsg(msg); - hash = calculateWarningHash(tokenList, toString(false)); + std::ostringstream hashWarning; + for (const ErrorPathItem &e: errorPath) + hashWarning << std::hex << (tok ? tok->index() : 0) << " "; + hashWarning << mShortMessage; + + hash = calculateWarningHash(tokenList, hashWarning.str()); } ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) From 2b968202bbc150f1ec6158749097ce713fde7bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 17:38:50 +0200 Subject: [PATCH 114/116] win_installer: include online-help in msi --- .github/workflows/release-windows.yml | 7 ++++++- win_installer/config.wxi | 1 + win_installer/cppcheck.wxs | 6 ++++++ win_installer/productInfo.wxi | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 8c30db65c..49961914f 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -50,7 +50,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - modules: 'qtcharts' + modules: 'qtcharts qthelp' - name: Create .qm run: | @@ -58,6 +58,11 @@ jobs: lupdate gui.pro lrelease gui.pro -removeidentical + - name: Create online-help + run: | + cd gui\help + qhelpgenerator online-help.qhcp -o online-help.qhc + - name: Matchcompiler run: python tools\matchcompiler.py --write-dir lib diff --git a/win_installer/config.wxi b/win_installer/config.wxi index fc95836b8..f37694229 100644 --- a/win_installer/config.wxi +++ b/win_installer/config.wxi @@ -2,6 +2,7 @@ + diff --git a/win_installer/cppcheck.wxs b/win_installer/cppcheck.wxs index 8db358f3b..8ed3271c2 100644 --- a/win_installer/cppcheck.wxs +++ b/win_installer/cppcheck.wxs @@ -55,6 +55,12 @@ + + + + + + diff --git a/win_installer/productInfo.wxi b/win_installer/productInfo.wxi index 7c910c35e..817f46048 100644 --- a/win_installer/productInfo.wxi +++ b/win_installer/productInfo.wxi @@ -21,4 +21,5 @@ + From aad455e1ea34fa47099ee6b604a1e58bfaf015ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 17:43:12 +0200 Subject: [PATCH 115/116] Fix compile error --- lib/errorlogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index d35e7edf9..081c9effb 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -165,7 +165,7 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis std::ostringstream hashWarning; for (const ErrorPathItem &e: errorPath) - hashWarning << std::hex << (tok ? tok->index() : 0) << " "; + hashWarning << std::hex << (e.first ? e.first->index() : 0) << " "; hashWarning << mShortMessage; hash = calculateWarningHash(tokenList, hashWarning.str()); From ca1fcfa8bc75c36b3b25ad91f2204a7996e2ebec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 21 Jul 2020 17:45:56 +0200 Subject: [PATCH 116/116] GUI: Only suppress warnings by hash during bug hunting --- gui/resultstree.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index ba10a94ed..d7a4bf4a6 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -661,7 +661,6 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) QAction *copy = new QAction(tr("Copy"), &menu); QAction *hide = new QAction(tr("Hide"), &menu); QAction *hideallid = new QAction(tr("Hide all with id"), &menu); - QAction *suppresshash = new QAction(tr("Suppress hash"), &menu); QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu); if (multipleSelection) { @@ -679,11 +678,15 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); menu.addAction(hide); menu.addAction(hideallid); - menu.addAction(suppresshash); + if (!bughunting) { QAction *suppress = new QAction(tr("Suppress selected id(s)"), &menu); menu.addAction(suppress); - connect(suppress, SIGNAL(triggered()), this, SLOT(suppressSelectedIds())); + connect(suppress, &QAction::triggered, this, &ResultsTree::suppressSelectedIds); + } else { + QAction *suppress = new QAction(tr("Suppress"), &menu); + menu.addAction(suppress); + connect(suppress, &QAction::triggered, this, &ResultsTree::suppressHash); } menu.addSeparator(); menu.addAction(opencontainingfolder); @@ -692,7 +695,6 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) connect(copy, SIGNAL(triggered()), this, SLOT(copy())); connect(hide, SIGNAL(triggered()), this, SLOT(hideResult())); connect(hideallid, SIGNAL(triggered()), this, SLOT(hideAllIdResult())); - connect(suppresshash, &QAction::triggered, this, &ResultsTree::suppressHash); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); const ProjectFile *currentProject = ProjectFile::getActiveProject();