From 6d28ccf9541290b4f94bae75a0ea7f31be90409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:54:39 +0200 Subject: [PATCH 01/19] Add test for #14509 (#8480) This was fixed by b599bb40488bd274d7ad2351a7f7622b05300ba5. --- test/testunusedvar.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index 9a116bdaa2b..b857844db68 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -249,6 +249,7 @@ class TestUnusedVar : public TestFixture { TEST_CASE(localvarDelete); TEST_CASE(localvarLambda); // #8941, #8948 TEST_CASE(localvarStructuredBinding); // #10368 + TEST_CASE(localvarPtrToPtr); TEST_CASE(localvarCppInitialization); TEST_CASE(localvarCpp11Initialization); @@ -6958,6 +6959,16 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void localvarPtrToPtr() { + functionVariableUsage("int main() {\n" + " int *p, **pp = &p;\n" + " int i = 123;\n" + " *pp = &i;\n" + " return *p;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void localvarCppInitialization() { functionVariableUsage("void foo() {\n" " int buf[6];\n" From 034ad1c6e02d7d0b258ed474c78eebb5fce6cb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:56:15 +0200 Subject: [PATCH 02/19] Fix #14468: Missing varid for nested struct members with designated initializers (#8466) --- lib/tokenize.cpp | 4 ++++ test/testvarid.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 34cb997a636..8ff6b972bbe 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -4496,6 +4496,10 @@ static void setVarIdStructMembers(Token *&tok1, tok->varId(it->second); } } + if (Token::Match(tok, "%name% = { . %name% =|{")) { + setVarIdStructMembers(tok, structMembers, varId); + tok = tok->linkAt(2); + } tok = tok->next(); } diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 007e16ffb29..64b7b6664be 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -4500,7 +4500,7 @@ class TestVarID : public TestFixture { "2: struct T { struct S s ; } ;\n" "3: struct U { struct T t ; } ;\n" "4: void f ( ) {\n" - "5: struct U u@4 ; u@4 = { .@UNIQUE t@5 = { . s = { . s = 1 } } } ;\n" + "5: struct U u@4 ; u@4 = { .@UNIQUE t@5 = { .@UNIQUE s@6 = { .@UNIQUE s@7 = 1 } } } ;\n" "6: }\n"; ASSERT_EQUALS(exp, tokenizeExpr(code)); } From e9d97293b48ea27b30de30afc5a4ed2039b66587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:56:30 +0200 Subject: [PATCH 03/19] Fix #14572: GUI: refresh warning details when rechecking code (#8479) --- gui/resultsview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index 698b6e82018..496c0793157 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -123,6 +123,7 @@ void ResultsView::clear(bool results) } mUI->mDetails->setText(QString()); + mUI->mCode->clear(); mStatistics->clear(); delete mCheckSettings; From cf3f0fac90cb21981f1e0ddb0bf9948e86abbb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:57:56 +0200 Subject: [PATCH 04/19] Fix #14676: Syntax error: qRow<> variable template (#8465) --- lib/tokenize.cpp | 2 +- test/testgarbage.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 8ff6b972bbe..0efc4e5bb7a 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9004,7 +9004,7 @@ void Tokenizer::findGarbageCode() const syntaxError(tok); if (Token::Match(tok, "[;([{] %comp%|%oror%|%or%|%|/")) syntaxError(tok); - if (Token::Match(tok, "%cop%|= ]") && !Token::simpleMatch(tok, "*") && !(cpp && Token::Match(tok->previous(), "%type%|[|,|%num% &|=|> ]"))) + if (Token::Match(tok, "%cop%|= ]") && !Token::simpleMatch(tok, "*") && !(cpp && Token::Match(tok->previous(), "<|%type%|[|,|%num% &|=|> ]"))) syntaxError(tok); if (Token::Match(tok, "[+-] [;,)]}]") && !(cpp && Token::simpleMatch(tok->previous(), "operator"))) syntaxError(tok); diff --git a/test/testgarbage.cpp b/test/testgarbage.cpp index 5f6b67867f7..68715b78ae8 100644 --- a/test/testgarbage.cpp +++ b/test/testgarbage.cpp @@ -320,7 +320,6 @@ class TestGarbage : public TestFixture { void final_class_x() { - const char code[] = "class __declspec(dllexport) x final { };"; SimpleTokenizer tokenizer(settings, *this); ASSERT(tokenizer.tokenize(code)); @@ -1897,6 +1896,12 @@ class TestGarbage : public TestFixture { // #13892 ASSERT_NO_THROW(checkCode("void foovm(int x[const *]);")); + + // #14676 + ASSERT_NO_THROW(checkCode("int main() {\n" + " auto value = m[1 + qRow<>];\n" + "}\n")); + ignore_errout(); } }; From 58cbb3331cb967423b200e798ff80687bd807efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:58:45 +0200 Subject: [PATCH 05/19] Fix #14590: Missing id in DumpData type-info (#8451) --- lib/tokenize.cpp | 22 ++++++++++++++++------ lib/tokenize.h | 1 + test/cli/other_test.py | 4 ++-- test/testsimplifytypedef.cpp | 8 ++++---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 0efc4e5bb7a..e173aabc30d 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -1115,14 +1115,18 @@ void Tokenizer::simplifyTypedef() syntaxError(t.second.getTypedefToken()); } else { const Token* const typedefToken = t.second.getTypedefToken(); + const Token* const nameToken = t.second.nameToken(); TypedefInfo typedefInfo; typedefInfo.name = t.second.name(); - typedefInfo.filename = list.file(typedefToken); - typedefInfo.lineNumber = typedefToken->linenr(); - typedefInfo.column = typedefToken->column(); - if (Token::Match(typedefToken->next(), "struct|enum|class|union %name% {") && typedefToken->strAt(2) == typedefInfo.name) { - typedefInfo.tagLine = typedefToken->tokAt(2)->linenr(); - typedefInfo.tagColumn = typedefToken->tokAt(2)->column(); + typedefInfo.filename = list.file(nameToken); + typedefInfo.lineNumber = nameToken->linenr(); + typedefInfo.column = nameToken->column(); + if (Token::Match(typedefToken->next(), "struct|enum|class|union %name% {")) { + typedefInfo.originalName = typedefToken->strAt(2); + if (typedefToken->strAt(2) == typedefInfo.name) { + typedefInfo.tagLine = typedefToken->tokAt(2)->linenr(); + typedefInfo.tagColumn = typedefToken->tokAt(2)->column(); + } } typedefInfo.used = t.second.isUsed(); typedefInfo.isFunctionPointer = isFunctionPointer(t.second.nameToken()); @@ -6380,6 +6384,12 @@ std::string Tokenizer::dumpTypedefInfo() const outs += typedefInfo.name; outs += "\""; + if (!typedefInfo.originalName.empty()) { + outs += " originalName=\""; + outs += typedefInfo.originalName; + outs += "\""; + } + outs += " file=\""; outs += ErrorLogger::toxml(typedefInfo.filename); outs += "\""; diff --git a/lib/tokenize.h b/lib/tokenize.h index 9ee4b29466f..16cd88fa30e 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -698,6 +698,7 @@ class CPPCHECKLIB Tokenizer { }; struct TypedefInfo { std::string name; + std::string originalName; std::string filename; int lineNumber; int column; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index e28a7305ce9..d10cb541e5b 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -420,7 +420,7 @@ def test_addon_misra(tmpdir): assert lines == [ 'Checking {} ...'.format(test_file) ] - assert stderr == '{}:2:1: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]\ntypedef int MISRA_5_6_VIOLATION;\n^\n'.format(test_file) + assert stderr == '{}:2:13: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]\ntypedef int MISRA_5_6_VIOLATION;\n ^\n'.format(test_file) def test_addon_y2038(tmpdir): @@ -2674,7 +2674,7 @@ def __test_addon_suppr(tmp_path, extra_args): assert exitcode == 0, stdout assert stdout == '' assert stderr.splitlines() == [ - '{}:4:1: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]'.format(test_file), + '{}:4:13: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]'.format(test_file), ] diff --git a/test/testsimplifytypedef.cpp b/test/testsimplifytypedef.cpp index 092c9e9df75..342cda59dc8 100644 --- a/test/testsimplifytypedef.cpp +++ b/test/testsimplifytypedef.cpp @@ -4615,7 +4615,7 @@ class TestSimplifyTypedef : public TestFixture { void typedefInfo1() { const std::string xml = dumpTypedefInfo("typedef int A;\nA x;"); ASSERT_EQUALS(" \n" - " \n" + " \n" " \n", xml); } @@ -4627,7 +4627,7 @@ class TestSimplifyTypedef : public TestFixture { " typedef fp16 ( *pfp16 ) ( void );\n" "}\n"); ASSERT_EQUALS(" \n" - " \n" + " \n" " \n" " \n" " \n" @@ -4639,7 +4639,7 @@ class TestSimplifyTypedef : public TestFixture { " \n" " \n" " \n" - " \n" + " \n" " \n" " \n" " \n" @@ -4677,7 +4677,7 @@ class TestSimplifyTypedef : public TestFixture { "} coord;\n" "coord c;"); ASSERT_EQUALS(" \n" - " \n" + " \n" " \n", xml); } }; From 92d9735221527dfa456a018fe26bb586fc1e0f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 25 Apr 2026 16:59:41 +0200 Subject: [PATCH 06/19] Fix #14637: FP unassignedVariable with reference to array (#8441) --- lib/checkunusedvar.cpp | 4 +++- test/testunusedvar.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 04532e1ecb9..6dd98de078e 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -565,7 +565,7 @@ static const Token* doAssignment(Variables &variables, const Token *tok, bool de variables.readAll(varid2, tok); } } - } else if (var1->mType == Variables::reference) { + } else if (var1->mType == Variables::reference || var1->mType == Variables::referenceArray) { variables.alias(varid1, varid2, true); } else if (var1->mType == Variables::standard && addressOf) { variables.alias(varid1, varid2, true); @@ -738,6 +738,8 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const if (type == Variables::none || isPartOfClassStructUnion(i->typeStartToken())) continue; const Token* defValTok = i->nameToken()->next(); + if (Token::Match(i->nameToken()->previous(), "& %var% )")) + defValTok = defValTok->next(); while (defValTok && defValTok->str() == "[") defValTok = defValTok->link()->next(); if (Token::simpleMatch(defValTok, ") (")) diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index b857844db68..13d288a3fdd 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -198,6 +198,7 @@ class TestUnusedVar : public TestFixture { TEST_CASE(localvarreturn); // ticket #9167 TEST_CASE(localvarmaybeunused); TEST_CASE(localvarrvalue); // ticket #13977 + TEST_CASE(localvarreferencearray); // ticket #14637 TEST_CASE(localvarthrow); // ticket #3687 @@ -6558,6 +6559,16 @@ class TestUnusedVar : public TestFixture { ASSERT_EQUALS("[test.cpp:3:21]: (style) Variable 'm' is assigned a value that is never used. [unreadVariable]\n", errout_str()); } + void localvarreferencearray() { // ticket #14637 + functionVariableUsage("int f() {\n" + " int a[1];\n" + " int(&r)[1] = a;\n" + " r[0] = 0;\n" + " return r[0];\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void localvarthrow() { // ticket #3687 functionVariableUsage("void foo() {\n" " try {}" From bafada0751cdcf780d4a1f654a5f846fa7c764f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 25 Apr 2026 18:26:12 +0200 Subject: [PATCH 07/19] Fix #14688 (test-my-pr.py: run against the debian ftp server directly) (#8491) --- tools/test-my-pr.py | 135 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/tools/test-my-pr.py b/tools/test-my-pr.py index 4a40f544ee1..e0ed773afef 100755 --- a/tools/test-my-pr.py +++ b/tools/test-my-pr.py @@ -8,9 +8,13 @@ import donate_cpu_lib as lib import argparse import glob +import gzip +import natsort import os import sys import random +import re +import requests import subprocess @@ -20,6 +24,100 @@ def format_float(a, b=1): return 'N/A' +def ftp_get(url): + try: + response = requests.get(url, timeout=300) + response.raise_for_status() + return response.content + except requests.RequestException as err: + print('Failed to fetch {}: {}'.format(url, err)) + return None + + +def latestvername(names): + s = natsort.natsorted(names, key=lambda x: x[x.index('_')+1:x.index('.orig.tar')]) + return s[-1] + + +def getpackages(): + debian = 'https://ftp.debian.org/debian/' + + data = ftp_get(debian + 'ls-lR.gz') + if data is None: + print('Failed to fetch ls-lR.gz') + sys.exit(1) + + lines = gzip.decompress(data).decode('utf-8', errors='replace').splitlines() + + # Example content in ls-lR: + #./pool/main/0/0xffff: + #total 1452 + #-rw-r--r-- 2 dak debadmin 6524 Dec 25 2016 0xffff_0.7-2.debian.tar.xz + #-rw-r--r-- 2 dak debadmin 1791 Dec 25 2016 0xffff_0.7-2.dsc + #-rw-r--r-- 2 dak debadmin 57168 Dec 25 2016 0xffff_0.7-2_amd64.deb + #-rw-r--r-- 2 dak debadmin 48578 Dec 26 2016 0xffff_0.7-2_arm64.deb + #-rw-r--r-- 2 dak debadmin 56730 Dec 26 2016 0xffff_0.7-2_armel.deb + #-rw-r--r-- 2 dak debadmin 57296 Dec 26 2016 0xffff_0.7-2_armhf.deb + #-rw-r--r-- 2 dak debadmin 60254 Dec 26 2016 0xffff_0.7-2_i386.deb + #-rw-r--r-- 2 dak debadmin 53130 Dec 26 2016 0xffff_0.7-2_mips.deb + #-rw-r--r-- 2 dak debadmin 52542 Dec 26 2016 0xffff_0.7-2_mips64el.deb + #-rw-r--r-- 2 dak debadmin 53712 Dec 26 2016 0xffff_0.7-2_mipsel.deb + #-rw-r--r-- 2 dak debadmin 51908 Dec 26 2016 0xffff_0.7-2_ppc64el.deb + #-rw-r--r-- 2 dak debadmin 53548 Dec 26 2016 0xffff_0.7-2_s390x.deb + #-rw-r--r-- 2 dak debadmin 65248 Dec 25 2016 0xffff_0.7.orig.tar.gz + #-rw-r--r-- 2 dak debadmin 6884 Jul 19 19:08 0xffff_0.8-1.debian.tar.xz + #-rw-r--r-- 2 dak debadmin 1807 Jul 19 19:08 0xffff_0.8-1.dsc + #-rw-r--r-- 2 dak debadmin 58908 Jul 19 19:08 0xffff_0.8-1_amd64.deb + #-rw-r--r-- 2 dak debadmin 51340 Jul 19 19:58 0xffff_0.8-1_arm64.deb + #-rw-r--r-- 2 dak debadmin 57612 Jul 19 20:13 0xffff_0.8-1_armel.deb + #-rw-r--r-- 2 dak debadmin 58584 Jul 19 19:58 0xffff_0.8-1_armhf.deb + #-rw-r--r-- 2 dak debadmin 57544 Jul 19 20:23 0xffff_0.8-1_hurd-i386.deb + #-rw-r--r-- 2 dak debadmin 62048 Jul 19 23:54 0xffff_0.8-1_i386.deb + #-rw-r--r-- 2 dak debadmin 55080 Jul 23 19:07 0xffff_0.8-1_kfreebsd-amd64.deb + #-rw-r--r-- 2 dak debadmin 58392 Jul 23 19:07 0xffff_0.8-1_kfreebsd-i386.deb + #-rw-r--r-- 2 dak debadmin 54144 Jul 19 22:28 0xffff_0.8-1_mips.deb + #-rw-r--r-- 2 dak debadmin 53648 Jul 20 00:56 0xffff_0.8-1_mips64el.deb + #-rw-r--r-- 2 dak debadmin 54740 Jul 19 22:58 0xffff_0.8-1_mipsel.deb + #-rw-r--r-- 2 dak debadmin 57424 Jul 19 19:58 0xffff_0.8-1_ppc64el.deb + #-rw-r--r-- 2 dak debadmin 53764 Jul 19 22:28 0xffff_0.8-1_s390x.deb + #-rw-r--r-- 2 dak debadmin 64504 Jul 19 19:08 0xffff_0.8.orig.tar.gz + # + + path = None + previous_path = '' + archives = [] + filename = None + filenames = [] + for line in lines: + line = line.strip() + if len(line) < 4: + if filename: + res1 = re.match(r'(.*)-[0-9.]+$', path) + if res1 is None: + res1 = re.match(r'(.*)[-.][0-9.]+$', path) + res2 = re.match(r'(.*)-[0-9.]+$', previous_path) + if res2 is None: + res2 = re.match(r'(.*)[-.][0-9.]+$', previous_path) + if res1 is None or res2 is None or res1.group(1) != res2.group(1): + archives.append(path + '/' + latestvername(filenames)) + else: + archives[-1] = path + '/' + latestvername(filenames) + if path: + previous_path = path + path = None + filename = None + filenames = [] + elif line.startswith('./pool/main/'): + path = debian + line[2:-1] + elif path and line.endswith(('.orig.tar.gz', '.orig.tar.bz2', '.orig.tar.xz')): + filename = line[1 + line.rfind(' '):] + filenames.append(filename) + + return archives + + + + if __name__ == "__main__": __my_script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] __work_path = os.path.expanduser(os.path.join('~', 'cppcheck-' + __my_script_name + '-workfolder')) @@ -40,6 +138,16 @@ def format_float(a, b=1): print(args) + if args.packages_path: + # You can download packages using daca2-download.py + args.packages = glob.glob(os.path.join(args.packages_path, '*.tar.xz')) + random.shuffle(args.packages) + elif args.packages is None: + args.packages = getpackages() + random.shuffle(args.packages) + + print('\n'.join(args.packages[:20])) + if not lib.check_requirements(): print("Error: Check requirements") sys.exit(1) @@ -100,35 +208,14 @@ def format_float(a, b=1): print('Failed to compile your version of Cppcheck') sys.exit(1) - if args.packages_path: - # You can download packages using daca2-download.py - args.packages = glob.glob(os.path.join(args.packages_path, '*.tar.xz')) - args.p = len(args.packages) - packages_idxs = list(range(args.p)) - random.shuffle(packages_idxs) - elif args.packages: - args.p = len(args.packages) - packages_idxs = [] - else: - packages_count = lib.get_packages_count() - if not packages_count: - print("network or server might be temporarily down..") - sys.exit(1) - - packages_idxs = list(range(packages_count)) - random.shuffle(packages_idxs) - packages_processed = 0 crashes = [] timeouts = [] - while (packages_processed < args.p and len(packages_idxs) > 0) or args.packages: - if args.packages: - package = args.packages.pop() - else: - package = lib.get_package(packages_idxs.pop()) + while packages_processed < args.p and args.packages: + package = args.packages.pop() - if package.startswith('ftp://') or package.startswith('http://'): + if package.startswith('ftp://') or package.startswith('https://'): tgz = lib.download_package(work_path, package, None) if tgz is None: print("No package downloaded") From 4e0a5688cefb0dae05acd955bc53f12ff82992f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Berder?= <18538310+francois-berder@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:26:32 +0200 Subject: [PATCH 08/19] Fix #14369: Move boolean literal evaluation to valueFlowSetConstantValue (#8468) Previously, boolean literals were processed in a separate loop after valueFlowSetConstantValue. This triggered nullPointer FP with code such as: int f() { const int* p = true ? new int() : nullptr; return *p; // nullPointer FP } Because the condition token had no known value, both branches of the ternary operator were treated as possible, leaking a spurious null value. --------- Signed-off-by: Francois Berder --- lib/valueflow.cpp | 7 +------ lib/vf_common.cpp | 5 +++++ test/testvalueflow.cpp | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 74763fde027..0f1c8a20ce7 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -430,12 +430,7 @@ static void valueFlowNumber(TokenList &tokenlist, const Settings& settings) if (tokenlist.isCPP() || settings.standards.c >= Standards::C23) { for (Token *tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->isName() && !tok->varId() && Token::Match(tok, "%bool%")) { - ValueFlow::Value value(tok->str() == "true"); - if (!tok->isTemplateArg()) - value.setKnown(); - setTokenValue(tok, std::move(value), settings); - } else if (Token::Match(tok, "[(,] NULL [,)]")) { + if (Token::Match(tok, "[(,] NULL [,)]")) { // NULL function parameters are not simplified in the // normal tokenlist ValueFlow::Value value(0); diff --git a/lib/vf_common.cpp b/lib/vf_common.cpp index ab01acc0f16..d6c6088b34b 100644 --- a/lib/vf_common.cpp +++ b/lib/vf_common.cpp @@ -154,6 +154,11 @@ namespace ValueFlow if (!tok->isTemplateArg()) value.setKnown(); setTokenValue(tok, std::move(value), settings); + } else if ((tok->isCpp() || settings.standards.c >= Standards::C23) && (tok->isName() && tok->varId() == 0 && Token::Match(tok, "%bool%"))) { + Value value(tok->str() == "true"); + if (!tok->isTemplateArg()) + value.setKnown(); + setTokenValue(tok, std::move(value), settings); } else if (Token::simpleMatch(tok, "sizeof (")) { if (tok->next()->astOperand2() && !tok->next()->astOperand2()->isLiteral() && tok->next()->astOperand2()->valueType() && (tok->next()->astOperand2()->valueType()->pointer == 0 || // <- TODO this is a bailout, abort when there are array->pointer conversions diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index b8b31e8d248..d8a5b533bf3 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -1207,6 +1207,16 @@ class TestValueFlow : public TestFixture { ASSERT_EQUALS(1U, values.size()); ASSERT_EQUALS(123, values.empty() ? 0 : values.front().intvalue); + code = "x = true ? 2 : 3;\n"; // #14369 + values = tokenValues(code, "?"); + ASSERT_EQUALS(1U, values.size()); + ASSERT_EQUALS(2, values.empty() ? 0 : values.front().intvalue); + + code = "x = false ? 2 : 3;\n"; // #14369 + values = tokenValues(code, "?"); + ASSERT_EQUALS(1U, values.size()); + ASSERT_EQUALS(3, values.empty() ? 0 : values.front().intvalue); + code = "int f() {\n" " const int i = 1;\n" " int x = i < 0 ? 0 : 1;\n" From 950567e8f7c1b9eaa749a96d6c985b990eebbc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 25 Apr 2026 20:19:59 +0200 Subject: [PATCH 09/19] Fix #14672 (GUI: cfg files in current folder are not shown) (#8455) --- gui/manualtest/projectfiledialog.md | 11 +++++++++-- gui/projectfile.cpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gui/manualtest/projectfiledialog.md b/gui/manualtest/projectfiledialog.md index eb76109e53e..f11b030f246 100644 --- a/gui/manualtest/projectfiledialog.md +++ b/gui/manualtest/projectfiledialog.md @@ -4,8 +4,7 @@ Some manual testing in the project file dialog interface - -## Test: Platform files +## Test: Platform file pic8.xml Ticket: #14489 @@ -14,6 +13,14 @@ EXPECTED: In the project file dialog it should be possible to select xml files i TODO: can this test be automated +## Test: Custom cfg file + +Ticket: #14672 + +1. Copy addons/avr.cfg to a file "aa.cfg" in same folder as a cppcheck GUI project file + +EXPECTED: It should not be possible to activate "aa.cfg" in the project file dialog, it should appear in alphabetical order. + ## Test: Misra C checkbox diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index d2b468443fa..a77c1a0fc94 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -1207,5 +1207,5 @@ QStringList ProjectFile::getSearchPaths(const QString& dir) const { const QString applicationFilePath = QCoreApplication::applicationFilePath(); const QString appPath = QFileInfo(applicationFilePath).canonicalPath(); const QString datadir = getDataDir(); - return getSearchPaths(inf.canonicalFilePath(), appPath, datadir, dir); + return getSearchPaths(inf.canonicalPath(), appPath, datadir, dir); } From 3c377a9faab86c03ae746c13cecee6c253f3d5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 25 Apr 2026 22:45:23 +0200 Subject: [PATCH 10/19] Fix #14687 (Move repository from danmar to cppcheck-opensource) (#8486) --- .github/workflows/cifuzz.yml | 2 +- .github/workflows/coverity.yml | 2 +- .github/workflows/iwyu.yml | 4 ++-- .github/workflows/release-windows.yml | 2 +- .github/workflows/selfcheck.yml | 2 +- CONTRIBUTING.md | 6 +++--- addons/README.md | 20 ++++++++++---------- createrelease | 4 ++-- htmlreport/setup.py | 2 +- man/manual-ja.docbook | 4 ++-- man/manual-premium.md | 6 +++--- man/manual.md | 8 ++++---- readme.md | 6 +++--- readmeja.md | 2 +- releasenotes.txt | 2 +- tools/bisect/bisect.sh | 2 +- tools/donate_cpu_lib.py | 6 +++--- 17 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 7b462c688f0..c54d47da4f1 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -10,7 +10,7 @@ permissions: jobs: Fuzzing: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} steps: - name: Build Fuzzers id: build diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 3c07b61d7c7..006160c7779 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -12,7 +12,7 @@ permissions: jobs: scan: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index ed6971128f5..05d5643bdf7 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -30,7 +30,7 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} container: image: ${{ matrix.image }} @@ -192,7 +192,7 @@ jobs: fail-fast: false runs-on: ubuntu-22.04 - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} env: QT_VERSION: 6.10.0 diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 607c8434baa..3b5036ff29e 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -22,7 +22,7 @@ jobs: build: runs-on: windows-2025 - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} env: PYTHON_VERSION: 3.14 diff --git a/.github/workflows/selfcheck.yml b/.github/workflows/selfcheck.yml index 389e39371ba..cfd1107e92d 100644 --- a/.github/workflows/selfcheck.yml +++ b/.github/workflows/selfcheck.yml @@ -172,7 +172,7 @@ jobs: - name: Fetch corpus run: | - wget https://github.com/danmar/cppcheck/archive/refs/tags/2.8.tar.gz + wget https://github.com/cppcheck-opensource/cppcheck/archive/refs/tags/2.8.tar.gz tar xvf 2.8.tar.gz - name: CMake (corpus / no test) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 715a913b6fc..09e88f021e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ These are some guidelines *any* contributor should follow. They will help to mak ## Code Changes -Code contributions are handled via GitHub pull requests: https://github.com/danmar/cppcheck/pulls. +Code contributions are handled via GitHub pull requests: https://github.com/cppcheck-opensource/cppcheck/pulls. If you file a pull request you might not get a reply immediately. We are a very small team and it might not fit in the current scope or time. @@ -18,7 +18,7 @@ Also after you filed a pull request please be ready to reply to questions and fe Please do not be discouraged if your change was rejected or if the review process might not have been as smooth as it could have been. -Each change should be accompanied with a unit ([C++](https://github.com/danmar/cppcheck/tree/main/test)) or integration ([Python](https://github.com/danmar/cppcheck/tree/main/test/cli)) test to ensure that it doesn't regress with future changes. Negative tests (testing the opposite behavior) would be favorable but might not be required or might already exist depending on the change. Tests which introduce `TODO_ASSERT_` or `@pytest.mark.skip`/`@pytest.mark.xfail` should have tickets filed. +Each change should be accompanied with a unit ([C++](https://github.com/cppcheck-opensource/cppcheck/tree/main/test)) or integration ([Python](https://github.com/cppcheck-opensource/cppcheck/tree/main/test/cli)) test to ensure that it doesn't regress with future changes. Negative tests (testing the opposite behavior) would be favorable but might not be required or might already exist depending on the change. Tests which introduce `TODO_ASSERT_` or `@pytest.mark.skip`/`@pytest.mark.xfail` should have tickets filed. If the change is modifying existing behavior (i.e. adding a feature or fixing a bug) it should be accompanied by an issue in the [tracker](https://trac.cppcheck.net) (if you do not have access we can assist with that). Depending on the change it might also warrant an entry in `releasenotes.txt`. @@ -56,7 +56,7 @@ So if you start spending a lot of time on these, you might want to get into touc ## simplecpp -At its core Cppcheck is relying on the `simplecpp` library which is a preprocessor implementation which was spun off into its [separate project](https://github.com/danmar/simplecpp) with its own [bug tracker](https://github.com/danmar/simplecpp/issues). This is also maintained by the Cppcheck developers and contributions to it are also welcome. +At its core Cppcheck is relying on the `simplecpp` library which is a preprocessor implementation which was spun off into its [separate project](https://github.com/cppcheck-opensource/simplecpp) with its own [bug tracker](https://github.com/cppcheck-opensource/simplecpp/issues). This is also maintained by the Cppcheck developers and contributions to it are also welcome. ## Translations diff --git a/addons/README.md b/addons/README.md index 8df8229b970..05502f547b6 100644 --- a/addons/README.md +++ b/addons/README.md @@ -4,19 +4,19 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with ## Supported addons -+ [misra.py](https://github.com/danmar/cppcheck/blob/main/addons/misra.py) - Used to verify compliance with MISRA C 2012 - a proprietary set of guidelines to avoid such questionable code, developed for embedded systems. Since this standard is proprietary, cppcheck does not display error text by specifying only the number of violated rules (for example, [c2012-21.3]). If you want to display full texts for violated rules, you will need to create a text file containing MISRA rules, which you will have to pass when calling the script with `--rule-texts` key. Some examples of rule texts files available in [tests directory](https://github.com/danmar/cppcheck/blob/main/addons/test/misra/). -+ [y2038.py](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) - Checks code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. See complete description [here](https://github.com/danmar/cppcheck/blob/main/addons/doc/y2038.md). -+ [threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) ++ [misra.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misra.py) + Used to verify compliance with MISRA C 2012 - a proprietary set of guidelines to avoid such questionable code, developed for embedded systems. Since this standard is proprietary, cppcheck does not display error text by specifying only the number of violated rules (for example, [c2012-21.3]). If you want to display full texts for violated rules, you will need to create a text file containing MISRA rules, which you will have to pass when calling the script with `--rule-texts` key. Some examples of rule texts files available in [tests directory](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/test/misra/). ++ [y2038.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) + Checks code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. See complete description [here](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/doc/y2038.md). ++ [threadsafety.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) Analyse Cppcheck dump files to locate threadsafety issues like static local objects used by multiple threads. -+ [naming.py](https://github.com/danmar/cppcheck/blob/main/addons/naming.py) ++ [naming.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/naming.py) Enforces naming conventions across the code. -+ [namingng.py](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) ++ [namingng.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) Enforces naming conventions across the code. Enhanced version with support for type prefixes in variable and function names. -+ [findcasts.py](https://github.com/danmar/cppcheck/blob/main/addons/findcasts.py) ++ [findcasts.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/findcasts.py) Locates casts in the code. -+ [misc.py](https://github.com/danmar/cppcheck/blob/main/addons/misc.py) ++ [misc.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misc.py) Performs miscellaneous checks. ### Other files @@ -68,5 +68,5 @@ This allows you to add additional parameters when calling the script (for exampl When using the graphical interface `cppcheck-gui`, the selection and configuration of addons is carried out on the tab `Addons and tools` in the project settings (`Edit Project File`): -![Screenshot](https://raw.githubusercontent.com/danmar/cppcheck/main/addons/doc/img/cppcheck-gui-addons.png) +![Screenshot](https://raw.githubusercontent.com/cppcheck-opensource/cppcheck/main/addons/doc/img/cppcheck-gui-addons.png) diff --git a/createrelease b/createrelease index 9dd79333119..de85d64395b 100755 --- a/createrelease +++ b/createrelease @@ -52,7 +52,7 @@ # Create 2.18.x branch # git checkout -b 2.18.x ; git push -u origin 2.18.x # in fork: -# * add upstream: git remote add upstream git@github.com:danmar/cppcheck.git +# * add upstream: git remote add upstream git@github.com:/cppcheck-opensource//cppcheck.git # * add branch: git fetch upstream 2.19.x # # Release notes: @@ -124,7 +124,7 @@ # * daca: cd /var && nice tar -cJf ~/daca.tar.xz daca@home # * git: git checkout -f && git checkout main && git pull && tar -cJf git.tar.xz .git # * git log 2.16.0..2.17.0 > Changelog -# * mkdir out && python3 ~/cppchecksolutions/release/getWorkflowAndIssueLogs.py -r danmar/cppcheck -t 2.15.0 -p out +# * mkdir out && python3 ~/cppchecksolutions/release/getWorkflowAndIssueLogs.py -r /cppcheck-opensource//cppcheck -t 2.15.0 -p out # Folder/tag to use folder=$1 diff --git a/htmlreport/setup.py b/htmlreport/setup.py index 3e049181ce6..9f85000a090 100755 --- a/htmlreport/setup.py +++ b/htmlreport/setup.py @@ -16,7 +16,7 @@ ), long_description=readme, author="Cppcheck Team", - url="https://github.com/danmar/cppcheck", + url="https://github.com/cppcheck-opensource/cppcheck", license="GPL", packages=find_packages(exclude=("tests", "docs")), use_scm_version={"root": ".."}, diff --git a/man/manual-ja.docbook b/man/manual-ja.docbook index 1c13d3d0526..0b2a39cc59e 100644 --- a/man/manual-ja.docbook +++ b/man/manual-ja.docbook @@ -1702,11 +1702,11 @@ Checking unusedvar.cpp... - Cppcheck プロジェクトはいくつかのアドオンを以下の場所で提供しています。: http://github.com/danmar/cppcheck/blob/master/addons + Cppcheck プロジェクトはいくつかのアドオンを以下の場所で提供しています。: http://github.com/cppcheck-opensource/cppcheck/blob/master/addons - ublinterは規格で定義されていない未定義動作に注力した"lint"です。: http://github.com/danmar/ublinter + ublinterは規格で定義されていない未定義動作に注力した"lint"です。: http://github.com/cppcheck-opensource/ublinter diff --git a/man/manual-premium.md b/man/manual-premium.md index 8acfb3c0130..4c30dc6d4b3 100644 --- a/man/manual-premium.md +++ b/man/manual-premium.md @@ -1044,7 +1044,7 @@ Cppcheck is distributed with a few addons which are listed below. ### `namingng.py` -[`namingng.py`](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. +[`namingng.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. You need to have a configuration file that defines your naming conventions. By default the filename `namingng.config.json` is used but there is an option so you can use any filename you want. @@ -1070,11 +1070,11 @@ Example configuration of naming conventions: ### `threadsafety.py` -[`threadsafety.py`](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. +[`threadsafety.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. ### `y2038.py` -[`y2038.py`](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. +[`y2038.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. ## Running addons diff --git a/man/manual.md b/man/manual.md index de59afe562c..8d8d5b22350 100644 --- a/man/manual.md +++ b/man/manual.md @@ -1045,7 +1045,7 @@ Cppcheck is distributed with a few addons which are listed below. ### `misra.py` -[`misra.py`](https://github.com/danmar/cppcheck/blob/main/addons/misra.py) is used to verify compliance with MISRA C 2012, a proprietary set of guidelines to avoid questionable code, developed for embedded systems. +[`misra.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misra.py) is used to verify compliance with MISRA C 2012, a proprietary set of guidelines to avoid questionable code, developed for embedded systems. The `misra.py` script does not provide rule texts, those should be downloaded from [MISRA](https://gitlab.com/MISRA/MISRA-C/MISRA-C-2012/tools). @@ -1067,7 +1067,7 @@ Misra checkers in open source Cppcheck only cover MISRA rules partially and for ### `namingng.py` -[`namingng.py`](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. +[`namingng.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. You need to have a configuration file that defines your naming conventions. By default the filename `namingng.config.json` is used but there is an option so you can use any filename you want. @@ -1093,11 +1093,11 @@ Example configuration of naming conventions: ### `threadsafety.py` -[`threadsafety.py`](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. +[`threadsafety.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. ### `y2038.py` -[`y2038.py`](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. +[`y2038.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. ## Running addons diff --git a/readme.md b/readme.md index 7bacadd3456..25db9d2513e 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ |release-windows|OSS-Fuzz|Coverity Scan Build Status|include-what-you-use|License| |:--:|:--:|:--:|:--:|:--:| -|[![release-windows](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml/badge.svg?branch=main)](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml)|[![OSS-Fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/cppcheck.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:cppcheck)|[![Coverity Scan Build Status](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512)|[![include-what-you-use](https://github.com/danmar/cppcheck/actions/workflows/iwyu.yml/badge.svg?branch=main)](https://github.com/danmar/cppcheck/actions/workflows/iwyu.yml)|[![License](https://img.shields.io/badge/license-GPL3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)| +|[![release-windows](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml/badge.svg?branch=main)](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml)|[![OSS-Fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/cppcheck.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:cppcheck)|[![Coverity Scan Build Status](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512)|[![include-what-you-use](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/iwyu.yml/badge.svg?branch=main)](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/iwyu.yml)|[![License](https://img.shields.io/badge/license-GPL3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)| ## About the name @@ -263,7 +263,7 @@ Besides building yourself on the platform of your choice there are also several Official packages are maintained by the Cppcheck team. * (Windows) An official Windows installer is available via the official Cppcheck SourceForge page: . -* (Windows) Official builds of the current development versions are available via the [release-windows](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml) workflow. They are built nightly for the `main` branch and for each commit for release branches. As these are development versions please refrain from using these in production environments! +* (Windows) Official builds of the current development versions are available via the [release-windows](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml) workflow. They are built nightly for the `main` branch and for each commit for release branches. As these are development versions please refrain from using these in production environments! * A portable package (i.e. does not require installation) is available as the `portable` artifact. This is still a work-in-progress - see for details. * An installer is available via the `installer` artifact. * (Multi-Platform) A premium version with additional features provided by the original author of Cppcheck is available for purchase via . @@ -274,7 +274,7 @@ Third-party packages are ***not*** maintained by the Cppcheck team but their res *Note:* The following list is purely informational and listed in no particular order. -*Note:* Please always try to obtain the package from the primary official source of your operating system/distro first and make sure you are getting the latest released/tagged version (see ). Some packages might not carry the latest patch version though. +*Note:* Please always try to obtain the package from the primary official source of your operating system/distro first and make sure you are getting the latest released/tagged version (see ). Some packages might not carry the latest patch version though. *Note:* Some issues might be related to additional patches carried by the builds in these packages or by the packaging itself. Please try to verify the issue with an official build before reporting it upstream. Otherwise you might need toreport it to the respective maintainer of the package. diff --git a/readmeja.md b/readmeja.md index 6d9dae60c95..7299d61745c 100644 --- a/readmeja.md +++ b/readmeja.md @@ -2,7 +2,7 @@ | Linux ビルド状態 | Windows ビルド状態 | Coverity Scan Build 状態 | |:--:|:--:|:--:| -| [![Linux ビルド状態](https://img.shields.io/travis/danmar/cppcheck/master.svg?label=Linux%20build)](https://travis-ci.org/danmar/cppcheck) | [![Windows ビルド状態](https://img.shields.io/appveyor/ci/danmar/cppcheck/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/danmar/cppcheck/branch/master) | [![Coverity Scan Build 状態](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512) | +| [![Linux ビルド状態](https://img.shields.io/travis/cppcheck-opensource/cppcheck/master.svg?label=Linux%20build)](https://travis-ci.org/cppcheck-opensource/cppcheck) | [![Windows ビルド状態](https://img.shields.io/appveyor/ci/cppcheck-opensource/cppcheck/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/cppcheck-opensource/cppcheck/branch/master) | [![Coverity Scan Build 状態](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512) | ## 名前について diff --git a/releasenotes.txt b/releasenotes.txt index 38a9e1a41ae..d2ff9c94044 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -23,5 +23,5 @@ Infrastructure & dependencies: Other: - Make it possible to specify the regular expression engine using the `engine` element in a rule XML. - Added CLI option `--exitcode-suppress` to specify an error ID which should not result in a non-zero exitcode. -- +- Moved source code from https://github.com/danmar/cppcheck to https://github.com/cppcheck-opensource/cppcheck - diff --git a/tools/bisect/bisect.sh b/tools/bisect/bisect.sh index cf0d4cd5b96..067cd9606aa 100644 --- a/tools/bisect/bisect.sh +++ b/tools/bisect/bisect.sh @@ -26,7 +26,7 @@ mkdir -p "$bisect_dir" || exit 1 cd "$bisect_dir" || exit 1 if [ ! -d 'cppcheck' ]; then - git clone https://github.com/danmar/cppcheck.git || exit 1 + git clone https://github.com/cppcheck-opensource/cppcheck.git || exit 1 fi bisect_repo_dir="$bisect_dir/cppcheck" diff --git a/tools/donate_cpu_lib.py b/tools/donate_cpu_lib.py index 44d3157bc40..c292d2b9a13 100644 --- a/tools/donate_cpu_lib.py +++ b/tools/donate_cpu_lib.py @@ -16,12 +16,12 @@ # Version scheme (MAJOR.MINOR.PATCH) should orientate on "Semantic Versioning" https://semver.org/ # Every change in this script should result in increasing the version number accordingly (exceptions may be cosmetic # changes) -CLIENT_VERSION = "1.3.70" +CLIENT_VERSION = "1.4.0" # Timeout for analysis with Cppcheck in seconds CPPCHECK_TIMEOUT = 30 * 60 -CPPCHECK_REPO_URL = "https://github.com/danmar/cppcheck.git" +CPPCHECK_REPO_URL = "https://github.com/cppcheck-opensource/cppcheck.git" # Return code that is used to mark a timed out analysis RETURN_CODE_TIMEOUT = -999 @@ -133,7 +133,7 @@ def checkout_cppcheck_version(repo_path, version, cppcheck_path): hash_old = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=cppcheck_path).strip() print('Pulling {}'.format(version)) - # --rebase is a workaround for a dropped commit - see https://github.com/danmar/cppcheck/pull/6904 + # --rebase is a workaround for a dropped commit - see https://github.com/cppcheck-opensource/cppcheck/pull/6904 # TODO: drop the commit in question # TOD: remove --rebase subprocess.check_call(['git', 'pull', '--rebase'], cwd=cppcheck_path) From d4c522245b27e0d59f6531e25cfe9880fc8826fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 25 Apr 2026 23:37:27 +0200 Subject: [PATCH 11/19] Fix #14690 (ci: update cppcheck premium version in CI) (#8492) --- .github/workflows/cppcheck-premium.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cppcheck-premium.yml b/.github/workflows/cppcheck-premium.yml index 5cb63ca4d5e..ed0f2a1bcd5 100644 --- a/.github/workflows/cppcheck-premium.yml +++ b/.github/workflows/cppcheck-premium.yml @@ -33,7 +33,7 @@ jobs: run: | premium_version=${{ inputs.premium_version }} if [ -z $premium_version ]; then - premium_version=25.8.3 + premium_version=26.3.0 #wget https://files.cppchecksolutions.com/devdrop/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz wget https://files.cppchecksolutions.com/$premium_version/ubuntu-24.04/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz else From 02a7c33b789bef2c4596731e15d990ba70bebe81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 02:50:48 +0200 Subject: [PATCH 12/19] valueflow.cpp: pass `ProgramMemory` by reference to `valueFlowForLoop2()` (#8488) --- lib/valueflow.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 0f1c8a20ce7..6db9ba6d1e3 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -5158,9 +5158,9 @@ struct SymbolicConditionHandler : SimpleConditionHandler { }; static bool valueFlowForLoop2(const Token *tok, - ProgramMemory *memory1, - ProgramMemory *memory2, - ProgramMemory *memoryAfter, + ProgramMemory &memory1, + ProgramMemory &memory2, + ProgramMemory &memoryAfter, const Settings& settings) { // for ( firstExpression ; secondExpression ; thirdExpression ) @@ -5203,13 +5203,10 @@ static bool valueFlowForLoop2(const Token *tok, } // TODO: add bailout message - if (memory1) - memory1->swap(startMemory); + memory1.swap(startMemory); if (!error) { - if (memory2) - memory2->swap(endMemory); - if (memoryAfter) - memoryAfter->swap(programMemory); + memory2.swap(endMemory); + memoryAfter.swap(programMemory); } return true; @@ -5384,7 +5381,7 @@ static void valueFlowForLoop(const TokenList &tokenlist, const SymbolDatabase& s valueFlowForLoopSimplifyAfter(tok, varid, afterValue, tokenlist, errorLogger, settings); } else { ProgramMemory mem1, mem2, memAfter; - if (valueFlowForLoop2(tok, &mem1, &mem2, &memAfter, settings)) { + if (valueFlowForLoop2(tok, mem1, mem2, memAfter, settings)) { for (const auto& p : mem1) { if (!p.second.isIntValue()) continue; From 6e6f628cebd0c8e7cf0fb9c3120909df4974921f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 02:54:29 +0200 Subject: [PATCH 13/19] fixed #14626 - `execinfo.h` detection in `Makefile` did not work (#8401) --- .github/workflows/CI-unixish.yml | 6 ++++++ Makefile | 8 +++++++- tools/dmake/dmake.cpp | 9 +++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index cf555a8ecc6..3251a6b5adb 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -679,6 +679,12 @@ jobs: test -z "$(nm processexecutor.o)" # TODO: test NO_* defines + - name: Test execinfo.h detection + run: | + make clean + make cli/stacktrace.o | grep HAVE_EXECINFO_H=1 + test -n "$(nm cli/stacktrace.o)" + - name: Test testrunner inclusion/exclusion run: | ! ./testrunner -d TestUtils | grep -v TestUtils > /dev/null diff --git a/Makefile b/Makefile index cd7a56377e2..02735d36ee8 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,13 @@ else ifneq ($(HAVE_RULES),) $(error invalid HAVE_RULES value '$(HAVE_RULES)') endif -HAVE_EXECINFO_H=$(shell echo "\#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +# older make versions do not support # in $(shell) and newer ones handle the escape sequence literally +REQUIRE_ESCAPE=$(shell echo "\#define DEF" | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +ifeq ($(REQUIRE_ESCAPE),1) + HAVE_EXECINFO_H=$(shell echo "\#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +else + HAVE_EXECINFO_H=$(shell echo "#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +endif override CPPFLAGS += -DHAVE_EXECINFO_H=$(HAVE_EXECINFO_H) override CXXFLAGS += $(CXXOPTS) diff --git a/tools/dmake/dmake.cpp b/tools/dmake/dmake.cpp index 82754f1df11..80f3aa35b83 100644 --- a/tools/dmake/dmake.cpp +++ b/tools/dmake/dmake.cpp @@ -771,8 +771,13 @@ int main(int argc, char **argv) << " $(error invalid HAVE_RULES value '$(HAVE_RULES)')\n" << "endif\n\n"; - // the # needs to be escaped on older make versions - fout << "HAVE_EXECINFO_H=$(shell echo \"\\#include \" | $(CXX) -c -xc - 2> /dev/null && echo \"1\" || echo \"0\")\n" + fout << "# older make versions do not support # in $(shell) and newer ones handle the escape sequence literally\n" + << "REQUIRE_ESCAPE=$(shell echo \"\\#define DEF\" | $(CXX) -c -xc - 2> /dev/null && echo \"1\" || echo \"0\")\n" + << "ifeq ($(REQUIRE_ESCAPE),1)\n" + << " HAVE_EXECINFO_H=$(shell echo \"\\#include \" | $(CXX) -c -xc - 2> /dev/null && echo \"1\" || echo \"0\")\n" + << "else\n" + << " HAVE_EXECINFO_H=$(shell echo \"#include \" | $(CXX) -c -xc - 2> /dev/null && echo \"1\" || echo \"0\")\n" + << "endif\n" << "override CPPFLAGS += -DHAVE_EXECINFO_H=$(HAVE_EXECINFO_H)\n\n"; fout << "override CXXFLAGS += $(CXXOPTS)\n"; From f59b0c99f0bda35e8737ef974b91bc1149f1826f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 02:55:50 +0200 Subject: [PATCH 14/19] refs #14599 - added `OneShotTimer` / moved some `ShowTime` logic out of `Timer` / added more `--showtime` tests (#8435) --- cli/cppcheckexecutor.cpp | 8 +-- lib/cppcheck.cpp | 8 +-- lib/timer.cpp | 42 ++++++++------ lib/timer.h | 23 ++++---- test/cli/other_test.py | 116 ++++++++++++++++++++++++++++++++------- test/cli/testutils.py | 17 ++++++ 6 files changed, 157 insertions(+), 57 deletions(-) diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 69f3e842785..680f7387c70 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -267,8 +267,9 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) return EXIT_SUCCESS; } - TimerResults overallTimerResults; - Timer realTimeClock("Overall time", settings.showtime, &overallTimerResults, Timer::Type::OVERALL); + std::unique_ptr overallTimer; + if (settings.showtime == ShowTime::SUMMARY || settings.showtime == ShowTime::TOP5_SUMMARY) + overallTimer.reset(new OneShotTimer("Overall time", settings.showtime)); settings.loadSummaries(); @@ -277,9 +278,6 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) const int ret = check_wrapper(settings, supprs); - realTimeClock.stop(); - overallTimerResults.showResults(settings.showtime, false, true); - return ret; } diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 0a8e721936b..db2b2e5091a 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -923,8 +923,9 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (Settings::terminated()) return mLogger->exitcode(); - TimerResults checkTimeResults; - Timer fileTotalTimer{"Check time: " + file.spath(), mSettings.showtime, &checkTimeResults, Timer::Type::FILE}; + std::unique_ptr checkTimeTimer; + if (mSettings.showtime == ShowTime::FILE || mSettings.showtime == ShowTime::FILE_TOTAL || mSettings.showtime == ShowTime::TOP5_FILE) + checkTimeTimer.reset(new OneShotTimer("Check time: " + file.spath(), mSettings.showtime)); if (!mSettings.quiet) { std::string fixedpath = Path::toNativeSeparators(file.spath()); @@ -1296,9 +1297,6 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (mTimerResults && (mSettings.showtime == ShowTime::FILE || mSettings.showtime == ShowTime::TOP5_FILE)) mTimerResults->showResults(mSettings.showtime); - fileTotalTimer.stop(); - checkTimeResults.showResults(mSettings.showtime, false, true); - return mLogger->exitcode(); } diff --git a/lib/timer.cpp b/lib/timer.cpp index 5a18953767a..495b114fd98 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -38,7 +38,7 @@ namespace { // TODO: this does not include any file context when SHOWTIME_FILE thus rendering it useless - should we include the logging with the progress logging? // that could also get rid of the broader locking -void TimerResults::showResults(ShowTime mode, bool metrics, bool format) const +void TimerResults::showResults(ShowTime mode, bool metrics) const { if (mode == ShowTime::NONE) return; @@ -60,11 +60,7 @@ void TimerResults::showResults(ShowTime mode, bool metrics, bool format) const const double sec = iter->second.getSeconds().count(); const double secAverage = sec / static_cast(iter->second.mNumberOfResults); if ((mode != ShowTime::TOP5_FILE && mode != ShowTime::TOP5_SUMMARY) || (ordinal<=5)) { - std::cout << iter->first << ": "; - if (format) - std::cout << TimerResultsData::durationToString(iter->second.mDuration); - else - std::cout << sec << "s"; + std::cout << iter->first << ": " << sec << "s"; if (metrics) std::cout << " (avg. " << secAverage << "s - " << iter->second.mNumberOfResults << " result(s))"; std::cout << std::endl; @@ -87,10 +83,9 @@ void TimerResults::reset() mResults.clear(); } -Timer::Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults, Type type) +Timer::Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults) : mName(std::move(str)) , mMode(showtimeMode) - , mType(type) , mStart(Clock::now()) , mResults(timerResults) {} @@ -104,14 +99,6 @@ void Timer::stop() { if (mMode == ShowTime::NONE) return; - if (mType == Type::OVERALL && mMode != ShowTime::TOP5_SUMMARY && mMode != ShowTime::SUMMARY) { - mMode = ShowTime::NONE; - return; - } - if (mType == Type::FILE && mMode != ShowTime::TOP5_FILE && mMode != ShowTime::FILE && mMode != ShowTime::FILE_TOTAL) { - mMode = ShowTime::NONE; - return; - } if (mStart != TimePoint{}) { if (!mResults) { assert(false); @@ -124,7 +111,7 @@ void Timer::stop() mMode = ShowTime::NONE; // prevent multiple stops } -std::string TimerResultsData::durationToString(std::chrono::milliseconds duration) +static std::string durationToString(std::chrono::milliseconds duration) { // Extract hours auto hours = std::chrono::duration_cast(duration); @@ -148,3 +135,24 @@ std::string TimerResultsData::durationToString(std::chrono::milliseconds duratio secondsStr.resize(pos + 4); // keep three decimal return (ellapsedTime + secondsStr + "s"); } + +OneShotTimer::OneShotTimer(std::string name, ShowTime showtime) +{ + if (showtime == ShowTime::NONE) + return; + + class MyResults : public TimerResultsIntf + { + private: + void addResults(const std::string &name, std::chrono::milliseconds duration) override + { + std::lock_guard l(stdCoutLock); + + // TODO: do not use std::cout directly + std::cout << name << ": " << durationToString(duration) << std::endl; + } + }; + + mResults.reset(new MyResults); + mTimer.reset(new Timer(std::move(name), showtime, mResults.get())); +} diff --git a/lib/timer.h b/lib/timer.h index e0c4ee2d45f..e6c6833ad54 100644 --- a/lib/timer.h +++ b/lib/timer.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -52,15 +53,13 @@ struct TimerResultsData { std::chrono::duration getSeconds() const { return std::chrono::duration_cast>(mDuration); } - - static std::string durationToString(std::chrono::milliseconds duration); }; class CPPCHECKLIB WARN_UNUSED TimerResults : public TimerResultsIntf { public: TimerResults() = default; - void showResults(ShowTime mode, bool metrics = true, bool format = false) const; + void showResults(ShowTime mode, bool metrics = true) const; void addResults(const std::string& str, std::chrono::milliseconds duration) override; void reset(); @@ -75,13 +74,7 @@ class CPPCHECKLIB Timer { using Clock = std::chrono::high_resolution_clock; using TimePoint = std::chrono::time_point; - enum class Type : std::uint8_t { - FILE, - OVERALL, - OTHER - }; - - Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults = nullptr, Type type = Type::OTHER); + Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults = nullptr); ~Timer(); Timer(const Timer&) = delete; @@ -98,10 +91,18 @@ class CPPCHECKLIB Timer { private: const std::string mName; ShowTime mMode{}; - Type mType{}; TimePoint mStart; TimerResultsIntf* mResults{}; }; +class CPPCHECKLIB OneShotTimer +{ +public: + OneShotTimer(std::string name, ShowTime showtime); +private: + std::unique_ptr mResults; + std::unique_ptr mTimer; +}; + //--------------------------------------------------------------------------- #endif // timerH diff --git a/test/cli/other_test.py b/test/cli/other_test.py index d10cb541e5b..9735c94482b 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -9,7 +9,7 @@ import subprocess import shutil -from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe +from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe, create_compile_commands from xml.etree import ElementTree @@ -956,10 +956,9 @@ def test_unused_function_include(tmpdir): # TODO: test with clang-tidy # TODO: test with --addon -# TODO: test with FileSettings # TODO: test with multiple files -def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): - test_file = tmp_path / 'test.cpp' +def __test_showtime(tmp_path, showtime, exp_res, exp_last, use_compdb, extra_args=None): + test_file = tmp_path / 'test.cpp' # the use of C++ is intentional with open(test_file, 'wt') as f: f.write( """ @@ -972,10 +971,17 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): args = [ f'--showtime={showtime}', '--quiet', - '--inline-suppr', - str(test_file) + '--inline-suppr' ] + if use_compdb: + compdb_file = tmp_path / 'compile_commands.json' + create_compile_commands(compdb_file, [test_file]) + + args.append(f'--project={compdb_file}') + else: + args.append(str(test_file)) + if extra_args: args += extra_args @@ -994,50 +1000,122 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): assert stderr == '' +def __test_showtime_top5_file(tmp_path, use_compdb): + __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ', use_compdb) + + def test_showtime_top5_file(tmp_path): - __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ') + __test_showtime_top5_file(tmp_path, False) + + +def test_showtime_top5_file_compdb(tmp_path): + __test_showtime_top5_file(tmp_path, True) + + +# TODO: remove extra args when --executor=process works +def __test_showtime_top5_summary(tmp_path, use_compdb): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', use_compdb, ['-j1']) -# TODO: remove extra args when --executor=process works works def test_showtime_top5_summary(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j1']) + __test_showtime_top5_summary(tmp_path, False) + +def test_showtime_top5_summary_compdb(tmp_path): + __test_showtime_top5_summary(tmp_path, True) -# TODO: remove when --executor=process works works + +# TODO: remove when --executor=process works def test_showtime_top5_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=thread']) + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', False, ['-j2', '--executor=thread']) + + +# TODO: remove when --executor=process works +def test_showtime_top5_summary_compdb_j_thread(tmp_path): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', True, ['-j2', '--executor=thread']) # TODO: remove override when fixed @pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") @pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 def test_showtime_top5_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=process']) + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', False, ['-j2', '--executor=process']) + + +# TODO: remove override when fixed +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 +def test_showtime_top5_summary_compdb_j_process(tmp_path): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', True, ['-j2', '--executor=process']) + + +def __test_showtime_file(tmp_path, use_compdb): + exp_res = 79 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'file', exp_res, 'Check time: ', use_compdb) def test_showtime_file(tmp_path): - __test_showtime(tmp_path, 'file', 79, 'Check time: ') + __test_showtime_file(tmp_path, False) + + +def test_showtime_file_compdb(tmp_path): + __test_showtime_file(tmp_path, True) + + +# TODO: remove extra args when --executor=process works +def __test_showtime_summary(tmp_path, use_compdb): + exp_res = 79 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'summary', exp_res, 'Overall time: ', use_compdb, ['-j1']) -# TODO: remove extra args when --executor=process works works def test_showtime_summary(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j1']) + __test_showtime_summary(tmp_path, False,) + +def test_showtime_summary_compdb(tmp_path): + __test_showtime_summary(tmp_path, True) -# TODO: remove when --executor=process works works + +# TODO: remove when --executor=process works def test_showtime_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=thread']) + __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', False, ['-j2', '--executor=thread']) + + +# TODO: remove when --executor=process works +def test_showtime_summary_compdb_j_thread(tmp_path): + __test_showtime(tmp_path, 'summary', 78, 'Overall time: ', True, ['-j2', '--executor=thread']) # TODO: remove override when fixed @pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") @pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 def test_showtime_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=process']) + __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', False, ['-j2', '--executor=process']) + + +# TODO: remove override when fixed +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 +def test_showtime_summary_compdb_j_process(tmp_path): + __test_showtime(tmp_path, 'summary', 78, 'Overall time: ', True, ['-j2', '--executor=process']) + + +def __test_showtime_file_total(tmp_path, use_compdb): + __test_showtime(tmp_path, 'file-total', 0, 'Check time: ', use_compdb) def test_showtime_file_total(tmp_path): - __test_showtime(tmp_path, 'file-total', 0, 'Check time: ') + __test_showtime_file_total(tmp_path, False) + + +def test_showtime_file_total_compdb(tmp_path): + __test_showtime_file_total(tmp_path, True) def test_showtime_unique(tmp_path): diff --git a/test/cli/testutils.py b/test/cli/testutils.py index f352af49f15..2140c42205b 100644 --- a/test/cli/testutils.py +++ b/test/cli/testutils.py @@ -5,6 +5,7 @@ import subprocess import time import tempfile +import json # Create Cppcheck project file import sys @@ -45,6 +46,22 @@ def create_gui_project_file(project_file, root_path=None, import_project=None, p f.write(cppcheck_xml) +def create_compile_commands(compdb_file, files): + compdb = [] + # TODO: bail out on empty list + for f in files: + compdb.append( + { + "file": str(f), + "directory": os.path.dirname(f), + "command": "gcc -c {}".format(os.path.basename(f)) + } + ) + compdb_j = json.dumps(compdb) + with open(compdb_file, 'wt') as f: + f.write(compdb_j) + + def __lookup_cppcheck_exe(): # path the script is located in script_path = os.path.dirname(os.path.realpath(__file__)) From c5e0a3dd71d0f2fbe72482fd73cdf6d1b537d46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 10:25:58 +0200 Subject: [PATCH 15/19] refs #10543 - config.h: removed platform checks in favor of `HAVE_EXECINFO_H` / added `__has_include()` as fallback (#8489) --- lib/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.h b/lib/config.h index 960c2c91aa4..c88c431288c 100644 --- a/lib/config.h +++ b/lib/config.h @@ -203,7 +203,7 @@ #if HAVE_EXECINFO_H #define USE_UNIX_BACKTRACE_SUPPORT #endif -#elif defined(__GNUC__) && !defined(__APPLE__) && !defined(__CYGWIN__) && !defined(__MINGW32__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__SVR4) && !defined(__QNX__) && !defined(_AIX) +#elif __has_include() #define USE_UNIX_BACKTRACE_SUPPORT #endif #endif From 0294253bc08389cf4dc5fb5d58aa31eb3734d3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 10:28:02 +0200 Subject: [PATCH 16/19] utils.h: reject unexpected preceding characters in `strToInt()` (#8490) --- lib/utils.h | 28 +++++++++++++--- test/testcmdlineparser.cpp | 34 ++++++++++---------- test/testerrorlogger.cpp | 4 +-- test/testsuppressions.cpp | 12 +++---- test/testutils.cpp | 66 ++++++++++++++++++++++++++++++-------- 5 files changed, 101 insertions(+), 43 deletions(-) diff --git a/lib/utils.h b/lib/utils.h index 14f5382a8bf..3f9c4ab949f 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -220,7 +220,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) tmp = std::stoll(str, &idx); if (idx != str.size()) { if (err) - *err = "not an integer"; + *err = "not an integer (pos)"; return false; } } catch (const std::out_of_range&) { @@ -229,7 +229,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) return false; } catch (const std::invalid_argument &) { if (err) - *err = "not an integer"; + *err = "not an integer (invalid_argument)"; return false; } if (str.front() == '-' && std::numeric_limits::min() == 0) { @@ -237,6 +237,16 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) *err = "needs to be positive"; return false; } + if (str.front() != '+' && str.front() != '-' && isdigit(str.front()) == 0) { + if (err) + *err = "not an integer"; + return false; + } + if (str.size() > 1 && str.front() == '0') { + if (err) + *err = "not an integer"; + return false; + } if (tmp < std::numeric_limits::min() || tmp > std::numeric_limits::max()) { if (err) *err = "out of range (limits)"; @@ -255,7 +265,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) tmp = std::stoull(str, &idx); if (idx != str.size()) { if (err) - *err = "not an integer"; + *err = "not an integer (pos)"; return false; } } catch (const std::out_of_range&) { @@ -264,7 +274,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) return false; } catch (const std::invalid_argument &) { if (err) - *err = "not an integer"; + *err = "not an integer (invalid_argument)"; return false; } if (str.front() == '-') { @@ -272,6 +282,16 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) *err = "needs to be positive"; return false; } + if (str.front() != '+' && isdigit(str.front()) == 0) { + if (err) + *err = "not an integer"; + return false; + } + if (str.size() > 1 && str.front() == '0') { + if (err) + *err = "not an integer"; + return false; + } if (tmp > std::numeric_limits::max()) { if (err) *err = "out of range (limits)"; diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 71dd068b39e..06ee30837d6 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -1236,7 +1236,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--error-exitcode=", "file.cpp"}; // Fails since exit code not given ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void errorExitcodeStr() { @@ -1244,7 +1244,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--error-exitcode=foo", "file.cpp"}; // Fails since invalid exit code ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void exitcodeSuppressionsOld() { @@ -1395,7 +1395,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-j", "file.cpp"}; // Fails since -j is missing thread count ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer (invalid_argument).\n", logger->str()); } void jobsInvalid() { @@ -1403,7 +1403,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-j", "e", "file.cpp"}; // Fails since invalid count given for -j ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer (invalid_argument).\n", logger->str()); } void jobsNoJobs() { @@ -1433,7 +1433,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--max-configs=", "file.cpp"}; // Fails since --max-configs= is missing limit ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void maxConfigsInvalid() { @@ -1441,7 +1441,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--max-configs=e", "file.cpp"}; // Fails since invalid count given for --max-configs= ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void maxConfigsTooSmall() { @@ -1672,7 +1672,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--report-progress=", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--report-progress=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--report-progress=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void reportProgress3() { @@ -2172,7 +2172,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--xml", "--xml-version=a", "file.cpp"}; // FAils since unknown XML format version ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--xml-version=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--xml-version=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void doc() { @@ -2395,7 +2395,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--valueflow-max-iterations=seven"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--valueflow-max-iterations=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--valueflow-max-iterations=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void valueFlowMaxIterationsInvalid3() { @@ -2423,7 +2423,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--checks-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--checks-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--checks-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } #ifdef HAS_THREADING_MODEL_FORK @@ -2445,7 +2445,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "-l", "one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-l' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-l' is not valid - not an integer (invalid_argument).\n", logger->str()); } #else void loadAverageNotSupported() { @@ -2482,7 +2482,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--max-ctu-depth=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-ctu-depth=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-ctu-depth=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void performanceValueflowMaxTime() { @@ -2496,7 +2496,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--performance-valueflow-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void performanceValueFlowMaxIfCount() { @@ -2510,7 +2510,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--performance-valueflow-max-if-count=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-if-count=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-if-count=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void templateMaxTime() { @@ -2524,7 +2524,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--template-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--template-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--template-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void templateMaxTimeInvalid2() { @@ -2545,7 +2545,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--typedef-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--typedef-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--typedef-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void typedefMaxTimeInvalid2() { @@ -3235,7 +3235,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--max-template-recursion=", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-template-recursion=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-template-recursion=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void emitDuplicates() { diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 812ea500299..23b3233995c 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -599,7 +599,7 @@ class TestErrorLogger : public TestFixture { "0 " "0 "; ErrorMessage msg; - ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid CWE ID - not an integer"); + ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid CWE ID - not an integer (invalid_argument)"); } { // invalid hash @@ -615,7 +615,7 @@ class TestErrorLogger : public TestFixture { "0 " "0 "; ErrorMessage msg; - ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid hash - not an integer"); + ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid hash - not an integer (invalid_argument)"); } { // out-of-range CWE ID diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index 3233a5918c7..49d46149c90 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -188,12 +188,12 @@ class TestSuppressions : public TestFixture { ASSERT_THROW_EQUALS(SuppressionList::parseLine("id::1"), std::runtime_error, "filename is missing"); // missing/invalid line - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer)"); - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:\n"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer)"); - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:\n1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer)"); - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:#1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer)"); // TODO: looks like a valid filename - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c://1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer)"); - ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:zero"), std::runtime_error, "invalid line number (converting 'zero' to integer failed - not an integer)"); + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer (invalid_argument))"); + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:\n"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer (invalid_argument))"); + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:\n1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer (invalid_argument))"); + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:#1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer (invalid_argument))"); // TODO: looks like a valid filename + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c://1"), std::runtime_error, "invalid line number (converting '' to integer failed - not an integer (invalid_argument))"); + ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:zero"), std::runtime_error, "invalid line number (converting 'zero' to integer failed - not an integer (invalid_argument))"); // invalid extras ASSERT_THROW_EQUALS(SuppressionList::parseLine("id:1.c:1\n"), std::runtime_error, "unexpected extra ''"); diff --git a/test/testutils.cpp b/test/testutils.cpp index 85da8c6c595..8e91108c3f4 100644 --- a/test/testutils.cpp +++ b/test/testutils.cpp @@ -223,19 +223,57 @@ class TestUtils : public TestFixture { void strToInt() { ASSERT_EQUALS(1, ::strToInt("1")); + ASSERT_EQUALS(1, ::strToInt("+1")); ASSERT_EQUALS(-1, ::strToInt("-1")); ASSERT_EQUALS(1, ::strToInt("1")); - ASSERT_THROW_EQUALS(::strToInt(""), std::runtime_error, "converting '' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt(""), std::runtime_error, "converting '' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt(" "), std::runtime_error, "converting ' ' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt(" "), std::runtime_error, "converting ' ' to integer failed - not an integer"); + ASSERT_EQUALS(1, ::strToInt("+1")); + ASSERT_EQUALS(0, ::strToInt("0")); + ASSERT_EQUALS(0, ::strToInt("+0")); + ASSERT_EQUALS(0, ::strToInt("-0")); + ASSERT_EQUALS(0, ::strToInt("0")); + ASSERT_EQUALS(0, ::strToInt("+0")); + ASSERT_THROW_EQUALS(::strToInt(""), std::runtime_error, "converting '' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt(""), std::runtime_error, "converting '' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt(" "), std::runtime_error, "converting ' ' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt(" "), std::runtime_error, "converting ' ' to integer failed - not an integer (invalid_argument)"); ASSERT_THROW_EQUALS(::strToInt("-1"), std::runtime_error, "converting '-1' to integer failed - needs to be positive"); - ASSERT_THROW_EQUALS(::strToInt("1ms"), std::runtime_error, "converting '1ms' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt("1.0"), std::runtime_error, "converting '1.0' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt("one"), std::runtime_error, "converting 'one' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt("1ms"), std::runtime_error, "converting '1ms' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt("1.0"), std::runtime_error, "converting '1.0' to integer failed - not an integer"); - ASSERT_THROW_EQUALS(::strToInt("one"), std::runtime_error, "converting 'one' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("-0"), std::runtime_error, "converting '-0' to integer failed - needs to be positive"); + ASSERT_THROW_EQUALS(::strToInt("-1"), std::runtime_error, "converting '-1' to integer failed - needs to be positive"); + ASSERT_THROW_EQUALS(::strToInt("-0"), std::runtime_error, "converting '-0' to integer failed - needs to be positive"); + ASSERT_THROW_EQUALS(::strToInt("1ms"), std::runtime_error, "converting '1ms' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1.0"), std::runtime_error, "converting '1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("+1.0"), std::runtime_error, "converting '+1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("-1.0"), std::runtime_error, "converting '-1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1U"), std::runtime_error, "converting '1U' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1L"), std::runtime_error, "converting '1L' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1Z"), std::runtime_error, "converting '1Z' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("01"), std::runtime_error, "converting '01' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("0x1"), std::runtime_error, "converting '0x1' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("0b1"), std::runtime_error, "converting '0b1' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("one"), std::runtime_error, "converting 'one' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt(" 1"), std::runtime_error, "converting ' 1' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("\t1"), std::runtime_error, "converting '\t1' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("1 "), std::runtime_error, "converting '1 ' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1\t"), std::runtime_error, "converting '1\t' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("+ 1"), std::runtime_error, "converting '+ 1' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt("O1"), std::runtime_error, "converting 'O1' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt("1ms"), std::runtime_error, "converting '1ms' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1.0"), std::runtime_error, "converting '1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("+1.0"), std::runtime_error, "converting '+1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("-1.0"), std::runtime_error, "converting '-1.0' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1U"), std::runtime_error, "converting '1U' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1L"), std::runtime_error, "converting '1L' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1Z"), std::runtime_error, "converting '1Z' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("01"), std::runtime_error, "converting '01' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("0x1"), std::runtime_error, "converting '0x1' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("0b1"), std::runtime_error, "converting '0b1' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("one"), std::runtime_error, "converting 'one' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt(" 1"), std::runtime_error, "converting ' 1' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("\t1"), std::runtime_error, "converting '\t1' to integer failed - not an integer"); + ASSERT_THROW_EQUALS(::strToInt("1 "), std::runtime_error, "converting '1 ' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("1\t"), std::runtime_error, "converting '1\t' to integer failed - not an integer (pos)"); + ASSERT_THROW_EQUALS(::strToInt("- 1"), std::runtime_error, "converting '- 1' to integer failed - not an integer (invalid_argument)"); + ASSERT_THROW_EQUALS(::strToInt("O1"), std::runtime_error, "converting 'O1' to integer failed - not an integer (invalid_argument)"); ASSERT_THROW_EQUALS(::strToInt(std::to_string(static_cast(std::numeric_limits::max()) + 1)), std::runtime_error, "converting '2147483648' to integer failed - out of range (limits)"); ASSERT_THROW_EQUALS(::strToInt(std::to_string(static_cast(std::numeric_limits::min()) - 1)), std::runtime_error, "converting '-2147483649' to integer failed - out of range (limits)"); ASSERT_THROW_EQUALS(::strToInt(std::to_string(static_cast(std::numeric_limits::max()) + 1)), std::runtime_error, "converting '128' to integer failed - out of range (limits)"); @@ -311,25 +349,25 @@ class TestUtils : public TestFixture { long tmp; std::string err; ASSERT(!::strToInt("1ms", tmp, &err)); - ASSERT_EQUALS("not an integer", err); + ASSERT_EQUALS("not an integer (pos)", err); } { long tmp; std::string err; ASSERT(!::strToInt("1.0", tmp, &err)); - ASSERT_EQUALS("not an integer", err); + ASSERT_EQUALS("not an integer (pos)", err); } { long tmp; std::string err; ASSERT(!::strToInt("one", tmp, &err)); - ASSERT_EQUALS("not an integer", err); + ASSERT_EQUALS("not an integer (invalid_argument)", err); } { std::size_t tmp; std::string err; ASSERT(!::strToInt("1ms", tmp, &err)); - ASSERT_EQUALS("not an integer", err); + ASSERT_EQUALS("not an integer (pos)", err); } { long tmp; From a374143aa25c215ed7f71c9fa6affe50d4853f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 10:29:20 +0200 Subject: [PATCH 17/19] fixed #14618 - added more metrics to timer summary / small `TimerResultsData` refactoring (#8458) --- lib/timer.cpp | 16 +++++++++------- lib/timer.h | 17 ++++++++++++----- test/options.cpp | 4 +--- test/testtimer.cpp | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/timer.cpp b/lib/timer.cpp index 495b114fd98..f4025fb3427 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -57,24 +57,26 @@ void TimerResults::showResults(ShowTime mode, bool metrics) const size_t ordinal = 1; // maybe it would be nice to have an ordinal in output later! for (auto iter=data.cbegin(); iter!=data.cend(); ++iter) { - const double sec = iter->second.getSeconds().count(); - const double secAverage = sec / static_cast(iter->second.mNumberOfResults); if ((mode != ShowTime::TOP5_FILE && mode != ShowTime::TOP5_SUMMARY) || (ordinal<=5)) { + const double sec = iter->second.getSeconds().count(); std::cout << iter->first << ": " << sec << "s"; - if (metrics) - std::cout << " (avg. " << secAverage << "s - " << iter->second.mNumberOfResults << " result(s))"; + if (metrics) { + const double secAverage = sec / static_cast(iter->second.mResults.size()); + const double secMin = asSeconds(*std::min_element(iter->second.mResults.cbegin(), iter->second.mResults.cend())).count(); + const double secMax = asSeconds(*std::max_element(iter->second.mResults.cbegin(), iter->second.mResults.cend())).count(); + std::cout << " (avg. " << secAverage << "s / min " << secMin << "s / max " << secMax << "s - " << iter->second.mResults.size() << " result(s))"; + } std::cout << std::endl; } ++ordinal; } } -void TimerResults::addResults(const std::string& str, std::chrono::milliseconds duration) +void TimerResults::addResults(const std::string& name, std::chrono::milliseconds duration) { std::lock_guard l(mResultsSync); - mResults[str].mDuration += duration; - mResults[str].mNumberOfResults++; + mResults[name].mResults.push_back(duration); } void TimerResults::reset() diff --git a/lib/timer.h b/lib/timer.h index e6c6833ad54..5611bc5304b 100644 --- a/lib/timer.h +++ b/lib/timer.h @@ -27,8 +27,10 @@ #include #include #include +#include #include #include +#include enum class ShowTime : std::uint8_t { NONE, @@ -43,15 +45,20 @@ class CPPCHECKLIB TimerResultsIntf { public: virtual ~TimerResultsIntf() = default; - virtual void addResults(const std::string& timerName, std::chrono::milliseconds duation) = 0; + virtual void addResults(const std::string& name, std::chrono::milliseconds duration) = 0; + + static std::chrono::duration asSeconds(std::chrono::milliseconds ms) { + return std::chrono::duration_cast>(ms); + } }; struct TimerResultsData { - std::chrono::milliseconds mDuration; - long mNumberOfResults{}; + std::vector mResults; std::chrono::duration getSeconds() const { - return std::chrono::duration_cast>(mDuration); + return std::accumulate(mResults.cbegin(), mResults.cend(), std::chrono::duration{}, [](std::chrono::duration secs, std::chrono::milliseconds duration) { + return secs + TimerResultsIntf::asSeconds(duration); + }); } }; @@ -60,7 +67,7 @@ class CPPCHECKLIB WARN_UNUSED TimerResults : public TimerResultsIntf { TimerResults() = default; void showResults(ShowTime mode, bool metrics = true) const; - void addResults(const std::string& str, std::chrono::milliseconds duration) override; + void addResults(const std::string& name, std::chrono::milliseconds duration) override; void reset(); diff --git a/test/options.cpp b/test/options.cpp index bdb4f553ac8..9db2bce8563 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -59,10 +59,8 @@ options::options(int argc, const char* const argv[]) options::~options() { // TODO: allow more than 5 results to be shown - // TODO: provide higher resolution in output - // TODO: disable the metrics if (mTimerResults) - mTimerResults->showResults(ShowTime::TOP5_FILE); + mTimerResults->showResults(ShowTime::TOP5_FILE, false); } bool options::quiet() const diff --git a/test/testtimer.cpp b/test/testtimer.cpp index c8d468b1afb..3f160c0c2b7 100644 --- a/test/testtimer.cpp +++ b/test/testtimer.cpp @@ -33,7 +33,7 @@ class TestTimer : public TestFixture { void result() const { TimerResultsData t1; - t1.mDuration = std::chrono::milliseconds{1234}; + t1.mResults.emplace_back(1234); ASSERT(t1.getSeconds().count() > 1.233 && t1.getSeconds().count() < 1.235); // TODO : more tests From 6fa5b6b943186c808dfddd13d8677a3015262e9e Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:57:53 +0200 Subject: [PATCH 18/19] Refs #4412: using namespace std; not simplified (constructor) (#8477) Co-authored-by: chrchr-github --- lib/tokenize.cpp | 14 ++++++++++---- test/testtokenize.cpp | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index e173aabc30d..4cce2a71434 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -10344,6 +10344,13 @@ static bool isStdSmartPointer(const Token* tok, const Settings& settings) return ptr && startsWith(ptr->name, "std::"); } +static bool isLibraryType(const Token* tok, const Settings& settings) +{ + return settings.library.hasAnyTypeCheck("std::" + tok->str()) || + settings.library.podtype("std::" + tok->str()) || + isStdContainerOrIterator(tok, settings); +} + // Add std:: in front of std classes, when using namespace std; was given void Tokenizer::simplifyNamespaceStd() { @@ -10371,14 +10378,13 @@ void Tokenizer::simplifyNamespaceStd() if (start != tok && start->isName() && !start->isKeyword() && (!start->previous() || Token::Match(start->previous(), "[;{}]"))) userFunctions.insert(tok->str()); } - if (userFunctions.find(tok->str()) == userFunctions.end() && mSettings.library.matchArguments(tok, "std::" + tok->str())) + if ((userFunctions.find(tok->str()) == userFunctions.end() && mSettings.library.matchArguments(tok, "std::" + tok->str())) || + (tok->tokAt(-1)->isKeyword() && isLibraryType(tok, mSettings))) insert = true; } else if (Token::simpleMatch(tok->next(), "<") && (isStdContainerOrIterator(tok, mSettings) || isStdSmartPointer(tok, mSettings))) insert = true; - else if (mSettings.library.hasAnyTypeCheck("std::" + tok->str()) || - mSettings.library.podtype("std::" + tok->str()) || - isStdContainerOrIterator(tok, mSettings)) + else if (isLibraryType(tok, mSettings)) insert = true; else if (Token::simpleMatch(tok, "aligned_storage")) insert = true; diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index 0798983c079..9b816a357b7 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -5288,12 +5288,7 @@ class TestTokenizer : public TestFixture { "if ( ! p ) {\n" "throw std :: runtime_error ( \"abc\" ) ; }\n" "}"; - TODO_ASSERT_EQUALS(expected, - "void f ( const std :: unique_ptr < int > & p ) {\n" - "if ( ! p ) {\n" - "throw runtime_error ( \"abc\" ) ; }\n" - "}", - tokenizeAndStringify(code)); + ASSERT_EQUALS(expected, tokenizeAndStringify(code)); } { @@ -5315,6 +5310,17 @@ class TestTokenizer : public TestFixture { "}"; ASSERT_EQUALS(expected, tokenizeAndStringify(code)); } + + { + const char code[] = "using namespace std;\n" + "string_view f() { return string(); }\n" + "void move() {}\n" + "void string() {}\n"; + expected = "std :: string_view f ( ) { return std :: string ( ) ; }\n" + "void move ( ) { }\n" + "void string ( ) { }"; + ASSERT_EQUALS(expected, tokenizeAndStringify(code)); + } } void microsoftMemory() { From aec1a7f17bad28a5b0c4cc93a041f745a5ea7b6c Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:05:24 +0200 Subject: [PATCH 19/19] Fix #6492 valueflow: for loop condition not used for conditional analysis (#8472) To be merged after #8471. Ignoring the debug messages since the line numbers were flaky. --------- Co-authored-by: chrchr-github --- lib/valueflow.cpp | 32 +++++++++++++++++++------------- test/testnullpointer.cpp | 7 +++++++ test/testsimplifytokens.cpp | 1 + 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 6db9ba6d1e3..614d568ea80 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4961,6 +4961,24 @@ static void valueFlowCondition(const ValuePtr& handler, handler->afterCondition(tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); } +static const Token* getConditionVariable(const Token* tok) +{ + if (tok->str() == "!") + return tok->astOperand1(); + + if (const Token* parent = tok->astParent()) { + if (Token::Match(parent, "%oror%|&&|?") || + Token::Match(parent->previous(), "if|while (") || + (parent->str() == ";" && astIsLHS(tok) && Token::simpleMatch(parent->astParent(), ";"))) { // for loop condition + if (Token::simpleMatch(tok, "=")) + return tok->astOperand1(); + if (!Token::Match(tok, "%comp%|%assign%")) + return tok; + } + } + return nullptr; +} + struct SimpleConditionHandler : ConditionHandler { std::vector parse(const Token* tok, const Settings& /*settings*/) const override { @@ -4979,19 +4997,7 @@ struct SimpleConditionHandler : ConditionHandler { if (!conds.empty()) return conds; - const Token* vartok = nullptr; - - if (tok->str() == "!") { - vartok = tok->astOperand1(); - - } else if (tok->astParent() && (Token::Match(tok->astParent(), "%oror%|&&|?") || - Token::Match(tok->astParent()->previous(), "if|while ("))) { - if (Token::simpleMatch(tok, "=")) - vartok = tok->astOperand1(); - else if (!Token::Match(tok, "%comp%|%assign%")) - vartok = tok; - } - + const Token* vartok = getConditionVariable(tok); if (!vartok) return {}; Condition cond; diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 83a19c40805..967cd0d2315 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -3723,6 +3723,13 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:19]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" "[test.cpp:8:9] -> [test.cpp:7:18]: (warning) Either the condition 's' is redundant or there is possible null pointer dereference: s. [nullPointerRedundantCheck]\n", errout_str()); + + check("struct S { int a; };\n" // #6492 + "void h(const S* s) {\n" + " for (int i = s->a; s; ++i) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:24] -> [test.cpp:3:18]: (warning) Either the condition 's' is redundant or there is possible null pointer dereference: s. [nullPointerRedundantCheck]\n", + errout_str()); } void nullpointerDeadCode() { diff --git a/test/testsimplifytokens.cpp b/test/testsimplifytokens.cpp index 4e227bfca17..88d1a4b2725 100644 --- a/test/testsimplifytokens.cpp +++ b/test/testsimplifytokens.cpp @@ -2207,6 +2207,7 @@ class TestSimplifyTokens : public TestFixture { "for ( i = 0 ; ( i < sz ) && ( sz > 3 ) ; ++ i ) { }\n" "}"; ASSERT_EQUALS(expected, tokenizeAndStringify(code, dinit(TokenizeAndStringifyOptions, $.cpp = false))); + ignore_errout(); } void simplifyKnownVariables49() { // #3691