diff --git a/src/XMakeBuildEngine/Evaluation/Expander.cs b/src/XMakeBuildEngine/Evaluation/Expander.cs index 176b5267346..0dbb3d0be54 100644 --- a/src/XMakeBuildEngine/Evaluation/Expander.cs +++ b/src/XMakeBuildEngine/Evaluation/Expander.cs @@ -1001,7 +1001,7 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped(string expression, I propertyBody = expression.Substring(propertyStartIndex + 2, propertyEndIndex - propertyStartIndex - 2); // This is likely to be a function expression - propertyValue = ExpandPropertyBody(propertyBody, propertyValue, properties, options, elementLocation, usedUninitializedProperties); + propertyValue = ExpandPropertyBody(propertyBody, null, properties, options, elementLocation, usedUninitializedProperties); } else // This is a regular property { @@ -1105,7 +1105,7 @@ internal static object ExpandPropertyBody(string propertyBody, object propertyVa { // We will have either extracted the actual property name // or realised that there is none (static function), and have recorded a null - propertyName = function.ExpressionRootName; + propertyName = function.Receiver; } else { @@ -2609,6 +2609,71 @@ internal enum InvokeType } #endif + private struct FunctionBuilder + where T : class, IProperty + { + /// + /// The type of this function's receiver + /// + public Type ReceiverType { get; set; } + + /// + /// The name of the function + /// + public string Name { get; set; } + + /// + /// The arguments for the function + /// + public string[] Arguments { get; set; } + + /// + /// The expression that this function is part of + /// + public string Expression { get; set; } + + /// + /// The property name that this function is applied on + /// + public string Receiver { get; set; } + + /// + /// The binding flags that will be used during invocation of this function + /// + public BindingFlags BindingFlags { get; set; } + +#if !FEATURE_TYPE_INVOKEMEMBER + public InvokeType InvokeType { get; set; } +#endif + + /// + /// The remainder of the body once the function and arguments have been extracted + /// + public string Remainder { get; set; } + + /// + /// List of properties which have been used but have not been initialized yet. + /// + public UsedUninitializedProperties UsedUninitializedProperties { get; set; } + + internal Function Build() + { + return new Function( + ReceiverType, + Expression, + Receiver, + Name, + Arguments, + BindingFlags, +#if !FEATURE_TYPE_INVOKEMEMBER + InvokeType, +#endif + Remainder, + UsedUninitializedProperties + ); + } + } + /// /// This class represents the function as extracted from an expression /// It is also responsible for executing the function @@ -2618,14 +2683,14 @@ private class Function where T : class, IProperty { /// - /// The type that this function will act on + /// The type of this function's receiver /// - private Type _objectType; + private Type _receiverType; /// /// The name of the function /// - private string _name; + private string _methodMethodName; /// /// The arguments for the function @@ -2633,14 +2698,14 @@ private class Function private string[] _arguments; /// - /// The expression that constitutes this function + /// The expression that this function is part of /// private string _expression; /// - /// The property name that is the context for this function + /// The property name that this function is applied on /// - private string _expressionRootName; + private string _receiver; /// /// The binding flags that will be used during invocation of this function @@ -2664,13 +2729,13 @@ private class Function /// /// Construct a function that will be executed during property evaluation /// - internal Function(Type objectType, string expression, string expressionRootName, string name, string[] arguments, BindingFlags bindingFlags, + internal Function(Type receiverType, string expression, string receiver, string methodName, string[] arguments, BindingFlags bindingFlags, #if !FEATURE_TYPE_INVOKEMEMBER InvokeType invokeType, #endif string remainder, UsedUninitializedProperties usedUninitializedProperties) { - _name = name; + _methodMethodName = methodName; if (arguments == null) { _arguments = new string[0]; @@ -2680,9 +2745,9 @@ internal Function(Type objectType, string expression, string expressionRootName, _arguments = arguments; } - _expressionRootName = expressionRootName; + _receiver = receiver; _expression = expression; - _objectType = objectType; + _receiverType = receiverType; _bindingFlags = bindingFlags; #if !FEATURE_TYPE_INVOKEMEMBER _invokeType = invokeType; @@ -2698,9 +2763,9 @@ internal Function(Type objectType, string expression, string expressionRootName, /// [System.Diagnostics.Process]::Start /// SomeMSBuildProperty /// - internal string ExpressionRootName + internal string Receiver { - get { return _expressionRootName; } + get { return _receiver; } } /// @@ -2708,18 +2773,15 @@ internal string ExpressionRootName /// internal static Function ExtractPropertyFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, UsedUninitializedProperties usedUnInitializedProperties) { - // If this a expression function rather than a static, then we'll capture the name of the property referenced - string propertyName = null; - - // The type of the object that this function is part - Type objectType = null; + // Used to aggregate all the components needed for a Function + FunctionBuilder functionBuilder = new FunctionBuilder(); // By default the expression root is the whole function expression - string expressionRoot = expressionFunction; + var expressionRoot = expressionFunction; // The arguments for this function start at the first '(' // If there are no arguments, then we're a property getter - int argumentStartIndex = expressionFunction.IndexOf('('); + var argumentStartIndex = expressionFunction.IndexOf('('); // If we have arguments, then we only want the content up to but not including the '(' if (argumentStartIndex > -1) @@ -2730,15 +2792,14 @@ internal static Function ExtractPropertyFunction(string expressionFunction, I // In case we ended up with something we don't understand ProjectErrorUtilities.VerifyThrowInvalidProject(!String.IsNullOrEmpty(expressionRoot), elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty); - // First we'll see if there is a static function being called - // A static method is the content that follows the last "::", the rest being - // the type - int methodStartIndex = -1; + functionBuilder.Expression = expressionFunction; + functionBuilder.UsedUninitializedProperties = usedUnInitializedProperties; // This is a static method call + // A static method is the content that follows the last "::", the rest being the type if (propertyValue == null && expressionRoot[0] == '[') { - int typeEndIndex = expressionRoot.IndexOf(']', 1); + var typeEndIndex = expressionRoot.IndexOf(']', 1); if (typeEndIndex < 1) { @@ -2746,18 +2807,8 @@ internal static Function ExtractPropertyFunction(string expressionFunction, I ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", expressionFunction, String.Empty); } - string typeName = expressionRoot.Substring(1, typeEndIndex - 1); - methodStartIndex = typeEndIndex + 1; - - // Make an attempt to locate a type that matches the body of the expression. - // We won't throw on error here - objectType = GetTypeForStaticMethod(typeName); - - if (objectType == null) - { - // We ended up with something other than a type - ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionTypeUnavailable", expressionFunction, typeName); - } + var typeName = expressionRoot.Substring(1, typeEndIndex - 1); + var methodStartIndex = typeEndIndex + 1; if (expressionRoot.Length > methodStartIndex + 2 && expressionRoot[methodStartIndex] == ':' && expressionRoot[methodStartIndex + 1] == ':') { @@ -2769,67 +2820,71 @@ internal static Function ExtractPropertyFunction(string expressionFunction, I // We ended up with something other than a static function expression ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", expressionFunction, String.Empty); } + + ConstructFunction(elementLocation, expressionFunction, argumentStartIndex, methodStartIndex, ref functionBuilder); + + // Locate a type that matches the body of the expression. + var receiverType = GetTypeForStaticMethod(typeName, functionBuilder.Name); + + if (receiverType == null) + { + // We ended up with something other than a type + ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionTypeUnavailable", expressionFunction, typeName); + } + + functionBuilder.ReceiverType = receiverType; } else if (expressionFunction[0] == '[') // We have an indexer { - objectType = propertyValue.GetType(); - int indexerEndIndex = expressionFunction.IndexOf(']', 1); - + var indexerEndIndex = expressionFunction.IndexOf(']', 1); if (indexerEndIndex < 1) { // We ended up with something other than a function expression ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, AssemblyResources.GetString("InvalidFunctionPropertyExpressionDetailMismatchedSquareBrackets")); } - string argumentsContent = expressionFunction.Substring(1, indexerEndIndex - 1); - methodStartIndex = indexerEndIndex + 1; - Function indexerFunction = ConstructIndexerFunction(expressionFunction, elementLocation, propertyValue, propertyName, objectType, methodStartIndex, argumentsContent, usedUnInitializedProperties); + var methodStartIndex = indexerEndIndex + 1; - return indexerFunction; + functionBuilder.ReceiverType = propertyValue.GetType(); + + ConstructIndexerFunction(expressionFunction, elementLocation, propertyValue, methodStartIndex, indexerEndIndex, ref functionBuilder); } - else + else // This could be a property reference, or a chain of function calls { - // No static function call was found, look for an instance function call next, such as in SomeStuff.ToLower() - methodStartIndex = expressionRoot.IndexOf('.'); + // Look for an instance function call next, such as in SomeStuff.ToLower() + var methodStartIndex = expressionRoot.IndexOf('.'); if (methodStartIndex == -1) { // We don't have a function invocation in the expression root, return null return null; } - else - { - // skip over the '.'; - methodStartIndex++; - } - } - // No type matched, therefore the content must be a property reference, or a recursive call as functions - // are chained together - if (objectType == null) - { - int rootEndIndex = expressionRoot.IndexOf('.'); - propertyName = expressionRoot.Substring(0, rootEndIndex); + // skip over the '.'; + methodStartIndex++; + + var rootEndIndex = expressionRoot.IndexOf('.'); + + // If this is an instance function rather than a static, then we'll capture the name of the property referenced + var functionReceiver = expressionRoot.Substring(0, rootEndIndex); // If propertyValue is null (we're not recursing), then we're expecting a valid property name - if (propertyValue == null && !IsValidPropertyName(propertyName)) + if (propertyValue == null && !IsValidPropertyName(functionReceiver)) { // We extracted something that wasn't a valid property name, fail. ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty); } - objectType = typeof(string); - } + // 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) + // Otherwise, the receiver of the function is a string + var receiverType = propertyValue?.GetType() ?? typeof(string); - // If we are recursively acting on a type that has been already produced - // then pass that type inwards - if (propertyValue != null) - { - objectType = propertyValue.GetType(); - } + functionBuilder.Receiver = functionReceiver; + functionBuilder.ReceiverType = receiverType; - Function function = ConstructFunction(elementLocation, expressionFunction, propertyName, objectType, argumentStartIndex, methodStartIndex, usedUnInitializedProperties); + ConstructFunction(elementLocation, expressionFunction, argumentStartIndex, methodStartIndex, ref functionBuilder); + } - return function; + return functionBuilder.Build(); } #if !FEATURE_TYPE_INVOKEMEMBER @@ -2838,15 +2893,15 @@ private MemberInfo BindFieldOrProperty() StringComparison nameComparison = ((_bindingFlags & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - var matchingMembers = _objectType.GetFields(_bindingFlags) + var matchingMembers = _receiverType.GetFields(_bindingFlags) .Cast() - .Concat(_objectType.GetProperties(_bindingFlags)) - .Where(member => member.Name.Equals(_name, nameComparison)) + .Concat(_receiverType.GetProperties(_bindingFlags)) + .Where(member => member.Name.Equals(_methodMethodName, nameComparison)) .ToArray(); if (matchingMembers.Length == 0) { - throw new MissingMemberException(_name); + throw new MissingMemberException(_methodMethodName); } else if (matchingMembers.Length == 1) { @@ -2854,7 +2909,7 @@ private MemberInfo BindFieldOrProperty() } else { - throw new AmbiguousMatchException(_name); + throw new AmbiguousMatchException(_methodMethodName); } } #endif @@ -2873,16 +2928,16 @@ internal object Execute(object objectInstance, IPropertyProvider properties, if (objectInstance == null) { // Check that the function that we're going to call is valid to call - if (!IsStaticMethodAvailable(_objectType, _name)) + if (!IsStaticMethodAvailable(_receiverType, _methodMethodName)) { - ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionMethodUnavailable", _name, _objectType.FullName); + ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionMethodUnavailable", _methodMethodName, _receiverType.FullName); } _bindingFlags |= BindingFlags.Static; // For our intrinsic function we need to support calling of internal methods // since we don't want them to be public - if (_objectType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) + if (_receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) { _bindingFlags |= BindingFlags.NonPublic; } @@ -2913,8 +2968,8 @@ internal object Execute(object objectInstance, IPropertyProvider properties, { // Unescape the value since we're about to send it out of the engine and into // the function being called. If a file or a directory function, fix the path - if (_objectType == typeof(System.IO.File) || _objectType == typeof(System.IO.Directory) - || _objectType == typeof(System.IO.Path)) + if (_receiverType == typeof(System.IO.File) || _receiverType == typeof(System.IO.Directory) + || _receiverType == typeof(System.IO.Path)) { argumentValue = FileUtilities.FixFilePath(argumentValue); } @@ -2933,7 +2988,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, // This special casing is to realize that its a comparison that is taking place and handle the // argument type coercion accordingly; effectively pre-preparing the argument type so // that it matches the left hand side ready for the default binder’s method invoke. - if (objectInstance != null && args.Length == 1 && (String.Equals("Equals", _name, StringComparison.OrdinalIgnoreCase) || String.Equals("CompareTo", _name, StringComparison.OrdinalIgnoreCase))) + if (objectInstance != null && args.Length == 1 && (String.Equals("Equals", _methodMethodName, StringComparison.OrdinalIgnoreCase) || String.Equals("CompareTo", _methodMethodName, StringComparison.OrdinalIgnoreCase))) { // change the type of the final unescaped string into the destination args[0] = Convert.ChangeType(args[0], objectInstance.GetType(), CultureInfo.InvariantCulture); @@ -2941,7 +2996,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, // If we've been asked to construct an instance, then we // need to locate an appropriate constructor and invoke it - if (String.Equals("new", _name, StringComparison.OrdinalIgnoreCase)) + if (String.Equals("new", _methodMethodName, StringComparison.OrdinalIgnoreCase)) { functionResult = LateBindExecute(null /* no previous exception */, BindingFlags.Public | BindingFlags.Instance, null /* no instance for a constructor */, args, true /* is constructor */); } @@ -2954,11 +3009,11 @@ internal object Execute(object objectInstance, IPropertyProvider properties, { #if FEATURE_TYPE_INVOKEMEMBER // First use InvokeMember using the standard binder - this will match and coerce as needed - functionResult = _objectType.InvokeMember(_name, _bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture); + functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture); #else if (_invokeType == InvokeType.InvokeMethod) { - functionResult = _objectType.InvokeMember(_name, _bindingFlags, objectInstance, args, null, CultureInfo.InvariantCulture, null); + functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, objectInstance, args, null, CultureInfo.InvariantCulture, null); } else if (_invokeType == InvokeType.GetPropertyOrField) { @@ -3005,7 +3060,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, // If the result of the function call is a string, then we need to escape the result // so that we maintain the "engine contains escaped data" state. // The exception is that the user is explicitly calling MSBuild::Unescape or MSBuild::Escape - if (functionResult is string && !String.Equals("Unescape", _name, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", _name, StringComparison.OrdinalIgnoreCase)) + if (functionResult is string && !String.Equals("Unescape", _methodMethodName, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", _methodMethodName, StringComparison.OrdinalIgnoreCase)) { functionResult = EscapingUtilities.Escape((string)functionResult); } @@ -3024,7 +3079,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, catch (TargetInvocationException ex) { // We ended up with something other than a function expression - string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _name, args); + string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args); ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.InnerException.Message.Replace("\r\n", " ")); return null; } @@ -3046,7 +3101,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, else { // We ended up with something other than a function expression - string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _name, args); + string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args); ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message); } @@ -3055,61 +3110,66 @@ internal object Execute(object objectInstance, IPropertyProvider properties, } /// - /// Return a Type object for the type we're trying to call static methods on + /// Given a type name and method name, try to resolve the type. /// - private static Type GetTypeForStaticMethod(string typeName) + /// May be full name or assembly qualified name + /// simple name of the method + /// + private static Type GetTypeForStaticMethod(string typeName, string simpleMethodName) { - // Ultimately this will be a more in-depth lookup, including assembly name etc. - // for now, we're only supporting a subset of what's in mscorlib + specific additional types - // If the env var MSBUILDENABLEALLPROPERTYFUNCTIONS=1 then we'll allow pretty much anything - Type objectType; - Tuple functionType; + Type receiverType; + Tuple cachedTypeInformation; // If we don't have a type name, we already know that we won't be able to find a type. // Go ahead and return here -- otherwise the Type.GetType() calls below will throw. - if (String.IsNullOrEmpty(typeName)) + if (string.IsNullOrWhiteSpace(typeName)) { return null; } - // For whole types we support them being in different assemblies than mscorlib - // Get the assembly qualified type name if one exists - if (AvailableStaticMethods.TryGetValue(typeName, out functionType) && functionType != null) + // Check if the type is in the whitelist cache. If it is, use it or load it. + cachedTypeInformation = AvailableStaticMethods.GetTypeInformationFromTypeCache(typeName, simpleMethodName); + if (cachedTypeInformation != null) { // We need at least one of these set - ErrorUtilities.VerifyThrow(functionType.Item1 != null || functionType.Item2 != null, "Function type information needs either string or type represented."); + ErrorUtilities.VerifyThrow(cachedTypeInformation.Item1 != null || cachedTypeInformation.Item2 != null, "Function type information needs either string or type represented."); // If we have the type information in Type form, then just return that - if (functionType.Item2 != null) + if (cachedTypeInformation.Item2 != null) { - return functionType.Item2; + return cachedTypeInformation.Item2; } - else if (functionType.Item1 != null) + else if (cachedTypeInformation.Item1 != null) { // This is a case where the Type is not available at compile time, so // we are forced to bind by name instead - typeName = functionType.Item1; + var assemblyQualifiedTypeName = cachedTypeInformation.Item1; // Get the type from the assembly qualified type name from AvailableStaticMethods - objectType = Type.GetType(typeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */); + receiverType = Type.GetType(assemblyQualifiedTypeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */); + + // If the type information from the cache is not loadable, it means the cache information got corrupted somehow + // Throw here to prevent adding null types in the cache + ErrorUtilities.VerifyThrowInternalNull(receiverType, $"Type information for {typeName} was present in the whitelist cache as {assemblyQualifiedTypeName} but the type could not be loaded."); // If we've used it once, chances are that we'll be using it again // We can record the type here since we know it's available for calling from the fact that is was in the AvailableStaticMethods table - AvailableStaticMethods.TryAdd(typeName, new Tuple(typeName, objectType)); + AvailableStaticMethods.TryAdd(typeName, simpleMethodName, new Tuple(assemblyQualifiedTypeName, receiverType)); - return objectType; + return receiverType; } } // Get the type from mscorlib (or the currently running assembly) - objectType = Type.GetType(typeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */); + receiverType = Type.GetType(typeName, false /* do not throw TypeLoadException if not found */, true /* ignore case */); - if (objectType != null) + if (receiverType != null) { // DO NOT CACHE THE TYPE HERE! // We don't add the resolved type here in the AvailableStaticMethods table. This is because that table is used // during function parse, but only later during execution do we check for the ability to call specific methods on specific types. - return objectType; + // Caching it here would load any type into the white list. + return receiverType; } // Note the following code path is only entered when MSBUILDENABLEALLPROPERTYFUNCTIONS == 1. @@ -3117,32 +3177,32 @@ private static Type GetTypeForStaticMethod(string typeName) if (Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1") { // We didn't find the type, so go probing. First in System - if (objectType == null) + if (receiverType == null) { - objectType = GetTypeFromAssembly(typeName, "System"); + receiverType = GetTypeFromAssembly(typeName, "System"); } // Next in System.Core - if (objectType == null) + if (receiverType == null) { - objectType = GetTypeFromAssembly(typeName, "System.Core"); + receiverType = GetTypeFromAssembly(typeName, "System.Core"); } // We didn't find the type, so try to find it using the namespace - if (objectType == null) + if (receiverType == null) { - objectType = GetTypeFromAssemblyUsingNamespace(typeName); + receiverType = GetTypeFromAssemblyUsingNamespace(typeName); } - if (objectType != null) + if (receiverType != null) { // If we've used it once, chances are that we'll be using it again // We can cache the type here, since all functions are enabled - AvailableStaticMethods.TryAdd(typeName, new Tuple(typeName, objectType)); + AvailableStaticMethods.TryAdd(typeName, new Tuple(typeName, receiverType)); } } - return objectType; + return receiverType; } /// @@ -3227,12 +3287,14 @@ private static Type GetTypeFromAssembly(string typeName, string candidateAssembl } /// - /// Factory method to construct an indexer function for property evaluation + /// Extracts the name, arguments, binding flags, and invocation type for an indexer + /// Also extracts the remainder of the expression that is not part of this indexer /// - private static Function ConstructIndexerFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, string propertyName, Type objectType, int methodStartIndex, string argumentsContent, UsedUninitializedProperties usedUnInitializedProperties) + private static void ConstructIndexerFunction(string expressionFunction, IElementLocation elementLocation, object propertyValue, int methodStartIndex, int indexerEndIndex, ref FunctionBuilder functionBuilder) { + string argumentsContent = expressionFunction.Substring(1, indexerEndIndex - 1); string remainder = expressionFunction.Substring(methodStartIndex); - string functionToInvoke; + string functionName; string[] functionArguments; // If there are no arguments, then just create an empty array @@ -3250,39 +3312,39 @@ private static Function ConstructIndexerFunction(string expressionFunction, I // are using. if (propertyValue is Array) { - functionToInvoke = "GetValue"; + functionName = "GetValue"; } else if (propertyValue is string) { - functionToInvoke = "get_Chars"; + functionName = "get_Chars"; } else // a regular indexer { - functionToInvoke = "get_Item"; + functionName = "get_Item"; } - Function indexerFunction; - - indexerFunction = new Function(objectType, expressionFunction, propertyName, functionToInvoke, functionArguments, + functionBuilder.Name = functionName; + functionBuilder.Arguments = functionArguments; #if FEATURE_TYPE_INVOKEMEMBER - BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.InvokeMethod, + functionBuilder.BindingFlags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.InvokeMethod; #else - BindingFlags.IgnoreCase | BindingFlags.Public, InvokeType.InvokeMethod, + functionBuilder.BindingFlags = BindingFlags.IgnoreCase | BindingFlags.Public; + functionBuilder.InvokeType = InvokeType.InvokeMethod; #endif - remainder, usedUnInitializedProperties); - return indexerFunction; + functionBuilder.Remainder = remainder; } /// - /// Factory method to construct a function for property evaluation + /// Extracts the name, arguments, binding flags, and invocation type for a static or instance function. + /// Also extracts the remainder of the expression that is not part of this function /// - private static Function ConstructFunction(IElementLocation elementLocation, string expressionFunction, string expressionRootName, Type objectType, int argumentStartIndex, int methodStartIndex, UsedUninitializedProperties usedUninitializedProperties) + private static void ConstructFunction(IElementLocation elementLocation, string expressionFunction, int argumentStartIndex, int methodStartIndex, ref FunctionBuilder functionBuilder) { // The unevaluated and unexpanded arguments for this function string[] functionArguments; // The name of the function that will be invoked - string functionToInvoke; + string functionName; // What's left of the expression once the function has been constructed string remainder = String.Empty; @@ -3299,7 +3361,7 @@ private static Function ConstructFunction(IElementLocation elementLocation, s string argumentsContent; // separate the function and the arguments - functionToInvoke = expressionFunction.Substring(methodStartIndex, argumentStartIndex - methodStartIndex).Trim(); + functionName = expressionFunction.Substring(methodStartIndex, argumentStartIndex - methodStartIndex).Trim(); // Skip the '(' argumentStartIndex++; @@ -3375,24 +3437,24 @@ private static Function ConstructFunction(IElementLocation elementLocation, s defaultInvokeType = InvokeType.GetPropertyOrField; #endif - functionToInvoke = netPropertyName; + functionName = netPropertyName; } // either there are no functions left or what we have is another function or an indexer if (String.IsNullOrEmpty(remainder) || remainder[0] == '.' || remainder[0] == '[') { - // Construct a FunctionInfo will all the content that we just gathered - return new Function(objectType, expressionFunction, expressionRootName, functionToInvoke, functionArguments, defaultBindingFlags, + functionBuilder.Name = functionName; + functionBuilder.Arguments = functionArguments; + functionBuilder.BindingFlags = defaultBindingFlags; + functionBuilder.Remainder = remainder; #if !FEATURE_TYPE_INVOKEMEMBER - defaultInvokeType, + functionBuilder.InvokeType = defaultInvokeType; #endif - remainder, usedUninitializedProperties); } else { // We ended up with something other than a function expression ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", expressionFunction, String.Empty); - return null; } } @@ -3490,11 +3552,11 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn if (objectInstance == null) { - string typeName = _objectType.FullName; + string typeName = _receiverType.FullName; // We don't want to expose the real type name of our intrinsics // so we'll replace it with "MSBuild" - if (_objectType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) + if (_receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) { typeName = "MSBuild"; } @@ -3531,39 +3593,23 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn } /// - /// For this initial implementation of inline functions, only very specific static methods on specific types are - /// available + /// Check the property function whitelist whether this method is available. /// - private bool IsStaticMethodAvailable(Type objectType, string methodName) + private static bool IsStaticMethodAvailable(Type receiverType, string methodName) { - if (objectType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) + if (receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions)) { // These are our intrinsic functions, so we're OK with those return true; } - else - { - string typeMethod = objectType.FullName + "::" + methodName; - if (AvailableStaticMethods.ContainsKey(objectType.FullName)) - { - // Check our set for the type name - // This enables all statics on the given type - return true; - } - else if (AvailableStaticMethods.ContainsKey(typeMethod)) - { - // Check for specific methods on types - return true; - } - else if (Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1") - { - // If MSBUILDENABLEALLPROPERTYFUNCTION == 1, then anything goes - return true; - } + if (Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1") + { + // If MSBUILDENABLEALLPROPERTYFUNCTION == 1, then anything goes + return true; } - return false; + return AvailableStaticMethods.GetTypeInformationFromTypeCache(receiverType.FullName, methodName) != null; } #if !FEATURE_TYPE_INVOKEMEMBER @@ -3601,16 +3647,16 @@ private object LateBindExecute(Exception ex, BindingFlags bindingFlags, object o if (isConstructor) { #if FEATURE_TYPE_INVOKEMEMBER - memberInfo = _objectType.GetConstructor(bindingFlags, null, types, null); + memberInfo = _receiverType.GetConstructor(bindingFlags, null, types, null); #else - memberInfo = _objectType.GetConstructors(bindingFlags) + memberInfo = _receiverType.GetConstructors(bindingFlags) .Where(c => ParametersBindToNStringArguments(c.GetParameters(), args.Length)) .FirstOrDefault(); #endif } else { - memberInfo = _objectType.GetMethod(_name, bindingFlags, null, types, null); + memberInfo = _receiverType.GetMethod(_methodMethodName, bindingFlags, null, types, null); } // If we didn't get a match on all string arguments, @@ -3620,11 +3666,11 @@ private object LateBindExecute(Exception ex, BindingFlags bindingFlags, object o // Gather all methods that may match if (isConstructor) { - members = _objectType.GetConstructors(bindingFlags); + members = _receiverType.GetConstructors(bindingFlags); } else { - members = _objectType.GetMethods(bindingFlags); + members = _receiverType.GetMethods(bindingFlags); } // Try to find a method with the right name, number of arguments and @@ -3637,7 +3683,7 @@ private object LateBindExecute(Exception ex, BindingFlags bindingFlags, object o // Simple match on name and number of params, we will be case insensitive if (parameters.Length == _arguments.Length) { - if (isConstructor || String.Equals(member.Name, _name, StringComparison.OrdinalIgnoreCase)) + if (isConstructor || String.Equals(member.Name, _methodMethodName, StringComparison.OrdinalIgnoreCase)) { // we have a match on the name and argument number // now let's try to coerce the arguments we have diff --git a/src/XMakeBuildEngine/Resources/Constants.cs b/src/XMakeBuildEngine/Resources/Constants.cs index d138ce930a1..66d1506b3cb 100644 --- a/src/XMakeBuildEngine/Resources/Constants.cs +++ b/src/XMakeBuildEngine/Resources/Constants.cs @@ -210,7 +210,7 @@ internal static long AssemblyTimestamp internal static class AvailableStaticMethods { /// - /// Static methods that are allowed in constants. Key = Type or Type::Method, Value = AssemblyQualifiedTypeName (where null = mscorlib) + /// Static methods that are allowed in constants. Key = Type or Type::Method, Value = Tuple of AssemblyQualifiedTypeName (where null = mscorlib) or the actual type object /// private static ConcurrentDictionary> s_availableStaticMethods; @@ -219,13 +219,16 @@ internal static class AvailableStaticMethods /// private static Object s_locker = new Object(); + static AvailableStaticMethods() + { + InitializeAvailableMethods(); + } + /// /// Whether a key is present /// internal static bool ContainsKey(string key) { - InitializeAvailableMethods(); - return s_availableStaticMethods.ContainsKey(key); } /// @@ -233,21 +236,68 @@ internal static bool ContainsKey(string key) /// internal static bool TryAdd(string key, Tuple value) { - InitializeAvailableMethods(); - return s_availableStaticMethods.TryAdd(key, value); } + /// + /// Constructs the fully qualified method name (:: and adds it to the cache) + /// + /// + /// + /// + /// + public static bool TryAdd(string typeFullName, string simpleMethodName, Tuple typeInformation) + { + return TryAdd(CreateQualifiedMethodName(typeFullName, simpleMethodName), typeInformation); + } + /// /// Get an entry if present /// internal static bool TryGetValue(string key, out Tuple value) { - InitializeAvailableMethods(); - return s_availableStaticMethods.TryGetValue(key, out value); } + /// + /// Get an entry or null if not present + /// + internal static Tuple GetValue(string key) + { + Tuple typeInformation; + return s_availableStaticMethods.TryGetValue(key, out typeInformation) ? typeInformation : null; + } + + /// + /// Tries to retrieve the type information for a type name / method name combination. + /// + /// It does 2 lookups: + /// 1st try: ; + /// 2nd try: :: + /// + /// + /// namespace qualified type name + /// name of the method + /// + internal static Tuple GetTypeInformationFromTypeCache(string typeFullName, string simpleMethodName) + { + return + GetValue(typeFullName) ?? + GetValue(CreateQualifiedMethodName(typeFullName, simpleMethodName)); + } + + /// + /// Returns the fully qualified format for the given method: :: + /// + /// namespace qualified type name + /// simple name of the method + /// + private static string CreateQualifiedMethodName(string typeFullName, string simpleMethodName) + { + return $"{typeFullName}::{simpleMethodName}"; + } + + /// /// Re-initialize. /// Unit tests need this when they enable "unsafe" methods -- which will then go in the collection, @@ -255,7 +305,11 @@ internal static bool TryGetValue(string key, out Tuple value) /// internal static void Reset_ForUnitTestsOnly() { - InitializeAvailableMethods(); + lock (s_locker) + { + s_availableStaticMethods = null; + InitializeAvailableMethods(); + } } /// @@ -279,98 +333,98 @@ private static void InitializeAvailableMethods() #endif // Make specific static methods available (Assembly qualified type names are *NOT* supported, only null which means mscorlib): - s_availableStaticMethods.TryAdd("System.Environment::ExpandEnvironmentVariables", environmentType); - s_availableStaticMethods.TryAdd("System.Environment::GetEnvironmentVariable", environmentType); - s_availableStaticMethods.TryAdd("System.Environment::GetEnvironmentVariables", environmentType); + TryAdd("System.Environment::ExpandEnvironmentVariables", environmentType); + TryAdd("System.Environment::GetEnvironmentVariable", environmentType); + TryAdd("System.Environment::GetEnvironmentVariables", environmentType); #if FEATURE_SPECIAL_FOLDERS - s_availableStaticMethods.TryAdd("System.Environment::GetFolderPath", environmentType); - s_availableStaticMethods.TryAdd("System.Environment::GetLogicalDrives", environmentType); + TryAdd("System.Environment::GetFolderPath", environmentType); + TryAdd("System.Environment::GetLogicalDrives", environmentType); #endif // All the following properties only have getters #if FEATURE_GET_COMMANDLINE - s_availableStaticMethods.TryAdd("System.Environment::CommandLine", environmentType); + TryAdd("System.Environment::CommandLine", environmentType); #endif #if FEATURE_64BIT_ENVIRONMENT_QUERY - s_availableStaticMethods.TryAdd("System.Environment::Is64BitOperatingSystem", environmentType); - s_availableStaticMethods.TryAdd("System.Environment::Is64BitProcess", environmentType); + TryAdd("System.Environment::Is64BitOperatingSystem", environmentType); + TryAdd("System.Environment::Is64BitProcess", environmentType); #endif - s_availableStaticMethods.TryAdd("System.Environment::MachineName", environmentType); + TryAdd("System.Environment::MachineName", environmentType); #if FEATURE_OSVERSION - s_availableStaticMethods.TryAdd("System.Environment::OSVersion", environmentType); + TryAdd("System.Environment::OSVersion", environmentType); #endif - s_availableStaticMethods.TryAdd("System.Environment::ProcessorCount", environmentType); - s_availableStaticMethods.TryAdd("System.Environment::StackTrace", environmentType); + TryAdd("System.Environment::ProcessorCount", environmentType); + TryAdd("System.Environment::StackTrace", environmentType); #if FEATURE_SPECIAL_FOLDERS - s_availableStaticMethods.TryAdd("System.Environment::SystemDirectory", environmentType); + TryAdd("System.Environment::SystemDirectory", environmentType); #endif #if FEATURE_SYSTEMPAGESIZE - s_availableStaticMethods.TryAdd("System.Environment::SystemPageSize", environmentType); + TryAdd("System.Environment::SystemPageSize", environmentType); #endif - s_availableStaticMethods.TryAdd("System.Environment::TickCount", environmentType); + TryAdd("System.Environment::TickCount", environmentType); #if FEATURE_USERDOMAINNAME - s_availableStaticMethods.TryAdd("System.Environment::UserDomainName", environmentType); + TryAdd("System.Environment::UserDomainName", environmentType); #endif #if FEATURE_USERINTERACTIVE - s_availableStaticMethods.TryAdd("System.Environment::UserInteractive", environmentType); + TryAdd("System.Environment::UserInteractive", environmentType); #endif - s_availableStaticMethods.TryAdd("System.Environment::UserName", environmentType); + TryAdd("System.Environment::UserName", environmentType); #if FEATURE_DOTNETVERSION - s_availableStaticMethods.TryAdd("System.Environment::Version", environmentType); + TryAdd("System.Environment::Version", environmentType); #endif #if FEATURE_WORKINGSET - s_availableStaticMethods.TryAdd("System.Environment::WorkingSet", environmentType); + TryAdd("System.Environment::WorkingSet", environmentType); #endif - s_availableStaticMethods.TryAdd("System.IO.Directory::GetDirectories", directoryType); - s_availableStaticMethods.TryAdd("System.IO.Directory::GetFiles", directoryType); - s_availableStaticMethods.TryAdd("System.IO.Directory::GetLastAccessTime", directoryType); - s_availableStaticMethods.TryAdd("System.IO.Directory::GetLastWriteTime", directoryType); - s_availableStaticMethods.TryAdd("System.IO.Directory::GetParent", directoryType); - s_availableStaticMethods.TryAdd("System.IO.File::Exists", fileType); - s_availableStaticMethods.TryAdd("System.IO.File::GetCreationTime", fileType); - s_availableStaticMethods.TryAdd("System.IO.File::GetAttributes", fileType); - s_availableStaticMethods.TryAdd("System.IO.File::GetLastAccessTime", fileType); - s_availableStaticMethods.TryAdd("System.IO.File::GetLastWriteTime", fileType); - s_availableStaticMethods.TryAdd("System.IO.File::ReadAllText", fileType); + TryAdd("System.IO.Directory::GetDirectories", directoryType); + TryAdd("System.IO.Directory::GetFiles", directoryType); + TryAdd("System.IO.Directory::GetLastAccessTime", directoryType); + TryAdd("System.IO.Directory::GetLastWriteTime", directoryType); + TryAdd("System.IO.Directory::GetParent", directoryType); + TryAdd("System.IO.File::Exists", fileType); + TryAdd("System.IO.File::GetCreationTime", fileType); + TryAdd("System.IO.File::GetAttributes", fileType); + TryAdd("System.IO.File::GetLastAccessTime", fileType); + TryAdd("System.IO.File::GetLastWriteTime", fileType); + TryAdd("System.IO.File::ReadAllText", fileType); #if FEATURE_CULTUREINFO_GETCULTUREINFO - s_availableStaticMethods.TryAdd("System.Globalization.CultureInfo::GetCultureInfo", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request + TryAdd("System.Globalization.CultureInfo::GetCultureInfo", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request #endif - s_availableStaticMethods.TryAdd("System.Globalization.CultureInfo::new", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request - s_availableStaticMethods.TryAdd("System.Globalization.CultureInfo::CurrentUICulture", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request + TryAdd("System.Globalization.CultureInfo::new", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request + TryAdd("System.Globalization.CultureInfo::CurrentUICulture", new Tuple(null, typeof(System.Globalization.CultureInfo))); // user request // All static methods of the following are available (Assembly qualified type names are supported): - s_availableStaticMethods.TryAdd("MSBuild", new Tuple(null, typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))); - s_availableStaticMethods.TryAdd("System.Byte", new Tuple(null, typeof(System.Byte))); - s_availableStaticMethods.TryAdd("System.Char", new Tuple(null, typeof(System.Char))); - s_availableStaticMethods.TryAdd("System.Convert", new Tuple(null, typeof(System.Convert))); - s_availableStaticMethods.TryAdd("System.DateTime", new Tuple(null, typeof(System.DateTime))); - s_availableStaticMethods.TryAdd("System.Decimal", new Tuple(null, typeof(System.Decimal))); - s_availableStaticMethods.TryAdd("System.Double", new Tuple(null, typeof(System.Double))); - s_availableStaticMethods.TryAdd("System.Enum", new Tuple(null, typeof(System.Enum))); - s_availableStaticMethods.TryAdd("System.Guid", new Tuple(null, typeof(System.Guid))); - s_availableStaticMethods.TryAdd("System.Int16", new Tuple(null, typeof(System.Int16))); - s_availableStaticMethods.TryAdd("System.Int32", new Tuple(null, typeof(System.Int32))); - s_availableStaticMethods.TryAdd("System.Int64", new Tuple(null, typeof(System.Int64))); - s_availableStaticMethods.TryAdd("System.IO.Path", new Tuple(null, typeof(System.IO.Path))); - s_availableStaticMethods.TryAdd("System.Math", new Tuple(null, typeof(System.Math))); - s_availableStaticMethods.TryAdd("System.UInt16", new Tuple(null, typeof(System.UInt16))); - s_availableStaticMethods.TryAdd("System.UInt32", new Tuple(null, typeof(System.UInt32))); - s_availableStaticMethods.TryAdd("System.UInt64", new Tuple(null, typeof(System.UInt64))); - s_availableStaticMethods.TryAdd("System.SByte", new Tuple(null, typeof(System.SByte))); - s_availableStaticMethods.TryAdd("System.Single", new Tuple(null, typeof(System.Single))); - s_availableStaticMethods.TryAdd("System.String", new Tuple(null, typeof(System.String))); - s_availableStaticMethods.TryAdd("System.StringComparer", new Tuple(null, typeof(System.StringComparer))); - s_availableStaticMethods.TryAdd("System.TimeSpan", new Tuple(null, typeof(System.TimeSpan))); - s_availableStaticMethods.TryAdd("System.Text.RegularExpressions.Regex", new Tuple(null, typeof(System.Text.RegularExpressions.Regex))); - s_availableStaticMethods.TryAdd("System.UriBuilder", new Tuple(null, typeof(System.UriBuilder))); - s_availableStaticMethods.TryAdd("System.Version", new Tuple(null, typeof(System.Version))); - s_availableStaticMethods.TryAdd("Microsoft.Build.Utilities.ToolLocationHelper", new Tuple("Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=" + MSBuildConstants.CurrentAssemblyVersion + ", Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", null)); + TryAdd("MSBuild", new Tuple(null, typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))); + TryAdd("System.Byte", new Tuple(null, typeof(System.Byte))); + TryAdd("System.Char", new Tuple(null, typeof(System.Char))); + TryAdd("System.Convert", new Tuple(null, typeof(System.Convert))); + TryAdd("System.DateTime", new Tuple(null, typeof(System.DateTime))); + TryAdd("System.Decimal", new Tuple(null, typeof(System.Decimal))); + TryAdd("System.Double", new Tuple(null, typeof(System.Double))); + TryAdd("System.Enum", new Tuple(null, typeof(System.Enum))); + TryAdd("System.Guid", new Tuple(null, typeof(System.Guid))); + TryAdd("System.Int16", new Tuple(null, typeof(System.Int16))); + TryAdd("System.Int32", new Tuple(null, typeof(System.Int32))); + TryAdd("System.Int64", new Tuple(null, typeof(System.Int64))); + TryAdd("System.IO.Path", new Tuple(null, typeof(System.IO.Path))); + TryAdd("System.Math", new Tuple(null, typeof(System.Math))); + TryAdd("System.UInt16", new Tuple(null, typeof(System.UInt16))); + TryAdd("System.UInt32", new Tuple(null, typeof(System.UInt32))); + TryAdd("System.UInt64", new Tuple(null, typeof(System.UInt64))); + TryAdd("System.SByte", new Tuple(null, typeof(System.SByte))); + TryAdd("System.Single", new Tuple(null, typeof(System.Single))); + TryAdd("System.String", new Tuple(null, typeof(System.String))); + TryAdd("System.StringComparer", new Tuple(null, typeof(System.StringComparer))); + TryAdd("System.TimeSpan", new Tuple(null, typeof(System.TimeSpan))); + TryAdd("System.Text.RegularExpressions.Regex", new Tuple(null, typeof(System.Text.RegularExpressions.Regex))); + TryAdd("System.UriBuilder", new Tuple(null, typeof(System.UriBuilder))); + TryAdd("System.Version", new Tuple(null, typeof(System.Version))); + TryAdd("Microsoft.Build.Utilities.ToolLocationHelper", new Tuple("Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=" + MSBuildConstants.CurrentAssemblyVersion + ", Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", null)); #if FEATURE_RUNTIMEINFORMATION - s_availableStaticMethods.TryAdd("System.Runtime.InteropServices.RuntimeInformation", runtimeInformationType); - s_availableStaticMethods.TryAdd("System.Runtime.InteropServices.OSPlatform", osPlatformType); + TryAdd("System.Runtime.InteropServices.RuntimeInformation", runtimeInformationType); + TryAdd("System.Runtime.InteropServices.OSPlatform", osPlatformType); #endif } }