1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System.Globalization;
5 using System.Collections;
6 using System.Xml;
7 using System;
8 using Microsoft.Build.Shared;
9 using ElementLocation = Microsoft.Build.Construction.ElementLocation;
10 using Microsoft.Build.Evaluation;
11 using Microsoft.Build.Framework;
12 
13 namespace Microsoft.Build.Evaluation
14 {
15     using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
16 
17     [Flags]
18     internal enum ParserOptions
19     {
20         None = 0x0,
21         AllowProperties = 0x1,
22         AllowItemLists = 0x2,
23         AllowPropertiesAndItemLists = AllowProperties | AllowItemLists,
24         AllowBuiltInMetadata = 0x4,
25         AllowCustomMetadata = 0x8,
26         AllowItemMetadata = AllowBuiltInMetadata | AllowCustomMetadata,
27         AllowPropertiesAndItemMetadata = AllowProperties | AllowItemMetadata,
28         AllowPropertiesAndCustomMetadata = AllowProperties | AllowCustomMetadata,
29         AllowAll = AllowProperties | AllowItemLists | AllowItemMetadata
30     };
31 
32     /// <summary>
33     /// This class implements the grammar for complex conditionals.
34     ///
35     /// The usage is:
36     ///    Parser p = new Parser(CultureInfo);
37     ///    ExpressionTree t = p.Parse(expression, XmlNode);
38     ///
39     /// The expression tree can then be evaluated and re-evaluated as needed.
40     /// </summary>
41     /// <remarks>
42     /// UNDONE: When we copied over the conditionals code, we didn't copy over the unit tests for scanner, parser, and expression tree.
43     /// </remarks>
44     internal sealed class Parser
45     {
46         private Scanner _lexer;
47         private ParserOptions _options;
48         private ElementLocation _elementLocation;
49         internal int errorPosition = 0; // useful for unit tests
50 
51         #region REMOVE_COMPAT_WARNING
52 
53         private bool _warnedForExpression = false;
54 
55         private BuildEventContext _logBuildEventContext;
56         /// <summary>
57         ///  Location contextual information which are attached to logging events to
58         ///  say where they are in relation to the process, engine, project, target,task which is executing
59         /// </summary>
60         internal BuildEventContext LogBuildEventContext
61         {
62             get
63             {
64                 return _logBuildEventContext;
65             }
66             set
67             {
68                 _logBuildEventContext = value;
69             }
70         }
71         private ILoggingService _loggingServices;
72         /// <summary>
73         /// Engine Logging Service reference where events will be logged to
74         /// </summary>
75         internal ILoggingService LoggingServices
76         {
77             set
78             {
79                 _loggingServices = value;
80             }
81 
82             get
83             {
84                 return _loggingServices;
85             }
86         }
87         #endregion
88 
Parser()89         internal Parser()
90         {
91             // nothing to see here, move along.
92         }
93 
94         //
95         // Main entry point for parser.
96         // You pass in the expression you want to parse, and you get an
97         // ExpressionTree out the back end.
98         //
Parse(string expression, ParserOptions optionSettings, ElementLocation elementLocation)99         internal GenericExpressionNode Parse(string expression, ParserOptions optionSettings, ElementLocation elementLocation)
100         {
101             // We currently have no support (and no scenarios) for disallowing property references
102             // in Conditions.
103             ErrorUtilities.VerifyThrow(0 != (optionSettings & ParserOptions.AllowProperties),
104                 "Properties should always be allowed.");
105 
106             _options = optionSettings;
107             _elementLocation = elementLocation;
108 
109             _lexer = new Scanner(expression, _options);
110             if (!_lexer.Advance())
111             {
112                 errorPosition = _lexer.GetErrorPosition();
113                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, _lexer.GetErrorResource(), expression, errorPosition, _lexer.UnexpectedlyFound);
114             }
115             GenericExpressionNode node = Expr(expression);
116             if (!_lexer.IsNext(Token.TokenType.EndOfInput))
117             {
118                 errorPosition = _lexer.GetErrorPosition();
119                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
120             }
121             return node;
122         }
123 
124         //
125         // Top node of grammar
126         //    See grammar for how the following methods relate to each
127         //    other.
128         //
Expr(string expression)129         private GenericExpressionNode Expr(string expression)
130         {
131             GenericExpressionNode node = BooleanTerm(expression);
132             if (!_lexer.IsNext(Token.TokenType.EndOfInput))
133             {
134                 node = ExprPrime(expression, node);
135             }
136 
137 
138             #region REMOVE_COMPAT_WARNING
139             // Check for potential change in behavior
140             if (LoggingServices != null && !_warnedForExpression &&
141                 node.PotentialAndOrConflict())
142             {
143                 // We only want to warn once even if there multiple () sub expressions
144                 _warnedForExpression = true;
145 
146                 // Log a warning regarding the fact the expression may have been evaluated
147                 // incorrectly in earlier version of MSBuild
148                 LoggingServices.LogWarning(_logBuildEventContext, null, new BuildEventFileInfo(_elementLocation), "ConditionMaybeEvaluatedIncorrectly", expression);
149             }
150             #endregion
151 
152             return node;
153         }
154 
ExprPrime(string expression, GenericExpressionNode lhs)155         private GenericExpressionNode ExprPrime(string expression, GenericExpressionNode lhs)
156         {
157             if (Same(expression, Token.TokenType.EndOfInput))
158             {
159                 return lhs;
160             }
161             else if (Same(expression, Token.TokenType.Or))
162             {
163                 OperatorExpressionNode orNode = new OrExpressionNode();
164                 GenericExpressionNode rhs = BooleanTerm(expression);
165                 orNode.LeftChild = lhs;
166                 orNode.RightChild = rhs;
167                 return ExprPrime(expression, orNode);
168             }
169             else
170             {
171                 // I think this is ok.  ExprPrime always shows up at
172                 // the rightmost side of the grammar rhs, the EndOfInput case
173                 // takes care of things
174                 return lhs;
175             }
176         }
177 
BooleanTerm(string expression)178         private GenericExpressionNode BooleanTerm(string expression)
179         {
180             GenericExpressionNode node = RelationalExpr(expression);
181             if (null == node)
182             {
183                 errorPosition = _lexer.GetErrorPosition();
184                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
185             }
186 
187             if (!_lexer.IsNext(Token.TokenType.EndOfInput))
188             {
189                 node = BooleanTermPrime(expression, node);
190             }
191             return node;
192         }
193 
BooleanTermPrime(string expression, GenericExpressionNode lhs)194         private GenericExpressionNode BooleanTermPrime(string expression, GenericExpressionNode lhs)
195         {
196             if (_lexer.IsNext(Token.TokenType.EndOfInput))
197             {
198                 return lhs;
199             }
200             else if (Same(expression, Token.TokenType.And))
201             {
202                 GenericExpressionNode rhs = RelationalExpr(expression);
203                 if (null == rhs)
204                 {
205                     errorPosition = _lexer.GetErrorPosition();
206                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
207                 }
208 
209                 OperatorExpressionNode andNode = new AndExpressionNode();
210                 andNode.LeftChild = lhs;
211                 andNode.RightChild = rhs;
212                 return BooleanTermPrime(expression, andNode);
213             }
214             else
215             {
216                 // Should this be error case?
217                 return lhs;
218             }
219         }
220 
RelationalExpr(string expression)221         private GenericExpressionNode RelationalExpr(string expression)
222         {
223             {
224                 GenericExpressionNode lhs = Factor(expression);
225                 if (null == lhs)
226                 {
227                     errorPosition = _lexer.GetErrorPosition();
228                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
229                 }
230 
231                 OperatorExpressionNode node = RelationalOperation(expression);
232                 if (node == null)
233                 {
234                     return lhs;
235                 }
236                 GenericExpressionNode rhs = Factor(expression);
237                 node.LeftChild = lhs;
238                 node.RightChild = rhs;
239                 return node;
240             }
241         }
242 
243 
RelationalOperation(string expression)244         private OperatorExpressionNode RelationalOperation(string expression)
245         {
246             OperatorExpressionNode node = null;
247             if (Same(expression, Token.TokenType.LessThan))
248             {
249                 node = new LessThanExpressionNode();
250             }
251             else if (Same(expression, Token.TokenType.GreaterThan))
252             {
253                 node = new GreaterThanExpressionNode();
254             }
255             else if (Same(expression, Token.TokenType.LessThanOrEqualTo))
256             {
257                 node = new LessThanOrEqualExpressionNode();
258             }
259             else if (Same(expression, Token.TokenType.GreaterThanOrEqualTo))
260             {
261                 node = new GreaterThanOrEqualExpressionNode();
262             }
263             else if (Same(expression, Token.TokenType.EqualTo))
264             {
265                 node = new EqualExpressionNode();
266             }
267             else if (Same(expression, Token.TokenType.NotEqualTo))
268             {
269                 node = new NotEqualExpressionNode();
270             }
271             return node;
272         }
273 
Factor(string expression)274         private GenericExpressionNode Factor(string expression)
275         {
276             // Checks for TokenTypes String, Numeric, Property, ItemMetadata, and ItemList.
277             GenericExpressionNode arg = this.Arg(expression);
278 
279             // If it's one of those, return it.
280             if (arg != null)
281             {
282                 return arg;
283             }
284 
285             // If it's not one of those, check for other TokenTypes.
286             Token current = _lexer.CurrentToken;
287             if (Same(expression, Token.TokenType.Function))
288             {
289                 if (!Same(expression, Token.TokenType.LeftParenthesis))
290                 {
291                     errorPosition = _lexer.GetErrorPosition();
292                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", _lexer.IsNextString(), errorPosition);
293                     return null;
294                 }
295                 ArrayList arglist = new ArrayList();
296                 Arglist(expression, arglist);
297                 if (!Same(expression, Token.TokenType.RightParenthesis))
298                 {
299                     errorPosition = _lexer.GetErrorPosition();
300                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
301                     return null;
302                 }
303                 return new FunctionCallExpressionNode(current.String, arglist);
304             }
305             else if (Same(expression, Token.TokenType.LeftParenthesis))
306             {
307                 GenericExpressionNode child = Expr(expression);
308                 if (Same(expression, Token.TokenType.RightParenthesis))
309                     return child;
310                 else
311                 {
312                     errorPosition = _lexer.GetErrorPosition();
313                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
314                 }
315             }
316             else if (Same(expression, Token.TokenType.Not))
317             {
318                 OperatorExpressionNode notNode = new NotExpressionNode();
319                 GenericExpressionNode expr = Factor(expression);
320                 if (expr == null)
321                 {
322                     errorPosition = _lexer.GetErrorPosition();
323                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
324                 }
325                 notNode.LeftChild = expr;
326                 return notNode;
327             }
328             else
329             {
330                 errorPosition = _lexer.GetErrorPosition();
331                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition);
332             }
333             return null;
334         }
335 
Arglist(string expression, ArrayList arglist)336         private void Arglist(string expression, ArrayList arglist)
337         {
338             if (!_lexer.IsNext(Token.TokenType.RightParenthesis))
339                 Args(expression, arglist);
340         }
341 
Args(string expression, ArrayList arglist)342         private void Args(string expression, ArrayList arglist)
343         {
344             GenericExpressionNode arg = Arg(expression);
345             arglist.Add(arg);
346             if (Same(expression, Token.TokenType.Comma))
347             {
348                 Args(expression, arglist);
349             }
350         }
351 
Arg(string expression)352         private GenericExpressionNode Arg(string expression)
353         {
354             Token current = _lexer.CurrentToken;
355             if (Same(expression, Token.TokenType.String))
356             {
357                 return new StringExpressionNode(current.String, current.Expandable);
358             }
359             else if (Same(expression, Token.TokenType.Numeric))
360             {
361                 return new NumericExpressionNode(current.String);
362             }
363             else if (Same(expression, Token.TokenType.Property))
364             {
365                 return new StringExpressionNode(current.String, true /* requires expansion */);
366             }
367             else if (Same(expression, Token.TokenType.ItemMetadata))
368             {
369                 return new StringExpressionNode(current.String, true /* requires expansion */);
370             }
371             else if (Same(expression, Token.TokenType.ItemList))
372             {
373                 return new StringExpressionNode(current.String, true /* requires expansion */);
374             }
375             else
376             {
377                 return null;
378             }
379         }
380 
Same(string expression, Token.TokenType token)381         private bool Same(string expression, Token.TokenType token)
382         {
383             if (_lexer.IsNext(token))
384             {
385                 if (!_lexer.Advance())
386                 {
387                     errorPosition = _lexer.GetErrorPosition();
388                     if (null != _lexer.UnexpectedlyFound)
389                     {
390                         ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, _lexer.GetErrorResource(), expression, errorPosition, _lexer.UnexpectedlyFound);
391                     }
392                     else
393                     {
394                         ProjectErrorUtilities.VerifyThrowInvalidProject(false, _elementLocation, _lexer.GetErrorResource(), expression, errorPosition);
395                     }
396                 }
397                 return true;
398             }
399             else
400             {
401                 return false;
402             }
403         }
404     }
405 }