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 /// @(<name>->'<transform>','<separator>') 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 <name>, <transform> and <separator> is ignored 1517 /// i.e. @(<name>, '<separator>') 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. @(<name>,'') 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