@@ -558,6 +558,7 @@ bool ImportProject::importSlnx(const std::string& filename, const std::vector<st
558558
559559namespace {
560560 struct ProjectConfiguration {
561+ ProjectConfiguration () = default ;
561562 explicit ProjectConfiguration (const tinyxml2::XMLElement *cfg) {
562563 const char *a = cfg->Attribute (" Include" );
563564 if (a)
@@ -603,18 +604,30 @@ namespace {
603604
604605 // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
605606 // properties are .NET String objects and you can call any of its members on them
606- bool conditionIsTrue (const ProjectConfiguration &p) const {
607+ bool conditionIsTrue (const ProjectConfiguration &p, const std::string &filename, std::vector<std::string> &errors ) const {
607608 if (mCondition .empty ())
608609 return true ;
609- std::string c = ' (' + mCondition + " );" ;
610+ try {
611+ return evalCondition (mCondition , p);
612+ }
613+ catch (const std::runtime_error& r)
614+ {
615+ errors.emplace_back (filename + " : Can not evaluate condition '" + mCondition + " ': " + r.what ());
616+ return false ;
617+ }
618+ }
619+
620+ static bool evalCondition (const std::string& condition, const ProjectConfiguration &p) {
621+ std::string c = ' (' + condition + " )" ;
610622 replaceAll (c, " $(Configuration)" , p.configuration );
611623 replaceAll (c, " $(Platform)" , p.platformStr );
612624
613- // TODO: improve evaluation
614625 const Settings s;
615626 TokenList tokenlist (s, Standards::Language::C);
616- tokenlist.createTokensFromBuffer (c.data (), c.size ()); // TODO: check result
617- // TODO: put in a helper
627+ if (!tokenlist.createTokensFromBuffer (c.data (), c.size ())) {
628+ throw std::runtime_error (" Can not tokenize condition" );
629+ }
630+
618631 // generate links
619632 {
620633 std::stack<Token*> lpar;
@@ -623,25 +636,88 @@ namespace {
623636 lpar.push (tok2);
624637 else if (tok2->str () == " )" ) {
625638 if (lpar.empty ())
626- break ;
639+ throw std::runtime_error ( " unmatched ')' in condition " + condition) ;
627640 Token::createMutualLinks (lpar.top (), tok2);
628641 lpar.pop ();
629642 }
630643 }
644+ if (!lpar.empty ())
645+ throw std::runtime_error (" '(' without closing ')'!" );
646+ }
647+
648+ // Replace "And" and "Or" with "&&" and "||"
649+ for (Token *tok = tokenlist.front (); tok; tok = tok->next ()) {
650+ if (tok->str () == " And" )
651+ tok->str (" &&" );
652+ else if (tok->str () == " Or" )
653+ tok->str (" ||" );
631654 }
655+
632656 tokenlist.createAst ();
657+
658+ // Locate ast top and execute the condition
633659 for (const Token *tok = tokenlist.front (); tok; tok = tok->next ()) {
634- if (tok->str () == " (" && tok->astOperand1 () && tok->astOperand2 ()) {
635- // TODO: this is wrong - it is Contains() not Equals()
636- if (tok->astOperand1 ()->expressionString () == " Configuration.Contains" )
637- return (' \' ' + p.configuration + ' \' ' ) == tok->astOperand2 ()->str ();
660+ if (tok->astParent ()) {
661+ return execute (tok->astTop (), p) == " True" ;
638662 }
639- if (tok->str () == " ==" && tok->astOperand1 () && tok->astOperand2 () && tok->astOperand1 ()->str () == tok->astOperand2 ()->str ())
640- return true ;
641663 }
642- return false ;
664+ throw std::runtime_error ( " Invalid condition: ' " + condition + " ' " ) ;
643665 }
666+
667+
644668 private:
669+
670+ static std::string executeOp1 (const Token* tok, const ProjectConfiguration &p) {
671+ return execute (tok->astOperand1 (), p);
672+ }
673+
674+ static std::string executeOp2 (const Token* tok, const ProjectConfiguration &p) {
675+ return execute (tok->astOperand2 (), p);
676+ }
677+
678+ static std::string execute (const Token* tok, const ProjectConfiguration &p) {
679+ if (!tok)
680+ throw std::runtime_error (" Missing operator" );
681+ auto boolResult = [](bool b) -> std::string {
682+ return b ? " True" : " False" ;
683+ };
684+ if (tok->isUnaryOp (" !" ))
685+ return boolResult (executeOp1 (tok, p) == " False" );
686+ if (tok->str () == " ==" )
687+ return boolResult (executeOp1 (tok, p) == executeOp2 (tok, p));
688+ if (tok->str () == " !=" )
689+ return boolResult (executeOp1 (tok, p) != executeOp2 (tok, p));
690+ if (tok->str () == " &&" )
691+ return boolResult (executeOp1 (tok, p) == " True" && executeOp2 (tok, p) == " True" );
692+ if (tok->str () == " ||" )
693+ return boolResult (executeOp1 (tok, p) == " True" || executeOp2 (tok, p) == " True" );
694+ if (tok->str () == " (" && Token::Match (tok->previous (), " $ ( %name% . %name% (" )) {
695+ const std::string& propertyName = tok->strAt (1 );
696+ std::string propertyValue;
697+ if (propertyName == " Configuration" )
698+ propertyValue = p.configuration ;
699+ else if (propertyName == " Platform" )
700+ propertyValue = p.platformStr ;
701+ else
702+ throw std::runtime_error (" Unhandled property '" + propertyName + " '" );
703+ const std::string& method = tok->strAt (3 );
704+ std::string arg = executeOp2 (tok->tokAt (4 ), p);
705+ if (arg.size () >= 2 && arg[0 ] == ' \' ' )
706+ arg = arg.substr (1 , arg.size () - 2 );
707+ if (method == " Contains" )
708+ return boolResult (propertyValue.find (arg) != std::string::npos);
709+ if (method == " EndsWith" )
710+ return boolResult (endsWith (propertyValue,arg.c_str (),arg.size ()));
711+ if (method == " StartsWith" )
712+ return boolResult (startsWith (propertyValue,arg));
713+ throw std::runtime_error (" Unhandled method '" + method + " '" );
714+ }
715+ if (tok->str ().size () >= 2 && tok->str ()[0 ] == ' \' ' ) // String Literal
716+ return tok->str ();
717+
718+ throw std::runtime_error (" Unknown/unhandled operator/operand '" + tok->str () + " '" );
719+ }
720+
645721 std::string mCondition ;
646722 };
647723
@@ -947,7 +1023,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
9471023 }
9481024 std::string additionalIncludePaths;
9491025 for (const ItemDefinitionGroup &i : itemDefinitionGroupList) {
950- if (!i.conditionIsTrue (p))
1026+ if (!i.conditionIsTrue (p, cfilename, errors ))
9511027 continue ;
9521028 fs.standard = Standards::getCPP (i.cppstd );
9531029 fs.defines += ' ;' + i.preprocessorDefinitions ;
@@ -965,7 +1041,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
9651041 }
9661042 bool useUnicode = false ;
9671043 for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) {
968- if (!c.conditionIsTrue (p))
1044+ if (!c.conditionIsTrue (p, cfilename, errors ))
9691045 continue ;
9701046 // in msbuild the last definition wins
9711047 useUnicode = c.useUnicode ;
@@ -1622,3 +1698,13 @@ void ImportProject::setRelativePaths(const std::string &filename)
16221698 }
16231699}
16241700
1701+ // only used by tests (testimportproject.cpp::testVcxprojConditions):
1702+ // cppcheck-suppress unusedFunction
1703+ bool cppcheck::testing::evaluateVcxprojCondition (const std::string& condition, const std::string& configuration,
1704+ const std::string& platform)
1705+ {
1706+ ProjectConfiguration p;
1707+ p.configuration = configuration;
1708+ p.platformStr = platform;
1709+ return ConditionalGroup::evalCondition (condition, p);
1710+ }
0 commit comments