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 // </copyright>
5 // <summary>Expands item/property/metadata in expressions.</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections;
10 using System.Collections.Concurrent;
11 using System.Collections.Generic;
12 using System.Diagnostics.CodeAnalysis;
13 using System.Globalization;
14 using System.IO;
15 using System.Linq;
16 using System.Reflection;
17 using System.Text.RegularExpressions;
18 using Microsoft.Build.Collections;
19 using Microsoft.Build.Execution;
20 using Microsoft.Build.Internal;
21 using Microsoft.Build.Shared;
22 using Microsoft.Build.Utilities;
23 using Microsoft.Win32;
24 using AvailableStaticMethods = Microsoft.Build.Internal.AvailableStaticMethods;
25 using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
26 using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
27 using TaskItemFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.TaskItemFactory;
28 
29 namespace Microsoft.Build.Evaluation
30 {
31     /// <summary>
32     /// Indicates to the expander what exactly it should expand.
33     /// </summary>
34     [Flags]
35     internal enum ExpanderOptions
36     {
37         /// <summary>
38         /// Invalid
39         /// </summary>
40         Invalid = 0x0,
41 
42         /// <summary>
43         /// Expand bare custom metadata, like %(foo), but not built-in
44         /// metadata, such as %(filename) or %(identity)
45         /// </summary>
46         ExpandCustomMetadata = 0x1,
47 
48         /// <summary>
49         /// Expand bare built-in metadata, such as %(filename) or %(identity)
50         /// </summary>
51         ExpandBuiltInMetadata = 0x2,
52 
53         /// <summary>
54         /// Expand all bare metadata
55         /// </summary>
56         ExpandMetadata = ExpandCustomMetadata | ExpandBuiltInMetadata,
57 
58         /// <summary>
59         /// Expand only properties
60         /// </summary>
61         ExpandProperties = 0x4,
62 
63         /// <summary>
64         /// Expand only item list expressions
65         /// </summary>
66         ExpandItems = 0x8,
67 
68         /// <summary>
69         /// If the expression is going to not be an empty string, break
70         /// out early
71         /// </summary>
72         BreakOnNotEmpty = 0x10,
73 
74         /// <summary>
75         /// When an error occurs expanding a property, just leave it unexpanded.  This should only be used when attempting to log a message with a best effort expansion of a string.
76         /// </summary>
77         LeavePropertiesUnexpandedOnError = 0x20,
78 
79         /// <summary>
80         /// Expand only properties and then item lists
81         /// </summary>
82         ExpandPropertiesAndItems = ExpandProperties | ExpandItems,
83 
84         /// <summary>
85         /// Expand only bare metadata and then properties
86         /// </summary>
87         ExpandPropertiesAndMetadata = ExpandMetadata | ExpandProperties,
88 
89         /// <summary>
90         /// Expand only bare custom metadata and then properties
91         /// </summary>
92         ExpandPropertiesAndCustomMetadata = ExpandCustomMetadata | ExpandProperties,
93 
94         /// <summary>
95         /// Expand bare metadata, then properties, then item expressions
96         /// </summary>
97         ExpandAll = ExpandMetadata | ExpandProperties | ExpandItems
98     }
99 
100     /// <summary>
101     /// Expands item/property/metadata in expressions.
102     /// Encapsulates the data necessary for expansion.
103     /// </summary>
104     /// <remarks>
105     /// Requires the caller to explicitly state what they wish to expand at the point of expansion (explicitly does not have a field for ExpanderOptions).
106     /// Callers typically use a single expander in many locations, and this forces the caller to make explicit what they wish to expand at the point of expansion.
107     ///
108     /// Requires the caller to have previously provided the necessary material for the expansion requested.
109     /// For example, if the caller requests ExpanderOptions.ExpandItems, the Expander will throw if it was not given items.
110     /// </remarks>
111     /// <typeparam name="P">Type of the properties used</typeparam>
112     /// <typeparam name="I">Type of the items used.</typeparam>
113     internal class Expander<P, I>
114         where P : class, IProperty
115         where I : class, IItem
116     {
117         private static readonly char[] s_singleQuoteChar = { '\'' };
118         private static readonly char[] s_backtickChar = { '`' };
119         private static readonly char[] s_doubleQuoteChar = { '"' };
120 
121         /// <summary>
122         /// Those characters which indicate that an expression may contain expandable
123         /// expressions
124         /// </summary>
125         private static char[] s_expandableChars = { '$', '%', '@' };
126 
127         /// <summary>
128         /// The CultureInfo from the invariant culture. Used to avoid allocations for
129         /// perfoming IndexOf etc.
130         /// </summary>
131         private static CompareInfo s_invariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo;
132 
133         /// <summary>
134         /// Properties to draw on for expansion
135         /// </summary>
136         private IPropertyProvider<P> _properties;
137 
138         /// <summary>
139         /// Items to draw on for expansion
140         /// </summary>
141         private IItemProvider<I> _items;
142 
143         /// <summary>
144         /// Metadata to draw on for expansion
145         /// </summary>
146         private IMetadataTable _metadata;
147 
148         /// <summary>
149         /// Set of properties which are null during expansion
150         /// </summary>
151         private UsedUninitializedProperties _usedUninitializedProperties;
152 
153         /// <summary>
154         /// Creates an expander passing it some properties to use.
155         /// Properties may be null.
156         /// </summary>
Expander(IPropertyProvider<P> properties)157         internal Expander(IPropertyProvider<P> properties)
158         {
159             _properties = properties;
160             _usedUninitializedProperties = new UsedUninitializedProperties();
161         }
162 
163         /// <summary>
164         /// Creates an expander passing it some properties and items to use.
165         /// Either or both may be null.
166         /// </summary>
Expander(IPropertyProvider<P> properties, IItemProvider<I> items)167         internal Expander(IPropertyProvider<P> properties, IItemProvider<I> items)
168             : this(properties)
169         {
170             _items = items;
171         }
172 
173         /// <summary>
174         /// Creates an expander passing it some properties, items, and/or metadata to use.
175         /// Any or all may be null.
176         /// </summary>
Expander(IPropertyProvider<P> properties, IItemProvider<I> items, IMetadataTable metadata)177         internal Expander(IPropertyProvider<P> properties, IItemProvider<I> items, IMetadataTable metadata)
178             : this(properties, items)
179         {
180             _metadata = metadata;
181         }
182 
183         /// <summary>
184         /// Whether to warn when we set a property for the first time, after it was previously used.
185         /// Default is false, unless MSBUILDWARNONUNINITIALIZEDPROPERTY is set.
186         /// </summary>
187         internal bool WarnForUninitializedProperties
188         {
189             get { return _usedUninitializedProperties.Warn; }
190             set { _usedUninitializedProperties.Warn = value; }
191         }
192 
193         /// <summary>
194         /// Accessor for the metadata.
195         /// Set temporarily during item metadata evaluation.
196         /// </summary>
197         internal IMetadataTable Metadata
198         {
199             get { return _metadata; }
200             set { _metadata = value; }
201         }
202 
203         /// <summary>
204         /// If a property is expanded but evaluates to null then it is considered to be un-initialized.
205         /// We want to keep track of these properties so that we can warn if the property gets set later on.
206         /// </summary>
207         internal UsedUninitializedProperties UsedUninitializedProperties
208         {
209             get { return _usedUninitializedProperties; }
210             set { _usedUninitializedProperties = value; }
211         }
212 
213         /// <summary>
214         /// Tests to see if the expression may contain expandable expressions, i.e.
215         /// contains $, % or @
216         /// </summary>
ExpressionMayContainExpandableExpressions(string expression)217         internal static bool ExpressionMayContainExpandableExpressions(string expression)
218         {
219             return expression.IndexOfAny(s_expandableChars) > -1;
220         }
221 
222         /// <summary>
223         /// Returns true if the expression contains an item vector pattern, else returns false.
224         /// Used to flag use of item expressions where they are illegal.
225         /// </summary>
ExpressionContainsItemVector(string expression)226         internal static bool ExpressionContainsItemVector(string expression)
227         {
228             List<ExpressionShredder.ItemExpressionCapture> transforms = ExpressionShredder.GetReferencedItemExpressions(expression);
229 
230             return (transforms != null);
231         }
232 
233         /// <summary>
234         /// Expands embedded item metadata, properties, and embedded item lists (in that order) as specified in the provided options.
235         /// This is the standard form. Before using the expanded value, it must be unescaped, and this does that for you.
236         ///
237         /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
238         /// </summary>
ExpandIntoStringAndUnescape(string expression, ExpanderOptions options, IElementLocation elementLocation)239         internal string ExpandIntoStringAndUnescape(string expression, ExpanderOptions options, IElementLocation elementLocation)
240         {
241             string result = ExpandIntoStringLeaveEscaped(expression, options, elementLocation);
242 
243             result = (result == null) ? null : EscapingUtilities.UnescapeAll(result);
244 
245             return result;
246         }
247 
248         /// <summary>
249         /// Expands embedded item metadata, properties, and embedded item lists (in that order) as specified in the provided options.
250         /// Use this form when the result is going to be processed further, for example by matching against the file system,
251         /// so literals must be distinguished, and you promise to unescape after that.
252         ///
253         /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
254         /// </summary>
ExpandIntoStringLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)255         internal string ExpandIntoStringLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)
256         {
257             if (expression.Length == 0)
258             {
259                 return String.Empty;
260             }
261 
262             ErrorUtilities.VerifyThrowInternalNull(elementLocation, "elementLocation");
263 
264             string result = MetadataExpander.ExpandMetadataLeaveEscaped(expression, _metadata, options);
265             result = PropertyExpander<P>.ExpandPropertiesLeaveEscaped(result, _properties, options, elementLocation, _usedUninitializedProperties);
266             result = ItemExpander.ExpandItemVectorsIntoString<I>(this, result, _items, options, elementLocation);
267             result = FileUtilities.MaybeAdjustFilePath(result);
268 
269             return result;
270         }
271 
272         /// <summary>
273         /// Used only for unit tests. Expands the property expression (including any metadata expressions) and returns
274         /// the result typed (i.e. not converted into a string if the result is a function return)
275         /// </summary>
ExpandPropertiesLeaveTypedAndEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)276         internal object ExpandPropertiesLeaveTypedAndEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)
277         {
278             if (expression.Length == 0)
279             {
280                 return String.Empty;
281             }
282 
283             ErrorUtilities.VerifyThrowInternalNull(elementLocation, "elementLocation");
284 
285             string metaExpanded = MetadataExpander.ExpandMetadataLeaveEscaped(expression, _metadata, options);
286             return PropertyExpander<P>.ExpandPropertiesLeaveTypedAndEscaped(metaExpanded, _properties, options, elementLocation, _usedUninitializedProperties);
287         }
288 
289         /// <summary>
290         /// Expands embedded item metadata, properties, and embedded item lists (in that order) as specified in the provided options,
291         /// then splits on semi-colons into a list of strings.
292         /// Use this form when the result is going to be processed further, for example by matching against the file system,
293         /// so literals must be distinguished, and you promise to unescape after that.
294         /// </summary>
ExpandIntoStringListLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)295         internal SemiColonTokenizer ExpandIntoStringListLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)
296         {
297             ErrorUtilities.VerifyThrow((options & ExpanderOptions.BreakOnNotEmpty) == 0, "not supported");
298 
299             return ExpressionShredder.SplitSemiColonSeparatedList(ExpandIntoStringLeaveEscaped(expression, options, elementLocation));
300         }
301 
302         /// <summary>
303         /// Expands embedded item metadata, properties, and embedded item lists (in that order) as specified in the provided options
304         /// and produces a list of TaskItems.
305         /// If the expression is empty, returns an empty list.
306         /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
307         /// </summary>
ExpandIntoTaskItemsLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)308         internal IList<TaskItem> ExpandIntoTaskItemsLeaveEscaped(string expression, ExpanderOptions options, IElementLocation elementLocation)
309         {
310             return ExpandIntoItemsLeaveEscaped(expression, (IItemFactory<I, TaskItem>)TaskItemFactory.Instance, options, elementLocation);
311         }
312 
313         /// <summary>
314         /// Expands embedded item metadata, properties, and embedded item lists (in that order) as specified in the provided options
315         /// and produces a list of items of the type for which it was specialized.
316         /// If the expression is empty, returns an empty list.
317         /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
318         ///
319         /// Use this form when the result is going to be processed further, for example by matching against the file system,
320         /// so literals must be distinguished, and you promise to unescape after that.
321         /// </summary>
322         /// <typeparam name="T">Type of items to return</typeparam>
323         internal IList<T> ExpandIntoItemsLeaveEscaped<T>(string expression, IItemFactory<I, T> itemFactory, ExpanderOptions options, IElementLocation elementLocation)
324             where T : class, IItem
325         {
326             if (expression.Length == 0)
327             {
328                 return Array.Empty<T>();
329             }
330 
ErrorUtilities.VerifyThrowInternalNull(elementLocation, R)331             ErrorUtilities.VerifyThrowInternalNull(elementLocation, "elementLocation");
332 
333             expression = MetadataExpander.ExpandMetadataLeaveEscaped(expression, _metadata, options);
334             expression = PropertyExpander<P>.ExpandPropertiesLeaveEscaped(expression, _properties, options, elementLocation, _usedUninitializedProperties);
335             expression = FileUtilities.MaybeAdjustFilePath(expression);
336 
337             List<T> result = new List<T>();
338 
339             if (expression.Length == 0)
340             {
341                 return result;
342             }
343 
344             var splits = ExpressionShredder.SplitSemiColonSeparatedList(expression);
345             foreach (string split in splits)
346             {
347                 bool isTransformExpression;
348                 IList<T> itemsToAdd = ItemExpander.ExpandSingleItemVectorExpressionIntoItems<I, T>(this, split, _items, itemFactory, options, false /* do not include null items */, out isTransformExpression, elementLocation);
349 
350                 if ((itemsToAdd == null /* broke out early non empty */ || (itemsToAdd.Count > 0)) && (options & ExpanderOptions.BreakOnNotEmpty) != 0)
351                 {
352                     return null;
353                 }
354 
355                 if (itemsToAdd != null)
356                 {
357                     result.AddRange(itemsToAdd);
358                 }
359                 else
360                 {
361                     // The expression is not of the form @(itemName).  Therefore, just
362                     // treat it as a string, and create a new item from that string.
363                     T itemToAdd = itemFactory.CreateItem(split, elementLocation.File);
364 
365                     result.Add(itemToAdd);
366                 }
367             }
368 
369             return result;
370         }
371 
372         /// <summary>
373         /// This is a specialized method for the use of TargetUpToDateChecker and Evaluator.EvaluateItemXml only.
374         ///
375         /// Extracts the items in the given SINGLE item vector.
376         /// For example, expands @(Compile->'%(foo)') to a set of items derived from the items in the "Compile" list.
377         ///
378         /// If there is in fact more than one vector in the expression, throws InvalidProjectFileException.
379         ///
380         /// If there are no item expressions in the expression (for example a literal "foo.cpp"), returns null.
381         /// If expression expands to no items, returns an empty list.
382         /// If item expansion is not allowed by the provided options, returns null.
383         /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
384         ///
385         /// If the expression is a transform, any transformations to an expression that evaluates to nothing (i.e., because
386         /// an item has no value for a piece of metadata) are optionally indicated with a null entry in the list. This means
387         /// that the length of the returned list is always the same as the length of the referenced item list in the input string.
388         /// That's important for any correlation the caller wants to do.
389         ///
390         /// If expression was a transform, 'isTransformExpression' is true, otherwise false.
391         ///
392         /// Item type of the items returned is determined by the IItemFactory passed in; if the IItemFactory does not
393         /// have an item type set on it, it will be given the item type of the item vector to use.
394         /// </summary>
395         /// <typeparam name="T">Type of the items that should be returned</typeparam>
396         internal IList<T> ExpandSingleItemVectorExpressionIntoItems<T>(string expression, IItemFactory<I, T> itemFactory, ExpanderOptions options, bool includeNullItems, out bool isTransformExpression, IElementLocation elementLocation)
397             where T : class, IItem
398         {
399             if (expression.Length == 0)
400             {
401                 isTransformExpression = false;
402                 return Array.Empty<T>();
403             }
404 
ErrorUtilities.VerifyThrowInternalNull(elementLocation, R)405             ErrorUtilities.VerifyThrowInternalNull(elementLocation, "elementLocation");
406 
407             return ItemExpander.ExpandSingleItemVectorExpressionIntoItems(this, expression, _items, itemFactory, options, includeNullItems, out isTransformExpression, elementLocation);
408         }
409 
ExpandSingleItemVectorExpressionIntoExpressionCapture( string expression, ExpanderOptions options, IElementLocation elementLocation)410         internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorExpressionIntoExpressionCapture(
411                 string expression, ExpanderOptions options, IElementLocation elementLocation)
412         {
413             return ItemExpander.ExpandSingleItemVectorExpressionIntoExpressionCapture(expression, options, elementLocation);
414         }
415 
416         internal IList<T> ExpandExpressionCaptureIntoItems<S, T>(
417                 ExpressionShredder.ItemExpressionCapture expressionCapture, IItemProvider<S> items, IItemFactory<S, T> itemFactory,
418                 ExpanderOptions options, bool includeNullEntries, out bool isTransformExpression, IElementLocation elementLocation)
419             where S : class, IItem
420             where T : class, IItem
421         {
422             return ItemExpander.ExpandExpressionCaptureIntoItems<S, T>(expressionCapture, this, items, itemFactory, options,
423                 includeNullEntries, out isTransformExpression, elementLocation);
424         }
425 
ExpandExpressionCapture( ExpressionShredder.ItemExpressionCapture expressionCapture, IElementLocation elementLocation, ExpanderOptions options, bool includeNullEntries, out bool isTransformExpression, out List<Pair<string, I>> itemsFromCapture)426         internal bool ExpandExpressionCapture(
427             ExpressionShredder.ItemExpressionCapture expressionCapture,
428             IElementLocation elementLocation,
429             ExpanderOptions options,
430             bool includeNullEntries,
431             out bool isTransformExpression,
432             out List<Pair<string, I>> itemsFromCapture)
433         {
434             return ItemExpander.ExpandExpressionCapture(this, expressionCapture, _items, elementLocation, options, includeNullEntries, out isTransformExpression, out itemsFromCapture);
435         }
436 
437         /// <summary>
438         /// Returns true if the supplied string contains a valid property name
439         /// </summary>
IsValidPropertyName(string propertyName)440         private static bool IsValidPropertyName(string propertyName)
441         {
442             if (propertyName.Length == 0 || !XmlUtilities.IsValidInitialElementNameCharacter(propertyName[0]))
443             {
444                 return false;
445             }
446 
447             for (int n = 1; n < propertyName.Length; n++)
448             {
449                 if (!XmlUtilities.IsValidSubsequentElementNameCharacter(propertyName[n]))
450                 {
451                     return false;
452                 }
453             }
454 
455             return true;
456         }
457 
458         /// <summary>
459         /// Scan for the closing bracket that matches the one we've already skipped;
460         /// essentially, pushes and pops on a stack of parentheses to do this.
461         /// Takes the expression and the index to start at.
462         /// Returns the index of the matching parenthesis, or -1 if it was not found.
463         /// </summary>
ScanForClosingParenthesis(string expression, int index)464         private static int ScanForClosingParenthesis(string expression, int index)
465         {
466             bool potentialPropertyFunction = false;
467             bool potentialRegistryFunction = false;
468             return ScanForClosingParenthesis(expression, index, out potentialPropertyFunction, out potentialRegistryFunction);
469         }
470 
471         /// <summary>
472         /// Scan for the closing bracket that matches the one we've already skipped;
473         /// essentially, pushes and pops on a stack of parentheses to do this.
474         /// Takes the expression and the index to start at.
475         /// Returns the index of the matching parenthesis, or -1 if it was not found.
476         /// Also returns flags to indicate if a propertyfunction or registry property is likely
477         /// to be found in the expression
478         /// </summary>
ScanForClosingParenthesis(string expression, int index, out bool potentialPropertyFunction, out bool potentialRegistryFunction)479         private static int ScanForClosingParenthesis(string expression, int index, out bool potentialPropertyFunction, out bool potentialRegistryFunction)
480         {
481             int nestLevel = 1;
482             int length = expression.Length;
483 
484             potentialPropertyFunction = false;
485             potentialRegistryFunction = false;
486 
487             unsafe
488             {
489                 fixed (char* pchar = expression)
490                 {
491                     // Scan for our closing ')'
492                     while (index < length && nestLevel > 0)
493                     {
494                         char character = pchar[index];
495 
496                         if (character == '\'' || character == '`' || character == '"')
497                         {
498                             index++;
499                             index = ScanForClosingQuote(character, expression, index);
500 
501                             if (index < 0)
502                             {
503                                 return -1;
504                             }
505                         }
506                         else if (character == '(')
507                         {
508                             nestLevel++;
509                         }
510                         else if (character == ')')
511                         {
512                             nestLevel--;
513                         }
514                         else if (character == '.' || character == '[' || character == '$')
515                         {
516                             potentialPropertyFunction = true;
517                         }
518                         else if (character == ':')
519                         {
520                             potentialRegistryFunction = true;
521                         }
522 
523                         index++;
524                     }
525                 }
526             }
527 
528             // We will have parsed past the ')', so step back one character
529             index--;
530 
531             return (nestLevel == 0) ? index : -1;
532         }
533 
534         /// <summary>
535         /// Skip all characters until we find the matching quote character
536         /// </summary>
ScanForClosingQuote(char quoteChar, string expression, int index)537         private static int ScanForClosingQuote(char quoteChar, string expression, int index)
538         {
539             unsafe
540             {
541                 fixed (char* pchar = expression)
542                 {
543                     // Scan for our closing quoteChar
544                     while (index < expression.Length)
545                     {
546                         if (pchar[index] == quoteChar)
547                         {
548                             return index;
549                         }
550 
551                         index++;
552                     }
553                 }
554             }
555 
556             return -1;
557         }
558 
559         /// <summary>
560         /// Add the argument in the StringBuilder to the arguments list, handling nulls
561         /// appropriately
562         /// </summary>
AddArgument(List<string> arguments, ReuseableStringBuilder argumentBuilder)563         private static void AddArgument(List<string> arguments, ReuseableStringBuilder argumentBuilder)
564         {
565             // If we don't have something that can be treated as an argument
566             // then we should treat it as a null so that passing nulls
567             // becomes possible through an empty argument between commas.
568             ErrorUtilities.VerifyThrowArgumentNull(argumentBuilder, "argumentBuilder");
569 
570             // we reached the end of an argument, add the builder's final result
571             // to our arguments.
572             string argValue = OpportunisticIntern.InternableToString(argumentBuilder).Trim();
573 
574             // We support passing of null through the argument constant value null
575             if (String.Compare("null", argValue, StringComparison.OrdinalIgnoreCase) == 0)
576             {
577                 arguments.Add(null);
578             }
579             else
580             {
581                 if (argValue.Length > 0)
582                 {
583                     if (argValue[0] == '\'' && argValue[argValue.Length - 1] == '\'')
584                     {
585                         arguments.Add(argValue.Trim(s_singleQuoteChar));
586                     }
587                     else if (argValue[0] == '`' && argValue[argValue.Length - 1] == '`')
588                     {
589                         arguments.Add(argValue.Trim(s_backtickChar));
590                     }
591                     else if (argValue[0] == '"' && argValue[argValue.Length - 1] == '"')
592                     {
593                         arguments.Add(argValue.Trim(s_doubleQuoteChar));
594                     }
595                     else
596                     {
597                         arguments.Add(argValue);
598                     }
599                 }
600                 else
601                 {
602                     arguments.Add(argValue);
603                 }
604             }
605         }
606 
607         /// <summary>
608         /// Extract the first level of arguments from the content.
609         /// Splits the content passed in at commas.
610         /// Returns an array of unexpanded arguments.
611         /// If there are no arguments, returns an empty array.
612         /// </summary>
ExtractFunctionArguments(IElementLocation elementLocation, string expressionFunction, string argumentsString)613         private static string[] ExtractFunctionArguments(IElementLocation elementLocation, string expressionFunction, string argumentsString)
614         {
615             int argumentsContentLength = argumentsString.Length;
616 
617             List<string> arguments = new List<string>();
618 
619             // With the reuseable string builder, there's no particular need to initialize the length as it will already have grown.
620             using (var argumentBuilder = new ReuseableStringBuilder())
621             {
622                 unsafe
623                 {
624                     fixed (char* argumentsContent = argumentsString)
625                     {
626                         // Iterate over the contents of the arguments extracting the
627                         // the individual arguments as we go
628                         for (int n = 0; n < argumentsContentLength; n++)
629                         {
630                             // We found a property expression.. skip over all of it.
631                             if ((n < argumentsContentLength - 1) && (argumentsContent[n] == '$' && argumentsContent[n + 1] == '('))
632                             {
633                                 int nestedPropertyStart = n;
634                                 n += 2; // skip over the opening '$('
635 
636                                 // Scan for the matching closing bracket, skipping any nested ones
637                                 n = ScanForClosingParenthesis(argumentsString, n);
638 
639                                 if (n == -1)
640                                 {
641                                     ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedParenthesis"));
642                                 }
643 
644                                 argumentBuilder.Append(argumentsString, nestedPropertyStart, (n - nestedPropertyStart) + 1);
645                             }
646                             else if (argumentsContent[n] == '`' || argumentsContent[n] == '"' || argumentsContent[n] == '\'')
647                             {
648                                 int quoteStart = n;
649                                 n += 1; // skip over the opening quote
650 
651                                 n = ScanForClosingQuote(argumentsString[quoteStart], argumentsString, n);
652 
653                                 if (n == -1)
654                                 {
655                                     ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedQuote"));
656                                 }
657 
658                                 argumentBuilder.Append(argumentsString, quoteStart, (n - quoteStart) + 1);
659                             }
660                             else if (argumentsContent[n] == ',')
661                             {
662                                 // We have reached the end of the current argument, go ahead and add it
663                                 // to our list
664                                 AddArgument(arguments, argumentBuilder);
665 
666                                 // Clear out the argument builder ready for the next argument
667                                 argumentBuilder.Remove(0, argumentBuilder.Length);
668                             }
669                             else
670                             {
671                                 argumentBuilder.Append(argumentsContent[n]);
672                             }
673                         }
674                     }
675                 }
676 
677                 // This will either be the one and only argument, or the last one
678                 // so add it to our list
679                 AddArgument(arguments, argumentBuilder);
680             }
681 
682             return arguments.ToArray();
683         }
684 
685         /// <summary>
686         /// Expands bare metadata expressions, like %(Compile.WarningLevel), or unqualified, like %(Compile).
687         /// </summary>
688         /// <remarks>
689         /// This is a private nested class, exposed only through the Expander class.
690         /// That allows it to hide its private methods even from Expander.
691         /// </remarks>
692         private static class MetadataExpander
693         {
694             /// <summary>
695             /// Expands all embedded item metadata in the given string, using the bucketed items.
696             /// Metadata may be qualified, like %(Compile.WarningLevel), or unqualified, like %(Compile)
697             /// </summary>
698             /// <param name="expression">The expression containing item metadata references</param>
699             /// <param name="metadata"></param>
700             /// <param name="options"></param>
701             /// <returns>The string with item metadata expanded in-place, escaped.</returns>
ExpandMetadataLeaveEscaped(string expression, IMetadataTable metadata, ExpanderOptions options)702             internal static string ExpandMetadataLeaveEscaped(string expression, IMetadataTable metadata, ExpanderOptions options)
703             {
704                 if (((options & ExpanderOptions.ExpandMetadata) == 0))
705                 {
706                     return expression;
707                 }
708 
709                 if (expression.Length == 0)
710                 {
711                     return expression;
712                 }
713 
714                 ErrorUtilities.VerifyThrow(metadata != null, "Cannot expand metadata without providing metadata");
715 
716                 // PERF NOTE: Regex matching is expensive, so if the string doesn't contain any item metadata references, just bail
717                 // out -- pre-scanning the string is actually cheaper than running the Regex, even when there are no matches!
718                 if (s_invariantCompareInfo.IndexOf(expression, "%(", CompareOptions.Ordinal) == -1)
719                 {
720                     return expression;
721                 }
722 
723                 string result = null;
724 
725                 if (s_invariantCompareInfo.IndexOf(expression, "@(", CompareOptions.Ordinal) == -1)
726                 {
727                     // if there are no item vectors in the string
728                     // run a simpler Regex to find item metadata references
729                     MetadataMatchEvaluator matchEvaluator = new MetadataMatchEvaluator(metadata, options);
730                     result = RegularExpressions.ItemMetadataPattern.Value.Replace(expression, new MatchEvaluator(matchEvaluator.ExpandSingleMetadata));
731                 }
732                 else
733                 {
734                     List<ExpressionShredder.ItemExpressionCapture> itemVectorExpressions = ExpressionShredder.GetReferencedItemExpressions(expression);
735 
736                     // The most common case is where the transform is the whole expression
737                     // Also if there were no valid item vector expressions found, then go ahead and do the replacement on
738                     // the whole expression (which is what Orcas did).
739                     if (itemVectorExpressions != null && itemVectorExpressions.Count == 1 && itemVectorExpressions[0].Value == expression && itemVectorExpressions[0].Separator == null)
740                     {
741                         return expression;
742                     }
743 
744                     // otherwise, run the more complex Regex to find item metadata references not contained in transforms
745                     // With the reuseable string builder, there's no particular need to initialize the length as it will already have grown.
746                     using (var finalResultBuilder = new ReuseableStringBuilder())
747                     {
748                         int start = 0;
749                         MetadataMatchEvaluator matchEvaluator = new MetadataMatchEvaluator(metadata, options);
750 
751                         if (itemVectorExpressions != null)
752                         {
753                             // Move over the expression, skipping those that have been recognized as an item vector expression
754                             // Anything other than an item vector expression we want to expand bare metadata in.
755                             for (int n = 0; n < itemVectorExpressions.Count; n++)
756                             {
757                                 string vectorExpression = itemVectorExpressions[n].Value;
758 
759                                 // Extract the part of the expression that appears before the item vector expression
760                                 // e.g. the ABC in ABC@(foo->'%(FullPath)')
761                                 string subExpressionToReplaceIn = expression.Substring(start, itemVectorExpressions[n].Index - start);
762                                 string replacementResult = RegularExpressions.NonTransformItemMetadataPattern.Value.Replace(subExpressionToReplaceIn, new MatchEvaluator(matchEvaluator.ExpandSingleMetadata));
763 
764                                 // Append the metadata replacement
765                                 finalResultBuilder.Append(replacementResult);
766 
767                                 // Expand any metadata that appears in the item vector expression's separator
768                                 if (itemVectorExpressions[n].Separator != null)
769                                 {
770                                     vectorExpression = RegularExpressions.NonTransformItemMetadataPattern.Value.Replace(itemVectorExpressions[n].Value, new MatchEvaluator(matchEvaluator.ExpandSingleMetadata), -1, itemVectorExpressions[n].SeparatorStart);
771                                 }
772 
773                                 // Append the item vector expression as is
774                                 // e.g. the @(foo->'%(FullPath)') in ABC@(foo->'%(FullPath)')
775                                 finalResultBuilder.Append(vectorExpression);
776 
777                                 // Move onto the next part of the expression that isn't an item vector expression
778                                 start = (itemVectorExpressions[n].Index + itemVectorExpressions[n].Length);
779                             }
780                         }
781 
782                         // If there's anything left after the last item vector expression
783                         // then we need to metadata replace and then append that
784                         if (start < expression.Length)
785                         {
786                             string subExpressionToReplaceIn = expression.Substring(start);
787                             string replacementResult = RegularExpressions.NonTransformItemMetadataPattern.Value.Replace(subExpressionToReplaceIn, new MatchEvaluator(matchEvaluator.ExpandSingleMetadata));
788 
789                             finalResultBuilder.Append(replacementResult);
790                         }
791 
792                         result = OpportunisticIntern.InternableToString(finalResultBuilder);
793                     }
794                 }
795 
796                 // Don't create more strings
797                 if (String.Equals(result, expression, StringComparison.Ordinal))
798                 {
799                     result = expression;
800                 }
801 
802                 return result;
803             }
804 
805             /// <summary>
806             /// A functor that returns the value of the metadata in the match
807             /// that is contained in the metadata dictionary it was created with.
808             /// </summary>
809             private class MetadataMatchEvaluator
810             {
811                 /// <summary>
812                 /// Source of the metadata
813                 /// </summary>
814                 private IMetadataTable _metadata;
815 
816                 /// <summary>
817                 /// Whether to expand built-in metadata, custom metadata, or both kinds.
818                 /// </summary>
819                 private ExpanderOptions _options;
820 
821                 /// <summary>
822                 /// Constructor taking a source of metadata
823                 /// </summary>
MetadataMatchEvaluator(IMetadataTable metadata, ExpanderOptions options)824                 internal MetadataMatchEvaluator(IMetadataTable metadata, ExpanderOptions options)
825                 {
826                     _metadata = metadata;
827                     _options = (options & ExpanderOptions.ExpandMetadata);
828 
829                     ErrorUtilities.VerifyThrow(options != ExpanderOptions.Invalid, "Must be expanding metadata of some kind");
830                 }
831 
832                 /// <summary>
833                 /// Expands a single item metadata, which may be qualified with an item type.
834                 /// </summary>
ExpandSingleMetadata(Match itemMetadataMatch)835                 internal string ExpandSingleMetadata(Match itemMetadataMatch)
836                 {
837                     ErrorUtilities.VerifyThrow(itemMetadataMatch.Success, "Need a valid item metadata.");
838 
839                     string metadataName = itemMetadataMatch.Groups[RegularExpressions.NameGroup].Value;
840                     string itemType = null;
841 
842                     // check if the metadata is qualified with the item type
843                     if (itemMetadataMatch.Groups[RegularExpressions.ItemSpecificationGroup].Length > 0)
844                     {
845                         itemType = itemMetadataMatch.Groups[RegularExpressions.ItemTypeGroup].Value;
846                     }
847 
848                     // look up the metadata - we may not have a value for it
849                     string metadataValue = itemMetadataMatch.Value;
850 
851                     bool isBuiltInMetadata = FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName);
852 
853                     if (
854                         (isBuiltInMetadata && ((_options & ExpanderOptions.ExpandBuiltInMetadata) != 0)) ||
855                        (!isBuiltInMetadata && ((_options & ExpanderOptions.ExpandCustomMetadata) != 0))
856                         )
857                     {
858                         metadataValue = _metadata.GetEscapedValue(itemType, metadataName);
859                     }
860 
861                     return metadataValue;
862                 }
863             }
864         }
865 
866         /// <summary>
867         /// Expands property expressions, like $(Configuration) and $(Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation)
868         /// </summary>
869         /// <remarks>
870         /// This is a private nested class, exposed only through the Expander class.
871         /// That allows it to hide its private methods even from Expander.
872         /// </remarks>
873         /// <typeparam name="T">Type of the properties used to expand the expression</typeparam>
874         private static class PropertyExpander<T>
875             where T : class, IProperty
876         {
877             /// <summary>
878             /// This method takes a string which may contain any number of
879             /// "$(propertyname)" tags in it.  It replaces all those tags with
880             /// the actual property values, and returns a new string.  For example,
881             ///
882             ///     string processedString =
883             ///         propertyBag.ExpandProperties("Value of NoLogo is $(NoLogo).");
884             ///
885             /// This code might produce:
886             ///
887             ///     processedString = "Value of NoLogo is true."
888             ///
889             /// If the sourceString contains an embedded property which doesn't
890             /// have a value, then we replace that tag with an empty string.
891             ///
892             /// This method leaves the result escaped.  Callers may need to unescape on their own as appropriate.
893             /// </summary>
ExpandPropertiesLeaveEscaped(string expression, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)894             internal static string ExpandPropertiesLeaveEscaped(string expression, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
895             {
896                 return ConvertToString(ExpandPropertiesLeaveTypedAndEscaped(expression, properties, options, elementLocation, usedUninitializedProperties));
897             }
898 
899             /// <summary>
900             /// This method takes a string which may contain any number of
901             /// "$(propertyname)" tags in it.  It replaces all those tags with
902             /// the actual property values, and returns a new string.  For example,
903             ///
904             ///     string processedString =
905             ///         propertyBag.ExpandProperties("Value of NoLogo is $(NoLogo).");
906             ///
907             /// This code might produce:
908             ///
909             ///     processedString = "Value of NoLogo is true."
910             ///
911             /// If the sourceString contains an embedded property which doesn't
912             /// have a value, then we replace that tag with an empty string.
913             ///
914             /// This method leaves the result typed and escaped.  Callers may need to convert to string, and unescape on their own as appropriate.
915             /// </summary>
ExpandPropertiesLeaveTypedAndEscaped(string expression, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)916             internal static object ExpandPropertiesLeaveTypedAndEscaped(string expression, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
917             {
918                 if (((options & ExpanderOptions.ExpandProperties) == 0) || String.IsNullOrEmpty(expression))
919                 {
920                     return expression;
921                 }
922 
923                 ErrorUtilities.VerifyThrow(properties != null, "Cannot expand properties without providing properties");
924 
925                 // These are also zero-based indices into the expression, but
926                 // these tell us where the current property tag begins and ends.
927                 int propertyStartIndex, propertyEndIndex;
928 
929                 // If there are no substitutions, then just return the string.
930                 propertyStartIndex = s_invariantCompareInfo.IndexOf(expression, "$(", CompareOptions.Ordinal);
931                 if (propertyStartIndex == -1)
932                 {
933                     return expression;
934                 }
935 
936                 // We will build our set of results as object components
937                 // so that we can either maintain the object's type in the event
938                 // that we have a single component, or convert to a string
939                 // if concatenation is required.
940                 List<object> results = null;
941                 object lastResult = null;
942 
943                 // The sourceIndex is the zero-based index into the expression,
944                 // where we've essentially read up to and copied into the target string.
945                 int sourceIndex = 0;
946                 int expressionLength = expression.Length;
947 
948                 // Search for "$(" in the expression.  Loop until we don't find it
949                 // any more.
950                 while (propertyStartIndex != -1)
951                 {
952                     if (lastResult != null)
953                     {
954                         if (results == null)
955                         {
956                             results = new List<object>(4);
957                         }
958 
959                         results.Add(lastResult);
960                     }
961 
962                     bool tryExtractPropertyFunction = false;
963                     bool tryExtractRegistryFunction = false;
964 
965                     // Append the result with the portion of the expression up to
966                     // (but not including) the "$(", and advance the sourceIndex pointer.
967                     if (propertyStartIndex - sourceIndex > 0)
968                     {
969                         if (results == null)
970                         {
971                             results = new List<object>(4);
972                         }
973 
974                         results.Add(expression.Substring(sourceIndex, propertyStartIndex - sourceIndex));
975                     }
976 
977                     sourceIndex = propertyStartIndex;
978 
979                     // Following the "$(" we need to locate the matching ')'
980                     // Scan for the matching closing bracket, skipping any nested ones
981                     // This is a very complete, fast validation of parenthesis matching including for nested
982                     // function calls.
983                     propertyEndIndex = ScanForClosingParenthesis(expression, propertyStartIndex + 2, out tryExtractPropertyFunction, out tryExtractRegistryFunction);
984 
985                     if (propertyEndIndex == -1)
986                     {
987                         // If we didn't find the closing parenthesis, that means this
988                         // isn't really a well-formed property tag.  Just literally
989                         // copy the remainder of the expression (starting with the "$("
990                         // that we found) into the result, and quit.
991                         lastResult = expression.Substring(propertyStartIndex, expression.Length - propertyStartIndex);
992                         sourceIndex = expression.Length;
993                     }
994                     else
995                     {
996                         // Aha, we found the closing parenthesis.  All the stuff in
997                         // between the "$(" and the ")" constitutes the property body.
998                         // Note: Current propertyStartIndex points to the "$", and
999                         // propertyEndIndex points to the ")".  That's why we have to
1000                         // add 2 for the start of the substring, and subtract 2 for
1001                         // the length.
1002                         string propertyBody;
1003 
1004                         // A property value of null will indicate that we're calling a static function on a type
1005                         object propertyValue = null;
1006 
1007                         // Compat: $() should return String.Empty
1008                         if (propertyStartIndex + 2 == propertyEndIndex)
1009                         {
1010                             propertyValue = String.Empty;
1011                         }
1012                         else if ((expression.Length - (propertyStartIndex + 2)) > 9 && tryExtractRegistryFunction && s_invariantCompareInfo.IndexOf(expression, "Registry:", propertyStartIndex + 2, 9, CompareOptions.OrdinalIgnoreCase) == propertyStartIndex + 2)
1013                         {
1014                             // if FEATURE_WIN32_REGISTRY is off, treat the property value as if there's no Registry value at that location, rather than fail
1015                             propertyBody = expression.Substring(propertyStartIndex + 2, propertyEndIndex - propertyStartIndex - 2);
1016 
1017                             // If the property body starts with any of our special objects, then deal with them
1018                             // This is a registry reference, like $(Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation)
1019                             propertyValue = ExpandRegistryValue(propertyBody, elementLocation); // This func returns an empty string if not FEATURE_WIN32_REGISTRY
1020                         }
1021 
1022                         // Compat hack: as a special case, $(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\VSTSDB@VSTSDBDirectory) should return String.Empty
1023                         // In this case, tryExtractRegistryFunction will be false. Note that very few properties are exactly 77 chars, so this check should be fast.
1024                         else if ((propertyEndIndex - (propertyStartIndex + 2)) == 77 && s_invariantCompareInfo.IndexOf(expression, @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\VSTSDB@VSTSDBDirectory", propertyStartIndex + 2, 77, CompareOptions.OrdinalIgnoreCase) == propertyStartIndex + 2)
1025                         {
1026                             propertyValue = String.Empty;
1027                         }
1028 
1029                         // Compat hack: WebProjects may have an import with a condition like:
1030                         //       Condition=" '$(Solutions.VSVersion)' == '8.0'"
1031                         // These would have been '' in prior versions of msbuild but would be treated as a possible string function in current versions.
1032                         // Be compatible by returning an empty string here.
1033                         else if ((propertyEndIndex - (propertyStartIndex + 2)) == 19 && String.Equals(expression, "$(Solutions.VSVersion)", StringComparison.Ordinal))
1034                         {
1035                             propertyValue = String.Empty;
1036                         }
1037                         else if (tryExtractPropertyFunction)
1038                         {
1039                             propertyBody = expression.Substring(propertyStartIndex + 2, propertyEndIndex - propertyStartIndex - 2);
1040 
1041                             // This is likely to be a function expression
1042                             propertyValue = ExpandPropertyBody(propertyBody, null, properties, options, elementLocation, usedUninitializedProperties);
1043                         }
1044                         else // This is a regular property
1045                         {
1046                             propertyValue = LookupProperty(properties, expression, propertyStartIndex + 2, propertyEndIndex - 1, elementLocation, usedUninitializedProperties);
1047                         }
1048 
1049                         // Record our result, and advance
1050                         // our sourceIndex pointer to the character just after the closing
1051                         // parenthesis.
1052                         lastResult = propertyValue;
1053                         sourceIndex = propertyEndIndex + 1;
1054                     }
1055 
1056                     propertyStartIndex = s_invariantCompareInfo.IndexOf(expression, "$(", sourceIndex, CompareOptions.Ordinal);
1057                 }
1058 
1059                 // If we have only a single result, then just return it
1060                 if (results == null && expression.Length == sourceIndex)
1061                 {
1062                     var resultString = lastResult as string;
1063                     return resultString != null ? FileUtilities.MaybeAdjustFilePath(resultString) : lastResult;
1064                 }
1065                 else
1066                 {
1067                     // The expression is constant, return it as is
1068                     if (sourceIndex == 0)
1069                     {
1070                         return expression;
1071                     }
1072 
1073                     // We have more than one result collected, therefore we need to concatenate
1074                     // into the final result string. This does mean that we will lose type information.
1075                     // However since the user wanted contatenation, then they clearly wanted that to happen.
1076 
1077                     // Initialize our output string to empty string.
1078                     // This method is called very often - of the order of 3,000 times per project.
1079                     // With the reuseable string builder, there's no particular need to initialize the length as it will already have grown.
1080                     using (var result = new ReuseableStringBuilder())
1081                     {
1082                         // Append our collected results
1083                         if (results != null)
1084                         {
1085                             // Create a combined result string from the result components that we've gathered
1086                             foreach (object component in results)
1087                             {
1088                                 result.Append(FileUtilities.MaybeAdjustFilePath(component.ToString()));
1089                             }
1090                         }
1091 
1092                         // Append the last result we collected (it wasn't added to the list)
1093                         if (lastResult != null)
1094                         {
1095                             result.Append(FileUtilities.MaybeAdjustFilePath(lastResult.ToString()));
1096                         }
1097 
1098                         // And if we couldn't find anymore property tags in the expression,
1099                         // so just literally copy the remainder into the result.
1100                         if (expression.Length - sourceIndex > 0)
1101                         {
1102                             result.Append(expression, sourceIndex, expression.Length - sourceIndex);
1103                         }
1104 
1105                         return OpportunisticIntern.InternableToString(result);
1106                     }
1107                 }
1108             }
1109 
1110             /// <summary>
1111             /// Expand the body of the property, including any functions that it may contain
1112             /// </summary>
ExpandPropertyBody(string propertyBody, object propertyValue, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)1113             internal static object ExpandPropertyBody(string propertyBody, object propertyValue, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
1114             {
1115                 Function<T> function = null;
1116                 string propertyName = propertyBody;
1117 
1118                 // Trim the body for compatibility reasons:
1119                 // Spaces are not valid property name chars, but $( Foo ) is allowed, and should always expand to BLANK.
1120                 // Do a very fast check for leading and trailing whitespace, and trim them from the property body if we have any.
1121                 // But we will do a property name lookup on the propertyName that we held onto.
1122                 if (Char.IsWhiteSpace(propertyBody[0]) || Char.IsWhiteSpace(propertyBody[propertyBody.Length - 1]))
1123                 {
1124                     propertyBody = propertyBody.Trim();
1125                 }
1126 
1127                 // If we don't have a clean propertybody then we'll do deeper checks to see
1128                 // if what we have is a function
1129                 if (!IsValidPropertyName(propertyBody))
1130                 {
1131                     if (propertyBody.Contains(".") || propertyBody[0] == '[')
1132                     {
1133                         if (BuildParameters.DebugExpansion)
1134                         {
1135                             Console.WriteLine("Expanding: {0}", propertyBody);
1136                         }
1137 
1138                         // This is a function
1139                         function = Function<T>.ExtractPropertyFunction(propertyBody, elementLocation, propertyValue, usedUninitializedProperties);
1140 
1141                         // We may not have been able to parse out a function
1142                         if (function != null)
1143                         {
1144                             // We will have either extracted the actual property name
1145                             // or realised that there is none (static function), and have recorded a null
1146                             propertyName = function.Receiver;
1147                         }
1148                         else
1149                         {
1150                             // In the event that we have been handed an unrecognized property body, throw
1151                             // an invalid function property exception.
1152                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", propertyBody, String.Empty);
1153                             return null;
1154                         }
1155                     }
1156                     else if (propertyValue == null && propertyBody.Contains("[")) // a single property indexer
1157                     {
1158                         int indexerStart = propertyBody.IndexOf('[');
1159                         int indexerEnd = propertyBody.IndexOf(']');
1160 
1161                         if (indexerStart < 0 || indexerEnd < 0)
1162                         {
1163                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", propertyBody, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedSquareBrackets"));
1164                         }
1165                         else
1166                         {
1167                             propertyValue = LookupProperty(properties, propertyBody, 0, indexerStart - 1, elementLocation, usedUninitializedProperties);
1168                             propertyBody = propertyBody.Substring(indexerStart);
1169 
1170                             // recurse so that the function representing the indexer can be executed on the property value
1171                             return ExpandPropertyBody(propertyBody, propertyValue, properties, options, elementLocation, usedUninitializedProperties);
1172                         }
1173                     }
1174                     else
1175                     {
1176                         // In the event that we have been handed an unrecognized property body, throw
1177                         // an invalid function property exception.
1178                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", propertyBody, String.Empty);
1179                         return null;
1180                     }
1181                 }
1182 
1183                 // Find the property value in our property collection.  This
1184                 // will automatically return "" (empty string) if the property
1185                 // doesn't exist in the collection, and we're not executing a static function
1186                 if (!String.IsNullOrEmpty(propertyName))
1187                 {
1188                     propertyValue = LookupProperty(properties, propertyName, elementLocation, usedUninitializedProperties);
1189                 }
1190 
1191                 if (function != null)
1192                 {
1193                     try
1194                     {
1195                         // Because of the rich expansion capabilities of MSBuild, we need to keep things
1196                         // as strings, since property expansion & string embedding can happen anywhere
1197                         // propertyValue can be null here, when we're invoking a static function
1198                         propertyValue = function.Execute(propertyValue, properties, options, elementLocation);
1199                     }
1200                     catch (Exception) when (options.HasFlag(ExpanderOptions.LeavePropertiesUnexpandedOnError))
1201                     {
1202                         propertyValue = propertyBody;
1203                     }
1204                 }
1205 
1206                 return propertyValue;
1207             }
1208 
1209             /// <summary>
1210             /// Convert the object into an MSBuild friendly string
1211             /// Arrays are supported.
1212             /// Will not return NULL
1213             /// </summary>
ConvertToString(object valueToConvert)1214             internal static string ConvertToString(object valueToConvert)
1215             {
1216                 if (valueToConvert != null)
1217                 {
1218                     Type valueType = valueToConvert.GetType();
1219                     string convertedString;
1220 
1221                     // If the type is a string, then there is nothing to do
1222                     if (valueType == typeof(string))
1223                     {
1224                         convertedString = (string)valueToConvert;
1225                     }
1226                     else if (valueToConvert is IDictionary)
1227                     {
1228                         // If the return type is an IDictionary, then we convert this to
1229                         // a semi-colon delimited set of A=B pairs.
1230                         // Key and Value are converted to string and escaped
1231                         IDictionary dictionary = valueToConvert as IDictionary;
1232                         using (var builder = new ReuseableStringBuilder())
1233                         {
1234                             foreach (DictionaryEntry entry in dictionary)
1235                             {
1236                                 if (builder.Length > 0)
1237                                 {
1238                                     builder.Append(';');
1239                                 }
1240 
1241                                 // convert and escape each key and value in the dictionary entry
1242                                 builder.Append(EscapingUtilities.Escape(ConvertToString(entry.Key)));
1243                                 builder.Append('=');
1244                                 builder.Append(EscapingUtilities.Escape(ConvertToString(entry.Value)));
1245                             }
1246 
1247                             convertedString = OpportunisticIntern.InternableToString(builder);
1248                         }
1249                     }
1250                     else if (valueToConvert is IEnumerable)
1251                     {
1252                         // If the return is enumerable, then we'll convert to semi-colon delimited elements
1253                         // each of which must be converted, so we'll recurse for each element
1254                         using (var builder = new ReuseableStringBuilder())
1255                         {
1256                             IEnumerable enumerable = (IEnumerable)valueToConvert;
1257 
1258                             foreach (object element in enumerable)
1259                             {
1260                                 if (builder.Length > 0)
1261                                 {
1262                                     builder.Append(';');
1263                                 }
1264 
1265                                 // we need to convert and escape each element of the array
1266                                 builder.Append(EscapingUtilities.Escape(ConvertToString(element)));
1267                             }
1268 
1269                             convertedString = OpportunisticIntern.InternableToString(builder);
1270                         }
1271                     }
1272                     else
1273                     {
1274                         // The fall back is always to just convert to a string directly.
1275                         convertedString = valueToConvert.ToString();
1276                     }
1277 
1278                     return convertedString;
1279                 }
1280                 else
1281                 {
1282                     return String.Empty;
1283                 }
1284             }
1285 
1286             /// <summary>
1287             /// Look up a simple property reference by the name of the property, e.g. "Foo" when expanding $(Foo)
1288             /// </summary>
LookupProperty(IPropertyProvider<T> properties, string propertyName, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)1289             private static object LookupProperty(IPropertyProvider<T> properties, string propertyName, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
1290             {
1291                 return LookupProperty(properties, propertyName, 0, propertyName.Length - 1, elementLocation, usedUninitializedProperties);
1292             }
1293 
1294             /// <summary>
1295             /// Look up a simple property reference by the name of the property, e.g. "Foo" when expanding $(Foo)
1296             /// </summary>
LookupProperty(IPropertyProvider<T> properties, string propertyName, int startIndex, int endIndex, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)1297             private static object LookupProperty(IPropertyProvider<T> properties, string propertyName, int startIndex, int endIndex, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
1298             {
1299                 T property = properties.GetProperty(propertyName, startIndex, endIndex);
1300 
1301                 object propertyValue;
1302 
1303                 if (property == null && ((endIndex - startIndex) >= 7) && MSBuildNameIgnoreCaseComparer.Default.Equals("MSBuild", propertyName, startIndex, 7))
1304                 {
1305                     // It could be one of the MSBuildThisFileXXXX properties,
1306                     // whose values vary according to the file they are in.
1307                     if (startIndex != 0 || endIndex != propertyName.Length)
1308                     {
1309                         propertyValue = ExpandMSBuildThisFileProperty(propertyName.Substring(startIndex, endIndex - startIndex + 1), elementLocation);
1310                     }
1311                     else
1312                     {
1313                         propertyValue = ExpandMSBuildThisFileProperty(propertyName, elementLocation);
1314                     }
1315                 }
1316                 else if (property == null)
1317                 {
1318                     // We have evaluated a property to null. We now need to see if we need to add it to the list of properties which are used before they have been initialized
1319                     //
1320                     // We also do not want to add the property to the list if the environment variable is not set, also we do not want to add the property to the list if we are currently
1321                     // evaluating a condition because a common pattern for msbuild projects is to see if the property evaluates to empty and then set a value as this would cause a considerable number of false positives.   <A Condition="'$(A)' == ''">default</A>
1322                     //
1323                     // Another pattern used is where a property concatonates with other values,  <a>$(a);something</a> however we do not want to add the a element to the list because again this would make a number of
1324                     // false positives. Therefore we check to see what element we are currently evaluating and if it is the same as our property we do not add the property to the list.
1325                     if (usedUninitializedProperties.Warn && usedUninitializedProperties.CurrentlyEvaluatingPropertyElementName != null)
1326                     {
1327                         // Check to see if the property name does not match the property we are currently evaluating, note the property we are currently evaluating in the element name, this means no $( or )
1328                         if (!MSBuildNameIgnoreCaseComparer.Default.Equals(usedUninitializedProperties.CurrentlyEvaluatingPropertyElementName, propertyName, startIndex, endIndex - startIndex + 1))
1329                         {
1330                             string propertyTrimed = propertyName.Substring(startIndex, endIndex - startIndex + 1);
1331                             if (!usedUninitializedProperties.Properties.ContainsKey(propertyTrimed))
1332                             {
1333                                 usedUninitializedProperties.Properties.Add(propertyTrimed, elementLocation);
1334                             }
1335                         }
1336                     }
1337 
1338                     propertyValue = String.Empty;
1339                 }
1340                 else
1341                 {
1342                     propertyValue = property.EvaluatedValueEscaped;
1343                 }
1344 
1345                 return propertyValue;
1346             }
1347 
1348             /// <summary>
1349             /// If the property name provided is one of the special
1350             /// per file properties named "MSBuildThisFileXXXX" then returns the value of that property.
1351             /// If the location provided does not have a path (eg., if it comes from a file that has
1352             /// never been saved) then returns empty string.
1353             /// If the property name is not one of those properties, returns empty string.
1354             /// </summary>
ExpandMSBuildThisFileProperty(string propertyName, IElementLocation elementLocation)1355             private static object ExpandMSBuildThisFileProperty(string propertyName, IElementLocation elementLocation)
1356             {
1357                 if (!ReservedPropertyNames.IsReservedProperty(propertyName))
1358                 {
1359                     return String.Empty;
1360                 }
1361 
1362                 if (elementLocation.File.Length == 0)
1363                 {
1364                     return String.Empty;
1365                 }
1366 
1367                 string value = String.Empty;
1368 
1369                 // Because String.Equals checks the length first, and these strings are almost
1370                 // all different lengths, this sequence is efficient.
1371                 if (String.Equals(propertyName, ReservedPropertyNames.thisFile, StringComparison.OrdinalIgnoreCase))
1372                 {
1373                     value = Path.GetFileName(elementLocation.File);
1374                 }
1375                 else if (String.Equals(propertyName, ReservedPropertyNames.thisFileName, StringComparison.OrdinalIgnoreCase))
1376                 {
1377                     value = Path.GetFileNameWithoutExtension(elementLocation.File);
1378                 }
1379                 else if (String.Equals(propertyName, ReservedPropertyNames.thisFileFullPath, StringComparison.OrdinalIgnoreCase))
1380                 {
1381                     value = FileUtilities.NormalizePath(elementLocation.File);
1382                 }
1383                 else if (String.Equals(propertyName, ReservedPropertyNames.thisFileExtension, StringComparison.OrdinalIgnoreCase))
1384                 {
1385                     value = Path.GetExtension(elementLocation.File);
1386                 }
1387                 else if (String.Equals(propertyName, ReservedPropertyNames.thisFileDirectory, StringComparison.OrdinalIgnoreCase))
1388                 {
1389                     value = FileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(elementLocation.File));
1390                 }
1391                 else if (String.Equals(propertyName, ReservedPropertyNames.thisFileDirectoryNoRoot, StringComparison.OrdinalIgnoreCase))
1392                 {
1393                     string directory = Path.GetDirectoryName(elementLocation.File);
1394                     int rootLength = Path.GetPathRoot(directory).Length;
1395                     string directoryNoRoot = directory.Substring(rootLength);
1396                     directoryNoRoot = FileUtilities.EnsureTrailingSlash(directoryNoRoot);
1397                     directoryNoRoot = FileUtilities.EnsureNoLeadingSlash(directoryNoRoot);
1398                     value = directoryNoRoot;
1399                 }
1400 
1401                 return value;
1402             }
1403 
1404 #if FEATURE_WIN32_REGISTRY
1405             /// <summary>
1406             /// Given a string like "Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation", return the value at that location
1407             /// in the registry. If the value isn't found, returns String.Empty.
1408             /// Properties may refer to a registry location by using the syntax for example
1409             /// "$(Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation)", where "HKEY_LOCAL_MACHINE\Software\Vendor\Tools" is the key and
1410             /// "TaskLocation" is the name of the value.  The name of the value and the preceding "@" may be omitted if
1411             /// the default value is desired.
1412             /// </summary>
ExpandRegistryValue(string registryExpression, IElementLocation elementLocation)1413             private static string ExpandRegistryValue(string registryExpression, IElementLocation elementLocation)
1414             {
1415                 // Remove "Registry:" prefix
1416                 string registryLocation = registryExpression.Substring(9);
1417 
1418                 // Split off the value name -- the part after the "@" sign. If there's no "@" sign, then it's the default value name
1419                 // we want.
1420                 int firstAtSignOffset = registryLocation.IndexOf('@');
1421                 int lastAtSignOffset = registryLocation.LastIndexOf('@');
1422 
1423                 ProjectErrorUtilities.VerifyThrowInvalidProject(firstAtSignOffset == lastAtSignOffset, elementLocation, "InvalidRegistryPropertyExpression", "$(" + registryExpression + ")", String.Empty);
1424 
1425                 string valueName = lastAtSignOffset == -1 || lastAtSignOffset == registryLocation.Length - 1
1426                     ? null : registryLocation.Substring(lastAtSignOffset + 1);
1427 
1428                 // If there's no '@', or '@' is first, then we'll use null or String.Empty for the location; otherwise
1429                 // the location is the part before the '@'
1430                 string registryKeyName = lastAtSignOffset != -1 ? registryLocation.Substring(0, lastAtSignOffset) : registryLocation;
1431 
1432                 string result = String.Empty;
1433                 if (registryKeyName != null)
1434                 {
1435                     // We rely on the '@' character to delimit the key and its value, but the registry
1436                     // allows this character to be used in the names of keys and the names of values.
1437                     // Hence we use our standard escaping mechanism to allow users to access such keys
1438                     // and values.
1439                     registryKeyName = EscapingUtilities.UnescapeAll(registryKeyName);
1440 
1441                     if (valueName != null)
1442                     {
1443                         valueName = EscapingUtilities.UnescapeAll(valueName);
1444                     }
1445 
1446                     try
1447                     {
1448                         // Unless we are running under Windows, don't bother with anything but the user keys
1449                         if (!NativeMethodsShared.IsWindows && !registryKeyName.StartsWith("HKEY_CURRENT_USER", StringComparison.OrdinalIgnoreCase))
1450                         {
1451                             // Fake common requests to HKLM that we can resolve
1452 
1453                             // This is the base path of the framework
1454                             if (registryKeyName.StartsWith(
1455                                 @"HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework",
1456                                 StringComparison.OrdinalIgnoreCase) &&
1457                                 valueName.Equals("InstallRoot", StringComparison.OrdinalIgnoreCase))
1458                             {
1459                                 return NativeMethodsShared.FrameworkBasePath + Path.DirectorySeparatorChar;
1460                             }
1461 
1462                             return string.Empty;
1463                         }
1464 
1465                         object valueFromRegistry = Registry.GetValue(registryKeyName, valueName, null /* default if key or value name is not found */);
1466 
1467                         if (null != valueFromRegistry)
1468                         {
1469                             // Convert the result to a string that is reasonable for MSBuild
1470                             result = ConvertToString(valueFromRegistry);
1471                         }
1472                         else
1473                         {
1474                             // This means either the key or value was not found in the registry.  In this case,
1475                             // we simply expand the property value to String.Empty to imitate the behavior of
1476                             // normal properties.
1477                             result = String.Empty;
1478                         }
1479                     }
1480                     catch (Exception ex)
1481                     {
1482                         if (ExceptionHandling.NotExpectedRegistryException(ex))
1483                         {
1484                             throw;
1485                         }
1486 
1487                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidRegistryPropertyExpression", "$(" + registryExpression + ")", ex.Message);
1488                     }
1489                 }
1490 
1491                 return result;
1492             }
1493 #else
1494             /// <summary>
1495             /// Given a string like "Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation", returns String.Empty, as FEATURE_WIN32_REGISTRY is off.
1496             /// </summary>
ExpandRegistryValue(string registryExpression, IElementLocation elementLocation)1497             private static string ExpandRegistryValue(string registryExpression, IElementLocation elementLocation)
1498             {
1499                 return String.Empty;
1500             }
1501 #endif
1502         }
1503 
1504         /// <summary>
1505         /// Expands item expressions, like @(Compile), possibly with transforms and/or separators.
1506         ///
1507         /// Item vectors are composed of a name, an optional transform, and an optional separator i.e.
1508         ///
1509         ///     @(&lt;name&gt;->'&lt;transform&gt;','&lt;separator&gt;')
1510         ///
1511         /// If a separator is not specified it defaults to a semi-colon. The transform expression is also optional, but if
1512         /// specified, it allows each item in the vector to have its item-spec converted to a different form. The transform
1513         /// expression can reference any custom metadata defined on the item, as well as the pre-defined item-spec modifiers.
1514         ///
1515         /// NOTE:
1516         /// 1) white space between &lt;name&gt;, &lt;transform&gt; and &lt;separator&gt; is ignored
1517         ///    i.e. @(&lt;name&gt;, '&lt;separator&gt;') is valid
1518         /// 2) the separator is not restricted to be a single character, it can be a string
1519         /// 3) the separator can be an empty string i.e. @(&lt;name&gt;,'')
1520         /// 4) specifying an empty transform is NOT the same as specifying no transform -- the former will reduce all item-specs
1521         ///    to empty strings
1522         ///
1523         /// if @(files) is a vector for the files a.txt and b.txt, then:
1524         ///
1525         ///     "my list: @(files)"                                 expands to string     "my list: a.txt;b.txt"
1526         ///
1527         ///     "my list: @(files,' ')"                             expands to string      "my list: a.txt b.txt"
1528         ///
1529         ///     "my list: @(files, '')"                             expands to string      "my list: a.txtb.txt"
1530         ///
1531         ///     "my list: @(files, '; ')"                           expands to string      "my list: a.txt; b.txt"
1532         ///
1533         ///     "my list: @(files->'%(Filename)')"                  expands to string      "my list: a;b"
1534         ///
1535         ///     "my list: @(files -> 'temp\%(Filename).xml', ' ')   expands to string      "my list: temp\a.xml temp\b.xml"
1536         ///
1537         ///     "my list: @(files->'')                              expands to string      "my list: ;"
1538         /// </summary>
1539         /// <remarks>
1540         /// This is a private nested class, exposed only through the Expander class.
1541         /// That allows it to hide its private methods even from Expander.
1542         /// </remarks>
1543         private static class ItemExpander
1544         {
1545             /// <summary>
1546             /// Execute the list of transform functions
1547             /// </summary>
1548             /// <typeparam name="S">class, IItem</typeparam>
1549             internal static IEnumerable<Pair<string, S>> Transform<S>(Expander<P, I> expander, bool includeNullEntries, Stack<TransformFunction<S>> transformFunctionStack, IEnumerable<Pair<string, S>> itemsOfType)
1550                 where S : class, IItem
1551             {
1552                 // If we have transforms on our stack, then we'll execute those first
1553                 // This effectively runs backwards through the set
1554                 if (transformFunctionStack.Count > 0)
1555                 {
1556                     TransformFunction<S> function = transformFunctionStack.Pop();
1557 
1558                     foreach (Pair<string, S> item in Transform(expander, includeNullEntries, transformFunctionStack, function.Execute(expander, includeNullEntries, itemsOfType)))
1559                     {
1560                         yield return item;
1561                     }
1562                 }
1563                 else
1564                 {
1565                     // When we have no more tranforms on the stack, iterate over the items
1566                     // that we have to return them
1567                     foreach (Pair<string, S> item in itemsOfType)
1568                     {
1569                         yield return item;
1570                     }
1571                 }
1572             }
1573 
1574             /// <summary>
1575             /// Expands any item vector in the expression into items.
1576             ///
1577             /// For example, expands @(Compile->'%(foo)') to a set of items derived from the items in the "Compile" list.
1578             ///
1579             /// If there is no item vector in the expression (for example a literal "foo.cpp"), returns null.
1580             /// If the item vector expression expands to no items, returns an empty list.
1581             /// If item expansion is not allowed by the provided options, returns null.
1582             /// If there is an item vector but concatenated with something else, throws InvalidProjectFileException.
1583             /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
1584             ///
1585             /// If the expression is a transform, any transformations to an expression that evaluates to nothing (i.e., because
1586             /// an item has no value for a piece of metadata) are optionally indicated with a null entry in the list. This means
1587             /// that the length of the returned list is always the same as the length of the referenced item list in the input string.
1588             /// That's important for any correlation the caller wants to do.
1589             ///
1590             /// If expression was a transform, 'isTransformExpression' is true, otherwise false.
1591             ///
1592             /// Item type of the items returned is determined by the IItemFactory passed in; if the IItemFactory does not
1593             /// have an item type set on it, it will be given the item type of the item vector to use.
1594             /// </summary>
1595             /// <typeparam name="S">Type of the items provided by the item source used for expansion</typeparam>
1596             /// <typeparam name="T">Type of the items that should be returned</typeparam>
1597             internal static IList<T> ExpandSingleItemVectorExpressionIntoItems<S, T>(
1598                     Expander<P, I> expander, string expression, IItemProvider<S> items, IItemFactory<S, T> itemFactory, ExpanderOptions options,
1599                     bool includeNullEntries, out bool isTransformExpression, IElementLocation elementLocation)
1600                 where S : class, IItem
1601                 where T : class, IItem
1602             {
1603                 isTransformExpression = false;
1604 
1605                 var expressionCapture = ExpandSingleItemVectorExpressionIntoExpressionCapture(expression, options, elementLocation);
1606                 if (expressionCapture == null)
1607                 {
1608                     return null;
1609                 }
1610 
1611                 return ExpandExpressionCaptureIntoItems(expressionCapture, expander, items, itemFactory, options, includeNullEntries,
1612                     out isTransformExpression, elementLocation);
1613             }
1614 
ExpandSingleItemVectorExpressionIntoExpressionCapture( string expression, ExpanderOptions options, IElementLocation elementLocation)1615             internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorExpressionIntoExpressionCapture(
1616                     string expression, ExpanderOptions options, IElementLocation elementLocation)
1617             {
1618                 if (((options & ExpanderOptions.ExpandItems) == 0) || (expression.Length == 0))
1619                 {
1620                     return null;
1621                 }
1622 
1623                 List<ExpressionShredder.ItemExpressionCapture> matches = null;
1624 
1625                 if (s_invariantCompareInfo.IndexOf(expression, '@') == -1)
1626                 {
1627                     return null;
1628                 }
1629                 else
1630                 {
1631                     matches = ExpressionShredder.GetReferencedItemExpressions(expression);
1632 
1633                     if (matches == null)
1634                     {
1635                         return null;
1636                     }
1637                 }
1638 
1639                 ExpressionShredder.ItemExpressionCapture match = matches[0];
1640 
1641                 // We have a single valid @(itemlist) reference in the given expression.
1642                 // If the passed-in expression contains exactly one item list reference,
1643                 // with nothing else concatenated to the beginning or end, then proceed
1644                 // with itemizing it, otherwise error.
1645                 ProjectErrorUtilities.VerifyThrowInvalidProject(match.Value == expression, elementLocation, "EmbeddedItemVectorCannotBeItemized", expression);
1646                 ErrorUtilities.VerifyThrow(matches.Count == 1, "Expected just one item vector");
1647 
1648                 return match;
1649             }
1650 
1651             internal static IList<T> ExpandExpressionCaptureIntoItems<S, T>(
1652                     ExpressionShredder.ItemExpressionCapture expressionCapture, Expander<P, I> expander, IItemProvider<S> items, IItemFactory<S, T> itemFactory,
1653                     ExpanderOptions options, bool includeNullEntries, out bool isTransformExpression, IElementLocation elementLocation)
1654                 where S : class, IItem
1655                 where T : class, IItem
1656             {
1657                 ErrorUtilities.VerifyThrow(items != null, "Cannot expand items without providing items");
1658 
1659                 IList<T> result = null;
1660                 isTransformExpression = false;
1661                 bool brokeEarlyNonEmpty;
1662 
1663                 // If the incoming factory doesn't have an item type that it can use to
1664                 // create items, it's our indication that the caller wants its items to have the type of the
1665                 // expression being expanded. For example, items from expanding "@(Compile") should
1666                 // have the item type "Compile".
1667                 if (itemFactory.ItemType == null)
1668                 {
1669                     itemFactory.ItemType = expressionCapture.ItemType;
1670                 }
1671 
1672                 if (expressionCapture.Separator != null)
1673                 {
1674                     // Reference contains a separator, for example @(Compile, ';').
1675                     // We need to flatten the list into
1676                     // a scalar and then create a single item. Basically we need this
1677                     // to be able to convert item lists with user specified separators into properties.
1678                     string expandedItemVector;
1679                     using (var builder = new ReuseableStringBuilder())
1680                     {
1681                         brokeEarlyNonEmpty = ExpandExpressionCaptureIntoStringBuilder(expander, expressionCapture, items, elementLocation, builder, options);
1682 
1683                         if (brokeEarlyNonEmpty)
1684                         {
1685                             return null;
1686                         }
1687 
1688                         expandedItemVector = OpportunisticIntern.InternableToString(builder);
1689                     }
1690 
1691                     result = new List<T>(1);
1692 
1693                     if (expandedItemVector.Length > 0)
1694                     {
1695                         T newItem = itemFactory.CreateItem(expandedItemVector, elementLocation.File);
1696 
1697                         result.Add(newItem);
1698                     }
1699 
1700                     return result;
1701                 }
1702 
1703                 List<Pair<string, S>> itemsFromCapture;
1704                 brokeEarlyNonEmpty = ExpandExpressionCapture(expander, expressionCapture, items, elementLocation /* including null items */, options, true, out isTransformExpression, out itemsFromCapture);
1705 
1706                 if (brokeEarlyNonEmpty)
1707                 {
1708                     return null;
1709                 }
1710 
1711                 result = new List<T>(itemsFromCapture.Count);
1712 
1713                 foreach (var itemTuple in itemsFromCapture)
1714                 {
1715                     var itemSpec = itemTuple.Key;
1716                     var originalItem = itemTuple.Value;
1717 
1718                     if (itemSpec != null && originalItem == null)
1719                     {
1720                         // We have an itemspec, but no base item
1721                         result.Add(itemFactory.CreateItem(itemSpec, elementLocation.File));
1722                     }
1723                     else if (itemSpec != null && originalItem != null)
1724                     {
1725                         result.Add(itemSpec.Equals(originalItem.EvaluatedIncludeEscaped)
1726                             ? itemFactory.CreateItem(originalItem, elementLocation.File) // itemspec came from direct item reference, no transforms
1727                             : itemFactory.CreateItem(itemSpec, originalItem, elementLocation.File)); // itemspec came from a transform and is different from its original item
1728                     }
1729                     else if (includeNullEntries)
1730                     {
1731                         // The itemspec is null and the base item doesn't matter
1732                         result.Add(null);
1733                     }
1734                 }
1735 
1736                 return result;
1737             }
1738 
1739             /// <summary>
1740             /// Expands an expression capture into a list of items
1741             /// If the capture uses a separator, then all the items are concatenated into one string using that separator.
1742             ///
1743             /// Returns true if ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and so it broke out early.
1744             /// </summary>
1745             /// <param name="isTransformExpression"></param>
1746             /// <param name="itemsFromCapture">
1747             /// List of items.
1748             ///
1749             /// Item1 represents the item string, escaped
1750             /// Item2 represents the original item.
1751             ///
1752             /// Item1 differs from Item2's string when it is coming from a transform
1753             ///
1754             /// </param>
1755             /// <param name="expander">The expander whose state will be used to expand any transforms</param>
1756             /// <param name="expressionCapture">The <see cref="ExpandSingleItemVectorExpressionIntoExpressionCapture"/> representing the structure of an item expression</param>
1757             /// <param name="evaluatedItems"><see cref="IItemProvider{T}"/> to provide the inital items (which may get subsequently transformed, if <paramref name="expressionCapture"/> is a transform expression)></param>
1758             /// <param name="elementLocation">Location of the xml element containing the <paramref name="expressionCapture"/></param>
1759             /// <param name="options">expander options</param>
1760             /// <param name="includeNullEntries">Wether to include items that evaluated to empty / null</param>
1761             internal static bool ExpandExpressionCapture<S>(
1762                 Expander<P, I> expander,
1763                 ExpressionShredder.ItemExpressionCapture expressionCapture,
1764                 IItemProvider<S> evaluatedItems,
1765                 IElementLocation elementLocation,
1766                 ExpanderOptions options,
1767                 bool includeNullEntries,
1768                 out bool isTransformExpression,
1769                 out List<Pair<string, S>> itemsFromCapture
1770                 )
1771                 where S : class, IItem
1772             {
1773                 ErrorUtilities.VerifyThrow(evaluatedItems != null, "Cannot expand items without providing items");
1774                 // There's something wrong with the expression, and we ended up with a blank item type
1775                 ProjectErrorUtilities.VerifyThrowInvalidProject(!string.IsNullOrEmpty(expressionCapture.ItemType), elementLocation, "InvalidFunctionPropertyExpression");
1776 
1777                 isTransformExpression = false;
1778 
1779                 var itemsOfType = evaluatedItems.GetItems(expressionCapture.ItemType);
1780 
1781                 // If there are no items of the given type, then bail out early
1782                 if (itemsOfType.Count == 0)
1783                 {
1784                     // .. but only if there isn't a function "Count()", since that will want to return something (zero) for an empty list
1785                     if (expressionCapture.Captures == null ||
1786                         !expressionCapture.Captures.Any(capture => string.Equals(capture.FunctionName, "Count", StringComparison.OrdinalIgnoreCase)))
1787                     {
1788                         itemsFromCapture = new List<Pair<string, S>>();
1789                         return false;
1790                     }
1791                 }
1792 
1793                 if (expressionCapture.Captures != null)
1794                 {
1795                     isTransformExpression = true;
1796                 }
1797 
1798                 itemsFromCapture = new List<Pair<string, S>>(itemsOfType.Count);
1799 
1800                 if (!isTransformExpression)
1801                 {
1802                     // No transform: expression is like @(Compile), so include the item spec without a transform base item
1803                     foreach (S item in itemsOfType)
1804                     {
1805                         if ((item.EvaluatedIncludeEscaped.Length > 0) && (options & ExpanderOptions.BreakOnNotEmpty) != 0)
1806                         {
1807                             return true;
1808                         }
1809 
1810                         itemsFromCapture.Add(new Pair<string, S>(item.EvaluatedIncludeEscaped, item));
1811                     }
1812                 }
1813                 else
1814                 {
1815                     Stack<TransformFunction<S>> transformFunctionStack = PrepareTransformStackFromMatch<S>(elementLocation, expressionCapture);
1816 
1817                     // iterate over the tranform chain, creating the final items from its results
1818                     foreach (Pair<string, S> itemTuple in Transform<S>(expander, includeNullEntries, transformFunctionStack, IntrinsicItemFunctions<S>.GetItemPairEnumerable(itemsOfType)))
1819                     {
1820                         if (!string.IsNullOrEmpty(itemTuple.Key) && (options & ExpanderOptions.BreakOnNotEmpty) != 0)
1821                         {
1822                             return true; // broke out early; result cannot be trusted
1823                         }
1824 
1825                         itemsFromCapture.Add(itemTuple);
1826                     }
1827                 }
1828 
1829                 if (expressionCapture.Separator != null)
1830                 {
1831                     var joinedItems = string.Join(expressionCapture.Separator, itemsFromCapture.Select(i => i.Key));
1832                     itemsFromCapture.Clear();
1833                     itemsFromCapture.Add(new Pair<string, S>(joinedItems, null));
1834                 }
1835 
1836                 return false; // did not break early
1837             }
1838 
1839             /// <summary>
1840             /// Expands all item vectors embedded in the given expression into a single string.
1841             /// If the expression is empty, returns empty string.
1842             /// If ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and it broke out early, returns null. Otherwise the result can be trusted.
1843             /// </summary>
1844             /// <typeparam name="T">Type of the items provided</typeparam>
1845             internal static string ExpandItemVectorsIntoString<T>(Expander<P, I> expander, string expression, IItemProvider<T> items, ExpanderOptions options, IElementLocation elementLocation)
1846                 where T : class, IItem
1847             {
1848                 if (((options & ExpanderOptions.ExpandItems) == 0) || (expression.Length == 0))
1849                 {
1850                     return expression;
1851                 }
1852 
1853                 ErrorUtilities.VerifyThrow(items != null, "Cannot expand items without providing items");
1854 
1855                 List<ExpressionShredder.ItemExpressionCapture> matches = ExpressionShredder.GetReferencedItemExpressions(expression);
1856 
1857                 if (matches == null)
1858                 {
1859                     return expression;
1860                 }
1861 
1862                 using (var builder = new ReuseableStringBuilder())
1863                 {
1864                     // As we walk through the matches, we need to copy out the original parts of the string which
1865                     // are not covered by the match.  This preserves original behavior which did not trim whitespace
1866                     // from between separators.
1867                     int lastStringIndex = 0;
1868                     for (int i = 0; i < matches.Count; i++)
1869                     {
1870                         if (matches[i].Index > lastStringIndex)
1871                         {
1872                             if ((options & ExpanderOptions.BreakOnNotEmpty) != 0)
1873                             {
1874                                 return null;
1875                             }
1876 
1877                             builder.Append(expression, lastStringIndex, matches[i].Index - lastStringIndex);
1878                         }
1879 
1880                         bool brokeEarlyNonEmpty = ExpandExpressionCaptureIntoStringBuilder(expander, matches[i], items, elementLocation, builder, options);
1881 
1882                         if (brokeEarlyNonEmpty)
1883                         {
1884                             return null;
1885                         }
1886 
1887                         lastStringIndex = matches[i].Index + matches[i].Length;
1888                     }
1889 
1890                     builder.Append(expression, lastStringIndex, expression.Length - lastStringIndex);
1891 
1892                     return OpportunisticIntern.InternableToString(builder);
1893                 }
1894             }
1895 
1896             /// <summary>
1897             /// Prepare the stack of transforms that will be executed on a given set of items
1898             /// </summary>
1899             /// <typeparam name="S">class, IItem</typeparam>
1900             private static Stack<TransformFunction<S>> PrepareTransformStackFromMatch<S>(IElementLocation elementLocation, ExpressionShredder.ItemExpressionCapture match)
1901                 where S : class, IItem
1902             {
1903                 // There's something wrong with the expression, and we ended up with no function names
1904                 ProjectErrorUtilities.VerifyThrowInvalidProject(match.Captures.Count > 0, elementLocation, "InvalidFunctionPropertyExpression");
1905 
1906                 Stack<TransformFunction<S>> transformFunctionStack = new Stack<TransformFunction<S>>(match.Captures.Count);
1907 
1908                 // Create a TransformFunction for each transform in the chain by extracting the relevant information
1909                 // from the regex parsing results
1910                 // Each will be pushed onto a stack in right to left order (i.e. the inner/right most will be on the
1911                 // bottom of the stack, the outer/left most will be on the top
1912                 for (int n = match.Captures.Count - 1; n >= 0; n--)
1913                 {
1914                     string function = match.Captures[n].Value;
1915                     string functionName = match.Captures[n].FunctionName;
1916                     string argumentsExpression = match.Captures[n].FunctionArguments;
1917 
1918                     string[] arguments = null;
1919 
1920                     if (functionName == null)
1921                     {
1922                         functionName = "ExpandQuotedExpressionFunction";
1923                         arguments = new string[] { function };
1924                     }
1925                     else if (argumentsExpression != null)
1926                     {
1927                         arguments = ExtractFunctionArguments(elementLocation, argumentsExpression, argumentsExpression);
1928                     }
1929 
1930                     IntrinsicItemFunctions<S>.ItemTransformFunction transformFunction = IntrinsicItemFunctions<S>.GetItemTransformFunction(elementLocation, functionName, typeof(S));
1931 
1932                     // Push our tranform on to the stack
1933                     transformFunctionStack.Push(new TransformFunction<S>(elementLocation, functionName, transformFunction, arguments));
1934                 }
1935 
1936                 return transformFunctionStack;
1937             }
1938 
1939             /// <summary>
1940             /// Expand the match provided into a string, and append that to the provided string builder.
1941             /// Returns true if ExpanderOptions.BreakOnNotEmpty was passed, expression was going to be non-empty, and so it broke out early.
1942             /// </summary>
1943             /// <typeparam name="S">Type of source items</typeparam>
1944             private static bool ExpandExpressionCaptureIntoStringBuilder<S>(
1945                 Expander<P, I> expander,
1946                 ExpressionShredder.ItemExpressionCapture capture,
1947                 IItemProvider<S> evaluatedItems,
1948                 IElementLocation elementLocation,
1949                 ReuseableStringBuilder builder,
1950                 ExpanderOptions options
1951                 )
1952                 where S : class, IItem
1953             {
1954                 List<Pair<string, S>> itemsFromCapture;
1955                 bool throwaway;
1956                 var brokeEarlyNonEmpty = ExpandExpressionCapture(expander, capture, evaluatedItems, elementLocation /* including null items */, options, true, out throwaway, out itemsFromCapture);
1957 
1958                 if (brokeEarlyNonEmpty)
1959                 {
1960                     return true;
1961                 }
1962 
1963                 // if the capture.Separator is not null, then ExpandExpressionCapture would have joined the items using that separator itself
1964                 foreach (var item in itemsFromCapture)
1965                 {
1966                     builder.Append(item.Key);
1967                     builder.Append(';');
1968                 }
1969 
1970                 // Remove trailing separator if we added one
1971                 if (itemsFromCapture.Count > 0)
1972                     builder.Length--;
1973 
1974                 return false;
1975             }
1976 
1977             /// <summary>
1978             /// The set of functions that called during an item transformation, e.g. @(CLCompile->ContainsMetadata('MetaName', 'metaValue'))
1979             /// </summary>
1980             /// <typeparam name="S">class, IItem</typeparam>
1981             internal static class IntrinsicItemFunctions<S>
1982                 where S : class, IItem
1983             {
1984                 /// <summary>
1985                 /// A cache of previously created item function delegates
1986                 /// </summary>
1987                 private static ConcurrentDictionary<string, ItemTransformFunction> s_transformFunctionDelegateCache = new ConcurrentDictionary<string, ItemTransformFunction>(StringComparer.OrdinalIgnoreCase);
1988 
1989                 /// <summary>
1990                 /// Delegate that represents the signature of all item transformation functions
1991                 /// This is used to support calling the functions by name
1992                 /// </summary>
ItemTransformFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)1993                 public delegate IEnumerable<Pair<string, S>> ItemTransformFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments);
1994 
1995                 /// <summary>
1996                 /// Get a delegate to the given item transformation function by supplying the name and the
1997                 /// Item type that should be used
1998                 /// </summary>
GetItemTransformFunction(IElementLocation elementLocation, string functionName, Type itemType)1999                 internal static ItemTransformFunction GetItemTransformFunction(IElementLocation elementLocation, string functionName, Type itemType)
2000                 {
2001                     ItemTransformFunction transformFunction = null;
2002                     string qualifiedFunctionName = itemType.FullName + "::" + functionName;
2003 
2004                     // We may have seen this delegate before, if so grab the one we already created
2005                     if (!s_transformFunctionDelegateCache.TryGetValue(qualifiedFunctionName, out transformFunction))
2006                     {
2007                         if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(functionName))
2008                         {
2009                             // Create a delegate to the function we're going to call
2010                             transformFunction = new ItemTransformFunction(ItemSpecModifierFunction);
2011                         }
2012                         else
2013                         {
2014                             MethodInfo itemFunctionInfo = typeof(IntrinsicItemFunctions<S>).GetMethod(functionName, BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Static);
2015 
2016                             if (itemFunctionInfo == null)
2017                             {
2018                                 functionName = "ExecuteStringFunction";
2019                                 itemFunctionInfo = typeof(IntrinsicItemFunctions<S>).GetMethod(functionName, BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Static);
2020                                 if (itemFunctionInfo == null)
2021                                 {
2022                                     ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "UnknownItemFunction", functionName);
2023                                     return null;
2024                                 }
2025                             }
2026                             try
2027                             {
2028                                 // Create a delegate to the function we're going to call
2029                                 transformFunction = (ItemTransformFunction)itemFunctionInfo.CreateDelegate(typeof(ItemTransformFunction));
2030                             }
2031                             catch (ArgumentException)
2032                             {
2033                                 //  Prior to porting to .NET Core, this code was passing false as the throwOnBindFailure parameter to Delegate.CreateDelegate.
2034                                 //  Since MethodInfo.CreateDelegate doesn't have this option, we catch the ArgumentException to preserve the previous behavior
2035                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "UnknownItemFunction", functionName);
2036                             }
2037                         }
2038 
2039                         if (transformFunction == null)
2040                         {
2041                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "UnknownItemFunction", functionName);
2042                             return null;
2043                         }
2044 
2045                         // record our delegate for future use
2046                         s_transformFunctionDelegateCache[qualifiedFunctionName] = transformFunction;
2047                     }
2048 
2049                     return transformFunction;
2050                 }
2051 
2052                 /// <summary>
2053                 /// Create an enumerator from a base IEnumerable of items into an enumerable
2054                 /// of transformation result which includes the new itemspec and the base item
2055                 /// </summary>
GetItemPairEnumerable(IEnumerable<S> itemsOfType)2056                 internal static IEnumerable<Pair<string, S>> GetItemPairEnumerable(IEnumerable<S> itemsOfType)
2057                 {
2058                     // iterate over the items, and yield out items in the tuple format
2059                     foreach (var item in itemsOfType)
2060                     {
2061                         if (Traits.Instance.UseLazyWildCardEvaluation)
2062                         {
2063                             foreach (
2064                                 var resultantItem in
2065                                 EngineFileUtilities.GetFileListEscaped(
2066                                     item.ProjectDirectory,
2067                                     item.EvaluatedIncludeEscaped,
2068                                     forceEvaluate: true))
2069                             {
2070                                 yield return new Pair<string, S>(resultantItem, item);
2071                             }
2072                         }
2073                         else
2074                         {
2075                             yield return new Pair<string, S>(item.EvaluatedIncludeEscaped, item);
2076                         }
2077                     }
2078                 }
2079 
2080                 /// <summary>
2081                 /// Intrinsic function that returns the number of items in the list
2082                 /// </summary>
Count(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2083                 internal static IEnumerable<Pair<string, S>> Count(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2084                 {
2085                     yield return new Pair<string, S>(Convert.ToString(itemsOfType.Count(), CultureInfo.InvariantCulture), null /* no base item */);
2086                 }
2087 
2088                 /// <summary>
2089                 /// Intrinsic function that returns the specified built-in modifer value of the items in itemsOfType
2090                 /// Tuple is {current item include, item under transformation}
2091                 /// </summary>
ItemSpecModifierFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2092                 internal static IEnumerable<Pair<string, S>> ItemSpecModifierFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2093                 {
2094                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments == null || arguments.Length == 0, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2095 
2096                     foreach (Pair<string, S> item in itemsOfType)
2097                     {
2098                         // If the item include has become empty,
2099                         // this is the end of the pipeline for this item
2100                         if (String.IsNullOrEmpty(item.Key))
2101                         {
2102                             continue;
2103                         }
2104 
2105                         string result = null;
2106 
2107                         try
2108                         {
2109                             // If we're not a ProjectItem or ProjectItemInstance, then ProjectDirectory will be null.
2110                             // In that case, we're safe to get the current directory as we'll be running on TaskItems which
2111                             // only exist within a target where we can trust the current directory
2112                             string directoryToUse = item.Value.ProjectDirectory ?? Directory.GetCurrentDirectory();
2113                             string definingProjectEscaped = item.Value.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath);
2114 
2115                             result = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(directoryToUse, item.Key, definingProjectEscaped, functionName);
2116                         }
2117                         catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception.
2118                         {
2119                             // InvalidOperationException is how GetItemSpecModifier communicates invalid conditions upwards, so
2120                             // we do not want to rethrow in that case.
2121                             if (ExceptionHandling.NotExpectedException(e) && !(e is InvalidOperationException))
2122                             {
2123                                 throw;
2124                             }
2125 
2126                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidItemFunctionExpression", functionName, item.Key, e.Message);
2127                         }
2128 
2129                         if (!String.IsNullOrEmpty(result))
2130                         {
2131                             // GetItemSpecModifier will have returned us an escaped string
2132                             // there is nothing more to do than yield it into the pipeline
2133                             yield return new Pair<string, S>(result, item.Value);
2134                         }
2135                         else if (includeNullEntries)
2136                         {
2137                             yield return new Pair<string, S>(null, item.Value);
2138                         }
2139                     }
2140                 }
2141 
2142                 /// <summary>
2143                 /// Intrinsic function that returns the DirectoryName of the items in itemsOfType
2144                 /// UNDONE: This can be removed in favor of a built-in %(DirectoryName) metadata in future.
2145                 /// </summary>
DirectoryName(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2146                 internal static IEnumerable<Pair<string, S>> DirectoryName(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2147                 {
2148                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments == null || arguments.Length == 0, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2149 
2150                     Dictionary<string, string> directoryNameTable = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
2151 
2152                     foreach (Pair<string, S> item in itemsOfType)
2153                     {
2154                         // If the item include has become empty,
2155                         // this is the end of the pipeline for this item
2156                         if (String.IsNullOrEmpty(item.Key))
2157                         {
2158                             continue;
2159                         }
2160 
2161                         string directoryName = null;
2162 
2163                         if (!directoryNameTable.TryGetValue(item.Key, out directoryName))
2164                         {
2165                             // Unescape as we are passing to the file system
2166                             string unescapedPath = EscapingUtilities.UnescapeAll(item.Key);
2167 
2168                             try
2169                             {
2170                                 string rootedPath;
2171 
2172                                 // If we're a projectitem instance then we need to get
2173                                 // the project directory and be relative to that
2174                                 if (Path.IsPathRooted(unescapedPath))
2175                                 {
2176                                     rootedPath = unescapedPath;
2177                                 }
2178                                 else
2179                                 {
2180                                     // If we're not a ProjectItem or ProjectItemInstance, then ProjectDirectory will be null.
2181                                     // In that case, we're safe to get the current directory as we'll be running on TaskItems which
2182                                     // only exist within a target where we can trust the current directory
2183                                     string baseDirectoryToUse = item.Value.ProjectDirectory ?? String.Empty;
2184                                     rootedPath = Path.Combine(baseDirectoryToUse, unescapedPath);
2185                                 }
2186 
2187                                 directoryName = Path.GetDirectoryName(rootedPath);
2188                             }
2189                             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
2190                             {
2191                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidItemFunctionExpression", functionName, item.Key, e.Message);
2192                             }
2193 
2194                             // Escape as this is going back into the engine
2195                             directoryName = EscapingUtilities.Escape(directoryName);
2196                             directoryNameTable[unescapedPath] = directoryName;
2197                         }
2198 
2199                         if (!String.IsNullOrEmpty(directoryName))
2200                         {
2201                             // return a result through the enumerator
2202                             yield return new Pair<string, S>(directoryName, item.Value);
2203                         }
2204                         else if (includeNullEntries)
2205                         {
2206                             yield return new Pair<string, S>(null, item.Value);
2207                         }
2208                     }
2209                 }
2210 
2211                 /// <summary>
2212                 /// Intrinsic function that returns the contents of the metadata in specified in argument[0]
2213                 /// </summary>
Metadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2214                 internal static IEnumerable<Pair<string, S>> Metadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2215                 {
2216                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments != null && arguments.Length == 1, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2217 
2218                     string metadataName = arguments[0];
2219 
2220                     foreach (Pair<string, S> item in itemsOfType)
2221                     {
2222                         if (item.Value != null)
2223                         {
2224                             string metadataValue = null;
2225 
2226                             try
2227                             {
2228                                 metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
2229                             }
2230                             catch (ArgumentException ex) // Blank metadata name
2231                             {
2232                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2233                             }
2234                             catch (InvalidOperationException ex)
2235                             {
2236                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2237                             }
2238 
2239                             if (!String.IsNullOrEmpty(metadataValue))
2240                             {
2241                                 // It may be that the itemspec has unescaped ';'s in it so we need to split here to handle
2242                                 // that case.
2243                                 if (s_invariantCompareInfo.IndexOf(metadataValue, ';') >= 0)
2244                                 {
2245                                     var splits = ExpressionShredder.SplitSemiColonSeparatedList(metadataValue);
2246 
2247                                     foreach (string itemSpec in splits)
2248                                     {
2249                                         // return a result through the enumerator
2250                                         yield return new Pair<string, S>(itemSpec, item.Value);
2251                                     }
2252                                 }
2253                                 else
2254                                 {
2255                                     // return a result through the enumerator
2256                                     yield return new Pair<string, S>(metadataValue, item.Value);
2257                                 }
2258                             }
2259                             else if (metadataValue != String.Empty && includeNullEntries)
2260                             {
2261                                 yield return new Pair<string, S>(metadataValue, item.Value);
2262                             }
2263                         }
2264                     }
2265                 }
2266 
2267                 /// <summary>
2268                 /// Intrinsic function that returns only the items from itemsOfType that have distinct Item1 in the Tuple
2269                 /// Using a case sensitive comparison
2270                 /// </summary>
DistinctWithCase(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2271                 internal static IEnumerable<Pair<string, S>> DistinctWithCase(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2272                 {
2273                     return DistinctWithComparer(expander, elementLocation, includeNullEntries, functionName, itemsOfType, arguments, StringComparer.Ordinal);
2274                 }
2275 
2276                 /// <summary>
2277                 /// Intrinsic function that returns only the items from itemsOfType that have distinct Item1 in the Tuple
2278                 /// Using a case insensitive comparison
2279                 /// </summary>
Distinct(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2280                 internal static IEnumerable<Pair<string, S>> Distinct(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2281                 {
2282                     return DistinctWithComparer(expander, elementLocation, includeNullEntries, functionName, itemsOfType, arguments, StringComparer.OrdinalIgnoreCase);
2283                 }
2284 
2285                 /// <summary>
2286                 /// Intrinsic function that returns only the items from itemsOfType that have distinct Item1 in the Tuple
2287                 /// Using a case insensitive comparison
2288                 /// </summary>
DistinctWithComparer(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments, StringComparer comparer)2289                 internal static IEnumerable<Pair<string, S>> DistinctWithComparer(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments, StringComparer comparer)
2290                 {
2291                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments == null || arguments.Length == 0, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2292 
2293                     // This dictionary will ensure that we only return one result per unique itemspec
2294                     Dictionary<string, S> seenItems = new Dictionary<string, S>(comparer);
2295 
2296                     foreach (Pair<string, S> item in itemsOfType)
2297                     {
2298                         if (item.Key != null && !seenItems.ContainsKey(item.Key))
2299                         {
2300                             seenItems[item.Key] = item.Value;
2301 
2302                             yield return new Pair<string, S>(item.Key, item.Value);
2303                         }
2304                     }
2305                 }
2306 
2307                 /// <summary>
2308                 /// Intrinsic function reverses the item list.
2309                 /// </summary>
Reverse(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2310                 internal static IEnumerable<Pair<string, S>> Reverse(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2311                 {
2312                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments == null || arguments.Length == 0, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2313                     foreach (Pair<String, S> item in itemsOfType.Reverse())
2314                     {
2315                         yield return new Pair<string, S>(item.Key, item.Value);
2316                     }
2317                 }
2318 
2319                 /// <summary>
2320                 /// Intrinsic function that transforms expressions like the %(foo) in @(Compile->'%(foo)')
2321                 /// </summary>
ExpandQuotedExpressionFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2322                 internal static IEnumerable<Pair<string, S>> ExpandQuotedExpressionFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2323                 {
2324                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments != null && arguments.Length == 1, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2325 
2326                     foreach (Pair<string, S> item in itemsOfType)
2327                     {
2328                         MetadataMatchEvaluator matchEvaluator;
2329                         string include = null;
2330 
2331                         // If we've been handed a null entry by an uptream tranform
2332                         // then we don't want to try to tranform it with an itempec modification.
2333                         // Simply allow the null to be passed along (if, we are including nulls as specified by includeNullEntries
2334                         if (item.Key != null)
2335                         {
2336                             matchEvaluator = new MetadataMatchEvaluator(item.Key, item.Value, elementLocation);
2337 
2338                             include = RegularExpressions.ItemMetadataPattern.Value.Replace(arguments[0], matchEvaluator.GetMetadataValueFromMatch);
2339                         }
2340 
2341                         // Include may be empty. Historically we have created items with empty include
2342                         // and ultimately set them on tasks, but we don't do that anymore as it's broken.
2343                         // Instead we optionally add a null, so that input and output lists are the same length; this allows
2344                         // the caller to possibly do correlation.
2345 
2346                         // We pass in the existing item so we can copy over its metadata
2347                         if (include != null && include.Length > 0)
2348                         {
2349                             yield return new Pair<string, S>(include, item.Value);
2350                         }
2351                         else if (includeNullEntries)
2352                         {
2353                             yield return new Pair<string, S>(null, item.Value);
2354                         }
2355                     }
2356                 }
2357 
2358                 /// <summary>
2359                 /// Intrinsic function that transforms expressions by invoking methods of System.String on the itemspec
2360                 /// of the item in the pipeline
2361                 /// </summary>
ExecuteStringFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2362                 internal static IEnumerable<Pair<string, S>> ExecuteStringFunction(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2363                 {
2364                     // Transform: expression is like @(Compile->'%(foo)'), so create completely new items,
2365                     // using the Include from the source items
2366                     foreach (Pair<string, S> item in itemsOfType)
2367                     {
2368                         Function<P> function = new Expander<P, I>.Function<P>(typeof(string), item.Key, item.Key, functionName, arguments,
2369 #if FEATURE_TYPE_INVOKEMEMBER
2370                             BindingFlags.Public | BindingFlags.InvokeMethod,
2371 #else
2372                             BindingFlags.Public, InvokeType.InvokeMethod,
2373 #endif
2374                             String.Empty, expander.UsedUninitializedProperties);
2375 
2376                         object result = function.Execute(item.Key, expander._properties, ExpanderOptions.ExpandAll, elementLocation);
2377 
2378                         string include = Expander<P, I>.PropertyExpander<P>.ConvertToString(result);
2379 
2380                         // We pass in the existing item so we can copy over its metadata
2381                         if (include.Length > 0)
2382                         {
2383                             yield return new Pair<string, S>(include, item.Value);
2384                         }
2385                         else if (includeNullEntries)
2386                         {
2387                             yield return new Pair<string, S>(null, item.Value);
2388                         }
2389                     }
2390                 }
2391 
2392                 /// <summary>
2393                 /// Intrinsic function that returns the items from itemsOfType with their metadata cleared, i.e. only the itemspec is retained
2394                 /// </summary>
ClearMetadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2395                 internal static IEnumerable<Pair<string, S>> ClearMetadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2396                 {
2397                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments == null || arguments.Length == 0, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2398 
2399                     foreach (Pair<string, S> item in itemsOfType)
2400                     {
2401                         if (includeNullEntries || item.Key != null)
2402                         {
2403                             yield return new Pair<string, S>(item.Key, null);
2404                         }
2405                     }
2406                 }
2407 
2408                 /// <summary>
2409                 /// Intrinsic function that returns only those items that have a not-blank value for the metadata specified
2410                 /// Using a case insensitive comparison
2411                 /// </summary>
HasMetadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2412                 internal static IEnumerable<Pair<string, S>> HasMetadata(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2413                 {
2414                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments != null && arguments.Length == 1, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2415 
2416                     string metadataName = arguments[0];
2417 
2418                     foreach (Pair<string, S> item in itemsOfType)
2419                     {
2420                         string metadataValue = null;
2421 
2422                         try
2423                         {
2424                             metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
2425                         }
2426                         catch (ArgumentException ex) // Blank metadata name
2427                         {
2428                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2429                         }
2430                         catch (InvalidOperationException ex)
2431                         {
2432                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2433                         }
2434 
2435                         // GetMetadataValueEscaped returns empty string for missing metadata,
2436                         // but IItem specifies it should return null
2437                         if (metadataValue != null && metadataValue.Length > 0)
2438                         {
2439                             // return a result through the enumerator
2440                             yield return new Pair<string, S>(item.Key, item.Value);
2441                         }
2442                     }
2443                 }
2444 
2445                 /// <summary>
2446                 /// Intrinsic function that returns only those items have the given metadata value
2447                 /// Using a case insensitive comparison
2448                 /// </summary>
WithMetadataValue(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2449                 internal static IEnumerable<Pair<string, S>> WithMetadataValue(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2450                 {
2451                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments != null && arguments.Length == 2, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2452 
2453                     string metadataName = arguments[0];
2454                     string metadataValueToFind = arguments[1];
2455 
2456                     foreach (Pair<string, S> item in itemsOfType)
2457                     {
2458                         string metadataValue = null;
2459 
2460                         try
2461                         {
2462                             metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
2463                         }
2464                         catch (ArgumentException ex) // Blank metadata name
2465                         {
2466                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2467                         }
2468                         catch (InvalidOperationException ex)
2469                         {
2470                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2471                         }
2472 
2473                         if (metadataValue != null && String.Equals(metadataValue, metadataValueToFind, StringComparison.OrdinalIgnoreCase))
2474                         {
2475                             // return a result through the enumerator
2476                             yield return new Pair<string, S>(item.Key, item.Value);
2477                         }
2478                     }
2479                 }
2480 
2481                 /// <summary>
2482                 /// Intrinsic function that returns a boolean to indicate if any of the items have the given metadata value
2483                 /// Using a case insensitive comparison
2484                 /// </summary>
AnyHaveMetadataValue(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)2485                 internal static IEnumerable<Pair<string, S>> AnyHaveMetadataValue(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
2486                 {
2487                     ProjectErrorUtilities.VerifyThrowInvalidProject(arguments != null && arguments.Length == 2, elementLocation, "InvalidItemFunctionSyntax", functionName, (arguments == null ? 0 : arguments.Length));
2488 
2489                     string metadataName = arguments[0];
2490                     string metadataValueToFind = arguments[1];
2491                     bool metadataFound = false;
2492 
2493                     foreach (Pair<string, S> item in itemsOfType)
2494                     {
2495                         if (item.Value != null)
2496                         {
2497                             string metadataValue = null;
2498 
2499                             try
2500                             {
2501                                 metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
2502                             }
2503                             catch (ArgumentException ex) // Blank metadata name
2504                             {
2505                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2506                             }
2507                             catch (InvalidOperationException ex)
2508                             {
2509                                 ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
2510                             }
2511 
2512                             if (metadataValue != null && String.Equals(metadataValue, metadataValueToFind, StringComparison.OrdinalIgnoreCase))
2513                             {
2514                                 metadataFound = true;
2515 
2516                                 // return a result through the enumerator
2517                                 yield return new Pair<string, S>("true", item.Value);
2518 
2519                                 // break out as soon as we found a match
2520                                 yield break;
2521                             }
2522                         }
2523                     }
2524 
2525                     if (!metadataFound)
2526                     {
2527                         // We did not locate an item with the required metadata
2528                         yield return new Pair<string, S>("false", null);
2529                     }
2530                 }
2531             }
2532 
2533             /// <summary>
2534             /// Represents all the components of a transform function, including the ability to execute it
2535             /// </summary>
2536             /// <typeparam name="S">class, IItem</typeparam>
2537             internal class TransformFunction<S>
2538                 where S : class, IItem
2539             {
2540                 /// <summary>
2541                 /// The delegate that points to the transform function
2542                 /// </summary>
2543                 private IntrinsicItemFunctions<S>.ItemTransformFunction _transform;
2544 
2545                 /// <summary>
2546                 /// Arguments to pass to the transform function as parsed out of the project file
2547                 /// </summary>
2548                 private string[] _arguments;
2549 
2550                 /// <summary>
2551                 /// The element location of the transform expression
2552                 /// </summary>
2553                 private IElementLocation _elementLocation;
2554 
2555                 /// <summary>
2556                 /// The name of the function that this class will call
2557                 /// </summary>
2558                 private string _functionName;
2559 
2560                 /// <summary>
2561                 /// TransformFunction constructor
2562                 /// </summary>
TransformFunction(IElementLocation elementLocation, string functionName, IntrinsicItemFunctions<S>.ItemTransformFunction transform, string[] arguments)2563                 public TransformFunction(IElementLocation elementLocation, string functionName, IntrinsicItemFunctions<S>.ItemTransformFunction transform, string[] arguments)
2564                 {
2565                     _elementLocation = elementLocation;
2566                     _functionName = functionName;
2567                     _transform = transform;
2568                     _arguments = arguments;
2569                 }
2570 
2571                 /// <summary>
2572                 /// Arguments to pass to the transform function as parsed out of the project file
2573                 /// </summary>
2574                 public string[] Arguments
2575                 {
2576                     get { return _arguments; }
2577                 }
2578 
2579                 /// <summary>
2580                 /// The element location of the transform expression
2581                 /// </summary>
2582                 public IElementLocation ElementLocation
2583                 {
2584                     get { return _elementLocation; }
2585                 }
2586 
2587                 /// <summary>
2588                 /// Execute this transform function with the arguments contained within this TransformFunction instance
2589                 /// </summary>
Execute(Expander<P, I> expander, bool includeNullEntries, IEnumerable<Pair<string, S>> itemsOfType)2590                 public IEnumerable<Pair<string, S>> Execute(Expander<P, I> expander, bool includeNullEntries, IEnumerable<Pair<string, S>> itemsOfType)
2591                 {
2592                     // Execute via the delegate
2593                     return _transform(expander, _elementLocation, includeNullEntries, _functionName, itemsOfType, _arguments);
2594                 }
2595             }
2596 
2597             /// <summary>
2598             /// A functor that returns the value of the metadata in the match
2599             /// that is on the item it was created with.
2600             /// </summary>
2601             private class MetadataMatchEvaluator
2602             {
2603                 /// <summary>
2604                 /// The current ItemSpec of the item being matched
2605                 /// </summary>
2606                 private string _itemSpec;
2607 
2608                 /// <summary>
2609                 /// Item used as the source of metadata
2610                 /// </summary>
2611                 private IItem _sourceOfMetadata;
2612 
2613                 /// <summary>
2614                 /// Location of the match
2615                 /// </summary>
2616                 private IElementLocation _elementLocation;
2617 
2618                 /// <summary>
2619                 /// Constructor
2620                 /// </summary>
MetadataMatchEvaluator(string itemSpec, IItem sourceOfMetadata, IElementLocation elementLocation)2621                 internal MetadataMatchEvaluator(string itemSpec, IItem sourceOfMetadata, IElementLocation elementLocation)
2622                 {
2623                     _itemSpec = itemSpec;
2624                     _sourceOfMetadata = sourceOfMetadata;
2625                     _elementLocation = elementLocation;
2626                 }
2627 
2628                 /// <summary>
2629                 /// Expands the metadata in the match provided into a string result.
2630                 /// The match is expected to be the content of a transform.
2631                 /// For example, representing "%(Filename.obj)" in the original expression "@(Compile->'%(Filename.obj)')"
2632                 /// </summary>
GetMetadataValueFromMatch(Match match)2633                 internal string GetMetadataValueFromMatch(Match match)
2634                 {
2635                     string name = match.Groups[RegularExpressions.NameGroup].Value;
2636 
2637                     ProjectErrorUtilities.VerifyThrowInvalidProject(match.Groups[RegularExpressions.ItemSpecificationGroup].Length == 0, _elementLocation, "QualifiedMetadataInTransformNotAllowed", match.Value, name);
2638 
2639                     string value = null;
2640                     try
2641                     {
2642                         if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(name))
2643                         {
2644                             // If we're not a ProjectItem or ProjectItemInstance, then ProjectDirectory will be null.
2645                             // In that case, we're safe to get the current directory as we'll be running on TaskItems which
2646                             // only exist within a target where we can trust the current directory
2647                             string directoryToUse = _sourceOfMetadata.ProjectDirectory ?? Directory.GetCurrentDirectory();
2648                             string definingProjectEscaped = _sourceOfMetadata.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath);
2649 
2650                             value = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(directoryToUse, _itemSpec, definingProjectEscaped, name);
2651                         }
2652                         else
2653                         {
2654                             value = _sourceOfMetadata.GetMetadataValueEscaped(name);
2655                         }
2656                     }
2657                     catch (InvalidOperationException ex)
2658                     {
2659                         ProjectErrorUtilities.ThrowInvalidProject(_elementLocation, "CannotEvaluateItemMetadata", name, ex.Message);
2660                     }
2661 
2662                     return value;
2663                 }
2664             }
2665         }
2666 
2667         /// <summary>
2668         /// Regular expressions used by the expander.
2669         /// The expander currently uses regular expressions rather than a parser to do its work.
2670         /// </summary>
2671         private static class RegularExpressions
2672         {
2673             /**************************************************************************************************************************
2674             * WARNING: The regular expressions below MUST be kept in sync with the expressions in the ProjectWriter class -- if the
2675             * description of an item vector changes, the expressions must be updated in both places.
2676             *************************************************************************************************************************/
2677 
2678             /// <summary>
2679             /// Regular expression used to match item metadata references embedded in strings.
2680             /// For example, %(Compile.DependsOn) or %(DependsOn).
2681             /// </summary>
2682             internal static readonly Lazy<Regex> ItemMetadataPattern = new Lazy<Regex>(
2683                 () => new Regex(ItemMetadataSpecification,
2684                     RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled));
2685 
2686             /// <summary>
2687             /// Name of the group matching the "name" of a metadatum.
2688             /// </summary>
2689             internal const string NameGroup = "NAME";
2690 
2691             /// <summary>
2692             /// Name of the group matching the prefix on a metadata expression, for example "Compile." in "%(Compile.Object)"
2693             /// </summary>
2694             internal const string ItemSpecificationGroup = "ITEM_SPECIFICATION";
2695 
2696             /// <summary>
2697             /// Name of the group matching the item type in an item expression or metadata expression.
2698             /// </summary>
2699             internal const string ItemTypeGroup = "ITEM_TYPE";
2700 
2701             /// <summary>
2702             /// regular expression used to match item metadata references outside of item vector transforms
2703             /// </summary>
2704             /// <remarks>PERF WARNING: this Regex is complex and tends to run slowly</remarks>
2705             internal static readonly Lazy<Regex> NonTransformItemMetadataPattern = new Lazy<Regex>(
2706                 () => new Regex
2707                     (
2708                     @"((?<=" + ItemVectorWithTransformLHS + @")" + ItemMetadataSpecification + @"(?!" +
2709                     ItemVectorWithTransformRHS + @")) | ((?<!" + ItemVectorWithTransformLHS + @")" +
2710                     ItemMetadataSpecification + @"(?=" + ItemVectorWithTransformRHS + @")) | ((?<!" +
2711                     ItemVectorWithTransformLHS + @")" + ItemMetadataSpecification + @"(?!" +
2712                     ItemVectorWithTransformRHS + @"))",
2713                     RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled
2714                     ));
2715 
2716             /// <summary>
2717             /// Complete description of an item metadata reference, including the optional qualifying item type.
2718             /// For example, %(Compile.DependsOn) or %(DependsOn).
2719             /// </summary>
2720             private const string ItemMetadataSpecification = @"%\(\s* (?<ITEM_SPECIFICATION>(?<ITEM_TYPE>" + ProjectWriter.itemTypeOrMetadataNameSpecification + @")\s*\.\s*)? (?<NAME>" + ProjectWriter.itemTypeOrMetadataNameSpecification + @") \s*\)";
2721 
2722             /// <summary>
2723             /// description of an item vector with a transform, left hand side
2724             /// </summary>
2725             private const string ItemVectorWithTransformLHS = @"@\(\s*" + ProjectWriter.itemTypeOrMetadataNameSpecification + @"\s*->\s*'[^']*";
2726 
2727             /// <summary>
2728             /// description of an item vector with a transform, right hand side
2729             /// </summary>
2730             private const string ItemVectorWithTransformRHS = @"[^']*'(\s*,\s*'[^']*')?\s*\)";
2731 
2732             /**************************************************************************************************************************
2733              * WARNING: The regular expressions above MUST be kept in sync with the expressions in the ProjectWriter class.
2734              *************************************************************************************************************************/
2735         }
2736 
2737 #if !FEATURE_TYPE_INVOKEMEMBER
2738         internal enum InvokeType
2739         {
2740             InvokeMethod,
2741             GetPropertyOrField
2742         }
2743 #endif
2744 
2745         private struct FunctionBuilder<T>
2746             where T : class, IProperty
2747         {
2748             /// <summary>
2749             /// The type of this function's receiver
2750             /// </summary>
2751             public Type ReceiverType { get; set; }
2752 
2753             /// <summary>
2754             /// The name of the function
2755             /// </summary>
2756             public string Name { get; set; }
2757 
2758             /// <summary>
2759             /// The arguments for the function
2760             /// </summary>
2761             public string[] Arguments { get; set; }
2762 
2763             /// <summary>
2764             /// The expression that this function is part of
2765             /// </summary>
2766             public string Expression { get; set; }
2767 
2768             /// <summary>
2769             /// The property name that this function is applied on
2770             /// </summary>
2771             public string Receiver { get; set; }
2772 
2773             /// <summary>
2774             /// The binding flags that will be used during invocation of this function
2775             /// </summary>
2776             public BindingFlags BindingFlags { get; set; }
2777 
2778 #if !FEATURE_TYPE_INVOKEMEMBER
2779             public InvokeType InvokeType { get; set; }
2780 #endif
2781 
2782             /// <summary>
2783             /// The remainder of the body once the function and arguments have been extracted
2784             /// </summary>
2785             public string Remainder { get; set; }
2786 
2787             /// <summary>
2788             /// List of properties which have been used but have not been initialized yet.
2789             /// </summary>
2790             public UsedUninitializedProperties UsedUninitializedProperties { get; set; }
2791 
BuildMicrosoft.Build.Evaluation.P.T2792             internal Function<T> Build()
2793             {
2794                 return new Function<T>(
2795                     ReceiverType,
2796                     Expression,
2797                     Receiver,
2798                     Name,
2799                     Arguments,
2800                     BindingFlags,
2801 #if !FEATURE_TYPE_INVOKEMEMBER
2802                     InvokeType,
2803 #endif
2804                     Remainder,
2805                     UsedUninitializedProperties
2806                     );
2807             }
2808         }
2809 
2810         /// <summary>
2811         /// This class represents the function as extracted from an expression
2812         /// It is also responsible for executing the function
2813         /// </summary>
2814         /// <typeparam name="T">Type of the properties used to expand the expression</typeparam>
2815         private class Function<T>
2816             where T : class, IProperty
2817         {
2818             /// <summary>
2819             /// The type of this function's receiver
2820             /// </summary>
2821             private Type _receiverType;
2822 
2823             /// <summary>
2824             /// The name of the function
2825             /// </summary>
2826             private string _methodMethodName;
2827 
2828             /// <summary>
2829             /// The arguments for the function
2830             /// </summary>
2831             private string[] _arguments;
2832 
2833             /// <summary>
2834             /// The expression that this function is part of
2835             /// </summary>
2836             private string _expression;
2837 
2838             /// <summary>
2839             /// The property name that this function is applied on
2840             /// </summary>
2841             private string _receiver;
2842 
2843             /// <summary>
2844             /// The binding flags that will be used during invocation of this function
2845             /// </summary>
2846             private BindingFlags _bindingFlags;
2847 
2848 #if !FEATURE_TYPE_INVOKEMEMBER
2849             private InvokeType _invokeType;
2850 #endif
2851 
2852             /// <summary>
2853             /// The remainder of the body once the function and arguments have been extracted
2854             /// </summary>
2855             private string _remainder;
2856 
2857             /// <summary>
2858             /// List of properties which have been used but have not been initialized yet.
2859             /// </summary>
2860             private UsedUninitializedProperties _usedUninitializedProperties;
2861 
2862             /// <summary>
2863             /// Construct a function that will be executed during property evaluation
2864             /// </summary>
Function(Type receiverType, string expression, string receiver, string methodName, string[] arguments, BindingFlags bindingFlags, InvokeType invokeType, string remainder, UsedUninitializedProperties usedUninitializedProperties)2865             internal Function(Type receiverType, string expression, string receiver, string methodName, string[] arguments, BindingFlags bindingFlags,
2866 #if !FEATURE_TYPE_INVOKEMEMBER
2867                 InvokeType invokeType,
2868 #endif
2869                 string remainder, UsedUninitializedProperties usedUninitializedProperties)
2870             {
2871                 _methodMethodName = methodName;
2872                 if (arguments == null)
2873                 {
2874                     _arguments = Array.Empty<string>();
2875                 }
2876                 else
2877                 {
2878                     _arguments = arguments;
2879                 }
2880 
2881                 _receiver = receiver;
2882                 _expression = expression;
2883                 _receiverType = receiverType;
2884                 _bindingFlags = bindingFlags;
2885 #if !FEATURE_TYPE_INVOKEMEMBER
2886                 _invokeType = invokeType;
2887 #endif
2888                 _remainder = remainder;
2889                 _usedUninitializedProperties = usedUninitializedProperties;
2890             }
2891 
2892             /// <summary>
2893             /// Part of the extraction may result in the name of the property
2894             /// This accessor is used by the Expander
2895             /// Examples of expression root:
2896             ///     [System.Diagnostics.Process]::Start
2897             ///     SomeMSBuildProperty
2898             /// </summary>
2899             internal string Receiver
2900             {
2901                 get { return _receiver; }
2902             }
2903 
2904             /// <summary>
2905             /// Extract the function details from the given property function expression
2906             /// </summary>
ExtractPropertyFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, UsedUninitializedProperties usedUnInitializedProperties)2907             internal static Function<T> ExtractPropertyFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, UsedUninitializedProperties usedUnInitializedProperties)
2908             {
2909                 // Used to aggregate all the components needed for a Function
2910                 FunctionBuilder<T> functionBuilder = new FunctionBuilder<T>();
2911 
2912                 // By default the expression root is the whole function expression
2913                 var expressionRoot = expressionFunction;
2914 
2915                 // The arguments for this function start at the first '('
2916                 // If there are no arguments, then we're a property getter
2917                 var argumentStartIndex = expressionFunction.IndexOf('(');
2918 
2919                 // If we have arguments, then we only want the content up to but not including the '('
2920                 if (argumentStartIndex > -1)
2921                 {
2922                     expressionRoot = expressionFunction.Substring(0, argumentStartIndex);
2923                 }
2924 
2925                 // In case we ended up with something we don't understand
2926                 ProjectErrorUtilities.VerifyThrowInvalidProject(!String.IsNullOrEmpty(expressionRoot), elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty);
2927 
2928                 functionBuilder.Expression = expressionFunction;
2929                 functionBuilder.UsedUninitializedProperties = usedUnInitializedProperties;
2930 
2931                 // This is a static method call
2932                 // A static method is the content that follows the last "::", the rest being the type
2933                 if (propertyValue == null && expressionRoot[0] == '[')
2934                 {
2935                     var typeEndIndex = expressionRoot.IndexOf(']', 1);
2936 
2937                     if (typeEndIndex < 1)
2938                     {
2939                         // We ended up with something other than a function expression
2940                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", expressionFunction, String.Empty);
2941                     }
2942 
2943                     var typeName = expressionRoot.Substring(1, typeEndIndex - 1);
2944                     var methodStartIndex = typeEndIndex + 1;
2945 
2946                     if (expressionRoot.Length > methodStartIndex + 2 && expressionRoot[methodStartIndex] == ':' && expressionRoot[methodStartIndex + 1] == ':')
2947                     {
2948                         // skip over the "::"
2949                         methodStartIndex += 2;
2950                     }
2951                     else
2952                     {
2953                         // We ended up with something other than a static function expression
2954                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", expressionFunction, String.Empty);
2955                     }
2956 
2957                     ConstructFunction(elementLocation, expressionFunction, argumentStartIndex, methodStartIndex, ref functionBuilder);
2958 
2959                     // Locate a type that matches the body of the expression.
2960                     var receiverType = GetTypeForStaticMethod(typeName, functionBuilder.Name);
2961 
2962                     if (receiverType == null)
2963                     {
2964                         // We ended up with something other than a type
2965                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionTypeUnavailable", expressionFunction, typeName);
2966                     }
2967 
2968                     functionBuilder.ReceiverType = receiverType;
2969                 }
2970                 else if (expressionFunction[0] == '[') // We have an indexer
2971                 {
2972                     var indexerEndIndex = expressionFunction.IndexOf(']', 1);
2973                     if (indexerEndIndex < 1)
2974                     {
2975                         // We ended up with something other than a function expression
2976                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedSquareBrackets"));
2977                     }
2978 
2979                     var methodStartIndex = indexerEndIndex + 1;
2980 
2981                     functionBuilder.ReceiverType = propertyValue.GetType();
2982 
2983                     ConstructIndexerFunction(expressionFunction, elementLocation, propertyValue, methodStartIndex, indexerEndIndex, ref functionBuilder);
2984                 }
2985                 else // This could be a property reference, or a chain of function calls
2986                 {
2987                     // Look for an instance function call next, such as in SomeStuff.ToLower()
2988                     var methodStartIndex = expressionRoot.IndexOf('.');
2989                     if (methodStartIndex == -1)
2990                     {
2991                         // We don't have a function invocation in the expression root, return null
2992                         return null;
2993                     }
2994 
2995                     // skip over the '.';
2996                     methodStartIndex++;
2997 
2998                     var rootEndIndex = expressionRoot.IndexOf('.');
2999 
3000                     // If this is an instance function rather than a static, then we'll capture the name of the property referenced
3001                     var functionReceiver = expressionRoot.Substring(0, rootEndIndex).Trim();
3002 
3003                     // If propertyValue is null (we're not recursing), then we're expecting a valid property name
3004                     if (propertyValue == null && !IsValidPropertyName(functionReceiver))
3005                     {
3006                         // We extracted something that wasn't a valid property name, fail.
3007                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty);
3008                     }
3009 
3010                     // If we are recursively acting on a type that has been already produced then pass that type inwards (e.g. we are interpreting a function call chain)
3011                     // Otherwise, the receiver of the function is a string
3012                     var receiverType = propertyValue?.GetType() ?? typeof(string);
3013 
3014                     functionBuilder.Receiver = functionReceiver;
3015                     functionBuilder.ReceiverType = receiverType;
3016 
3017                     ConstructFunction(elementLocation, expressionFunction, argumentStartIndex, methodStartIndex, ref functionBuilder);
3018                 }
3019 
3020                 return functionBuilder.Build();
3021             }
3022 
3023 #if !FEATURE_TYPE_INVOKEMEMBER
BindFieldOrProperty()3024             private MemberInfo BindFieldOrProperty()
3025             {
3026                 StringComparison nameComparison =
3027                     ((_bindingFlags & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
3028 
3029                 var matchingMembers = _receiverType.GetFields(_bindingFlags)
3030                     .Cast<MemberInfo>()
3031                     .Concat(_receiverType.GetProperties(_bindingFlags))
3032                     .Where(member => member.Name.Equals(_methodMethodName, nameComparison))
3033                     .ToArray();
3034 
3035                 if (matchingMembers.Length == 0)
3036                 {
3037                     throw new MissingMemberException(_methodMethodName);
3038                 }
3039                 else if (matchingMembers.Length == 1)
3040                 {
3041                     return matchingMembers[0];
3042                 }
3043                 else
3044                 {
3045                     throw new AmbiguousMatchException(_methodMethodName);
3046                 }
3047             }
3048 #endif
3049 
3050             /// <summary>
3051             /// Execute the function on the given instance
3052             /// </summary>
Execute(object objectInstance, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation)3053             internal object Execute(object objectInstance, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation)
3054             {
3055                 object functionResult = String.Empty;
3056                 object[] args = null;
3057 
3058                 try
3059                 {
3060                     // If there is no object instance, then the method invocation will be a static
3061                     if (objectInstance == null)
3062                     {
3063                         // Check that the function that we're going to call is valid to call
3064                         if (!IsStaticMethodAvailable(_receiverType, _methodMethodName))
3065                         {
3066                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionMethodUnavailable", _methodMethodName, _receiverType.FullName);
3067                         }
3068 
3069                         _bindingFlags |= BindingFlags.Static;
3070 
3071                         // For our intrinsic function we need to support calling of internal methods
3072                         // since we don't want them to be public
3073                         if (_receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))
3074                         {
3075                             _bindingFlags |= BindingFlags.NonPublic;
3076                         }
3077                     }
3078                     else
3079                     {
3080                         _bindingFlags |= BindingFlags.Instance;
3081 
3082                         // The object that we're about to call methods on may have escaped characters
3083                         // in it, we want to operate on the unescaped string in the function, just as we
3084                         // want to pass arguments that are unescaped (see below)
3085                         if (objectInstance is string)
3086                         {
3087                             objectInstance = EscapingUtilities.UnescapeAll((string)objectInstance);
3088                         }
3089                     }
3090 
3091                     // We have a methodinfo match, need to plug in the arguments
3092                     args = new object[_arguments.Length];
3093 
3094                     // Assemble our arguments ready for passing to our method
3095                     for (int n = 0; n < _arguments.Length; n++)
3096                     {
3097                         object argument = PropertyExpander<T>.ExpandPropertiesLeaveTypedAndEscaped(_arguments[n], properties, options, elementLocation, _usedUninitializedProperties);
3098                         string argumentValue = argument as string;
3099 
3100                         if (argumentValue != null)
3101                         {
3102                             // Unescape the value since we're about to send it out of the engine and into
3103                             // the function being called. If a file or a directory function, fix the path
3104                             if (_receiverType == typeof(System.IO.File) || _receiverType == typeof(System.IO.Directory)
3105                                 || _receiverType == typeof(System.IO.Path))
3106                             {
3107                                 argumentValue = FileUtilities.FixFilePath(argumentValue);
3108                             }
3109 
3110                             args[n] = EscapingUtilities.UnescapeAll(argumentValue);
3111                         }
3112                         else
3113                         {
3114                             args[n] = argument;
3115                         }
3116                     }
3117 
3118                     // Handle special cases where the object type needs to affect the choice of method
3119                     // The default binder and method invoke, often chooses the incorrect Equals and CompareTo and
3120                     // fails the comparison, because what we have on the right is generally a string.
3121                     // This special casing is to realize that its a comparison that is taking place and handle the
3122                     // argument type coercion accordingly; effectively pre-preparing the argument type so
3123                     // that it matches the left hand side ready for the default binder’s method invoke.
3124                     if (objectInstance != null && args.Length == 1 && (String.Equals("Equals", _methodMethodName, StringComparison.OrdinalIgnoreCase) || String.Equals("CompareTo", _methodMethodName, StringComparison.OrdinalIgnoreCase)))
3125                     {
3126                         // change the type of the final unescaped string into the destination
3127                         args[0] = Convert.ChangeType(args[0], objectInstance.GetType(), CultureInfo.InvariantCulture);
3128                     }
3129 
3130                     if (_receiverType == typeof(IntrinsicFunctions))
3131                     {
3132                         // Special case a few methods that take extra parameters that can't be passed in by the user
3133                         //
3134 
3135                         if (_methodMethodName.Equals("GetPathOfFileAbove") && args.Length == 1)
3136                         {
3137                             // Append the IElementLocation as a parameter to GetPathOfFileAbove if the user only
3138                             // specified the file name.  This is syntactic sugar so they don't have to always
3139                             // include $(MSBuildThisFileDirectory) as a parameter.
3140                             //
3141                             string startingDirectory = String.IsNullOrWhiteSpace(elementLocation.File) ? String.Empty : Path.GetDirectoryName(elementLocation.File);
3142 
3143                             args = new []
3144                             {
3145                                 args[0],
3146                                 startingDirectory,
3147                             };
3148                         }
3149                     }
3150 
3151                     // If we've been asked to construct an instance, then we
3152                     // need to locate an appropriate constructor and invoke it
3153                     if (String.Equals("new", _methodMethodName, StringComparison.OrdinalIgnoreCase))
3154                     {
3155                         functionResult = LateBindExecute(null /* no previous exception */, BindingFlags.Public | BindingFlags.Instance, null /* no instance for a constructor */, args, true /* is constructor */);
3156                     }
3157                     else
3158                     {
3159                         bool wellKnownFunctionSuccess = false;
3160 
3161                         try
3162                         {
3163                             // First attempt to recognize some well-known functions to avoid binding
3164                             // and potential first-chance MissingMethodExceptions
3165                             wellKnownFunctionSuccess = TryExecuteWellKnownFunction(out functionResult, objectInstance, args);
3166                         }
3167                         // we need to preserve the same behavior on exceptions as the actual binder
3168                         catch (Exception ex)
3169                         {
3170                             string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args);
3171                             if (options.HasFlag(ExpanderOptions.LeavePropertiesUnexpandedOnError))
3172                             {
3173                                 return partiallyEvaluated;
3174                             }
3175 
3176                             ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message.Replace("\r\n", " "));
3177                         }
3178 
3179                         if (!wellKnownFunctionSuccess)
3180                         {
3181                             // Execute the function given converted arguments
3182                             // The only exception that we should catch to try a late bind here is missing method
3183                             // otherwise there is the potential of running a function twice!
3184                             try
3185                             {
3186 #if FEATURE_TYPE_INVOKEMEMBER
3187                                 // First use InvokeMember using the standard binder - this will match and coerce as needed
3188                                 functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture);
3189 #else
3190                                 if (_invokeType == InvokeType.InvokeMethod)
3191                                 {
3192                                     functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, objectInstance, args, null, CultureInfo.InvariantCulture, null);
3193                                 }
3194                                 else if (_invokeType == InvokeType.GetPropertyOrField)
3195                                 {
3196                                     MemberInfo memberInfo = BindFieldOrProperty();
3197                                     if (memberInfo is FieldInfo)
3198                                     {
3199                                         functionResult = ((FieldInfo)memberInfo).GetValue(objectInstance);
3200                                     }
3201                                     else
3202                                     {
3203                                         functionResult = ((PropertyInfo)memberInfo).GetValue(objectInstance);
3204                                     }
3205                                 }
3206                                 else
3207                                 {
3208                                     throw new InvalidOperationException(_invokeType.ToString());
3209                                 }
3210 #endif
3211                             }
3212                             catch (MissingMethodException ex) // Don't catch and retry on any other exception
3213                             {
3214                                 // If we're invoking a method, then there are deeper attempts that
3215                                 // can be made to invoke the method
3216 #if FEATURE_TYPE_INVOKEMEMBER
3217                                 if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
3218 #else
3219                                 if (_invokeType == InvokeType.InvokeMethod)
3220 #endif
3221                                 {
3222                                     // The standard binder failed, so do our best to coerce types into the arguments for the function
3223                                     // This may happen if the types need coercion, but it may also happen if the object represents a type that contains open type parameters, that is, ContainsGenericParameters returns true.
3224                                     functionResult = LateBindExecute(ex, _bindingFlags, objectInstance, args, false /* is not constructor */);
3225                                 }
3226                                 else
3227                                 {
3228                                     // We were asked to get a property or field, and we found that we cannot
3229                                     // locate it. Since there is no further argument coersion possible
3230                                     // we'll throw right now.
3231                                     throw;
3232                                 }
3233                             }
3234                         }
3235                     }
3236 
3237                     // If the result of the function call is a string, then we need to escape the result
3238                     // so that we maintain the "engine contains escaped data" state.
3239                     // The exception is that the user is explicitly calling MSBuild::Unescape or MSBuild::Escape
3240                     if (functionResult is string && !String.Equals("Unescape", _methodMethodName, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", _methodMethodName, StringComparison.OrdinalIgnoreCase))
3241                     {
3242                         functionResult = EscapingUtilities.Escape((string)functionResult);
3243                     }
3244 
3245                     // We have nothing left to parse, so we'll return what we have
3246                     if (String.IsNullOrEmpty(_remainder))
3247                     {
3248                         return functionResult;
3249                     }
3250 
3251                     // Recursively expand the remaining property body after execution
3252                     return PropertyExpander<T>.ExpandPropertyBody(_remainder, functionResult, properties, options, elementLocation, _usedUninitializedProperties);
3253                 }
3254 
3255                 // Exceptions coming from the actual function called are wrapped in a TargetInvocationException
3256                 catch (TargetInvocationException ex)
3257                 {
3258                     // We ended up with something other than a function expression
3259                     string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args);
3260                     if (options.HasFlag(ExpanderOptions.LeavePropertiesUnexpandedOnError))
3261                     {
3262                         // If the caller wants to ignore errors (in a log statement for example), just return the partially evaluated value
3263                         return partiallyEvaluated;
3264                     }
3265                     ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.InnerException.Message.Replace("\r\n", " "));
3266                     return null;
3267                 }
3268 
3269                 // Any other exception was thrown by trying to call it
3270                 catch (Exception ex)
3271                 {
3272                     if (ExceptionHandling.NotExpectedFunctionException(ex))
3273                     {
3274                         throw;
3275                     }
3276 
3277                     // If there's a :: in the expression, they were probably trying for a static function
3278                     // invocation. Give them some more relevant info in that case
3279                     if (s_invariantCompareInfo.IndexOf(_expression, "::", CompareOptions.OrdinalIgnoreCase) > -1)
3280                     {
3281                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", _expression, ex.Message.Replace("Microsoft.Build.Evaluation.IntrinsicFunctions.", "[MSBuild]::"));
3282                     }
3283                     else
3284                     {
3285                         // We ended up with something other than a function expression
3286                         string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args);
3287                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message);
3288                     }
3289 
3290                     return null;
3291                 }
3292             }
3293 
3294             /// <summary>
3295             /// Shortcut to avoid calling into binding if we recognize some most common functions.
3296             /// Binding is expensive and throws first-chance MissingMethodExceptions, which is
3297             /// bad for debugging experience and has a performance cost.
3298             /// A typical binding operation with exception can take ~1.500 ms; this call is ~0.050 ms
3299             /// (rough numbers just for comparison).
3300             /// See https://github.com/Microsoft/msbuild/issues/2217
3301             /// </summary>
3302             /// <param name="returnVal">The value returned from the function call</param>
3303             /// <param name="objectInstance">Object that the function is called on</param>
3304             /// <param name="args">arguments</param>
3305             /// <returns>True if the well known function call binding was successful</returns>
TryExecuteWellKnownFunction(out object returnVal, object objectInstance, object[] args)3306             private bool TryExecuteWellKnownFunction(out object returnVal, object objectInstance, object[] args)
3307             {
3308                 if (objectInstance is string)
3309                 {
3310                     string text = (string)objectInstance;
3311                     if (string.Equals(_methodMethodName, "Substring", StringComparison.OrdinalIgnoreCase))
3312                     {
3313                         int startIndex;
3314                         int length;
3315                         if (TryGetArg(args, out startIndex))
3316                         {
3317                             returnVal = text.Substring(startIndex);
3318                             return true;
3319                         }
3320                         else if (TryGetArgs(args, out startIndex, out length))
3321                         {
3322                             returnVal = text.Substring(startIndex, length);
3323                             return true;
3324                         }
3325                     }
3326                     else if (string.Equals(_methodMethodName, "Split", StringComparison.OrdinalIgnoreCase))
3327                     {
3328                         string separator;
3329                         if (TryGetArg(args, out separator) && separator.Length == 1)
3330                         {
3331                             returnVal = text.Split(separator[0]);
3332                             return true;
3333                         }
3334                     }
3335                     else if (string.Equals(_methodMethodName, "PadLeft", StringComparison.OrdinalIgnoreCase))
3336                     {
3337                         int totalWidth;
3338                         string paddingChar;
3339                         if (TryGetArg(args, out totalWidth))
3340                         {
3341                             returnVal = text.PadLeft(totalWidth);
3342                             return true;
3343                         }
3344                         else if (TryGetArgs(args, out totalWidth, out paddingChar) && paddingChar.Length == 1)
3345                         {
3346                             returnVal = text.PadLeft(totalWidth, paddingChar[0]);
3347                             return true;
3348                         }
3349                     }
3350                     else if (string.Equals(_methodMethodName, "PadRight", StringComparison.OrdinalIgnoreCase))
3351                     {
3352                         int totalWidth;
3353                         string paddingChar;
3354                         if (TryGetArg(args, out totalWidth))
3355                         {
3356                             returnVal = text.PadRight(totalWidth);
3357                             return true;
3358                         }
3359                         else if (TryGetArgs(args, out totalWidth, out paddingChar) && paddingChar.Length == 1)
3360                         {
3361                             returnVal = text.PadRight(totalWidth, paddingChar[0]);
3362                             return true;
3363                         }
3364                     }
3365                     else if (string.Equals(_methodMethodName, "TrimStart", StringComparison.OrdinalIgnoreCase))
3366                     {
3367                         string trimChars;
3368                         if (TryGetArg(args, out trimChars) && trimChars.Length > 0)
3369                         {
3370                             returnVal = text.TrimStart(trimChars.ToCharArray());
3371                             return true;
3372                         }
3373                     }
3374                     else if (string.Equals(_methodMethodName, "TrimEnd", StringComparison.OrdinalIgnoreCase))
3375                     {
3376                         string trimChars;
3377                         if (TryGetArg(args, out trimChars) && trimChars.Length > 0)
3378                         {
3379                             returnVal = text.TrimEnd(trimChars.ToCharArray());
3380                             return true;
3381                         }
3382                     }
3383                     else if (string.Equals(_methodMethodName, "get_Chars", StringComparison.OrdinalIgnoreCase))
3384                     {
3385                         int index;
3386                         if (TryGetArg(args, out index))
3387                         {
3388                             returnVal = text[index];
3389                             return true;
3390                         }
3391                     }
3392                 }
3393                 else if (objectInstance is string[])
3394                 {
3395                     string[] stringArray = (string[])objectInstance;
3396                     if (string.Equals(_methodMethodName, "GetValue", StringComparison.OrdinalIgnoreCase))
3397                     {
3398                         int index;
3399                         if (TryGetArg(args, out index))
3400                         {
3401                             returnVal = stringArray[index];
3402                             return true;
3403                         }
3404                     }
3405                 }
3406                 else if (objectInstance == null)
3407                 {
3408                     if (_receiverType == typeof(Math))
3409                     {
3410                         if (string.Equals(_methodMethodName, "Max", StringComparison.OrdinalIgnoreCase))
3411                         {
3412                             double arg0, arg1;
3413                             if (TryGetArgs(args, out arg0, out arg1))
3414                             {
3415                                 returnVal = Math.Max(arg0, arg1);
3416                                 return true;
3417                             }
3418                         }
3419                         else if (string.Equals(_methodMethodName, "Min", StringComparison.OrdinalIgnoreCase))
3420                         {
3421                             double arg0, arg1;
3422                             if (TryGetArgs(args, out arg0, out arg1))
3423                             {
3424                                 returnVal = Math.Min(arg0, arg1);
3425                                 return true;
3426                             }
3427                         }
3428                     }
3429                     else if (_receiverType == typeof(IntrinsicFunctions))
3430                     {
3431                         if (string.Equals(_methodMethodName, "Add", StringComparison.OrdinalIgnoreCase))
3432                         {
3433                             double arg0, arg1;
3434                             if (TryGetArgs(args, out arg0, out arg1))
3435                             {
3436                                 returnVal = arg0 + arg1;
3437                                 return true;
3438                             }
3439                         }
3440                         else if (string.Equals(_methodMethodName, "Subtract", StringComparison.OrdinalIgnoreCase))
3441                         {
3442                             double arg0, arg1;
3443                             if (TryGetArgs(args, out arg0, out arg1))
3444                             {
3445                                 returnVal = arg0 - arg1;
3446                                 return true;
3447                             }
3448                         }
3449                         else if (string.Equals(_methodMethodName, "Multiply", StringComparison.OrdinalIgnoreCase))
3450                         {
3451                             double arg0, arg1;
3452                             if (TryGetArgs(args, out arg0, out arg1))
3453                             {
3454                                 returnVal = arg0 * arg1;
3455                                 return true;
3456                             }
3457                         }
3458                         else if (string.Equals(_methodMethodName, "Divide", StringComparison.OrdinalIgnoreCase))
3459                         {
3460                             double arg0, arg1;
3461                             if (TryGetArgs(args, out arg0, out arg1))
3462                             {
3463                                 returnVal = arg0 / arg1;
3464                                 return true;
3465                             }
3466                         }
3467                     }
3468                 }
3469 
3470                 returnVal = null;
3471                 return false;
3472             }
3473 
TryGetArg(object[] args, out int arg0)3474             private static bool TryGetArg(object[] args, out int arg0)
3475             {
3476                 if (args.Length != 1)
3477                 {
3478                     arg0 = 0;
3479                     return false;
3480                 }
3481 
3482                 var value = args[0];
3483                 if (value is string && int.TryParse((string)value, out arg0))
3484                 {
3485                     return true;
3486                 }
3487 
3488                 arg0 = 0;
3489                 return false;
3490             }
3491 
TryGetArg(object[] args, out string arg0)3492             private static bool TryGetArg(object[] args, out string arg0)
3493             {
3494                 if (args.Length != 1)
3495                 {
3496                     arg0 = null;
3497                     return false;
3498                 }
3499 
3500                 arg0 = args[0] as string;
3501                 return arg0 != null;
3502             }
3503 
TryGetArgs(object[] args, out int arg0, out int arg1)3504             private static bool TryGetArgs(object[] args, out int arg0, out int arg1)
3505             {
3506                 arg0 = 0;
3507                 arg1 = 0;
3508 
3509                 if (args.Length != 2)
3510                 {
3511                     return false;
3512                 }
3513 
3514                 var value0 = args[0] as string;
3515                 var value1 = args[1] as string;
3516                 if (value0 != null &&
3517                     value1 != null &&
3518                     int.TryParse(value0, out arg0) &&
3519                     int.TryParse(value1, out arg1))
3520                 {
3521                     return true;
3522                 }
3523 
3524                 return false;
3525             }
3526 
TryGetArgs(object[] args, out double arg0, out double arg1)3527             private static bool TryGetArgs(object[] args, out double arg0, out double arg1)
3528             {
3529                 arg0 = 0;
3530                 arg1 = 0;
3531 
3532                 if (args.Length != 2)
3533                 {
3534                     return false;
3535                 }
3536 
3537                 var value0 = args[0] as string;
3538                 var value1 = args[1] as string;
3539                 if (value0 != null &&
3540                     value1 != null &&
3541                     double.TryParse(value0, out arg0) &&
3542                     double.TryParse(value1, out arg1))
3543                 {
3544                     return true;
3545                 }
3546 
3547                 return false;
3548             }
3549 
TryGetArgs(object[] args, out int arg0, out string arg1)3550             private static bool TryGetArgs(object[] args, out int arg0, out string arg1)
3551             {
3552                 arg0 = 0;
3553                 arg1 = null;
3554 
3555                 if (args.Length != 2)
3556                 {
3557                     return false;
3558                 }
3559 
3560                 var value0 = args[0] as string;
3561                 arg1 = args[1] as string;
3562                 if (value0 != null &&
3563                     arg1 != null &&
3564                     int.TryParse(value0, out arg0))
3565                 {
3566                     return true;
3567                 }
3568 
3569                 return false;
3570             }
3571 
3572             /// <summary>
3573             /// Given a type name and method name, try to resolve the type.
3574             /// </summary>
3575             /// <param name="typeName">May be full name or assembly qualified name</param>
3576             /// <param name="simpleMethodName">simple name of the method</param>
3577             /// <returns></returns>
GetTypeForStaticMethod(string typeName, string simpleMethodName)3578             private static Type GetTypeForStaticMethod(string typeName, string simpleMethodName)
3579             {
3580                 Type receiverType;
3581                 Tuple<string, Type> cachedTypeInformation;
3582 
3583                 // If we don't have a type name, we already know that we won't be able to find a type.
3584                 // Go ahead and return here -- otherwise the Type.GetType() calls below will throw.
3585                 if (string.IsNullOrWhiteSpace(typeName))
3586                 {
3587                     return null;
3588                 }
3589 
3590                 // Check if the type is in the whitelist cache. If it is, use it or load it.
3591                 cachedTypeInformation = AvailableStaticMethods.GetTypeInformationFromTypeCache(typeName, simpleMethodName);
3592                 if (cachedTypeInformation != null)
3593                 {
3594                     // We need at least one of these set
3595                     ErrorUtilities.VerifyThrow(cachedTypeInformation.Item1 != null || cachedTypeInformation.Item2 != null, "Function type information needs either string or type represented.");
3596 
3597                     // If we have the type information in Type form, then just return that
3598                     if (cachedTypeInformation.Item2 != null)
3599                     {
3600                         return cachedTypeInformation.Item2;
3601                     }
3602                     else if (cachedTypeInformation.Item1 != null)
3603                     {
3604                         // This is a case where the Type is not available at compile time, so
3605                         // we are forced to bind by name instead
3606                         var assemblyQualifiedTypeName = cachedTypeInformation.Item1;
3607 
3608                         // Get the type from the assembly qualified type name from AvailableStaticMethods
3609                         receiverType = Type.GetType(assemblyQualifiedTypeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */);
3610 
3611                         // If the type information from the cache is not loadable, it means the cache information got corrupted somehow
3612                         // Throw here to prevent adding null types in the cache
3613                         ErrorUtilities.VerifyThrowInternalNull(receiverType, $"Type information for {typeName} was present in the whitelist cache as {assemblyQualifiedTypeName} but the type could not be loaded.");
3614 
3615                         // If we've used it once, chances are that we'll be using it again
3616                         // We can record the type here since we know it's available for calling from the fact that is was in the AvailableStaticMethods table
3617                         AvailableStaticMethods.TryAdd(typeName, simpleMethodName, new Tuple<string, Type>(assemblyQualifiedTypeName, receiverType));
3618 
3619                         return receiverType;
3620                     }
3621                 }
3622 
3623                 // Get the type from mscorlib (or the currently running assembly)
3624                 receiverType = Type.GetType(typeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */);
3625 
3626                 if (receiverType != null)
3627                 {
3628                     // DO NOT CACHE THE TYPE HERE!
3629                     // We don't add the resolved type here in the AvailableStaticMethods table. This is because that table is used
3630                     // during function parse, but only later during execution do we check for the ability to call specific methods on specific types.
3631                     // Caching it here would load any type into the white list.
3632                     return receiverType;
3633                 }
3634 
3635                 // Note the following code path is only entered when MSBUILDENABLEALLPROPERTYFUNCTIONS == 1.
3636                 // This environment variable must not be cached - it should be dynamically settable while the application is executing.
3637                 if (Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1")
3638                 {
3639                     // We didn't find the type, so go probing. First in System
3640                     receiverType = GetTypeFromAssembly(typeName, "System");
3641 
3642                     // Next in System.Core
3643                     if (receiverType == null)
3644                     {
3645                         receiverType = GetTypeFromAssembly(typeName, "System.Core");
3646                     }
3647 
3648                     // We didn't find the type, so try to find it using the namespace
3649                     if (receiverType == null)
3650                     {
3651                         receiverType = GetTypeFromAssemblyUsingNamespace(typeName);
3652                     }
3653 
3654                     if (receiverType != null)
3655                     {
3656                         // If we've used it once, chances are that we'll be using it again
3657                         // We can cache the type here, since all functions are enabled
3658                         AvailableStaticMethods.TryAdd(typeName, new Tuple<string, Type>(typeName, receiverType));
3659                     }
3660                 }
3661 
3662                 return receiverType;
3663             }
3664 
3665             /// <summary>
3666             /// Gets the specified type using the namespace to guess the assembly that its in
3667             /// </summary>
GetTypeFromAssemblyUsingNamespace(string typeName)3668             private static Type GetTypeFromAssemblyUsingNamespace(string typeName)
3669             {
3670                 string baseName = typeName;
3671                 int assemblyNameEnd = baseName.Length;
3672                 Type foundType = null;
3673 
3674                 // If the string has no dot, or is nothing but a dot, we have no
3675                 // namespace to look for, so we can't help.
3676                 if (assemblyNameEnd <= 0)
3677                 {
3678                     return null;
3679                 }
3680 
3681                 // We will work our way up the namespace looking for an assembly that matches
3682                 while (assemblyNameEnd > 0)
3683                 {
3684                     string candidateAssemblyName = baseName.Substring(0, assemblyNameEnd);
3685 
3686                     // Try to load the assembly with the computed name
3687                     foundType = GetTypeFromAssembly(typeName, candidateAssemblyName);
3688 
3689                     if (foundType != null)
3690                     {
3691                         // We have a match, so get the type from that assembly
3692                         return foundType;
3693                     }
3694                     else
3695                     {
3696                         // Keep looking as we haven't found a match yet
3697                         baseName = candidateAssemblyName;
3698                         assemblyNameEnd = baseName.LastIndexOf('.');
3699                     }
3700                 }
3701 
3702                 // We didn't find it, so we need to give up
3703                 return null;
3704             }
3705 
3706             /// <summary>
3707             /// Get the specified type from the assembly partial name supplied
3708             /// </summary>
3709             [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadWithPartialName", Justification = "Necessary since we don't have the full assembly name. ")]
GetTypeFromAssembly(string typeName, string candidateAssemblyName)3710             private static Type GetTypeFromAssembly(string typeName, string candidateAssemblyName)
3711             {
3712                 Type objectType = null;
3713 
3714                 // Try to load the assembly with the computed name
3715 #if FEATURE_GAC
3716 #pragma warning disable 618, 612
3717                 // Unfortunately Assembly.Load is not an alternative to LoadWithPartialName, since
3718                 // Assembly.Load requires the full assembly name to be passed to it.
3719                 // Therefore we must ignore the deprecated warning.
3720                 Assembly candidateAssembly = Assembly.LoadWithPartialName(candidateAssemblyName);
3721 #pragma warning restore 618, 612
3722 #else
3723                 Assembly candidateAssembly = null;
3724                 try
3725                 {
3726                     candidateAssembly = Assembly.Load(new AssemblyName(candidateAssemblyName));
3727                 }
3728                 catch (FileNotFoundException)
3729                 {
3730                     // Swallow the error; LoadWithPartialName returned null when the partial name
3731                     // was not found but Load throws.  Either way we'll provide a nice "couldn't
3732                     // resolve this" error later.
3733                 }
3734 #endif
3735 
3736                 if (candidateAssembly != null)
3737                 {
3738                     objectType = candidateAssembly.GetType(typeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */);
3739                 }
3740 
3741                 return objectType;
3742             }
3743 
3744             /// <summary>
3745             /// Extracts the name, arguments, binding flags, and invocation type for an indexer
3746             /// Also extracts the remainder of the expression that is not part of this indexer
3747             /// </summary>
ConstructIndexerFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, int methodStartIndex, int indexerEndIndex, ref FunctionBuilder<T> functionBuilder)3748             private static void ConstructIndexerFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, int methodStartIndex, int indexerEndIndex, ref FunctionBuilder<T> functionBuilder)
3749             {
3750                 string argumentsContent = expressionFunction.Substring(1, indexerEndIndex - 1);
3751                 string remainder = expressionFunction.Substring(methodStartIndex);
3752                 string functionName;
3753                 string[] functionArguments;
3754 
3755                 // If there are no arguments, then just create an empty array
3756                 if (String.IsNullOrEmpty(argumentsContent))
3757                 {
3758                     functionArguments = Array.Empty<string>();
3759                 }
3760                 else
3761                 {
3762                     // We will keep empty entries so that we can treat them as null
3763                     functionArguments = ExtractFunctionArguments(elementLocation, expressionFunction, argumentsContent);
3764                 }
3765 
3766                 // choose the name of the function based on the type of the object that we
3767                 // are using.
3768                 if (propertyValue is Array)
3769                 {
3770                     functionName = "GetValue";
3771                 }
3772                 else if (propertyValue is string)
3773                 {
3774                     functionName = "get_Chars";
3775                 }
3776                 else // a regular indexer
3777                 {
3778                     functionName = "get_Item";
3779                 }
3780 
3781                 functionBuilder.Name = functionName;
3782                 functionBuilder.Arguments = functionArguments;
3783 #if FEATURE_TYPE_INVOKEMEMBER
3784                 functionBuilder.BindingFlags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.InvokeMethod;
3785 #else
3786                 functionBuilder.BindingFlags = BindingFlags.IgnoreCase | BindingFlags.Public;
3787                 functionBuilder.InvokeType = InvokeType.InvokeMethod;
3788 #endif
3789                 functionBuilder.Remainder = remainder;
3790             }
3791 
3792             /// <summary>
3793             /// Extracts the name, arguments, binding flags, and invocation type for a static or instance function.
3794             /// Also extracts the remainder of the expression that is not part of this function
3795             /// </summary>
ConstructFunction(IElementLocation elementLocation, string expressionFunction, int argumentStartIndex, int methodStartIndex, ref FunctionBuilder<T> functionBuilder)3796             private static void ConstructFunction(IElementLocation elementLocation, string expressionFunction, int argumentStartIndex, int methodStartIndex, ref FunctionBuilder<T> functionBuilder)
3797             {
3798                 // The unevaluated and unexpanded arguments for this function
3799                 string[] functionArguments;
3800 
3801                 // The name of the function that will be invoked
3802                 string functionName;
3803 
3804                 // What's left of the expression once the function has been constructed
3805                 string remainder = String.Empty;
3806 
3807                 // The binding flags that we will use for this function's execution
3808                 BindingFlags defaultBindingFlags = BindingFlags.IgnoreCase | BindingFlags.Public;
3809 #if !FEATURE_TYPE_INVOKEMEMBER
3810                 InvokeType defaultInvokeType;
3811 #endif
3812 
3813                 // There are arguments that need to be passed to the function
3814                 if (argumentStartIndex > -1 && !expressionFunction.Substring(methodStartIndex, argumentStartIndex - methodStartIndex).Contains("."))
3815                 {
3816                     string argumentsContent;
3817 
3818                     // separate the function and the arguments
3819                     functionName = expressionFunction.Substring(methodStartIndex, argumentStartIndex - methodStartIndex).Trim();
3820 
3821                     // Skip the '('
3822                     argumentStartIndex++;
3823 
3824                     // Scan for the matching closing bracket, skipping any nested ones
3825                     int argumentsEndIndex = ScanForClosingParenthesis(expressionFunction, argumentStartIndex);
3826 
3827                     if (argumentsEndIndex == -1)
3828                     {
3829                         ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedParenthesis"));
3830                     }
3831 
3832                     // We have been asked for a method invocation
3833 #if FEATURE_TYPE_INVOKEMEMBER
3834                     defaultBindingFlags |= BindingFlags.InvokeMethod;
3835 #else
3836                     defaultInvokeType = InvokeType.InvokeMethod;
3837 #endif
3838 
3839                     // It may be that there are '()' but no actual arguments content
3840                     if (argumentStartIndex == expressionFunction.Length - 1)
3841                     {
3842                         argumentsContent = String.Empty;
3843                         functionArguments = Array.Empty<string>();
3844                     }
3845                     else
3846                     {
3847                         // we have content within the '()' so let's extract and deal with it
3848                         argumentsContent = expressionFunction.Substring(argumentStartIndex, argumentsEndIndex - argumentStartIndex);
3849 
3850                         // If there are no arguments, then just create an empty array
3851                         if (String.IsNullOrEmpty(argumentsContent))
3852                         {
3853                             functionArguments = Array.Empty<string>();
3854                         }
3855                         else
3856                         {
3857                             // We will keep empty entries so that we can treat them as null
3858                             functionArguments = ExtractFunctionArguments(elementLocation, expressionFunction, argumentsContent);
3859                         }
3860 
3861                         remainder = expressionFunction.Substring(argumentsEndIndex + 1).Trim();
3862                     }
3863                 }
3864                 else
3865                 {
3866                     int nextMethodIndex = expressionFunction.IndexOf('.', methodStartIndex);
3867                     int methodLength = expressionFunction.Length - methodStartIndex;
3868                     int indexerIndex = expressionFunction.IndexOf('[', methodStartIndex);
3869 
3870                     // We don't want to consume the indexer
3871                     if (indexerIndex >= 0 && indexerIndex < nextMethodIndex)
3872                     {
3873                         nextMethodIndex = indexerIndex;
3874                     }
3875 
3876                     functionArguments = Array.Empty<string>();
3877 
3878                     if (nextMethodIndex > 0)
3879                     {
3880                         methodLength = nextMethodIndex - methodStartIndex;
3881                         remainder = expressionFunction.Substring(nextMethodIndex).Trim();
3882                     }
3883 
3884                     string netPropertyName = expressionFunction.Substring(methodStartIndex, methodLength).Trim();
3885 
3886                     ProjectErrorUtilities.VerifyThrowInvalidProject(netPropertyName.Length > 0, elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty);
3887 
3888                     // We have been asked for a property or a field
3889 #if FEATURE_TYPE_INVOKEMEMBER
3890                     defaultBindingFlags |= (BindingFlags.GetProperty | BindingFlags.GetField);
3891 #else
3892                     defaultInvokeType = InvokeType.GetPropertyOrField;
3893 #endif
3894 
3895                     functionName = netPropertyName;
3896                 }
3897 
3898                 // either there are no functions left or what we have is another function or an indexer
3899                 if (String.IsNullOrEmpty(remainder) || remainder[0] == '.' || remainder[0] == '[')
3900                 {
3901                     functionBuilder.Name = functionName;
3902                     functionBuilder.Arguments = functionArguments;
3903                     functionBuilder.BindingFlags = defaultBindingFlags;
3904                     functionBuilder.Remainder = remainder;
3905 #if !FEATURE_TYPE_INVOKEMEMBER
3906                     functionBuilder.InvokeType = defaultInvokeType;
3907 #endif
3908                 }
3909                 else
3910                 {
3911                     // We ended up with something other than a function expression
3912                     ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty);
3913                 }
3914             }
3915 
3916             /// <summary>
3917             /// Coerce the arguments according to the parameter types
3918             /// Will only return null if the coercion didn't work due to an InvalidCastException
3919             /// </summary>
CoerceArguments(object[] args, ParameterInfo[] parameters)3920             private static object[] CoerceArguments(object[] args, ParameterInfo[] parameters)
3921             {
3922                 object[] coercedArguments = new object[args.Length];
3923 
3924                 try
3925                 {
3926                     // Do our best to coerce types into the arguments for the function
3927                     for (int n = 0; n < parameters.Length; n++)
3928                     {
3929                         if (args[n] == null)
3930                         {
3931                             // We can't coerce (object)null -- that's as general
3932                             // as it can get!
3933                             continue;
3934                         }
3935 
3936                         // Here we have special case conversions on a type basis
3937                         if (parameters[n].ParameterType == typeof(char[]))
3938                         {
3939                             coercedArguments[n] = args[n].ToString().ToCharArray();
3940                         }
3941                         else if (parameters[n].ParameterType.GetTypeInfo().IsEnum && args[n] is string && ((string)args[n]).Contains("."))
3942                         {
3943                             Type enumType = parameters[n].ParameterType;
3944                             string typeLeafName = enumType.Name + ".";
3945                             string typeFullName = enumType.FullName + ".";
3946 
3947                             // Enum.parse expects commas between enum components
3948                             // We'll support the C# type | syntax too
3949                             // We'll also allow the user to specify the leaf or full type name on the enum
3950                             string argument = args[n].ToString().Replace('|', ',').Replace(typeFullName, "").Replace(typeLeafName, "");
3951 
3952                             // Parse the string representation of the argument into the destination enum
3953                             coercedArguments[n] = Enum.Parse(enumType, argument);
3954                         }
3955                         else
3956                         {
3957                             // change the type of the final unescaped string into the destination
3958                             coercedArguments[n] = Convert.ChangeType(args[n], parameters[n].ParameterType, CultureInfo.InvariantCulture);
3959                         }
3960                     }
3961                 }
3962                 // The coercion failed therefore we return null
3963                 catch (InvalidCastException)
3964                 {
3965                     return null;
3966                 }
3967                 catch (FormatException)
3968                 {
3969                     return null;
3970                 }
3971                 catch (OverflowException)
3972                 {
3973                     // https://github.com/Microsoft/msbuild/issues/2882
3974                     // test: PropertyFunctionMathMaxOverflow
3975                     return null;
3976                 }
3977 
3978                 return coercedArguments;
3979             }
3980 
3981             /// <summary>
3982             /// Make an attempt to create a string showing what we were trying to execute when we failed.
3983             /// This will show any intermediate evaluation which may help the user figure out what happened.
3984             /// </summary>
GenerateStringOfMethodExecuted(string expression, object objectInstance, string name, object[] args)3985             private string GenerateStringOfMethodExecuted(string expression, object objectInstance, string name, object[] args)
3986             {
3987                 string parameters = String.Empty;
3988                 if (args != null)
3989                 {
3990                     foreach (object arg in args)
3991                     {
3992                         if (arg == null)
3993                         {
3994                             parameters += "null";
3995                         }
3996                         else
3997                         {
3998                             string argString = arg.ToString();
3999                             if (arg is string && argString.Length == 0)
4000                             {
4001                                 parameters += "''";
4002                             }
4003                             else
4004                             {
4005                                 parameters += arg.ToString();
4006                             }
4007                         }
4008 
4009                         parameters += ", ";
4010                     }
4011 
4012                     if (parameters.Length > 2)
4013                     {
4014                         parameters = parameters.Substring(0, parameters.Length - 2);
4015                     }
4016                 }
4017 
4018                 if (objectInstance == null)
4019                 {
4020                     string typeName = _receiverType.FullName;
4021 
4022                     // We don't want to expose the real type name of our intrinsics
4023                     // so we'll replace it with "MSBuild"
4024                     if (_receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))
4025                     {
4026                         typeName = "MSBuild";
4027                     }
4028 #if FEATURE_TYPE_INVOKEMEMBER
4029                     if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
4030 #else
4031                     if (_invokeType == InvokeType.InvokeMethod)
4032 #endif
4033                     {
4034                         return "[" + typeName + "]::" + name + "(" + parameters + ")";
4035                     }
4036                     else
4037                     {
4038                         return "[" + typeName + "]::" + name;
4039                     }
4040                 }
4041                 else
4042                 {
4043                     string propertyValue = "\"" + objectInstance as string + "\"";
4044 
4045 #if FEATURE_TYPE_INVOKEMEMBER
4046                     if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
4047 #else
4048                     if (_invokeType == InvokeType.InvokeMethod)
4049 #endif
4050                     {
4051                         return propertyValue + "." + name + "(" + parameters + ")";
4052                     }
4053                     else
4054                     {
4055                         return propertyValue + "." + name;
4056                     }
4057                 }
4058             }
4059 
4060             /// <summary>
4061             /// Check the property function whitelist whether this method is available.
4062             /// </summary>
IsStaticMethodAvailable(Type receiverType, string methodName)4063             private static bool IsStaticMethodAvailable(Type receiverType, string methodName)
4064             {
4065                 if (receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))
4066                 {
4067                     // These are our intrinsic functions, so we're OK with those
4068                     return true;
4069                 }
4070 
4071                 if (Traits.Instance.EnableAllPropertyFunctions)
4072                 {
4073                     // anything goes
4074                     return true;
4075                 }
4076 
4077                 return AvailableStaticMethods.GetTypeInformationFromTypeCache(receiverType.FullName, methodName) != null;
4078             }
4079 
4080 #if !FEATURE_TYPE_INVOKEMEMBER
ParametersBindToNStringArguments(ParameterInfo[] parameters, int n)4081             private static bool ParametersBindToNStringArguments(ParameterInfo[] parameters, int n)
4082             {
4083                 if (parameters.Length != n)
4084                 {
4085                     return false;
4086                 }
4087                 if (parameters.Any(p => !p.ParameterType.IsAssignableFrom(typeof(string))))
4088                 {
4089                     return false;
4090                 }
4091                 return true;
4092             }
4093 #endif
4094 
4095             /// <summary>
4096             /// Construct and instance of objectType based on the constructor or method arguments provided.
4097             /// Arguments must never be null.
4098             /// </summary>
LateBindExecute(Exception ex, BindingFlags bindingFlags, object objectInstance , object[] args, bool isConstructor)4099             private object LateBindExecute(Exception ex, BindingFlags bindingFlags, object objectInstance /* null unless instance method */, object[] args, bool isConstructor)
4100             {
4101                 ParameterInfo[] parameters = null;
4102                 MethodBase[] members = null;
4103                 MethodBase memberInfo = null;
4104 
4105                 // First let's try for a method where all arguments are strings..
4106                 Type[] types = new Type[_arguments.Length];
4107                 for (int n = 0; n < _arguments.Length; n++)
4108                 {
4109                     types[n] = typeof(string);
4110                 }
4111 
4112                 if (isConstructor)
4113                 {
4114 #if FEATURE_TYPE_INVOKEMEMBER
4115                     memberInfo = _receiverType.GetConstructor(bindingFlags, null, types, null);
4116 #else
4117                     memberInfo = _receiverType.GetConstructors(bindingFlags)
4118                         .Where(c => ParametersBindToNStringArguments(c.GetParameters(), args.Length))
4119                         .FirstOrDefault();
4120 #endif
4121                 }
4122                 else
4123                 {
4124                     memberInfo = _receiverType.GetMethod(_methodMethodName, bindingFlags, null, types, null);
4125                 }
4126 
4127                 // If we didn't get a match on all string arguments,
4128                 // search for a method with the right number of arguments
4129                 if (memberInfo == null)
4130                 {
4131                     // Gather all methods that may match
4132                     if (isConstructor)
4133                     {
4134                         members = _receiverType.GetConstructors(bindingFlags);
4135                     }
4136                     else
4137                     {
4138                         members = _receiverType.GetMethods(bindingFlags);
4139                     }
4140 
4141                     // Try to find a method with the right name, number of arguments and
4142                     // compatible argument types
4143                     object[] coercedArguments = null;
4144                     foreach (MethodBase member in members)
4145                     {
4146                         parameters = member.GetParameters();
4147 
4148                         // Simple match on name and number of params, we will be case insensitive
4149                         if (parameters.Length == _arguments.Length)
4150                         {
4151                             if (isConstructor || String.Equals(member.Name, _methodMethodName, StringComparison.OrdinalIgnoreCase))
4152                             {
4153                                 // we have a match on the name and argument number
4154                                 // now let's try to coerce the arguments we have
4155                                 // into the arguments on the matching method
4156                                 coercedArguments = CoerceArguments(args, parameters);
4157 
4158                                 if (coercedArguments != null)
4159                                 {
4160                                     // We have a complete match
4161                                     memberInfo = member;
4162                                     args = coercedArguments;
4163                                     break;
4164                                 }
4165                             }
4166                         }
4167                     }
4168                 }
4169 
4170                 object functionResult = null;
4171 
4172                 // We have a match and coerced arguments, let's construct..
4173                 if (memberInfo != null && args != null)
4174                 {
4175                     if (isConstructor)
4176                     {
4177                         functionResult = ((ConstructorInfo)memberInfo).Invoke(args);
4178                     }
4179                     else
4180                     {
4181                         functionResult = ((MethodInfo)memberInfo).Invoke(objectInstance /* null if static method */, args);
4182                     }
4183                 }
4184                 else if (!isConstructor)
4185                 {
4186                     throw ex;
4187                 }
4188 
4189                 if (functionResult == null && isConstructor)
4190                 {
4191                     throw new TargetInvocationException(new MissingMethodException());
4192                 }
4193 
4194                 return functionResult;
4195             }
4196         }
4197     }
4198 
4199     /// <summary>
4200     /// This class wraps information about properties which have been used before they are initialized
4201     /// </summary>
4202     internal class UsedUninitializedProperties
4203     {
4204         /// <summary>
4205         /// This class wraps information about properties which have been used before they are initialized
4206         /// </summary>
UsedUninitializedProperties()4207         internal UsedUninitializedProperties()
4208         {
4209             Properties = new Dictionary<string, IElementLocation>(StringComparer.OrdinalIgnoreCase);
4210         }
4211 
4212         /// <summary>
4213         /// Hash set of properties which have been used before being initialized
4214         /// </summary>
4215         internal IDictionary<string, IElementLocation> Properties
4216         {
4217             get;
4218             set;
4219         }
4220 
4221         /// <summary>
4222         ///  Are we currently supposed to warn if we used an uninitialized property.
4223         /// </summary>
4224         internal bool Warn
4225         {
4226             get;
4227             set;
4228         }
4229 
4230         /// <summary>
4231         ///  What is the currently evaluating property element, this is so that we do not add a un initialized property if we are evaluating that property
4232         /// </summary>
4233         internal string CurrentlyEvaluatingPropertyElementName
4234         {
4235             get;
4236             set;
4237         }
4238     }
4239 }
4240