From 2b366fa2297b1befd76752655830099f45ca061d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 13 Jan 2026 15:43:03 +0100 Subject: [PATCH 1/6] Fix #14334 (Support more msbuild conditional constructs) --- .github/workflows/selfcheck.yml | 2 +- lib/importproject.cpp | 104 ++++++++++++++++++++++++++++---- lib/importproject.h | 9 +++ test/testimportproject.cpp | 50 +++++++++------ 4 files changed, 132 insertions(+), 33 deletions(-) diff --git a/.github/workflows/selfcheck.yml b/.github/workflows/selfcheck.yml index 246631213c2..eac16bc318d 100644 --- a/.github/workflows/selfcheck.yml +++ b/.github/workflows/selfcheck.yml @@ -121,7 +121,7 @@ jobs: - name: Self check (unusedFunction / no test / no gui) run: | - supprs="--suppress=unusedFunction:lib/errorlogger.h:197 --suppress=unusedFunction:lib/importproject.cpp:1584 --suppress=unusedFunction:lib/importproject.cpp:1608" + supprs="--suppress=unusedFunction:lib/errorlogger.h:196 --suppress=unusedFunction:lib/importproject.cpp:1673 --suppress=unusedFunction:lib/importproject.cpp:1697" ./cppcheck -q --template=selfcheck --error-exitcode=1 --library=cppcheck-lib -D__CPPCHECK__ -D__GNUC__ --enable=unusedFunction,information --exception-handling -rp=. --project=cmake.output.notest_nogui/compile_commands.json --suppressions-list=.selfcheck_unused_suppressions --inline-suppr $supprs env: DISABLE_VALUEFLOW: 1 diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 4033b678d02..fdbe2d5fc37 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -558,6 +558,7 @@ bool ImportProject::importSlnx(const std::string& filename, const std::vectorAttribute("Include"); if (a) @@ -603,10 +604,14 @@ namespace { // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions // properties are .NET String objects and you can call any of its members on them - bool conditionIsTrue(const ProjectConfiguration &p) const { - if (mCondition.empty()) + bool conditionIsTrue(const ProjectConfiguration &p, const std::string &filename, std::vector &errors) const { + return conditionIsTrue(mCondition, p, filename, errors); + } + + static bool conditionIsTrue(const std::string& condition, const ProjectConfiguration &p, const std::string &filename, std::vector &errors) { + if (condition.empty()) return true; - std::string c = '(' + mCondition + ");"; + std::string c = '(' + condition + ");"; replaceAll(c, "$(Configuration)", p.configuration); replaceAll(c, "$(Platform)", p.platformStr); @@ -629,19 +634,83 @@ namespace { } } } + + // Replace "And" and "Or" with "&&" and "||" + for (Token *tok = tokenlist.front(); tok; tok = tok->next()) { + if (tok->str() == "And") + tok->str("&&"); + else if (tok->str() == "Or") + tok->str("||"); + } + tokenlist.createAst(); + + // Locate ast top and execute the condition for (const Token *tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->str() == "(" && tok->astOperand1() && tok->astOperand2()) { - // TODO: this is wrong - it is Contains() not Equals() - if (tok->astOperand1()->expressionString() == "Configuration.Contains") - return ('\'' + p.configuration + '\'') == tok->astOperand2()->str(); + if (tok->astParent()) { + return execute(tok->astTop(), p) == "True"; } - if (tok->str() == "==" && tok->astOperand1() && tok->astOperand2() && tok->astOperand1()->str() == tok->astOperand2()->str()) - return true; } - return false; + + throw std::runtime_error("Invalid condition: '" + condition + "'"); } private: + + static std::string executeOp1(const Token* tok, const ProjectConfiguration &p, bool b=false) { + const std::string result = execute(tok->astOperand1(), p); + if (b) + return (result != "False" && !result.empty()) ? "True" : "False"; + return result; + } + + static std::string executeOp2(const Token* tok, const ProjectConfiguration &p, bool b=false) { + const std::string result = execute(tok->astOperand2(), p); + if (b) + return (result != "False" && !result.empty()) ? "True" : "False"; + return result; + } + + static std::string execute(const Token* tok, const ProjectConfiguration &p) { + if (!tok) + throw std::runtime_error("Missing operator"); + auto boolResult = [](bool b) -> std::string { return b ? "True" : "False"; }; + if (tok->isUnaryOp("!")) + return boolResult(executeOp1(tok, p, true) == "False"); + if (tok->str() == "==") + return boolResult(executeOp1(tok, p) == executeOp2(tok, p)); + if (tok->str() == "!=") + return boolResult(executeOp1(tok, p) != executeOp2(tok, p)); + if (tok->str() == "&&") + return boolResult(executeOp1(tok, p, true) == "True" && executeOp2(tok, p, true) == "True"); + if (tok->str() == "||") + return boolResult(executeOp1(tok, p, true) == "True" || executeOp2(tok, p, true) == "True"); + if (tok->str() == "(" && Token::Match(tok->previous(), "$ ( %name% . %name% (")) { + const std::string propertyName = tok->next()->str(); + std::string propertyValue; + if (propertyName == "Configuration") + propertyValue = p.configuration; + else if (propertyName == "Platform") + propertyValue = p.platform; + else + throw std::runtime_error("Unhandled property '" + propertyName + "'"); + const std::string method = tok->strAt(3); + std::string arg = executeOp2(tok->tokAt(4), p); + if (arg.size() >= 2 && arg[0] == '\'') + arg = arg.substr(1, arg.size() - 2); + if (method == "Contains") + return boolResult(propertyValue.find(arg) != std::string::npos); + if (method == "EndsWith") + return boolResult(endsWith(propertyValue,arg.c_str(),arg.size())); + if (method == "StartsWith") + return boolResult(startsWith(propertyValue,arg)); + throw std::runtime_error("Unhandled method '" + method + "'"); + } + if (tok->str().size() >= 2 && tok->str()[0] == '\'') + return tok->str(); + + throw std::runtime_error("Unknown/unhandled operator/operand '" + tok->str() + "'"); + } + std::string mCondition; }; @@ -947,7 +1016,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X } std::string additionalIncludePaths; for (const ItemDefinitionGroup &i : itemDefinitionGroupList) { - if (!i.conditionIsTrue(p)) + if (!i.conditionIsTrue(p, cfilename, errors)) continue; fs.standard = Standards::getCPP(i.cppstd); fs.defines += ';' + i.preprocessorDefinitions; @@ -965,7 +1034,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X } bool useUnicode = false; for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) { - if (!c.conditionIsTrue(p)) + if (!c.conditionIsTrue(p, cfilename, errors)) continue; // in msbuild the last definition wins useUnicode = c.useUnicode; @@ -1622,3 +1691,14 @@ void ImportProject::setRelativePaths(const std::string &filename) } } +// only used by tests (testimportproject.cpp::testVcxprojConditions): +// cppcheck-suppress unusedFunction +bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, + const std::string& platform) +{ + ProjectConfiguration p; + p.configuration = configuration; + p.platformStr = platform; + std::vector errors; + return ConditionalGroup::conditionIsTrue(condition, p, "file.vcxproj", errors) && errors.empty(); +} diff --git a/lib/importproject.h b/lib/importproject.h index a5ea6026af0..4a6e318c02d 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -49,6 +49,11 @@ namespace cppcheck { return caseInsensitiveStringCompare(lhs,rhs) < 0; } }; + + namespace testing + { + CPPCHECKLIB bool evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, const std::string& platform); + } } /** @@ -196,6 +201,10 @@ namespace CppcheckXml { static constexpr char ProjectNameElementName[] = "project-name"; } +namespace testing +{ + CPPCHECKLIB bool evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, const std::string& platform); +} /// @} //--------------------------------------------------------------------------- #endif // importprojectH diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index a0daea25bb2..a5ad9f6b443 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ class TestImportProject : public TestFixture { TEST_CASE(testCollectArgs5); TEST_CASE(testCollectArgs6); TEST_CASE(testCollectArgs7); + TEST_CASE(testVcxprojConditions); } void setDefines() const { @@ -682,28 +684,36 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS("Missing closing quote in command string", error); } + void testVcxprojConditions() const + { + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(Configuration)'=='Debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(Platform)'=='Win32'", "Debug", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'$(Configuration)'=='Release'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' == 'Debug' ", "Debug", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' != 'Debug' ", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(Configuration)|$(Platform)' == 'Debug|Win32' ", "Debug", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("!('$(Configuration)|$(Platform)' == 'Debug|Win32' )", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' == 'Debug' And '$(Platform)' == 'Win32'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' == 'Debug' Or '$(Platform)' == 'Win32'", "Release", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.StartsWith('Debug'))", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.EndsWith('AddressSanitizer'))", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains('Address'))", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains ( 'Address' ) )", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains('Address')) And '$(Platform)' == 'Win32'", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" ($(Configuration.Contains('Address')) ) And ( '$(Platform)' == 'Win32')", "Debug-AddressSanitizer", "Win32")); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("And", "", ""), std::runtime_error, "'And' without previous expression!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("Or", "", ""), std::runtime_error, "'Or' without previous expression!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("!", "", ""), std::runtime_error, "Expected expression here!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '' And ", "", ""), std::runtime_error, "Expected expression here!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("('' == ''", "", ""), std::runtime_error, "'(' without closing ')'!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '')", "", ""), std::runtime_error, "Unhandled expression!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Within a string comparison. We expect at least a =='' or !='' !"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Within a string comparison. We expect at least a =='' or !='' !"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Unexpected function call!"); + } + // TODO: test fsParseCommand() - // TODO: test vcxproj conditions - /* - - - - - Debug - x64 - - - - - CPPCHECKLIB_IMPORT - - - - - - - */ }; REGISTER_TEST(TestImportProject) From db11aabf66e5dbd760a7629912d92e423d7866fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 13 Jan 2026 20:03:32 +0100 Subject: [PATCH 2/6] fix --- lib/importproject.cpp | 4 +++- test/testimportproject.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index fdbe2d5fc37..ee343981e4d 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -628,11 +628,13 @@ namespace { lpar.push(tok2); else if (tok2->str() == ")") { if (lpar.empty()) - break; + throw std::runtime_error("unmatched ')' in condition " + condition); Token::createMutualLinks(lpar.top(), tok2); lpar.pop(); } } + if (!lpar.empty()) + throw std::runtime_error("'(' without closing ')'!"); } // Replace "And" and "Or" with "&&" and "||" diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index a5ad9f6b443..f5e095e6f30 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -701,15 +701,15 @@ class TestImportProject : public TestFixture { ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains ( 'Address' ) )", "Debug-AddressSanitizer", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains('Address')) And '$(Platform)' == 'Win32'", "Debug-AddressSanitizer", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" ($(Configuration.Contains('Address')) ) And ( '$(Platform)' == 'Win32')", "Debug-AddressSanitizer", "Win32")); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("And", "", ""), std::runtime_error, "'And' without previous expression!"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("Or", "", ""), std::runtime_error, "'Or' without previous expression!"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("!", "", ""), std::runtime_error, "Expected expression here!"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '' And ", "", ""), std::runtime_error, "Expected expression here!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("And", "", ""), std::runtime_error, "Invalid condition: 'And'"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("Or", "", ""), std::runtime_error, "Invalid condition: 'Or'"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("!", "", ""), std::runtime_error, "Invalid condition: '!'"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '' And ", "", ""), std::runtime_error, "Missing operator"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("('' == ''", "", ""), std::runtime_error, "'(' without closing ')'!"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '')", "", ""), std::runtime_error, "Unhandled expression!"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Within a string comparison. We expect at least a =='' or !='' !"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Within a string comparison. We expect at least a =='' or !='' !"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Unexpected function call!"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '')", "", ""), std::runtime_error, "unmatched ')' in condition '' == '')"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Invalid condition: ''''"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Invalid condition: ''' == ''"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Missing operator"); } // TODO: test fsParseCommand() From b01f29a531976646d012dba4bad04fe73c70c05c Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Tue, 24 Feb 2026 19:47:59 +0100 Subject: [PATCH 3/6] Fix wrong logic where should be thrown and there not --- lib/importproject.cpp | 55 ++++++++++++++++++++------------------ test/testimportproject.cpp | 3 ++- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index ee343981e4d..ee279bd085d 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -605,21 +605,29 @@ namespace { // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions // properties are .NET String objects and you can call any of its members on them bool conditionIsTrue(const ProjectConfiguration &p, const std::string &filename, std::vector &errors) const { - return conditionIsTrue(mCondition, p, filename, errors); + if (mCondition.empty()) + return true; + try { + return evalCondition(mCondition, p); + } + catch (const std::runtime_error& r) + { + errors.emplace_back(filename + ": Can not evaluate condition '" + mCondition + "': " + r.what()); + return false; + } } - static bool conditionIsTrue(const std::string& condition, const ProjectConfiguration &p, const std::string &filename, std::vector &errors) { - if (condition.empty()) - return true; - std::string c = '(' + condition + ");"; + static bool evalCondition(const std::string& condition, const ProjectConfiguration &p) { + std::string c = '(' + condition + ")"; replaceAll(c, "$(Configuration)", p.configuration); replaceAll(c, "$(Platform)", p.platformStr); - // TODO: improve evaluation const Settings s; TokenList tokenlist(s, Standards::Language::C); - tokenlist.createTokensFromBuffer(c.data(), c.size()); // TODO: check result - // TODO: put in a helper + if (!tokenlist.createTokensFromBuffer(c.data(), c.size())) { + throw std::runtime_error("Can not tokenize condition"); + } + // generate links { std::stack lpar; @@ -653,23 +661,18 @@ namespace { return execute(tok->astTop(), p) == "True"; } } - throw std::runtime_error("Invalid condition: '" + condition + "'"); } + + private: - static std::string executeOp1(const Token* tok, const ProjectConfiguration &p, bool b=false) { - const std::string result = execute(tok->astOperand1(), p); - if (b) - return (result != "False" && !result.empty()) ? "True" : "False"; - return result; + static std::string executeOp1(const Token* tok, const ProjectConfiguration &p) { + return execute(tok->astOperand1(), p); } - static std::string executeOp2(const Token* tok, const ProjectConfiguration &p, bool b=false) { - const std::string result = execute(tok->astOperand2(), p); - if (b) - return (result != "False" && !result.empty()) ? "True" : "False"; - return result; + static std::string executeOp2(const Token* tok, const ProjectConfiguration &p) { + return execute(tok->astOperand2(), p); } static std::string execute(const Token* tok, const ProjectConfiguration &p) { @@ -677,25 +680,25 @@ namespace { throw std::runtime_error("Missing operator"); auto boolResult = [](bool b) -> std::string { return b ? "True" : "False"; }; if (tok->isUnaryOp("!")) - return boolResult(executeOp1(tok, p, true) == "False"); + return boolResult(executeOp1(tok, p) == "False"); if (tok->str() == "==") return boolResult(executeOp1(tok, p) == executeOp2(tok, p)); if (tok->str() == "!=") return boolResult(executeOp1(tok, p) != executeOp2(tok, p)); if (tok->str() == "&&") - return boolResult(executeOp1(tok, p, true) == "True" && executeOp2(tok, p, true) == "True"); + return boolResult(executeOp1(tok, p) == "True" && executeOp2(tok, p) == "True"); if (tok->str() == "||") - return boolResult(executeOp1(tok, p, true) == "True" || executeOp2(tok, p, true) == "True"); + return boolResult(executeOp1(tok, p) == "True" || executeOp2(tok, p) == "True"); if (tok->str() == "(" && Token::Match(tok->previous(), "$ ( %name% . %name% (")) { const std::string propertyName = tok->next()->str(); std::string propertyValue; if (propertyName == "Configuration") propertyValue = p.configuration; else if (propertyName == "Platform") - propertyValue = p.platform; + propertyValue = p.platformStr; else throw std::runtime_error("Unhandled property '" + propertyName + "'"); - const std::string method = tok->strAt(3); + const std::string& method = tok->strAt(3); std::string arg = executeOp2(tok->tokAt(4), p); if (arg.size() >= 2 && arg[0] == '\'') arg = arg.substr(1, arg.size() - 2); @@ -707,7 +710,7 @@ namespace { return boolResult(startsWith(propertyValue,arg)); throw std::runtime_error("Unhandled method '" + method + "'"); } - if (tok->str().size() >= 2 && tok->str()[0] == '\'') + if (tok->str().size() >= 2 && tok->str()[0] == '\'') // String Literal return tok->str(); throw std::runtime_error("Unknown/unhandled operator/operand '" + tok->str() + "'"); @@ -1702,5 +1705,5 @@ bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, c p.configuration = configuration; p.platformStr = platform; std::vector errors; - return ConditionalGroup::conditionIsTrue(condition, p, "file.vcxproj", errors) && errors.empty(); + return ConditionalGroup::evalCondition(condition, p); } diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index f5e095e6f30..1af544b3a40 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -708,8 +708,9 @@ class TestImportProject : public TestFixture { ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("('' == ''", "", ""), std::runtime_error, "'(' without closing ')'!"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '')", "", ""), std::runtime_error, "unmatched ')' in condition '' == '')"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Invalid condition: ''''"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Invalid condition: ''' == ''"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Can not tokenize condition"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Missing operator"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("' ' && ' '", "", ""), std::runtime_error, "Missing operator"); } // TODO: test fsParseCommand() From ce96fef3e27561fb42e3101a735bc7be2bf209f2 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 10 Apr 2026 16:10:30 +0200 Subject: [PATCH 4/6] format --- lib/importproject.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/importproject.cpp b/lib/importproject.cpp index ee279bd085d..68c036cf634 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -668,7 +668,7 @@ namespace { private: static std::string executeOp1(const Token* tok, const ProjectConfiguration &p) { - return execute(tok->astOperand1(), p); + return execute(tok->astOperand1(), p); } static std::string executeOp2(const Token* tok, const ProjectConfiguration &p) { @@ -678,7 +678,9 @@ namespace { static std::string execute(const Token* tok, const ProjectConfiguration &p) { if (!tok) throw std::runtime_error("Missing operator"); - auto boolResult = [](bool b) -> std::string { return b ? "True" : "False"; }; + auto boolResult = [](bool b) -> std::string { + return b ? "True" : "False"; + }; if (tok->isUnaryOp("!")) return boolResult(executeOp1(tok, p) == "False"); if (tok->str() == "==") From 97490327b693c3c5bc6ece0cd39141115df89b06 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 10 Apr 2026 16:15:13 +0200 Subject: [PATCH 5/6] note that it ok get get an result for an invalid expression --- test/testimportproject.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index 1af544b3a40..f7c53f3d3a6 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -710,7 +710,8 @@ class TestImportProject : public TestFixture { ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Invalid condition: ''''"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Can not tokenize condition"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Missing operator"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("' ' && ' '", "", ""), std::runtime_error, "Missing operator"); + // invalid expression in => no error. We are ok with that as long as we don't crash + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("' ' && ' '", "", "")); } // TODO: test fsParseCommand() From 866c0cec7362cd77be65eab3a612a69d9152c364 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 10 Apr 2026 17:54:36 +0200 Subject: [PATCH 6/6] make ci happy --- .github/workflows/selfcheck.yml | 2 +- lib/importproject.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/selfcheck.yml b/.github/workflows/selfcheck.yml index eac16bc318d..1adcc540cf9 100644 --- a/.github/workflows/selfcheck.yml +++ b/.github/workflows/selfcheck.yml @@ -121,7 +121,7 @@ jobs: - name: Self check (unusedFunction / no test / no gui) run: | - supprs="--suppress=unusedFunction:lib/errorlogger.h:196 --suppress=unusedFunction:lib/importproject.cpp:1673 --suppress=unusedFunction:lib/importproject.cpp:1697" + supprs="--suppress=unusedFunction:lib/errorlogger.h:197 --suppress=unusedFunction:lib/importproject.cpp:1660 --suppress=unusedFunction:lib/importproject.cpp:1684" ./cppcheck -q --template=selfcheck --error-exitcode=1 --library=cppcheck-lib -D__CPPCHECK__ -D__GNUC__ --enable=unusedFunction,information --exception-handling -rp=. --project=cmake.output.notest_nogui/compile_commands.json --suppressions-list=.selfcheck_unused_suppressions --inline-suppr $supprs env: DISABLE_VALUEFLOW: 1 diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 68c036cf634..401f1334b81 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -692,7 +692,7 @@ namespace { if (tok->str() == "||") return boolResult(executeOp1(tok, p) == "True" || executeOp2(tok, p) == "True"); if (tok->str() == "(" && Token::Match(tok->previous(), "$ ( %name% . %name% (")) { - const std::string propertyName = tok->next()->str(); + const std::string& propertyName = tok->strAt(1); std::string propertyValue; if (propertyName == "Configuration") propertyValue = p.configuration; @@ -1706,6 +1706,5 @@ bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, c ProjectConfiguration p; p.configuration = configuration; p.platformStr = platform; - std::vector errors; return ConditionalGroup::evalCondition(condition, p); }