in src/Build/Evaluation/Expander.cs [3006:3217]
internal object Execute(object objectInstance, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation)
{
object functionResult = String.Empty;
object[] args = null;
try
{
// If there is no object instance, then the method invocation will be a static
if (objectInstance == null)
{
// Check that the function that we're going to call is valid to call
if (!IsStaticMethodAvailable(_receiverType, _methodMethodName))
{
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 (_receiverType == typeof(Microsoft.Build.Evaluation.IntrinsicFunctions))
{
_bindingFlags |= BindingFlags.NonPublic;
}
}
else
{
_bindingFlags |= BindingFlags.Instance;
// The object that we're about to call methods on may have escaped characters
// in it, we want to operate on the unescaped string in the function, just as we
// want to pass arguments that are unescaped (see below)
if (objectInstance is string)
{
objectInstance = EscapingUtilities.UnescapeAll((string)objectInstance);
}
}
// We have a methodinfo match, need to plug in the arguments
args = new object[_arguments.Length];
// Assemble our arguments ready for passing to our method
for (int n = 0; n < _arguments.Length; n++)
{
object argument = PropertyExpander<T>.ExpandPropertiesLeaveTypedAndEscaped(_arguments[n], properties, options, elementLocation, _usedUninitializedProperties);
string argumentValue = argument as string;
if (argumentValue != null)
{
// 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 (_receiverType == typeof(System.IO.File) || _receiverType == typeof(System.IO.Directory)
|| _receiverType == typeof(System.IO.Path))
{
argumentValue = FileUtilities.FixFilePath(argumentValue);
}
args[n] = EscapingUtilities.UnescapeAll(argumentValue);
}
else
{
args[n] = argument;
}
}
// Handle special cases where the object type needs to affect the choice of method
// The default binder and method invoke, often chooses the incorrect Equals and CompareTo and
// fails the comparison, because what we have on the right is generally a string.
// 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", _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);
}
if (_receiverType == typeof(IntrinsicFunctions))
{
// Special case a few methods that take extra parameters that can't be passed in by the user
//
if (_methodMethodName.Equals("GetPathOfFileAbove") && args.Length == 1)
{
// Append the IElementLocation as a parameter to GetPathOfFileAbove if the user only
// specified the file name. This is syntactic sugar so they don't have to always
// include $(MSBuildThisFileDirectory) as a parameter.
//
string startingDirectory = String.IsNullOrWhiteSpace(elementLocation.File) ? String.Empty : Path.GetDirectoryName(elementLocation.File);
args = new []
{
args[0],
startingDirectory,
};
}
}
// If we've been asked to construct an instance, then we
// need to locate an appropriate constructor and invoke it
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 */);
}
else
{
// Execute the function given converted arguments
// The only exception that we should catch to try a late bind here is missing method
// otherwise there is the potential of running a function twice!
try
{
#if FEATURE_TYPE_INVOKEMEMBER
// First use InvokeMember using the standard binder - this will match and coerce as needed
functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, Type.DefaultBinder, objectInstance, args, CultureInfo.InvariantCulture);
#else
if (_invokeType == InvokeType.InvokeMethod)
{
functionResult = _receiverType.InvokeMember(_methodMethodName, _bindingFlags, objectInstance, args, null, CultureInfo.InvariantCulture, null);
}
else if (_invokeType == InvokeType.GetPropertyOrField)
{
MemberInfo memberInfo = BindFieldOrProperty();
if (memberInfo is FieldInfo)
{
functionResult = ((FieldInfo)memberInfo).GetValue(objectInstance);
}
else
{
functionResult = ((PropertyInfo)memberInfo).GetValue(objectInstance);
}
}
else
{
throw new InvalidOperationException(_invokeType.ToString());
}
#endif
}
catch (MissingMethodException ex) // Don't catch and retry on any other exception
{
// If we're invoking a method, then there are deeper attempts that
// can be made to invoke the method
#if FEATURE_TYPE_INVOKEMEMBER
if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
#else
if (_invokeType == InvokeType.InvokeMethod)
#endif
{
// The standard binder failed, so do our best to coerce types into the arguments for the function
// 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.
functionResult = LateBindExecute(ex, _bindingFlags, objectInstance, args, false /* is not constructor */);
}
else
{
// We were asked to get a property or field, and we found that we cannot
// locate it. Since there is no further argument coersion possible
// we'll throw right now.
throw;
}
}
}
// 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", _methodMethodName, StringComparison.OrdinalIgnoreCase) && !String.Equals("Escape", _methodMethodName, StringComparison.OrdinalIgnoreCase))
{
functionResult = EscapingUtilities.Escape((string)functionResult);
}
// We have nothing left to parse, so we'll return what we have
if (String.IsNullOrEmpty(_remainder))
{
return functionResult;
}
// Recursively expand the remaining property body after execution
return PropertyExpander<T>.ExpandPropertyBody(_remainder, functionResult, properties, options, elementLocation, _usedUninitializedProperties);
}
// Exceptions coming from the actual function called are wrapped in a TargetInvocationException
catch (TargetInvocationException ex)
{
// We ended up with something other than a function expression
string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args);
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.InnerException.Message.Replace("\r\n", " "));
return null;
}
// Any other exception was thrown by trying to call it
catch (Exception ex)
{
if (ExceptionHandling.NotExpectedFunctionException(ex))
{
throw;
}
// If there's a :: in the expression, they were probably trying for a static function
// invocation. Give them some more relevant info in that case
if (s_invariantCompareInfo.IndexOf(_expression, "::", CompareOptions.OrdinalIgnoreCase) > -1)
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionStaticMethodSyntax", _expression, ex.Message.Replace("Microsoft.Build.Evaluation.IntrinsicFunctions.", "[MSBuild]::"));
}
else
{
// We ended up with something other than a function expression
string partiallyEvaluated = GenerateStringOfMethodExecuted(_expression, objectInstance, _methodMethodName, args);
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidFunctionPropertyExpression", partiallyEvaluated, ex.Message);
}
return null;
}
}