3535#include < iostream>
3636#include < iterator>
3737#include < stack>
38+ #include < string>
3839#include < unordered_set>
3940#include < utility>
4041#include < vector>
@@ -505,6 +506,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
505506
506507namespace {
507508 struct ProjectConfiguration {
509+ ProjectConfiguration () = default ;
508510 explicit ProjectConfiguration (const tinyxml2::XMLElement *cfg) {
509511 const char *a = cfg->Attribute (" Include" );
510512 if (a)
@@ -548,45 +550,189 @@ namespace {
548550 }
549551 }
550552
553+ /* *
554+ * @brief evalExpression tries to evaluate the expression or throws an exception if it can not
555+ * @param expr The msbuild condition
556+ * @return the result of the expression
557+ * @throws std::runtime_error if the expression can not be evaluated
558+ */
559+ static bool evalExpression (const std::string& expr /* should be a std::string_view */ , const ProjectConfiguration& p)
560+ {
561+ const auto startsHere = [&](size_t & i, const std::string& string)
562+ {
563+ if (expr.compare (i, string.length (), string) == 0 )
564+ {
565+ i += string.length ();
566+ return true ;
567+ }
568+ return false ;
569+ };
570+ const auto skipSpaces = [&](size_t & i)
571+ {
572+ i = expr.find_first_not_of (' ' , i);
573+ };
574+ const auto expectAfterSpaces = [&](size_t &i, const std::string& expected, const char * error)
575+ {
576+ skipSpaces (i);
577+ if (!startsHere (i, expected))
578+ {
579+ throw std::runtime_error (error);
580+ }
581+ };
582+ const auto parseString = [&](size_t & i)
583+ {
584+ expectAfterSpaces (i, " \' " , " Expecting a start of a string!" );
585+ const auto start = i;
586+ const auto end = expr.find (' \' ' , i);
587+ if (end == std::string::npos)
588+ {
589+ throw std::runtime_error (" Expecting an end of a string!" );
590+ }
591+ std::string string = expr.substr (start, end - start);
592+ i = end + 1 ;
593+ return string;
594+ };
595+ bool currentVal = false ; // should be std::optional<bool>
596+ bool initialized = false ;
597+ for (std::size_t i = 0 ; i < expr.length ();)
598+ {
599+ if (expr[i] == ' ' )
600+ {
601+ ++i;
602+ continue ;
603+ }
604+ if (expr[i] == ' !' )
605+ {
606+ return !evalExpression (expr.substr (i + 1 ), p);
607+ }
608+ if (startsHere (i, " And" ))
609+ {
610+ if (!initialized)
611+ {
612+ throw std::runtime_error (" 'And' without previous expression!" );
613+ }
614+ return currentVal && evalExpression (expr.substr (i), p);
615+ }
616+ if (startsHere (i, " Or" ))
617+ {
618+ if (!initialized)
619+ {
620+ throw std::runtime_error (" 'Or' without previous expression!" );
621+ }
622+ return currentVal || evalExpression (expr.substr (i), p);
623+ }
624+ if (expr[i] == ' (' )
625+ {
626+ // find closing )
627+ int count = 1 ;
628+ bool inString = false ;
629+ auto end = std::string::npos;
630+ for (int j = i + 1 ; j < expr.length (); ++j)
631+ {
632+ if (inString)
633+ {
634+ if (expr[j] == ' \' ' )
635+ inString = false ;
636+ }
637+ else if (expr[j] == ' \' ' )
638+ {
639+ inString = true ;
640+ }
641+ else if (expr[j] == ' (' )
642+ {
643+ ++count;
644+ }
645+ else if (expr[j] == ' )' )
646+ {
647+ --count;
648+ if (count == 0 )
649+ {
650+ end = j;
651+ break ;
652+ }
653+ }
654+ }
655+ if (end == std::string::npos)
656+ {
657+ throw std::runtime_error (" '(' without closing ')'!" );
658+ }
659+ initialized = true ;
660+ currentVal = evalExpression (expr.substr (i + 1 , end - i - 1 ), p);
661+ i = end + 1 ;
662+ continue ;
663+ }
664+ if (expr[i] == ' \' ' )
665+ {
666+ auto left = parseString (i);
667+ skipSpaces (i);
668+ if (i + 4 >= expr.length ())
669+ {
670+ throw std::runtime_error (" Within a string comparison. We expect at least a =='' or !='' !" );
671+ }
672+ bool equal = expr[i] == ' =' ;
673+ i += 2 ;
674+ // expect a string now
675+ auto right = parseString (i);
676+ replaceAll (left, " $(Configuration)" , p.configuration );
677+ replaceAll (left, " $(Platform)" , p.platformStr );
678+ replaceAll (right, " $(Configuration)" , p.configuration );
679+ replaceAll (right, " $(Platform)" , p.platformStr );
680+ initialized = true ;
681+ currentVal = equal ? left == right : left != right;
682+ continue ;
683+ }
684+ if (startsHere (i, " $(Configuration." ))
685+ {
686+ initialized = true ;
687+ if (startsHere (i, " Contains" ))
688+ {
689+ expectAfterSpaces (i, " (" , " Expected start of call" );
690+ auto containsParam = parseString (i);
691+ currentVal = p.configuration .find (containsParam) != std::string::npos;
692+ }
693+ else if (startsHere (i, " StartsWith" ))
694+ {
695+ expectAfterSpaces (i, " (" , " Expected start of call" );
696+ auto startsWithParam = parseString (i);
697+ currentVal = p.configuration .find (startsWithParam) == 0 ;
698+ }
699+ else if (startsHere (i, " EndsWith" ))
700+ {
701+ expectAfterSpaces (i, " (" , " Expected start of call" );
702+ auto endsWithParam = parseString (i);
703+ currentVal = p.configuration .rfind (endsWithParam) == p.configuration .length () - endsWithParam.length ();
704+ }
705+ else
706+ {
707+ throw std::runtime_error (" Unexpected function call!" );
708+ }
709+ expectAfterSpaces (i, " )" , " Expecting end of function call!" );
710+ expectAfterSpaces (i, " )" , " Expecting end of expression!" );
711+ continue ;
712+ }
713+ throw std::runtime_error (" Unhandled expression!" );
714+ }
715+ if (!initialized)
716+ {
717+ throw std::runtime_error (" Expected expression here!" );
718+ }
719+ return currentVal;
720+ }
721+
551722 // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
552723 // properties are .NET String objects and you can call any of its members on them
553- bool conditionIsTrue (const ProjectConfiguration &p) const {
724+ bool conditionIsTrue (const ProjectConfiguration &p, std::vector<std::string> &errors, const std::string &filename ) const {
554725 if (mCondition .empty ())
555726 return true ;
556- std::string c = ' (' + mCondition + " );" ;
557- replaceAll (c, " $(Configuration)" , p.configuration );
558- replaceAll (c, " $(Platform)" , p.platformStr );
559-
560- // TODO: improve evaluation
561- const Settings s;
562- TokenList tokenlist (s, Standards::Language::C);
563- tokenlist.createTokensFromBuffer (c.data (), c.size ()); // TODO: check result
564- // TODO: put in a helper
565- // generate links
727+ try
566728 {
567- std::stack<Token*> lpar;
568- for (Token* tok2 = tokenlist.front (); tok2; tok2 = tok2->next ()) {
569- if (tok2->str () == " (" )
570- lpar.push (tok2);
571- else if (tok2->str () == " )" ) {
572- if (lpar.empty ())
573- break ;
574- Token::createMutualLinks (lpar.top (), tok2);
575- lpar.pop ();
576- }
577- }
729+ return evalExpression (mCondition , p);
578730 }
579- tokenlist.createAst ();
580- for (const Token *tok = tokenlist.front (); tok; tok = tok->next ()) {
581- if (tok->str () == " (" && tok->astOperand1 () && tok->astOperand2 ()) {
582- // TODO: this is wrong - it is Contains() not Equals()
583- if (tok->astOperand1 ()->expressionString () == " Configuration.Contains" )
584- return (' \' ' + p.configuration + ' \' ' ) == tok->astOperand2 ()->str ();
585- }
586- if (tok->str () == " ==" && tok->astOperand1 () && tok->astOperand2 () && tok->astOperand1 ()->str () == tok->astOperand2 ()->str ())
587- return true ;
731+ catch (const std::runtime_error& r)
732+ {
733+ errors.emplace_back (filename + " : Can not evaluate condition '" + mCondition + " ': " + r.what ());
734+ return false ;
588735 }
589- return false ;
590736 }
591737 private:
592738 std::string mCondition ;
@@ -659,6 +805,17 @@ namespace {
659805 };
660806}
661807
808+ // only used by tests (testimportproject.cpp::testVcxprojConditions):
809+ // cppcheck-suppress unusedFunction
810+ bool cppcheck::testing::evaluateVcxprojCondition (const std::string& condition, const std::string& configuration,
811+ const std::string& platform)
812+ {
813+ ProjectConfiguration p;
814+ p.configuration = configuration;
815+ p.platformStr = platform;
816+ return ConditionalGroup::evalExpression (condition, p);
817+ }
818+
662819static std::list<std::string> toStringList (const std::string &s)
663820{
664821 std::list<std::string> ret;
@@ -894,7 +1051,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
8941051 }
8951052 std::string additionalIncludePaths;
8961053 for (const ItemDefinitionGroup &i : itemDefinitionGroupList) {
897- if (!i.conditionIsTrue (p))
1054+ if (!i.conditionIsTrue (p, errors, cfilename ))
8981055 continue ;
8991056 fs.standard = Standards::getCPP (i.cppstd );
9001057 fs.defines += ' ;' + i.preprocessorDefinitions ;
@@ -912,7 +1069,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
9121069 }
9131070 bool useUnicode = false ;
9141071 for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) {
915- if (!c.conditionIsTrue (p))
1072+ if (!c.conditionIsTrue (p, errors, cfilename ))
9161073 continue ;
9171074 // in msbuild the last definition wins
9181075 useUnicode = c.useUnicode ;
0 commit comments