// // Top node of grammar // See grammar for how the following methods relate to each // other. // private GenericExpressionNode Expr(string expression) { GenericExpressionNode node = BooleanTerm(expression); if (!_lexer.IsNext(Token.TokenType.EndOfInput)) { node = ExprPrime(expression, node); } #region REMOVE_COMPAT_WARNING // Check for potential change in behavior if (LoggingServices != null && !_warnedForExpression && node.PotentialAndOrConflict()) { // We only want to warn once even if there multiple () sub expressions _warnedForExpression = true; // Log a warning regarding the fact the expression may have been evaluated // incorrectly in earlier version of MSBuild LoggingServices.LogWarning(_logBuildEventContext, null, new BuildEventFileInfo(_elementLocation), "ConditionMaybeEvaluatedIncorrectly", expression); } #endregion return(node); }
// // Main entry point for parser. // You pass in the expression you want to parse, and you get an // ExpressionTree out the back end. // internal GenericExpressionNode Parse(string expression, ParserOptions optionSettings, ElementLocation elementLocation) { // We currently have no support (and no scenarios) for disallowing property references // in Conditions. ErrorUtilities.VerifyThrow(0 != (optionSettings & ParserOptions.AllowProperties), "Properties should always be allowed."); _options = optionSettings; _elementLocation = elementLocation; _lexer = new Scanner(expression, _options); if (!_lexer.Advance()) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(elementLocation, _lexer.GetErrorResource(), expression, errorPosition, _lexer.UnexpectedlyFound); } GenericExpressionNode node = Expr(expression); if (!_lexer.IsNext(Token.TokenType.EndOfInput)) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } return(node); }
private GenericExpressionNode BooleanTermPrime(string expression, GenericExpressionNode lhs) { if (_lexer.IsNext(Token.TokenType.EndOfInput)) { return(lhs); } else if (Same(expression, Token.TokenType.And)) { GenericExpressionNode rhs = RelationalExpr(expression); if (rhs == null) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } OperatorExpressionNode andNode = new AndExpressionNode(); andNode.LeftChild = lhs; andNode.RightChild = rhs; return(BooleanTermPrime(expression, andNode)); } else { // Should this be error case? return(lhs); } }
/// <summary> /// Expands properties and items in the argument, and verifies that the result is consistent /// with a scalar parameter type. /// </summary> /// <param name="function">Function name for errors</param> /// <param name="argumentNode">Argument to be expanded</param> /// <returns>Scalar result</returns> private string ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluator.IConditionEvaluationState state) { string argument = argumentNode.GetUnexpandedValue(state); IList <TaskItem> items; items = state.ExpandIntoTaskItems(argument); string expandedValue = String.Empty; if (items.Count == 0) { // Empty argument, that's fine. } else if (items.Count == 1) { expandedValue = items[0].ItemSpec; } else // too many items for the function { // We only allow a single item to be passed into a scalar parameter. ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, "CannotPassMultipleItemsIntoScalarFunction", function, argument, state.ExpandIntoString(argument)); } return(expandedValue); }
private List <string> ExpandArgumentAsFileList(GenericExpressionNode argumentNode, ConditionEvaluator.IConditionEvaluationState state, bool isFilePath = true) { string argument = argumentNode.GetUnexpandedValue(state); // Fix path before expansion if (isFilePath) { argument = FileUtilities.FixFilePath(argument); } IList <TaskItem> expanded = state.ExpandIntoTaskItems(argument); var expandedCount = expanded.Count; if (expandedCount == 0) { return(null); } var list = new List <string>(capacity: expandedCount); for (var i = 0; i < expandedCount; i++) { var item = expanded[i]; if (state.EvaluationDirectory != null && !Path.IsPathRooted(item.ItemSpec)) { list.Add(Path.GetFullPath(Path.Combine(state.EvaluationDirectory, item.ItemSpec))); } else { list.Add(item.ItemSpec); } } return(list); }
private void Args(string expression, List <GenericExpressionNode> arglist) { GenericExpressionNode arg = Arg(expression); arglist.Add(arg); if (Same(expression, Token.TokenType.Comma)) { Args(expression, arglist); } }
private GenericExpressionNode BooleanTerm(string expression) { GenericExpressionNode node = RelationalExpr(expression); if (node == null) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } if (!_lexer.IsNext(Token.TokenType.EndOfInput)) { node = BooleanTermPrime(expression, node); } return(node); }
private IEnumerable <string> ExpandArgumentAsFileList(GenericExpressionNode argumentNode, ConditionEvaluator.IConditionEvaluationState state, bool isFilePath = true) { string argument = argumentNode.GetUnexpandedValue(state); // Fix path before expansion if (isFilePath) { argument = FileUtilities.FixFilePath(argument); } return(state.ExpandIntoTaskItems(argument) .Select(i => state.EvaluationDirectory != null && !Path.IsPathRooted(i.ItemSpec) ? Path.GetFullPath(Path.Combine(state.EvaluationDirectory, i.ItemSpec)) : i.ItemSpec)); }
private GenericExpressionNode RelationalExpr(string expression) { { GenericExpressionNode lhs = Factor(expression); if (lhs == null) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } OperatorExpressionNode node = RelationalOperation(expression); if (node == null) { return(lhs); } GenericExpressionNode rhs = Factor(expression); node.LeftChild = lhs; node.RightChild = rhs; return(node); } }
private GenericExpressionNode ExprPrime(string expression, GenericExpressionNode lhs) { if (Same(expression, Token.TokenType.EndOfInput)) { return(lhs); } else if (Same(expression, Token.TokenType.Or)) { OperatorExpressionNode orNode = new OrExpressionNode(); GenericExpressionNode rhs = BooleanTerm(expression); orNode.LeftChild = lhs; orNode.RightChild = rhs; return(ExprPrime(expression, orNode)); } else { // I think this is ok. ExprPrime always shows up at // the rightmost side of the grammar rhs, the EndOfInput case // takes care of things return(lhs); } }
private GenericExpressionNode Factor(string expression) { // Checks for TokenTypes String, Numeric, Property, ItemMetadata, and ItemList. GenericExpressionNode arg = this.Arg(expression); // If it's one of those, return it. if (arg != null) { return(arg); } // If it's not one of those, check for other TokenTypes. Token current = _lexer.CurrentToken; if (Same(expression, Token.TokenType.Function)) { if (!Same(expression, Token.TokenType.LeftParenthesis)) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", _lexer.IsNextString(), errorPosition); return(null); } var arglist = new List <GenericExpressionNode>(); Arglist(expression, arglist); if (!Same(expression, Token.TokenType.RightParenthesis)) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); return(null); } return(new FunctionCallExpressionNode(current.String, arglist)); } else if (Same(expression, Token.TokenType.LeftParenthesis)) { GenericExpressionNode child = Expr(expression); if (Same(expression, Token.TokenType.RightParenthesis)) { return(child); } else { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } } else if (Same(expression, Token.TokenType.Not)) { OperatorExpressionNode notNode = new NotExpressionNode(); GenericExpressionNode expr = Factor(expression); if (expr == null) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } notNode.LeftChild = expr; return(notNode); } else { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } return(null); }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// Logging service may be null. /// </summary> internal static bool EvaluateConditionCollectingConditionedProperties <P, I> ( string condition, ParserOptions options, Expander <P, I> expander, ExpanderOptions expanderOptions, Dictionary <string, List <string> > conditionedPropertiesTable, string evaluationDirectory, ElementLocation elementLocation, ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache = null ) where P : class, IProperty where I : class, IItem { ErrorUtilities.VerifyThrowArgumentNull(condition, "condition"); ErrorUtilities.VerifyThrowArgumentNull(expander, "expander"); ErrorUtilities.VerifyThrowArgumentLength(evaluationDirectory, "evaluationDirectory"); ErrorUtilities.VerifyThrowArgumentNull(buildEventContext, "buildEventContext"); // An empty condition is equivalent to a "true" condition. if (condition.Length == 0) { return(true); } // If the condition wasn't empty, there must be a location for it ErrorUtilities.VerifyThrowArgumentNull(elementLocation, "elementLocation"); Hashtable cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees[(int)options]; // We only need to lock on writes to the table if (cachedExpressionTreesForCurrentOptions == null) { // Given property functions, casing in conditional expressions isn't necessarily ignored. cachedExpressionTreesForCurrentOptions = new Hashtable(50, StringComparer.Ordinal); lock (s_cachedExpressionTrees) { s_cachedExpressionTrees[(int)options] = cachedExpressionTreesForCurrentOptions; } } // VS stress tests could fill up this cache without end, for example if they use // random configuration names - those appear in conditional expressions. // So if we hit a limit that we should never hit in normal circumstances in VS, // and rarely, periodically hit in normal circumstances in large tree builds, // just clear out the cache. It can start repopulating again. Some kind of prioritized // aging isn't worth it: although the hit rate of these caches is excellent (nearly 100%) // the cost of reparsing expressions should the cache be cleared is not particularly large. // Loading Australian Government in VS, there are 3 of these tables, two with about 50 // entries and one with about 650 entries. So 3000 seems large enough. if (cachedExpressionTreesForCurrentOptions.Count > 3000) // threadsafe { lock (cachedExpressionTreesForCurrentOptions) { cachedExpressionTreesForCurrentOptions.Clear(); } } // Try and see if we have an expression tree for this condition already GenericExpressionNode parsedExpression = (GenericExpressionNode)cachedExpressionTreesForCurrentOptions[condition]; if (parsedExpression == null) { Parser conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, options, elementLocation); // It's possible two threads will add a different tree to the same entry in the hashtable, // but it should be rare and it's not a problem - the previous entry will be thrown away. // We could ensure no dupes with double check locking but it's not really necessary here. // Also, we don't want to lock on every read. lock (cachedExpressionTreesForCurrentOptions) { if (!s_disableExpressionCaching) { cachedExpressionTreesForCurrentOptions[condition] = parsedExpression; } } } bool result; ConditionEvaluationState <P, I> state = new ConditionEvaluationState <P, I>(condition, expander, expanderOptions, conditionedPropertiesTable, evaluationDirectory, elementLocation, projectRootElementCache); // We are evaluating this expression now and it can cache some state for the duration, // so we don't want multiple threads working on the same expression lock (parsedExpression) { try { result = parsedExpression.Evaluate(state); } finally { parsedExpression.ResetState(); } } return(result); }
private GenericExpressionNode BooleanTermPrime(string expression, GenericExpressionNode lhs) { if (_lexer.IsNext(Token.TokenType.EndOfInput)) { return lhs; } else if (Same(expression, Token.TokenType.And)) { GenericExpressionNode rhs = RelationalExpr(expression); if (null == rhs) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } OperatorExpressionNode andNode = new AndExpressionNode(); andNode.LeftChild = lhs; andNode.RightChild = rhs; return BooleanTermPrime(expression, andNode); } else { // Should this be error case? return lhs; } }
private GenericExpressionNode ExprPrime(string expression, GenericExpressionNode lhs) { if (Same(expression, Token.TokenType.EndOfInput)) { return lhs; } else if (Same(expression, Token.TokenType.Or)) { OperatorExpressionNode orNode = new OrExpressionNode(); GenericExpressionNode rhs = BooleanTerm(expression); orNode.LeftChild = lhs; orNode.RightChild = rhs; return ExprPrime(expression, orNode); } else { // I think this is ok. ExprPrime always shows up at // the rightmost side of the grammar rhs, the EndOfInput case // takes care of things return lhs; } }
/// <summary> /// Expands properties and items in the argument, and verifies that the result is consistent /// with a scalar parameter type. /// </summary> /// <param name="function">Function name for errors</param> /// <param name="argumentNode">Argument to be expanded</param> /// <returns>Scalar result</returns> private string ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluator.IConditionEvaluationState state) { string argument = argumentNode.GetUnexpandedValue(state); IList<TaskItem> items; items = state.ExpandIntoTaskItems(argument); string expandedValue = String.Empty; if (items.Count == 0) { // Empty argument, that's fine. } else if (items.Count == 1) { expandedValue = items[0].ItemSpec; } else // too many items for the function { // We only allow a single item to be passed into a scalar parameter. ProjectErrorUtilities.ThrowInvalidProject( state.ElementLocation, "CannotPassMultipleItemsIntoScalarFunction", function, argument, state.ExpandIntoString(argument)); } return expandedValue; }