3535#include < iostream>
3636#include < iterator>
3737#include < stack>
38+ #include < string>
3839#include < unordered_set>
3940#include < utility>
4041#include < vector>
@@ -558,6 +559,7 @@ bool ImportProject::importSlnx(const std::string& filename, const std::vector<st
558559
559560namespace {
560561 struct ProjectConfiguration {
562+ ProjectConfiguration () = default ;
561563 explicit ProjectConfiguration (const tinyxml2::XMLElement *cfg) {
562564 const char *a = cfg->Attribute (" Include" );
563565 if (a)
@@ -601,45 +603,189 @@ namespace {
601603 }
602604 }
603605
606+ /* *
607+ * @brief evalExpression tries to evaluate the expression or throws an exception if it can not
608+ * @param expr The msbuild condition
609+ * @return the result of the expression
610+ * @throws std::runtime_error if the expression can not be evaluated
611+ */
612+ static bool evalExpression (const std::string& expr /* should be a std::string_view */ , const ProjectConfiguration& p)
613+ {
614+ const auto startsHere = [&](size_t & i, const std::string& string)
615+ {
616+ if (expr.compare (i, string.length (), string) == 0 )
617+ {
618+ i += string.length ();
619+ return true ;
620+ }
621+ return false ;
622+ };
623+ const auto skipSpaces = [&](size_t & i)
624+ {
625+ i = expr.find_first_not_of (' ' , i);
626+ };
627+ const auto expectAfterSpaces = [&](size_t &i, const std::string& expected, const char * error)
628+ {
629+ skipSpaces (i);
630+ if (!startsHere (i, expected))
631+ {
632+ throw std::runtime_error (error);
633+ }
634+ };
635+ const auto parseString = [&](size_t & i)
636+ {
637+ expectAfterSpaces (i, " \' " , " Expecting a start of a string!" );
638+ const auto start = i;
639+ const auto end = expr.find (' \' ' , i);
640+ if (end == std::string::npos)
641+ {
642+ throw std::runtime_error (" Expecting an end of a string!" );
643+ }
644+ std::string string = expr.substr (start, end - start);
645+ i = end + 1 ;
646+ return string;
647+ };
648+ bool currentVal = false ; // should be std::optional<bool>
649+ bool initialized = false ;
650+ for (std::size_t i = 0 ; i < expr.length ();)
651+ {
652+ if (expr[i] == ' ' )
653+ {
654+ ++i;
655+ continue ;
656+ }
657+ if (expr[i] == ' !' )
658+ {
659+ return !evalExpression (expr.substr (i + 1 ), p);
660+ }
661+ if (startsHere (i, " And" ))
662+ {
663+ if (!initialized)
664+ {
665+ throw std::runtime_error (" 'And' without previous expression!" );
666+ }
667+ return currentVal && evalExpression (expr.substr (i), p);
668+ }
669+ if (startsHere (i, " Or" ))
670+ {
671+ if (!initialized)
672+ {
673+ throw std::runtime_error (" 'Or' without previous expression!" );
674+ }
675+ return currentVal || evalExpression (expr.substr (i), p);
676+ }
677+ if (expr[i] == ' (' )
678+ {
679+ // find closing )
680+ int count = 1 ;
681+ bool inString = false ;
682+ auto end = std::string::npos;
683+ for (int j = i + 1 ; j < expr.length (); ++j)
684+ {
685+ if (inString)
686+ {
687+ if (expr[j] == ' \' ' )
688+ inString = false ;
689+ }
690+ else if (expr[j] == ' \' ' )
691+ {
692+ inString = true ;
693+ }
694+ else if (expr[j] == ' (' )
695+ {
696+ ++count;
697+ }
698+ else if (expr[j] == ' )' )
699+ {
700+ --count;
701+ if (count == 0 )
702+ {
703+ end = j;
704+ break ;
705+ }
706+ }
707+ }
708+ if (end == std::string::npos)
709+ {
710+ throw std::runtime_error (" '(' without closing ')'!" );
711+ }
712+ initialized = true ;
713+ currentVal = evalExpression (expr.substr (i + 1 , end - i - 1 ), p);
714+ i = end + 1 ;
715+ continue ;
716+ }
717+ if (expr[i] == ' \' ' )
718+ {
719+ auto left = parseString (i);
720+ skipSpaces (i);
721+ if (i + 4 >= expr.length ())
722+ {
723+ throw std::runtime_error (" Within a string comparison. We expect at least a =='' or !='' !" );
724+ }
725+ bool equal = expr[i] == ' =' ;
726+ i += 2 ;
727+ // expect a string now
728+ auto right = parseString (i);
729+ replaceAll (left, " $(Configuration)" , p.configuration );
730+ replaceAll (left, " $(Platform)" , p.platformStr );
731+ replaceAll (right, " $(Configuration)" , p.configuration );
732+ replaceAll (right, " $(Platform)" , p.platformStr );
733+ initialized = true ;
734+ currentVal = equal ? left == right : left != right;
735+ continue ;
736+ }
737+ if (startsHere (i, " $(Configuration." ))
738+ {
739+ initialized = true ;
740+ if (startsHere (i, " Contains" ))
741+ {
742+ expectAfterSpaces (i, " (" , " Expected start of call" );
743+ auto containsParam = parseString (i);
744+ currentVal = p.configuration .find (containsParam) != std::string::npos;
745+ }
746+ else if (startsHere (i, " StartsWith" ))
747+ {
748+ expectAfterSpaces (i, " (" , " Expected start of call" );
749+ auto startsWithParam = parseString (i);
750+ currentVal = p.configuration .find (startsWithParam) == 0 ;
751+ }
752+ else if (startsHere (i, " EndsWith" ))
753+ {
754+ expectAfterSpaces (i, " (" , " Expected start of call" );
755+ auto endsWithParam = parseString (i);
756+ currentVal = p.configuration .rfind (endsWithParam) == p.configuration .length () - endsWithParam.length ();
757+ }
758+ else
759+ {
760+ throw std::runtime_error (" Unexpected function call!" );
761+ }
762+ expectAfterSpaces (i, " )" , " Expecting end of function call!" );
763+ expectAfterSpaces (i, " )" , " Expecting end of expression!" );
764+ continue ;
765+ }
766+ throw std::runtime_error (" Unhandled expression!" );
767+ }
768+ if (!initialized)
769+ {
770+ throw std::runtime_error (" Expected expression here!" );
771+ }
772+ return currentVal;
773+ }
774+
604775 // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
605776 // properties are .NET String objects and you can call any of its members on them
606- bool conditionIsTrue (const ProjectConfiguration &p) const {
777+ bool conditionIsTrue (const ProjectConfiguration &p, std::vector<std::string> &errors, const std::string &filename ) const {
607778 if (mCondition .empty ())
608779 return true ;
609- std::string c = ' (' + mCondition + " );" ;
610- replaceAll (c, " $(Configuration)" , p.configuration );
611- replaceAll (c, " $(Platform)" , p.platformStr );
612-
613- // TODO: improve evaluation
614- const Settings s;
615- TokenList tokenlist (s, Standards::Language::C);
616- tokenlist.createTokensFromBuffer (c.data (), c.size ()); // TODO: check result
617- // TODO: put in a helper
618- // generate links
780+ try
619781 {
620- std::stack<Token*> lpar;
621- for (Token* tok2 = tokenlist.front (); tok2; tok2 = tok2->next ()) {
622- if (tok2->str () == " (" )
623- lpar.push (tok2);
624- else if (tok2->str () == " )" ) {
625- if (lpar.empty ())
626- break ;
627- Token::createMutualLinks (lpar.top (), tok2);
628- lpar.pop ();
629- }
630- }
782+ return evalExpression (mCondition , p);
631783 }
632- tokenlist.createAst ();
633- 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 ();
638- }
639- if (tok->str () == " ==" && tok->astOperand1 () && tok->astOperand2 () && tok->astOperand1 ()->str () == tok->astOperand2 ()->str ())
640- return true ;
784+ catch (const std::runtime_error& r)
785+ {
786+ errors.emplace_back (filename + " : Can not evaluate condition '" + mCondition + " ': " + r.what ());
787+ return false ;
641788 }
642- return false ;
643789 }
644790 private:
645791 std::string mCondition ;
@@ -712,6 +858,17 @@ namespace {
712858 };
713859}
714860
861+ // only used by tests (testimportproject.cpp::testVcxprojConditions):
862+ // cppcheck-suppress unusedFunction
863+ bool cppcheck::testing::evaluateVcxprojCondition (const std::string& condition, const std::string& configuration,
864+ const std::string& platform)
865+ {
866+ ProjectConfiguration p;
867+ p.configuration = configuration;
868+ p.platformStr = platform;
869+ return ConditionalGroup::evalExpression (condition, p);
870+ }
871+
715872static std::list<std::string> toStringList (const std::string &s)
716873{
717874 std::list<std::string> ret;
@@ -947,7 +1104,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
9471104 }
9481105 std::string additionalIncludePaths;
9491106 for (const ItemDefinitionGroup &i : itemDefinitionGroupList) {
950- if (!i.conditionIsTrue (p))
1107+ if (!i.conditionIsTrue (p, errors, cfilename ))
9511108 continue ;
9521109 fs.standard = Standards::getCPP (i.cppstd );
9531110 fs.defines += ' ;' + i.preprocessorDefinitions ;
@@ -965,7 +1122,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
9651122 }
9661123 bool useUnicode = false ;
9671124 for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) {
968- if (!c.conditionIsTrue (p))
1125+ if (!c.conditionIsTrue (p, errors, cfilename ))
9691126 continue ;
9701127 // in msbuild the last definition wins
9711128 useUnicode = c.useUnicode ;
0 commit comments