You've already forked linux-packaging-mono
Rewrite with hard-coded offsets into the PE file format to discern if a binary is PE32 or PE32+, and then to determine if it contains a "CLR Data Directory" entry that looks valid. Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native binaries, and a random assortment of garbage files. Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
3898 lines
156 KiB
C#
3898 lines
156 KiB
C#
// ---------------------------------------------------------------------------
|
|
// Copyright (C) 2006 Microsoft Corporation All Rights Reserved
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define CODE_ANALYSIS
|
|
using System.CodeDom;
|
|
using System.Collections.Generic;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Workflow.ComponentModel;
|
|
using System.Workflow.ComponentModel.Compiler;
|
|
using System.Workflow.Activities.Common;
|
|
|
|
namespace System.Workflow.Activities.Rules
|
|
{
|
|
#region ExpressionInfo
|
|
|
|
// Public base class (which just holds the Type of the expression).
|
|
public class RuleExpressionInfo
|
|
{
|
|
private Type expressionType;
|
|
|
|
public RuleExpressionInfo(Type expressionType)
|
|
{
|
|
this.expressionType = expressionType;
|
|
}
|
|
|
|
public Type ExpressionType
|
|
{
|
|
get { return expressionType; }
|
|
}
|
|
}
|
|
|
|
// Internal derivation for CodeMethodInvokeExpression
|
|
internal class RuleMethodInvokeExpressionInfo : RuleExpressionInfo
|
|
{
|
|
private MethodInfo methodInfo;
|
|
private bool needsParamsExpansion;
|
|
|
|
internal RuleMethodInvokeExpressionInfo(MethodInfo mi, bool needsParamsExpansion)
|
|
: base(mi.ReturnType)
|
|
{
|
|
this.methodInfo = mi;
|
|
this.needsParamsExpansion = needsParamsExpansion;
|
|
}
|
|
|
|
internal MethodInfo MethodInfo
|
|
{
|
|
get { return methodInfo; }
|
|
}
|
|
|
|
internal bool NeedsParamsExpansion
|
|
{
|
|
get { return needsParamsExpansion; }
|
|
}
|
|
}
|
|
|
|
// Internal derivation for CodeBinaryExpression
|
|
internal class RuleBinaryExpressionInfo : RuleExpressionInfo
|
|
{
|
|
private Type leftType;
|
|
private Type rightType;
|
|
private MethodInfo methodInfo;
|
|
|
|
// no overridden method needed
|
|
internal RuleBinaryExpressionInfo(Type lhsType, Type rhsType, Type resultType)
|
|
: base(resultType)
|
|
{
|
|
this.leftType = lhsType;
|
|
this.rightType = rhsType;
|
|
}
|
|
|
|
// overridden method found
|
|
internal RuleBinaryExpressionInfo(Type lhsType, Type rhsType, MethodInfo mi)
|
|
: base(mi.ReturnType)
|
|
{
|
|
this.leftType = lhsType;
|
|
this.rightType = rhsType;
|
|
this.methodInfo = mi;
|
|
}
|
|
|
|
internal Type LeftType
|
|
{
|
|
get { return leftType; }
|
|
}
|
|
|
|
internal Type RightType
|
|
{
|
|
get { return rightType; }
|
|
}
|
|
|
|
internal MethodInfo MethodInfo
|
|
{
|
|
get { return methodInfo; }
|
|
}
|
|
}
|
|
|
|
// Internal derivation for CodeFieldReferenceExpression
|
|
internal class RuleFieldExpressionInfo : RuleExpressionInfo
|
|
{
|
|
private FieldInfo fieldInfo;
|
|
|
|
internal RuleFieldExpressionInfo(FieldInfo fi)
|
|
: base(fi.FieldType)
|
|
{
|
|
fieldInfo = fi;
|
|
}
|
|
|
|
internal FieldInfo FieldInfo
|
|
{
|
|
get { return fieldInfo; }
|
|
}
|
|
}
|
|
|
|
// Internal derivation for CodePropertyReferenceExpression
|
|
internal class RulePropertyExpressionInfo : RuleExpressionInfo
|
|
{
|
|
private PropertyInfo propertyInfo;
|
|
private bool needsParamsExpansion;
|
|
|
|
// Note that the type pi.PropertyType may differ from the "exprType" argument if this
|
|
// property is a Bind.
|
|
internal RulePropertyExpressionInfo(PropertyInfo pi, Type exprType, bool needsParamsExpansion)
|
|
: base(exprType)
|
|
{
|
|
this.propertyInfo = pi;
|
|
this.needsParamsExpansion = needsParamsExpansion;
|
|
}
|
|
|
|
internal PropertyInfo PropertyInfo
|
|
{
|
|
get { return propertyInfo; }
|
|
}
|
|
|
|
internal bool NeedsParamsExpansion
|
|
{
|
|
get { return needsParamsExpansion; }
|
|
}
|
|
}
|
|
|
|
// Internal derivation for CodeMethodInvokeExpression
|
|
internal class RuleConstructorExpressionInfo : RuleExpressionInfo
|
|
{
|
|
private ConstructorInfo constructorInfo;
|
|
private bool needsParamsExpansion;
|
|
|
|
internal RuleConstructorExpressionInfo(ConstructorInfo ci, bool needsParamsExpansion)
|
|
: base(ci.DeclaringType)
|
|
{
|
|
this.constructorInfo = ci;
|
|
this.needsParamsExpansion = needsParamsExpansion;
|
|
}
|
|
|
|
internal ConstructorInfo ConstructorInfo
|
|
{
|
|
get { return constructorInfo; }
|
|
}
|
|
|
|
internal bool NeedsParamsExpansion
|
|
{
|
|
get { return needsParamsExpansion; }
|
|
}
|
|
}
|
|
|
|
internal class ExtensionMethodInfo : MethodInfo
|
|
{
|
|
MethodInfo actualMethod;
|
|
int actualParameterLength;
|
|
ParameterInfo[] expectedParameters;
|
|
Type assumedDeclaringType;
|
|
bool hasOutOrRefParameters = false;
|
|
|
|
public ExtensionMethodInfo(MethodInfo method, ParameterInfo[] actualParameters)
|
|
: base()
|
|
{
|
|
Debug.Assert(method.IsStatic, "Expected static method as an extension method");
|
|
|
|
actualMethod = method;
|
|
// modify parameters
|
|
actualParameterLength = actualParameters.Length;
|
|
if (actualParameterLength < 2)
|
|
expectedParameters = new ParameterInfo[0];
|
|
else
|
|
{
|
|
expectedParameters = new ParameterInfo[actualParameterLength - 1];
|
|
Array.Copy(actualParameters, 1, expectedParameters, 0, actualParameterLength - 1);
|
|
foreach (ParameterInfo pi in expectedParameters)
|
|
{
|
|
if (pi.ParameterType.IsByRef)
|
|
hasOutOrRefParameters = true;
|
|
}
|
|
}
|
|
// get the type we pretend this method is on (which happens to be the first actual parameter)
|
|
assumedDeclaringType = actualParameters[0].ParameterType;
|
|
}
|
|
|
|
public override MethodInfo GetBaseDefinition()
|
|
{
|
|
return actualMethod.GetBaseDefinition();
|
|
}
|
|
|
|
public override ICustomAttributeProvider ReturnTypeCustomAttributes
|
|
{
|
|
get { return actualMethod.ReturnTypeCustomAttributes; }
|
|
}
|
|
|
|
public override MethodAttributes Attributes
|
|
{
|
|
get { return actualMethod.Attributes & ~MethodAttributes.Static; }
|
|
}
|
|
|
|
public override MethodImplAttributes GetMethodImplementationFlags()
|
|
{
|
|
return actualMethod.GetMethodImplementationFlags();
|
|
}
|
|
|
|
public override ParameterInfo[] GetParameters()
|
|
{
|
|
return expectedParameters;
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
object[] actualParameters = new object[actualParameterLength];
|
|
if (actualParameterLength > 1)
|
|
Array.Copy(parameters, 0, actualParameters, 1, actualParameterLength - 1);
|
|
if (obj == null)
|
|
actualParameters[0] = null;
|
|
else
|
|
actualParameters[0] = Executor.AdjustType(obj.GetType(), obj, assumedDeclaringType);
|
|
object result = actualMethod.Invoke(null, invokeAttr, binder, actualParameters, culture);
|
|
// may be out/ref parameters, so copy back the results
|
|
if (hasOutOrRefParameters)
|
|
Array.Copy(actualParameters, 1, parameters, 0, actualParameterLength - 1);
|
|
return result;
|
|
}
|
|
|
|
public override RuntimeMethodHandle MethodHandle
|
|
{
|
|
get { return actualMethod.MethodHandle; }
|
|
}
|
|
|
|
public override Type DeclaringType
|
|
{
|
|
get { return actualMethod.DeclaringType; }
|
|
}
|
|
|
|
public Type AssumedDeclaringType
|
|
{
|
|
get { return assumedDeclaringType; }
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
|
{
|
|
return actualMethod.GetCustomAttributes(attributeType, inherit);
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(bool inherit)
|
|
{
|
|
return actualMethod.GetCustomAttributes(inherit);
|
|
}
|
|
|
|
public override bool IsDefined(Type attributeType, bool inherit)
|
|
{
|
|
return actualMethod.IsDefined(attributeType, inherit);
|
|
}
|
|
|
|
public override string Name
|
|
{
|
|
get { return actualMethod.Name; }
|
|
}
|
|
|
|
public override Type ReflectedType
|
|
{
|
|
get { return actualMethod.ReflectedType; }
|
|
}
|
|
|
|
public override Type ReturnType
|
|
{
|
|
get { return actualMethod.ReturnType; }
|
|
}
|
|
}
|
|
|
|
internal class SimpleParameterInfo : ParameterInfo
|
|
{
|
|
// only thing we look at is ParameterType, so no need to override anything else
|
|
Type parameterType;
|
|
|
|
public SimpleParameterInfo(ParameterInfo parameter)
|
|
: base()
|
|
{
|
|
parameterType = typeof(Nullable<>).MakeGenericType(parameter.ParameterType);
|
|
}
|
|
|
|
public SimpleParameterInfo(Type parameter)
|
|
: base()
|
|
{
|
|
parameterType = parameter;
|
|
}
|
|
|
|
public override Type ParameterType
|
|
{
|
|
get
|
|
{
|
|
return parameterType;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal abstract class BaseMethodInfo : MethodInfo
|
|
{
|
|
protected MethodInfo actualMethod;
|
|
protected ParameterInfo[] expectedParameters;
|
|
protected Type resultType;
|
|
|
|
public BaseMethodInfo(MethodInfo method)
|
|
: base()
|
|
{
|
|
Debug.Assert(method.IsStatic, "Expected static method as an lifted method");
|
|
actualMethod = method;
|
|
resultType = method.ReturnType;
|
|
expectedParameters = method.GetParameters();
|
|
}
|
|
|
|
public override MethodInfo GetBaseDefinition()
|
|
{
|
|
return actualMethod.GetBaseDefinition();
|
|
}
|
|
|
|
public override ICustomAttributeProvider ReturnTypeCustomAttributes
|
|
{
|
|
get { return actualMethod.ReturnTypeCustomAttributes; }
|
|
}
|
|
|
|
public override MethodAttributes Attributes
|
|
{
|
|
get { return actualMethod.Attributes & ~MethodAttributes.Static; }
|
|
}
|
|
|
|
public override MethodImplAttributes GetMethodImplementationFlags()
|
|
{
|
|
return actualMethod.GetMethodImplementationFlags();
|
|
}
|
|
|
|
public override ParameterInfo[] GetParameters()
|
|
{
|
|
return expectedParameters;
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override RuntimeMethodHandle MethodHandle
|
|
{
|
|
get { return actualMethod.MethodHandle; }
|
|
}
|
|
|
|
public override Type DeclaringType
|
|
{
|
|
get { return actualMethod.DeclaringType; }
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
|
{
|
|
return actualMethod.GetCustomAttributes(attributeType, inherit);
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(bool inherit)
|
|
{
|
|
return actualMethod.GetCustomAttributes(inherit);
|
|
}
|
|
|
|
public override bool IsDefined(Type attributeType, bool inherit)
|
|
{
|
|
return actualMethod.IsDefined(attributeType, inherit);
|
|
}
|
|
|
|
public override string Name
|
|
{
|
|
get { return actualMethod.Name; }
|
|
}
|
|
|
|
public override Type ReflectedType
|
|
{
|
|
get { return actualMethod.ReflectedType; }
|
|
}
|
|
|
|
public override Type ReturnType
|
|
{
|
|
get { return resultType; }
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
BaseMethodInfo other = obj as BaseMethodInfo;
|
|
if ((other == null)
|
|
|| (actualMethod != other.actualMethod)
|
|
|| (resultType != other.resultType)
|
|
|| (expectedParameters.Length != other.expectedParameters.Length))
|
|
return false;
|
|
for (int i = 0; i < expectedParameters.Length; ++i)
|
|
if (expectedParameters[i].ParameterType != other.expectedParameters[i].ParameterType)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
int result = actualMethod.GetHashCode() ^ resultType.GetHashCode();
|
|
for (int i = 0; i < expectedParameters.Length; ++i)
|
|
result ^= expectedParameters[i].GetHashCode();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
internal class LiftedConversionMethodInfo : BaseMethodInfo
|
|
{
|
|
public LiftedConversionMethodInfo(MethodInfo method)
|
|
: base(method)
|
|
{
|
|
Debug.Assert(expectedParameters.Length == 1, "not 1 parameters");
|
|
|
|
// modify result
|
|
resultType = typeof(Nullable<>).MakeGenericType(method.ReturnType);
|
|
|
|
// modify parameter (exactly 1)
|
|
ParameterInfo[] actualParameters = method.GetParameters();
|
|
expectedParameters = new ParameterInfo[1];
|
|
expectedParameters[0] = new SimpleParameterInfo(actualParameters[0]);
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
// null in, then result is null
|
|
if (parameters[0] == null)
|
|
return Activator.CreateInstance(resultType);
|
|
|
|
// invoke the conversion from S -> T
|
|
object result = actualMethod.Invoke(null, invokeAttr, binder, parameters, culture);
|
|
// return a T?
|
|
return Executor.AdjustType(actualMethod.ReturnType, result, resultType);
|
|
}
|
|
}
|
|
|
|
internal class LiftedArithmeticOperatorMethodInfo : BaseMethodInfo
|
|
{
|
|
public LiftedArithmeticOperatorMethodInfo(MethodInfo method)
|
|
: base(method)
|
|
{
|
|
Debug.Assert(expectedParameters.Length == 2, "not 2 parameters");
|
|
|
|
// modify parameters (exactly 2, both need to be lifted)
|
|
ParameterInfo[] actualParameters = method.GetParameters();
|
|
expectedParameters = new ParameterInfo[2];
|
|
expectedParameters[0] = new SimpleParameterInfo(actualParameters[0]);
|
|
expectedParameters[1] = new SimpleParameterInfo(actualParameters[1]);
|
|
|
|
// modify result
|
|
resultType = typeof(Nullable<>).MakeGenericType(method.ReturnType);
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
// null in, then result is null
|
|
if (parameters[0] == null)
|
|
return null;
|
|
if (parameters[1] == null)
|
|
return null;
|
|
|
|
// apply the underlying operator
|
|
object result = actualMethod.Invoke(null, invokeAttr, binder, parameters, culture);
|
|
// return a T?
|
|
return Executor.AdjustType(actualMethod.ReturnType, result, resultType);
|
|
}
|
|
}
|
|
|
|
internal class LiftedEqualityOperatorMethodInfo : BaseMethodInfo
|
|
{
|
|
public LiftedEqualityOperatorMethodInfo(MethodInfo method)
|
|
: base(method)
|
|
{
|
|
Debug.Assert(method.ReturnType == typeof(bool), "not a bool result");
|
|
Debug.Assert(expectedParameters.Length == 2, "not 2 parameters");
|
|
|
|
// modify parameters (exactly 2, both need to be lifted)
|
|
ParameterInfo[] actualParameters = method.GetParameters();
|
|
expectedParameters = new ParameterInfo[2];
|
|
expectedParameters[0] = new SimpleParameterInfo(actualParameters[0]);
|
|
expectedParameters[1] = new SimpleParameterInfo(actualParameters[1]);
|
|
|
|
// set the result type
|
|
resultType = typeof(bool);
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
// null == null is true, null == something else is false, else call method
|
|
if (parameters[0] == null)
|
|
return (parameters[1] == null);
|
|
else if (parameters[1] == null)
|
|
return false;
|
|
|
|
// invoke the actual comparison (parameters are unwrapped)
|
|
return actualMethod.Invoke(null, invokeAttr, binder, parameters, culture);
|
|
}
|
|
}
|
|
|
|
internal class LiftedRelationalOperatorMethodInfo : BaseMethodInfo
|
|
{
|
|
public LiftedRelationalOperatorMethodInfo(MethodInfo method)
|
|
: base(method)
|
|
{
|
|
Debug.Assert(method.ReturnType == typeof(bool), "not a bool result");
|
|
Debug.Assert(expectedParameters.Length == 2, "not 2 parameters");
|
|
|
|
// modify parameters (exactly 2, both need to be lifted)
|
|
ParameterInfo[] actualParameters = method.GetParameters();
|
|
expectedParameters = new ParameterInfo[2];
|
|
expectedParameters[0] = new SimpleParameterInfo(actualParameters[0]);
|
|
expectedParameters[1] = new SimpleParameterInfo(actualParameters[1]);
|
|
|
|
// set the result type
|
|
resultType = typeof(bool);
|
|
}
|
|
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
// if either parameter is null, then result is false
|
|
if (parameters[0] == null)
|
|
return false;
|
|
if (parameters[1] == null)
|
|
return false;
|
|
|
|
// invoke the actual comparison (parameters are unwrapped)
|
|
return actualMethod.Invoke(null, invokeAttr, binder, parameters, culture);
|
|
}
|
|
}
|
|
|
|
internal class EnumOperationMethodInfo : MethodInfo
|
|
{
|
|
CodeBinaryOperatorType op;
|
|
ParameterInfo[] expectedParameters;
|
|
Type resultType; // may be nullable, enum, or value type
|
|
bool resultIsNullable; // true if resultType is nullable
|
|
|
|
Type lhsBaseType; // non-Nullable, may be enum
|
|
Type rhsBaseType;
|
|
Type resultBaseType;
|
|
|
|
Type lhsRootType; // underlying type (int, long, ushort, etc)
|
|
Type rhsRootType;
|
|
Type resultRootType;
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
public EnumOperationMethodInfo(Type lhs, CodeBinaryOperatorType operation, Type rhs, bool isZero)
|
|
{
|
|
// only 5 arithmetic cases (U = underlying type of E):
|
|
// E = E + U
|
|
// E = U + E
|
|
// U = E - E
|
|
// E = E - U
|
|
// E = U - E
|
|
// plus 5 comparison cases
|
|
// E == E
|
|
// E < E
|
|
// E <= E
|
|
// E > E
|
|
// E >= E
|
|
// either E can be nullable
|
|
|
|
op = operation;
|
|
|
|
// parameters are easy -- they are the same as the type passed in
|
|
expectedParameters = new ParameterInfo[2];
|
|
expectedParameters[0] = new SimpleParameterInfo(lhs);
|
|
expectedParameters[1] = new SimpleParameterInfo(rhs);
|
|
|
|
// compute return type (depends on type of operation)
|
|
// start by getting the types without Nullable<>
|
|
bool lhsNullable = ConditionHelper.IsNullableValueType(lhs);
|
|
bool rhsNullable = ConditionHelper.IsNullableValueType(rhs);
|
|
lhsBaseType = (lhsNullable) ? Nullable.GetUnderlyingType(lhs) : lhs;
|
|
rhsBaseType = (rhsNullable) ? Nullable.GetUnderlyingType(rhs) : rhs;
|
|
// determine the underlying types for both sides
|
|
if (lhsBaseType.IsEnum)
|
|
lhsRootType = EnumHelper.GetUnderlyingType(lhsBaseType);
|
|
else
|
|
lhsRootType = lhsBaseType;
|
|
|
|
if (rhsBaseType.IsEnum)
|
|
rhsRootType = EnumHelper.GetUnderlyingType(rhsBaseType);
|
|
else
|
|
rhsRootType = rhsBaseType;
|
|
|
|
switch (op)
|
|
{
|
|
case CodeBinaryOperatorType.Add:
|
|
// add always produces an enum, except enum + enum
|
|
if ((lhsBaseType.IsEnum) && (rhs.IsEnum))
|
|
resultBaseType = lhsRootType;
|
|
else if (lhsBaseType.IsEnum)
|
|
resultBaseType = lhsBaseType;
|
|
else
|
|
resultBaseType = rhsBaseType;
|
|
// if either side is nullable, result is nullable
|
|
resultIsNullable = (lhsNullable || rhsNullable);
|
|
resultType = (resultIsNullable) ? typeof(Nullable<>).MakeGenericType(resultBaseType) : resultBaseType;
|
|
break;
|
|
case CodeBinaryOperatorType.Subtract:
|
|
// subtract can be an enum or the underlying type
|
|
if (rhsBaseType.IsEnum && lhsBaseType.IsEnum)
|
|
{
|
|
resultRootType = rhsRootType;
|
|
resultBaseType = rhsRootType;
|
|
}
|
|
else if (lhsBaseType.IsEnum)
|
|
{
|
|
// special case for E - 0
|
|
// if 0 is the underlying type, then use E - U
|
|
// if not the underlying type, then 0 becomes E, use E - E
|
|
resultRootType = lhsRootType;
|
|
if (isZero && rhsBaseType != lhsRootType)
|
|
resultBaseType = lhsRootType;
|
|
else
|
|
resultBaseType = lhsBaseType;
|
|
}
|
|
else // rhsType.IsEnum
|
|
{
|
|
// special case for 0 - E
|
|
// in all cases 0 becomes E, use E - E
|
|
resultRootType = rhsRootType;
|
|
if (isZero)
|
|
resultBaseType = rhsRootType;
|
|
else
|
|
resultBaseType = rhsBaseType;
|
|
}
|
|
resultIsNullable = (lhsNullable || rhsNullable);
|
|
resultType = (resultIsNullable) ? typeof(Nullable<>).MakeGenericType(resultBaseType) : resultBaseType;
|
|
break;
|
|
case CodeBinaryOperatorType.ValueEquality:
|
|
case CodeBinaryOperatorType.LessThan:
|
|
case CodeBinaryOperatorType.LessThanOrEqual:
|
|
case CodeBinaryOperatorType.GreaterThan:
|
|
case CodeBinaryOperatorType.GreaterThanOrEqual:
|
|
resultType = typeof(bool);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override MethodInfo GetBaseDefinition()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public override ICustomAttributeProvider ReturnTypeCustomAttributes
|
|
{
|
|
get { return null; }
|
|
}
|
|
|
|
public override MethodAttributes Attributes
|
|
{
|
|
get { return MethodAttributes.Static; }
|
|
}
|
|
|
|
public override MethodImplAttributes GetMethodImplementationFlags()
|
|
{
|
|
return MethodImplAttributes.Runtime;
|
|
}
|
|
|
|
public override ParameterInfo[] GetParameters()
|
|
{
|
|
return expectedParameters;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Performance", "CA1803:AvoidCostlyCallsWherePossible")]
|
|
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
|
|
{
|
|
// we should get passed in 2 values that correspond to the parameter types
|
|
|
|
object result;
|
|
ArithmeticLiteral leftArithmetic, rightArithmetic;
|
|
Literal leftLiteral, rightLiteral;
|
|
|
|
// for design-time types we couldn't find the underlying type, so do it now
|
|
if (lhsRootType == null)
|
|
lhsRootType = Enum.GetUnderlyingType(lhsBaseType);
|
|
if (rhsRootType == null)
|
|
rhsRootType = Enum.GetUnderlyingType(rhsBaseType);
|
|
|
|
switch (op)
|
|
{
|
|
case CodeBinaryOperatorType.Add:
|
|
// if either is null, then the result is null
|
|
if ((parameters[0] == null) || (parameters[1] == null))
|
|
return null;
|
|
leftArithmetic = ArithmeticLiteral.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightArithmetic = ArithmeticLiteral.MakeLiteral(rhsRootType, parameters[1]);
|
|
result = leftArithmetic.Add(rightArithmetic);
|
|
result = Executor.AdjustType(result.GetType(), result, resultBaseType);
|
|
if (resultIsNullable)
|
|
result = Activator.CreateInstance(resultType, result);
|
|
return result;
|
|
case CodeBinaryOperatorType.Subtract:
|
|
// if either is null, then the result is null
|
|
if ((parameters[0] == null) || (parameters[1] == null))
|
|
return null;
|
|
leftArithmetic = ArithmeticLiteral.MakeLiteral(resultRootType,
|
|
Executor.AdjustType(lhsRootType, parameters[0], resultRootType));
|
|
rightArithmetic = ArithmeticLiteral.MakeLiteral(resultRootType,
|
|
Executor.AdjustType(rhsRootType, parameters[1], resultRootType));
|
|
result = leftArithmetic.Subtract(rightArithmetic);
|
|
result = Executor.AdjustType(result.GetType(), result, resultBaseType);
|
|
if (resultIsNullable)
|
|
result = Activator.CreateInstance(resultType, result);
|
|
return result;
|
|
|
|
case CodeBinaryOperatorType.ValueEquality:
|
|
leftLiteral = Literal.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightLiteral = Literal.MakeLiteral(rhsRootType, parameters[1]);
|
|
return leftLiteral.Equal(rightLiteral);
|
|
case CodeBinaryOperatorType.LessThan:
|
|
leftLiteral = Literal.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightLiteral = Literal.MakeLiteral(rhsRootType, parameters[1]);
|
|
return leftLiteral.LessThan(rightLiteral);
|
|
case CodeBinaryOperatorType.LessThanOrEqual:
|
|
leftLiteral = Literal.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightLiteral = Literal.MakeLiteral(rhsRootType, parameters[1]);
|
|
return leftLiteral.LessThanOrEqual(rightLiteral);
|
|
case CodeBinaryOperatorType.GreaterThan:
|
|
leftLiteral = Literal.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightLiteral = Literal.MakeLiteral(rhsRootType, parameters[1]);
|
|
return leftLiteral.GreaterThan(rightLiteral);
|
|
case CodeBinaryOperatorType.GreaterThanOrEqual:
|
|
leftLiteral = Literal.MakeLiteral(lhsRootType, parameters[0]);
|
|
rightLiteral = Literal.MakeLiteral(rhsRootType, parameters[1]);
|
|
return leftLiteral.GreaterThanOrEqual(rightLiteral);
|
|
}
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.BinaryOpNotSupported, op.ToString());
|
|
throw new RuleEvaluationException(message);
|
|
}
|
|
|
|
public override RuntimeMethodHandle MethodHandle
|
|
{
|
|
get { return new RuntimeMethodHandle(); }
|
|
}
|
|
|
|
public override Type DeclaringType
|
|
{
|
|
get { return typeof(Enum); }
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
|
{
|
|
return new object[0];
|
|
}
|
|
|
|
public override object[] GetCustomAttributes(bool inherit)
|
|
{
|
|
return new object[0];
|
|
}
|
|
|
|
public override bool IsDefined(Type attributeType, bool inherit)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override string Name
|
|
{
|
|
get { return "op_Enum"; }
|
|
}
|
|
|
|
public override Type ReflectedType
|
|
{
|
|
get { return resultType; }
|
|
}
|
|
|
|
public override Type ReturnType
|
|
{
|
|
get { return resultType; }
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region SimpleRunTimeTypeProvider
|
|
|
|
internal class SimpleRunTimeTypeProvider : ITypeProvider
|
|
{
|
|
private Assembly root;
|
|
private List<Assembly> references;
|
|
|
|
internal SimpleRunTimeTypeProvider(Assembly startingAssembly)
|
|
{
|
|
root = startingAssembly;
|
|
}
|
|
|
|
public Type GetType(string name)
|
|
{
|
|
return GetType(name, false);
|
|
}
|
|
|
|
public Type GetType(string name, bool throwOnError)
|
|
{
|
|
// is the type available in the main workflow assembly?
|
|
Type type = root.GetType(name, throwOnError, false);
|
|
if (type != null)
|
|
return type;
|
|
|
|
// now try mscorlib or this assembly
|
|
// (or if the name is an assembly qualified name)
|
|
type = Type.GetType(name, throwOnError, false);
|
|
if (type != null)
|
|
return type;
|
|
|
|
// no luck so far, so try all referenced assemblies
|
|
foreach (Assembly a in ReferencedAssemblies)
|
|
{
|
|
type = a.GetType(name, throwOnError, false);
|
|
if (type != null)
|
|
return type;
|
|
}
|
|
|
|
// keep going by trying all loaded assemblies
|
|
Assembly[] loaded = AppDomain.CurrentDomain.GetAssemblies();
|
|
for (int i = 0; i < loaded.Length; ++i)
|
|
{
|
|
type = loaded[i].GetType(name, throwOnError, false);
|
|
if (type != null)
|
|
return type;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Type[] GetTypes()
|
|
{
|
|
List<Type> types = new List<Type>();
|
|
try
|
|
{
|
|
types.AddRange(root.GetTypes());
|
|
}
|
|
catch (ReflectionTypeLoadException e)
|
|
{
|
|
// problems loading all the types, take what we can get
|
|
foreach (Type type in e.Types)
|
|
if (type != null)
|
|
types.Add(type);
|
|
}
|
|
foreach (Assembly a in ReferencedAssemblies)
|
|
{
|
|
try
|
|
{
|
|
types.AddRange(a.GetTypes());
|
|
}
|
|
catch (ReflectionTypeLoadException e)
|
|
{
|
|
// problems loading all the types, take what we can get
|
|
foreach (Type type in e.Types)
|
|
if (type != null)
|
|
types.Add(type);
|
|
}
|
|
}
|
|
return types.ToArray();
|
|
}
|
|
|
|
public Assembly LocalAssembly
|
|
{
|
|
get { return root; }
|
|
}
|
|
|
|
public ICollection<Assembly> ReferencedAssemblies
|
|
{
|
|
get
|
|
{
|
|
// references is created on demand, does not include root
|
|
if (references == null)
|
|
{
|
|
List<Assembly> list = new List<Assembly>();
|
|
foreach (AssemblyName a in root.GetReferencedAssemblies())
|
|
{
|
|
list.Add(Assembly.Load(a));
|
|
}
|
|
references = list;
|
|
}
|
|
return references;
|
|
}
|
|
}
|
|
|
|
public IDictionary<object, Exception> TypeLoadErrors
|
|
{
|
|
get
|
|
{
|
|
// we never use this method, so add use of EventHandlers to keep compiler happy
|
|
TypesChanged.Invoke(this, null);
|
|
TypeLoadErrorsChanged.Invoke(this, null);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public event EventHandler TypesChanged;
|
|
|
|
public event EventHandler TypeLoadErrorsChanged;
|
|
}
|
|
#endregion
|
|
|
|
#region RuleValidation
|
|
|
|
public class RuleValidation
|
|
{
|
|
private Type thisType;
|
|
private ITypeProvider typeProvider;
|
|
private ValidationErrorCollection errors = new ValidationErrorCollection();
|
|
private Dictionary<string, Type> typesUsed = new Dictionary<string, Type>(16);
|
|
private Dictionary<string, Type> typesUsedAuthorized;
|
|
private Stack<CodeExpression> activeParentNodes = new Stack<CodeExpression>();
|
|
private Dictionary<CodeExpression, RuleExpressionInfo> expressionInfoMap = new Dictionary<CodeExpression, RuleExpressionInfo>();
|
|
private Dictionary<CodeTypeReference, Type> typeRefMap = new Dictionary<CodeTypeReference, Type>();
|
|
private bool checkStaticType;
|
|
private IList<AuthorizedType> authorizedTypes;
|
|
private static readonly Type voidType = typeof(void);
|
|
private static string voidTypeName = voidType.AssemblyQualifiedName;
|
|
|
|
#region Constructors
|
|
|
|
// Validate at design time.
|
|
public RuleValidation(Activity activity, ITypeProvider typeProvider, bool checkStaticType)
|
|
{
|
|
if (activity == null)
|
|
throw new ArgumentNullException("activity");
|
|
if (typeProvider == null)
|
|
throw new ArgumentNullException("typeProvider");
|
|
|
|
this.thisType = ConditionHelper.GetContextType(typeProvider, activity);
|
|
this.typeProvider = typeProvider;
|
|
this.checkStaticType = checkStaticType;
|
|
if (checkStaticType)
|
|
{
|
|
Debug.Assert(WorkflowCompilationContext.Current != null, "Can't have checkTypes set to true without a context in scope");
|
|
this.authorizedTypes = WorkflowCompilationContext.Current.GetAuthorizedTypes();
|
|
this.typesUsedAuthorized = new Dictionary<string, Type>();
|
|
this.typesUsedAuthorized.Add(voidTypeName, voidType);
|
|
}
|
|
}
|
|
|
|
// Validate at runtime when we have the actual subject instance. This is
|
|
// mostly for conditions used in activities like IfElse.
|
|
internal RuleValidation(object thisObject)
|
|
{
|
|
if (thisObject == null)
|
|
throw new ArgumentNullException("thisObject");
|
|
|
|
this.thisType = thisObject.GetType();
|
|
this.typeProvider = new SimpleRunTimeTypeProvider(this.thisType.Assembly);
|
|
}
|
|
|
|
// Validate at runtime when we have just the type. This is mostly for rules.
|
|
public RuleValidation(Type thisType, ITypeProvider typeProvider)
|
|
{
|
|
if (thisType == null)
|
|
throw new ArgumentNullException("thisType");
|
|
|
|
this.thisType = thisType;
|
|
this.typeProvider = (typeProvider != null) ? typeProvider : new SimpleRunTimeTypeProvider(this.thisType.Assembly);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal validation methods
|
|
|
|
internal bool ValidateConditionExpression(CodeExpression expression)
|
|
{
|
|
if (expression == null)
|
|
throw new ArgumentNullException("expression");
|
|
|
|
// Run the validation pass.
|
|
RuleExpressionInfo exprInfo = RuleExpressionWalker.Validate(this, expression, false);
|
|
if (exprInfo == null)
|
|
return false;
|
|
|
|
Type resultType = exprInfo.ExpressionType;
|
|
if (!IsValidBooleanResult(resultType))
|
|
{
|
|
// not a boolean, so complain unless another error may have caused this problem
|
|
if (resultType != null || Errors.Count == 0)
|
|
{
|
|
string message = Messages.ConditionMustBeBoolean;
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_ConditionMustBeBoolean);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = expression;
|
|
Errors.Add(error);
|
|
}
|
|
}
|
|
|
|
return Errors.Count == 0;
|
|
}
|
|
|
|
internal static bool IsValidBooleanResult(Type type)
|
|
{
|
|
return ((type == typeof(bool))
|
|
|| (type == typeof(bool?))
|
|
|| (ImplicitConversion(type, typeof(bool))));
|
|
}
|
|
|
|
internal static bool IsPrivate(MethodInfo methodInfo)
|
|
{
|
|
return methodInfo.IsPrivate
|
|
|| methodInfo.IsFamily
|
|
|| methodInfo.IsFamilyOrAssembly
|
|
|| methodInfo.IsFamilyAndAssembly;
|
|
}
|
|
|
|
internal static bool IsPrivate(FieldInfo fieldInfo)
|
|
{
|
|
return fieldInfo.IsPrivate
|
|
|| fieldInfo.IsFamily
|
|
|| fieldInfo.IsFamilyOrAssembly
|
|
|| fieldInfo.IsFamilyAndAssembly;
|
|
}
|
|
|
|
internal static bool IsInternal(MethodInfo methodInfo)
|
|
{
|
|
return methodInfo.IsAssembly
|
|
|| methodInfo.IsFamilyAndAssembly;
|
|
}
|
|
|
|
internal static bool IsInternal(FieldInfo fieldInfo)
|
|
{
|
|
return fieldInfo.IsAssembly
|
|
|| fieldInfo.IsFamilyAndAssembly;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Miscellaneous public properties & methods
|
|
|
|
public Type ThisType
|
|
{
|
|
get { return thisType; }
|
|
}
|
|
|
|
internal ITypeProvider GetTypeProvider()
|
|
{
|
|
return typeProvider;
|
|
}
|
|
|
|
public ValidationErrorCollection Errors
|
|
{
|
|
get { return errors; }
|
|
}
|
|
|
|
internal bool AllowInternalMembers(Type type)
|
|
{
|
|
return type.Assembly == thisType.Assembly;
|
|
}
|
|
|
|
internal void AddError(ValidationError error)
|
|
{
|
|
this.Errors.Add(error);
|
|
}
|
|
|
|
public bool PushParentExpression(CodeExpression newParent)
|
|
{
|
|
if (newParent == null)
|
|
throw new ArgumentNullException("newParent");
|
|
|
|
if (activeParentNodes.Contains(newParent))
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.CyclicalExpression);
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_CyclicalExpression);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = newParent;
|
|
Errors.Add(error);
|
|
return false;
|
|
}
|
|
|
|
activeParentNodes.Push(newParent);
|
|
return true;
|
|
}
|
|
|
|
public void PopParentExpression()
|
|
{
|
|
activeParentNodes.Pop();
|
|
}
|
|
|
|
// Get the ExpressionInfo associated with a given CodeExpression
|
|
public RuleExpressionInfo ExpressionInfo(CodeExpression expression)
|
|
{
|
|
if (expression == null)
|
|
throw new ArgumentNullException("expression");
|
|
|
|
RuleExpressionInfo exprInfo = null;
|
|
expressionInfoMap.TryGetValue(expression, out exprInfo);
|
|
|
|
return exprInfo;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CodeDom Expression Validation methods
|
|
|
|
internal RuleExpressionInfo ValidateSubexpression(CodeExpression expr, RuleExpressionInternal ruleExpr, bool isWritten)
|
|
{
|
|
Debug.Assert(ruleExpr != null, "Validation::ValidateSubexpression - IRuleExpression is null");
|
|
Debug.Assert(expr != null, "Validation::ValidateSubexpression - CodeExpression is null");
|
|
|
|
RuleExpressionInfo exprInfo = ruleExpr.Validate(expr, this, isWritten);
|
|
|
|
if (exprInfo != null)
|
|
{
|
|
// Add the CodeExpression object to the info map. We don't want to add the IRuleExpression guy
|
|
// as the key, since it might likely be just a tearoff wrapper.
|
|
expressionInfoMap[expr] = exprInfo;
|
|
}
|
|
|
|
return exprInfo;
|
|
}
|
|
|
|
internal static bool TypesAreAssignable(Type rhsType, Type lhsType, CodeExpression rhsExpression, out ValidationError error)
|
|
{
|
|
// determine if rhsType can be implicitly converted to lhsType,
|
|
// following the rules in C# specification section 6.1,
|
|
// plus support for Nullable<T>
|
|
|
|
// all but 6.1.7 handled as a standard implicit conversion
|
|
if (StandardImplicitConversion(rhsType, lhsType, rhsExpression, out error))
|
|
return true;
|
|
if (error != null)
|
|
return false;
|
|
|
|
// no standard implicit conversion works, see if user specified one
|
|
// from section 6.4.3, start by determining what types to check
|
|
// as we find each type, add the list of implicit conversions available
|
|
if (FindImplicitConversion(rhsType, lhsType, out error) == null)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
internal static bool ExplicitConversionSpecified(Type fromType, Type toType, out ValidationError error)
|
|
{
|
|
// determine if fromType can be implicitly converted to toType,
|
|
// following the rules in C# specification section 6.2
|
|
|
|
// start by seeing if there is a standard implicit conversion
|
|
if (StandardImplicitConversion(fromType, toType, null, out error))
|
|
return true;
|
|
if (error != null)
|
|
return false;
|
|
|
|
// explicit numeric conversions
|
|
// also handles Enum conversions, since GetTypeCode returns the underlying type
|
|
if (fromType.IsValueType && toType.IsValueType && IsExplicitNumericConversion(fromType, toType))
|
|
return true;
|
|
|
|
// explicit reference conversions
|
|
// this looks like the inverse of implicit conversions
|
|
ValidationError dummyError; // so we don't return an error
|
|
if (StandardImplicitConversion(toType, fromType, null, out dummyError))
|
|
return true;
|
|
// include interface checks
|
|
if (toType.IsInterface)
|
|
{
|
|
// from any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.
|
|
// latter part should be handled by implicit conversion, so we are ok as long as class is not sealed
|
|
if ((fromType.IsClass) && (!fromType.IsSealed))
|
|
return true;
|
|
// from any interface-type S to any interface-type T, provided S is not derived from T.
|
|
// again, if S derived from T, handled by implicit conversion above
|
|
if (fromType.IsInterface)
|
|
return true;
|
|
}
|
|
if (fromType.IsInterface)
|
|
{
|
|
// from any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
|
|
if ((toType.IsClass) && ((!toType.IsSealed) || (InterfaceMatch(toType.GetInterfaces(), fromType))))
|
|
return true;
|
|
}
|
|
|
|
// no look for user-defined conversions
|
|
// from section 6.4.4, start by determining what types to check
|
|
// as we find each type, add the list of implicit conversions available
|
|
if (FindExplicitConversion(fromType, toType, out error) == null)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
private static bool InterfaceMatch(Type[] types, Type fromType)
|
|
{
|
|
foreach (Type t in types)
|
|
{
|
|
if (t == fromType)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
internal static MethodInfo FindImplicitConversion(Type fromType, Type toType, out ValidationError error)
|
|
{
|
|
List<MethodInfo> candidates = new List<MethodInfo>();
|
|
|
|
bool fromIsNullable = ConditionHelper.IsNullableValueType(fromType);
|
|
bool toIsNullable = ConditionHelper.IsNullableValueType(toType);
|
|
Type fromType0 = (fromIsNullable) ? Nullable.GetUnderlyingType(fromType) : fromType;
|
|
Type toType0 = (toIsNullable) ? Nullable.GetUnderlyingType(toType) : toType;
|
|
|
|
if (fromType0.IsClass)
|
|
{
|
|
AddImplicitConversions(fromType0, fromType, toType, candidates);
|
|
Type baseType = fromType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddImplicitConversions(baseType, fromType, toType, candidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(fromType0))
|
|
{
|
|
AddImplicitConversions(fromType0, fromType, toType, candidates);
|
|
}
|
|
if ((toType0.IsClass) || (IsStruct(toType0)))
|
|
{
|
|
AddImplicitConversions(toType0, fromType, toType, candidates);
|
|
}
|
|
|
|
// if both types are nullable, add the lifted operators
|
|
if (fromIsNullable && toIsNullable)
|
|
{
|
|
// start by finding all the conversion operators from S0 -> T0
|
|
List<MethodInfo> liftedCandidates = new List<MethodInfo>();
|
|
if (fromType0.IsClass)
|
|
{
|
|
AddImplicitConversions(fromType0, fromType0, toType0, liftedCandidates);
|
|
Type baseType = fromType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddImplicitConversions(baseType, fromType0, toType0, liftedCandidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(fromType0))
|
|
{
|
|
AddImplicitConversions(fromType0, fromType0, toType0, liftedCandidates);
|
|
}
|
|
if ((toType0.IsClass) || (IsStruct(toType0)))
|
|
{
|
|
AddImplicitConversions(toType0, fromType0, toType0, liftedCandidates);
|
|
}
|
|
|
|
// add them all to the candidates list as lifted methods (which wraps them appropriately)
|
|
foreach (MethodInfo mi in liftedCandidates)
|
|
{
|
|
// only lift candidates that convert from a non-nullable value type
|
|
// to a non-nullable value type
|
|
ParameterInfo[] parameters = mi.GetParameters();
|
|
if (ConditionHelper.IsNonNullableValueType(mi.ReturnType) && ConditionHelper.IsNonNullableValueType(parameters[0].ParameterType))
|
|
candidates.Add(new LiftedConversionMethodInfo(mi));
|
|
}
|
|
}
|
|
|
|
if (candidates.Count == 0)
|
|
{
|
|
// no overrides, so must be false
|
|
string message = string.Format(CultureInfo.CurrentCulture,
|
|
Messages.NoConversion,
|
|
RuleDecompiler.DecompileType(fromType),
|
|
RuleDecompiler.DecompileType(toType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return null;
|
|
}
|
|
|
|
// find the most specific source type
|
|
ValidationError dummyError; // so we don't return an error
|
|
Type sx = candidates[0].GetParameters()[0].ParameterType;
|
|
if (sx != fromType)
|
|
{
|
|
for (int i = 1; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].GetParameters()[0].ParameterType;
|
|
if (testType == fromType)
|
|
{
|
|
// we have a match with the source type, so that's the correct answer
|
|
sx = fromType;
|
|
break;
|
|
}
|
|
if (StandardImplicitConversion(testType, sx, null, out dummyError))
|
|
sx = testType;
|
|
}
|
|
}
|
|
|
|
// find the most specific target type
|
|
Type tx = candidates[0].ReturnType;
|
|
if (tx != toType)
|
|
{
|
|
for (int i = 1; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].ReturnType;
|
|
if (testType == toType)
|
|
{
|
|
// we have a match with the target type, so that's the correct answer
|
|
tx = toType;
|
|
break;
|
|
}
|
|
if (StandardImplicitConversion(tx, testType, null, out dummyError))
|
|
tx = testType;
|
|
}
|
|
}
|
|
|
|
// see how many candidates convert from sx to tx, ignoring lifted methods
|
|
int numMatches = 0;
|
|
int position = 0;
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
if ((candidates[i].ReturnType == tx) &&
|
|
(candidates[i].GetParameters()[0].ParameterType == sx) &&
|
|
(!(candidates[i] is LiftedConversionMethodInfo)))
|
|
{
|
|
position = i;
|
|
++numMatches;
|
|
}
|
|
}
|
|
if (numMatches == 1)
|
|
{
|
|
// found what we are looking for
|
|
error = null;
|
|
return candidates[position];
|
|
}
|
|
|
|
// now check for lifted conversions
|
|
if ((toIsNullable) && (numMatches == 0))
|
|
{
|
|
if (fromIsNullable)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
if ((candidates[i].ReturnType == tx) &&
|
|
(candidates[i].GetParameters()[0].ParameterType == sx) &&
|
|
(candidates[i] is LiftedConversionMethodInfo))
|
|
{
|
|
position = i;
|
|
++numMatches;
|
|
}
|
|
}
|
|
if (numMatches == 1)
|
|
{
|
|
// found what we are looking for
|
|
error = null;
|
|
return candidates[position];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we are doing a conversion T? = S, so a conversion from S -> T is valid
|
|
MethodInfo result = FindImplicitConversion(fromType, toType0, out error);
|
|
if (result != null)
|
|
{
|
|
error = null;
|
|
// return it as a lifted method so the wrapping to T? is done
|
|
return new LiftedConversionMethodInfo(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// no exact matches, so it's an error
|
|
string message2 = string.Format(CultureInfo.CurrentCulture,
|
|
Messages.AmbiguousConversion,
|
|
RuleDecompiler.DecompileType(fromType),
|
|
RuleDecompiler.DecompileType(toType));
|
|
error = new ValidationError(message2, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return null;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
internal static MethodInfo FindExplicitConversion(Type fromType, Type toType, out ValidationError error)
|
|
{
|
|
List<MethodInfo> candidates = new List<MethodInfo>();
|
|
ValidationError dummyError; // don't return transient errors
|
|
|
|
bool fromIsNullable = ConditionHelper.IsNullableValueType(fromType);
|
|
bool toIsNullable = ConditionHelper.IsNullableValueType(toType);
|
|
Type fromType0 = (fromIsNullable) ? Nullable.GetUnderlyingType(fromType) : fromType;
|
|
Type toType0 = (toIsNullable) ? Nullable.GetUnderlyingType(toType) : toType;
|
|
|
|
if (fromType0.IsClass)
|
|
{
|
|
AddExplicitConversions(fromType0, fromType, toType, candidates);
|
|
Type baseType = fromType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddExplicitConversions(baseType, fromType, toType, candidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(fromType0))
|
|
{
|
|
AddExplicitConversions(fromType0, fromType, toType, candidates);
|
|
}
|
|
if (toType0.IsClass)
|
|
{
|
|
AddExplicitConversions(toType0, fromType, toType, candidates);
|
|
Type baseType = toType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddExplicitConversions(baseType, fromType, toType, candidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(toType0))
|
|
{
|
|
AddExplicitConversions(toType0, fromType, toType, candidates);
|
|
}
|
|
|
|
// if both types are nullable, add the lifted operators
|
|
if (fromIsNullable && toIsNullable)
|
|
{
|
|
// start by finding all the conversion operators from S0 -> T0
|
|
List<MethodInfo> liftedCandidates = new List<MethodInfo>();
|
|
if (fromType0.IsClass)
|
|
{
|
|
AddExplicitConversions(fromType0, fromType0, toType0, liftedCandidates);
|
|
Type baseType = fromType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddExplicitConversions(baseType, fromType0, toType0, liftedCandidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(fromType0))
|
|
{
|
|
AddExplicitConversions(fromType0, fromType0, toType0, liftedCandidates);
|
|
}
|
|
if (toType0.IsClass)
|
|
{
|
|
AddExplicitConversions(toType0, fromType0, toType0, liftedCandidates);
|
|
Type baseType = toType0.BaseType;
|
|
while ((baseType != null) && (baseType != typeof(object)))
|
|
{
|
|
AddExplicitConversions(baseType, fromType0, toType0, liftedCandidates);
|
|
baseType = baseType.BaseType;
|
|
}
|
|
}
|
|
else if (IsStruct(toType0))
|
|
{
|
|
AddExplicitConversions(toType0, fromType0, toType0, liftedCandidates);
|
|
}
|
|
|
|
// add them all to the candidates list as lifted methods (which wraps them appropriately)
|
|
foreach (MethodInfo mi in liftedCandidates)
|
|
candidates.Add(new LiftedConversionMethodInfo(mi));
|
|
}
|
|
|
|
if (candidates.Count == 0)
|
|
{
|
|
// no overrides, so must be false
|
|
string message = string.Format(CultureInfo.CurrentCulture,
|
|
Messages.NoConversion,
|
|
RuleDecompiler.DecompileType(fromType),
|
|
RuleDecompiler.DecompileType(toType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return null;
|
|
}
|
|
|
|
// find the most specific source type
|
|
// if any are s, s is the answer
|
|
Type sx = null;
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].GetParameters()[0].ParameterType;
|
|
if (testType == fromType)
|
|
{
|
|
// we have a match with the source type, so that's the correct answer
|
|
sx = fromType;
|
|
break;
|
|
}
|
|
}
|
|
// if no match, find the most encompassed type if the type encompasses s
|
|
if (sx == null)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].GetParameters()[0].ParameterType;
|
|
if (StandardImplicitConversion(fromType, testType, null, out dummyError))
|
|
{
|
|
if (sx == null)
|
|
sx = testType;
|
|
else if (StandardImplicitConversion(testType, sx, null, out dummyError))
|
|
sx = testType;
|
|
}
|
|
}
|
|
}
|
|
// still no match, find most encompassing type
|
|
if (sx == null)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].GetParameters()[0].ParameterType;
|
|
if (StandardImplicitConversion(testType, fromType, null, out dummyError))
|
|
{
|
|
if (sx == null)
|
|
sx = testType;
|
|
else if (StandardImplicitConversion(sx, testType, null, out dummyError))
|
|
sx = testType;
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the most specific target type
|
|
// if any are t, t is the answer
|
|
Type tx = null;
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].ReturnType;
|
|
if (testType == toType)
|
|
{
|
|
// we have a match with the target type, so that's the correct answer
|
|
tx = toType;
|
|
break;
|
|
}
|
|
}
|
|
// if no match, find the most encompassed type if the type encompasses s
|
|
if (tx == null)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].ReturnType;
|
|
if (StandardImplicitConversion(testType, toType, null, out dummyError))
|
|
{
|
|
if (tx == null)
|
|
tx = testType;
|
|
else if (StandardImplicitConversion(tx, testType, null, out dummyError))
|
|
tx = testType;
|
|
}
|
|
}
|
|
}
|
|
// still no match, find most encompassing type
|
|
if (tx == null)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
Type testType = candidates[i].ReturnType;
|
|
if (StandardImplicitConversion(toType, testType, null, out dummyError))
|
|
{
|
|
if (tx == null)
|
|
tx = testType;
|
|
else if (StandardImplicitConversion(testType, tx, null, out dummyError))
|
|
tx = testType;
|
|
}
|
|
}
|
|
}
|
|
|
|
// see how many candidates convert from sx to tx, ignoring lifted methods
|
|
int numMatches = 0;
|
|
int position = 0;
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
if ((candidates[i].ReturnType == tx) &&
|
|
(candidates[i].GetParameters()[0].ParameterType == sx) &&
|
|
(!(candidates[i] is LiftedConversionMethodInfo)))
|
|
{
|
|
position = i;
|
|
++numMatches;
|
|
}
|
|
}
|
|
if (numMatches == 1)
|
|
{
|
|
// found what we are looking for
|
|
error = null;
|
|
return candidates[position];
|
|
}
|
|
|
|
// now check for lifted conversions
|
|
if ((toIsNullable) && (numMatches == 0))
|
|
{
|
|
if (fromIsNullable)
|
|
{
|
|
for (int i = 0; i < candidates.Count; ++i)
|
|
{
|
|
if ((candidates[i].ReturnType == tx) &&
|
|
(candidates[i].GetParameters()[0].ParameterType == sx) &&
|
|
(candidates[i] is LiftedConversionMethodInfo))
|
|
{
|
|
position = i;
|
|
++numMatches;
|
|
}
|
|
}
|
|
if (numMatches == 1)
|
|
{
|
|
// found what we are looking for
|
|
error = null;
|
|
return candidates[position];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we are doing a conversion T? = S, so a conversion from S -> T is valid
|
|
MethodInfo result = FindExplicitConversion(fromType, toType0, out error);
|
|
if (result != null)
|
|
{
|
|
error = null;
|
|
// return it as a lifted method so the wrapping to T? is done
|
|
return new LiftedConversionMethodInfo(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// no exact matches, so it's an error
|
|
string message2 = string.Format(CultureInfo.CurrentCulture,
|
|
Messages.AmbiguousConversion,
|
|
RuleDecompiler.DecompileType(fromType),
|
|
RuleDecompiler.DecompileType(toType));
|
|
error = new ValidationError(message2, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return null;
|
|
}
|
|
|
|
private static bool IsStruct(Type type)
|
|
{
|
|
return ((type.IsValueType) && (!type.IsPrimitive));
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
private static bool IsExplicitNumericConversion(Type sourceType, Type testType)
|
|
{
|
|
// includes the implicit conversions as well
|
|
|
|
// unwrap nullables
|
|
TypeCode sourceTypeCode = (ConditionHelper.IsNullableValueType(sourceType))
|
|
? Type.GetTypeCode(sourceType.GetGenericArguments()[0])
|
|
: Type.GetTypeCode(sourceType);
|
|
TypeCode testTypeCode = (ConditionHelper.IsNullableValueType(testType))
|
|
? Type.GetTypeCode(testType.GetGenericArguments()[0])
|
|
: Type.GetTypeCode(testType);
|
|
|
|
switch (sourceTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Byte:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.SByte:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int16:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt16:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int32:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt32:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int64:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt64:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Char:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.Char:
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Single:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
case TypeCode.Single:
|
|
case TypeCode.Decimal:
|
|
|
|
case TypeCode.Double:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Double:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Decimal:
|
|
switch (testTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
internal static bool ImplicitConversion(Type fromType, Type toType)
|
|
{
|
|
ValidationError error;
|
|
|
|
// is there a standard conversion we can use
|
|
if (StandardImplicitConversion(fromType, toType, null, out error))
|
|
return true;
|
|
|
|
// no standard one, did the user provide one?
|
|
return (FindImplicitConversion(fromType, toType, out error) != null);
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
internal static bool StandardImplicitConversion(Type rhsType, Type lhsType, CodeExpression rhsExpression, out ValidationError error)
|
|
{
|
|
error = null;
|
|
|
|
// 6.1.1 identity conversion
|
|
if (rhsType == lhsType)
|
|
{
|
|
// Easy special case... they're the same type.
|
|
return true;
|
|
}
|
|
|
|
// 6.1.4 (h) from the null type to any reference-type
|
|
if (rhsType == typeof(NullLiteral))
|
|
{
|
|
// Special case if the RHS is 'null'; just make sure the LHS type can be assigned a null value.
|
|
if (ConditionHelper.IsNonNullableValueType(lhsType))
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.AssignNotAllowed, Messages.NullValue, RuleDecompiler.DecompileType(lhsType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// check for nullables
|
|
bool lhsIsNullable = ConditionHelper.IsNullableValueType(lhsType);
|
|
bool rhsIsNullable = ConditionHelper.IsNullableValueType(rhsType);
|
|
if (rhsIsNullable)
|
|
{
|
|
if (!lhsIsNullable)
|
|
{
|
|
// We had T1 = T2?, which is not valid for any T1 or T2, unless T1 is object
|
|
return (lhsType == typeof(object));
|
|
}
|
|
|
|
rhsType = Nullable.GetUnderlyingType(rhsType);
|
|
}
|
|
|
|
if (lhsIsNullable)
|
|
lhsType = Nullable.GetUnderlyingType(lhsType);
|
|
|
|
if (lhsType == rhsType)
|
|
{
|
|
// We had T? = T, which is valid.
|
|
return true;
|
|
}
|
|
|
|
// handle rest of 6.1.4
|
|
if (TypeProvider.IsAssignable(lhsType, rhsType))
|
|
{
|
|
// They are assignable, which will handle inheritance and trivial up-casting.
|
|
return true;
|
|
}
|
|
|
|
// 6.1.3 implicit enumeration conversions
|
|
if (lhsType.IsEnum)
|
|
{
|
|
// right-hand side can be decimal-integer-literal 0
|
|
CodePrimitiveExpression primitive = rhsExpression as CodePrimitiveExpression;
|
|
if ((primitive == null) || (primitive.Value == null))
|
|
{
|
|
// not a constant
|
|
return false;
|
|
}
|
|
switch (Type.GetTypeCode(primitive.Value.GetType()))
|
|
{
|
|
case TypeCode.SByte:
|
|
return ((sbyte)primitive.Value == 0);
|
|
case TypeCode.Byte:
|
|
return ((byte)primitive.Value == 0);
|
|
case TypeCode.Int16:
|
|
return ((short)primitive.Value == 0);
|
|
case TypeCode.UInt16:
|
|
return ((ushort)primitive.Value == 0);
|
|
case TypeCode.Int32:
|
|
return ((int)primitive.Value == 0);
|
|
case TypeCode.UInt32:
|
|
return ((uint)primitive.Value == 0);
|
|
case TypeCode.Int64:
|
|
return ((long)primitive.Value == 0);
|
|
case TypeCode.UInt64:
|
|
return ((ulong)primitive.Value == 0);
|
|
case TypeCode.Char:
|
|
return ((char)primitive.Value == 0);
|
|
}
|
|
return false;
|
|
}
|
|
if (rhsType.IsEnum)
|
|
{
|
|
// don't treat enums as numbers
|
|
return false;
|
|
}
|
|
|
|
// 6.1.2 implicit numeric conversions
|
|
// 6.1.6 implicit constant expression conversions
|
|
// not assignable, but the assignment might still be valid for
|
|
// value types if a free conversion is available.
|
|
TypeCode lhsTypeCode = Type.GetTypeCode(lhsType);
|
|
TypeCode rhsTypeCode = Type.GetTypeCode(rhsType);
|
|
|
|
switch (lhsTypeCode)
|
|
{
|
|
case TypeCode.Decimal:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Decimal:
|
|
case TypeCode.Char:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Double:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Char:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Single:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Char:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Char:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.SByte:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
return true;
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Byte:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
return true;
|
|
case TypeCode.SByte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int16:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
return true;
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int32:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.Int64:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
case TypeCode.Byte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.UInt64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt16:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.SByte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt32:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.SByte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
case TypeCode.UInt64:
|
|
switch (rhsTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Char:
|
|
return true;
|
|
case TypeCode.SByte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Int64:
|
|
// Maybe, if the value is in range.
|
|
return CheckValueRange(rhsExpression, lhsType, out error);
|
|
}
|
|
return false;
|
|
|
|
default:
|
|
// It wasn't a numeric type, it was some other kind of value type (e.g., bool,
|
|
// DateTime, etc). There will be no conversions.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void AddImplicitConversions(Type t, Type source, Type target, List<MethodInfo> methods)
|
|
{
|
|
// append the list of methods that match the name specified
|
|
// s is the source type, so the parameter must encompass it
|
|
// t is the target type, so it must encompass the result
|
|
MethodInfo[] possible = t.GetMethods(BindingFlags.Static | BindingFlags.Public);
|
|
foreach (MethodInfo mi in possible)
|
|
{
|
|
if ((mi.Name == "op_Implicit") && (mi.GetParameters().Length == 1))
|
|
{
|
|
Type sourceType = mi.GetParameters()[0].ParameterType;
|
|
Type targetType = mi.ReturnType;
|
|
ValidationError error;
|
|
if (StandardImplicitConversion(source, sourceType, null, out error) &&
|
|
StandardImplicitConversion(targetType, target, null, out error))
|
|
{
|
|
if (!methods.Contains(mi))
|
|
methods.Add(mi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddExplicitConversions(Type t, Type source, Type target, List<MethodInfo> methods)
|
|
{
|
|
// append the list of methods that match the name specified
|
|
// s is the source type, so the parameter must encompass it
|
|
// t is the target type, so it must encompass the result
|
|
MethodInfo[] possible = t.GetMethods(BindingFlags.Static | BindingFlags.Public);
|
|
foreach (MethodInfo mi in possible)
|
|
{
|
|
if (((mi.Name == "op_Implicit") || (mi.Name == "op_Explicit")) && (mi.GetParameters().Length == 1))
|
|
{
|
|
Type sourceType = mi.GetParameters()[0].ParameterType;
|
|
Type targetType = mi.ReturnType;
|
|
ValidationError error;
|
|
if ((StandardImplicitConversion(source, sourceType, null, out error) || StandardImplicitConversion(sourceType, source, null, out error))
|
|
&& (StandardImplicitConversion(target, targetType, null, out error) || StandardImplicitConversion(targetType, target, null, out error)))
|
|
{
|
|
if (!methods.Contains(mi))
|
|
methods.Add(mi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
|
private static bool CheckValueRange(CodeExpression rhsExpression, Type lhsType, out ValidationError error)
|
|
{
|
|
error = null;
|
|
|
|
CodePrimitiveExpression primitive = rhsExpression as CodePrimitiveExpression;
|
|
if (primitive != null)
|
|
{
|
|
try
|
|
{
|
|
System.Convert.ChangeType(primitive.Value, lhsType, CultureInfo.CurrentCulture);
|
|
// If we get here without throwing, it's valid.
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
error = new ValidationError(e.Message, ErrorNumbers.Error_OperandTypesIncompatible);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal bool ValidateMemberAccess(
|
|
CodeExpression targetExpression, Type targetType, FieldInfo accessorMethod, string memberName, CodeExpression parentExpr)
|
|
{
|
|
return this.ValidateMemberAccess(
|
|
targetExpression, targetType, memberName, parentExpr,
|
|
accessorMethod.DeclaringType.Assembly, RuleValidation.IsPrivate(accessorMethod), RuleValidation.IsInternal(accessorMethod), accessorMethod.IsStatic);
|
|
}
|
|
|
|
internal bool ValidateMemberAccess(
|
|
CodeExpression targetExpression, Type targetType, MethodInfo accessorMethod, string memberName, CodeExpression parentExpr)
|
|
{
|
|
return this.ValidateMemberAccess(
|
|
targetExpression, targetType, memberName, parentExpr,
|
|
accessorMethod.DeclaringType.Assembly, RuleValidation.IsPrivate(accessorMethod), RuleValidation.IsInternal(accessorMethod), accessorMethod.IsStatic);
|
|
}
|
|
|
|
private bool ValidateMemberAccess(
|
|
CodeExpression targetExpression, Type targetType, string memberName, CodeExpression parentExpr,
|
|
Assembly methodAssembly, bool isPrivate, bool isInternal, bool isStatic)
|
|
{
|
|
string message;
|
|
|
|
if (isStatic != (targetExpression is CodeTypeReferenceExpression))
|
|
{
|
|
// If it's static, then the target object must be a type ref, and vice versa.
|
|
|
|
int errorNumber;
|
|
|
|
if (isStatic)
|
|
{
|
|
// We have "object.StaticMember"
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.StaticMember, memberName);
|
|
errorNumber = ErrorNumbers.Error_StaticMember;
|
|
}
|
|
else
|
|
{
|
|
// We have "TypeName.NonStaticMember"
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.NonStaticMember, memberName);
|
|
errorNumber = ErrorNumbers.Error_NonStaticMember;
|
|
}
|
|
|
|
ValidationError error = new ValidationError(message, errorNumber);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = parentExpr;
|
|
Errors.Add(error);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (isPrivate && targetType != ThisType)
|
|
{
|
|
// Can't access private members except on the subject type.
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.CannotAccessPrivateMember, memberName, RuleDecompiler.DecompileType(targetType));
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_CannotResolveMember);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = parentExpr;
|
|
Errors.Add(error);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (isInternal && ThisType.Assembly != methodAssembly)
|
|
{
|
|
// Can't access internal members except on the subject assembly.
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.CannotAccessInternalMember, memberName, RuleDecompiler.DecompileType(targetType));
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_CannotResolveMember);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = parentExpr;
|
|
Errors.Add(error);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#region Field and property resolution
|
|
|
|
internal MemberInfo ResolveFieldOrProperty(Type targetType, string name)
|
|
{
|
|
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy;
|
|
if (AllowInternalMembers(targetType))
|
|
bindingFlags |= BindingFlags.NonPublic;
|
|
|
|
// Look up a field or property of the given name.
|
|
MemberInfo[] results = targetType.GetMember(name, MemberTypes.Field | MemberTypes.Property, bindingFlags);
|
|
|
|
if (results != null)
|
|
{
|
|
int numResults = results.Length;
|
|
if (numResults == 1)
|
|
{
|
|
// If we found exactly one, we're good.
|
|
return results[0];
|
|
}
|
|
else if (numResults > 1)
|
|
{
|
|
// We may have found more than one property if it's overloaded. If we find one without
|
|
// any parameters, return that one.
|
|
for (int i = 0; i < numResults; ++i)
|
|
{
|
|
MemberInfo member = results[i];
|
|
System.Diagnostics.Debug.Assert(member.MemberType == MemberTypes.Property, "only properties can be overloaded");
|
|
|
|
PropertyInfo pi = (PropertyInfo)member;
|
|
ParameterInfo[] parms = pi.GetIndexParameters();
|
|
if (parms == null || parms.Length == 0)
|
|
{
|
|
if (pi != null)
|
|
{
|
|
IsAuthorized(pi.PropertyType);
|
|
}
|
|
return pi;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't find it, and if the target type is an interface, try resolving a property
|
|
// that may exist in its inheritance chain. (Fields cannot appear on interfaces.)
|
|
if (targetType.IsInterface)
|
|
return ResolveProperty(targetType, name, bindingFlags);
|
|
|
|
// Otherwise, it's no good.
|
|
return null;
|
|
}
|
|
|
|
internal PropertyInfo ResolveProperty(Type targetType, string propertyName, BindingFlags bindingFlags)
|
|
{
|
|
|
|
PropertyInfo pi = GetProperty(targetType, propertyName, bindingFlags);
|
|
if (pi == null && targetType.IsInterface)
|
|
{
|
|
Type[] parentInterfacesArray = targetType.GetInterfaces();
|
|
List<Type> parentInterfaces = new List<Type>();
|
|
parentInterfaces.AddRange(parentInterfacesArray);
|
|
|
|
int index = 0;
|
|
while (index < parentInterfaces.Count)
|
|
{
|
|
pi = GetProperty(parentInterfaces[index], propertyName, bindingFlags);
|
|
if (pi != null)
|
|
break;
|
|
|
|
Type[] pInterfaces = parentInterfaces[index].GetInterfaces();
|
|
if (pInterfaces.Length > 0)
|
|
parentInterfaces.AddRange(pInterfaces);
|
|
++index;
|
|
}
|
|
}
|
|
|
|
if (pi != null)
|
|
{
|
|
IsAuthorized(pi.PropertyType);
|
|
}
|
|
return pi;
|
|
}
|
|
|
|
private static PropertyInfo GetProperty(Type targetType, string propertyName, BindingFlags bindingFlags)
|
|
{
|
|
// Properties may be overloaded (in VB), so we have to ---- out those that we can support,
|
|
// i.e., those that have no parameters.
|
|
|
|
MemberInfo[] members = targetType.GetMember(propertyName, MemberTypes.Property, bindingFlags);
|
|
for (int m = 0; m < members.Length; ++m)
|
|
{
|
|
PropertyInfo pi = (PropertyInfo)members[m];
|
|
|
|
ParameterInfo[] parms = pi.GetIndexParameters();
|
|
if (parms == null || parms.Length == 0)
|
|
return pi;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Method resolution
|
|
|
|
private class Argument
|
|
{
|
|
internal CodeExpression expression;
|
|
internal FieldDirection direction;
|
|
internal Type type;
|
|
|
|
internal Argument(CodeExpression expr, RuleValidation validation)
|
|
{
|
|
this.expression = expr;
|
|
|
|
this.direction = FieldDirection.In;
|
|
CodeDirectionExpression directionExpr = expr as CodeDirectionExpression;
|
|
if (directionExpr != null)
|
|
this.direction = directionExpr.Direction;
|
|
|
|
this.type = validation.ExpressionInfo(expr).ExpressionType;
|
|
}
|
|
|
|
internal Argument(Type type)
|
|
{
|
|
this.direction = FieldDirection.In;
|
|
this.type = type;
|
|
}
|
|
}
|
|
|
|
private class CandidateParameter
|
|
{
|
|
private Type type;
|
|
private FieldDirection direction;
|
|
|
|
internal CandidateParameter(Type type)
|
|
{
|
|
this.type = type;
|
|
this.direction = FieldDirection.In;
|
|
}
|
|
|
|
internal CandidateParameter(ParameterInfo paramInfo)
|
|
{
|
|
this.direction = FieldDirection.In;
|
|
if (paramInfo.IsOut)
|
|
this.direction = FieldDirection.Out;
|
|
else if (paramInfo.ParameterType.IsByRef)
|
|
this.direction = FieldDirection.Ref;
|
|
|
|
this.type = paramInfo.ParameterType;
|
|
}
|
|
|
|
internal bool Match(Argument argument, string methodName, int argPosition, out ValidationError error)
|
|
{
|
|
string message;
|
|
|
|
// If we don't agree on the argument direction, this method is not a candidate.
|
|
if (this.direction != argument.direction)
|
|
{
|
|
string dirString = "";
|
|
switch (this.direction)
|
|
{
|
|
case FieldDirection.In:
|
|
dirString = "in"; // No localization required, this is a keyword.
|
|
break;
|
|
case FieldDirection.Out:
|
|
dirString = "out"; // No localization required, this is a keyword.
|
|
break;
|
|
case FieldDirection.Ref:
|
|
dirString = "ref"; // No localization required, this is a keyword.
|
|
break;
|
|
}
|
|
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.MethodDirectionMismatch, argPosition, methodName, dirString);
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodDirectionMismatch);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (this.type.IsByRef && this.type != argument.type)
|
|
{
|
|
// If the parameter is "ref" or "out", then the types must match exactly.
|
|
// If not, this method can't be a candidate.
|
|
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.MethodArgumentTypeMismatch, argPosition, methodName, RuleDecompiler.DecompileType(argument.type), RuleDecompiler.DecompileType(this.type));
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodArgumentTypeMismatch);
|
|
|
|
return false;
|
|
}
|
|
|
|
// If the argument type is not assignable to the corresponding parameter type,
|
|
// this method can't be a candidate.
|
|
if (!RuleValidation.TypesAreAssignable(argument.type, this.type, argument.expression, out error))
|
|
{
|
|
if (error == null)
|
|
{
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.MethodArgumentTypeMismatch, argPosition, methodName, RuleDecompiler.DecompileType(argument.type), RuleDecompiler.DecompileType(this.type));
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodArgumentTypeMismatch);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// If we passed the above checks, this argument is a candidate match for the parameter.
|
|
return true;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
CandidateParameter otherParam = obj as CandidateParameter;
|
|
if (otherParam == null)
|
|
return false;
|
|
|
|
return this.direction == otherParam.direction && this.type == otherParam.type;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return this.direction.GetHashCode() ^ this.type.GetHashCode();
|
|
}
|
|
|
|
internal int CompareConversion(CandidateParameter otherParam, Argument argument)
|
|
{
|
|
// Return 0 if they are equal; 1 if this is better; -1 if this is worse.
|
|
// This follows the C# specification 7.4.2.3
|
|
int better = 1;
|
|
int worse = -1;
|
|
int equal = 0;
|
|
|
|
// If the two candidate parameters have the same type, neither one is better.
|
|
if (this.type == otherParam.type)
|
|
return equal;
|
|
|
|
// If the argument type is the same as one of the parameter types, that parameter is better.
|
|
if (argument.type == this.type)
|
|
return better;
|
|
if (argument.type == otherParam.type)
|
|
return worse;
|
|
|
|
// If this parameter can be converted to the other parameter, and not vice versa, then
|
|
// this is a better conversion. (And in the reverse situation, it's a worse conversion.)
|
|
ValidationError dummy;
|
|
bool thisConvertsToOther = RuleValidation.TypesAreAssignable(this.type, otherParam.type, null, out dummy);
|
|
bool otherConvertsToThis = RuleValidation.TypesAreAssignable(otherParam.type, this.type, null, out dummy);
|
|
if (thisConvertsToOther && !otherConvertsToThis)
|
|
return better;
|
|
if (otherConvertsToThis && !thisConvertsToOther)
|
|
return worse;
|
|
|
|
// See if one is a better sign-preserving conversion than the other.
|
|
if (BetterSignedConversion(this.type, otherParam.type))
|
|
return better;
|
|
if (BetterSignedConversion(otherParam.type, this.type))
|
|
return worse;
|
|
|
|
// Otherwise, neither conversion is better.
|
|
return equal;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
private static bool BetterSignedConversion(Type t1, Type t2)
|
|
{
|
|
TypeCode tc1 = Type.GetTypeCode(t1);
|
|
TypeCode tc2 = Type.GetTypeCode(t2);
|
|
|
|
switch (tc1)
|
|
{
|
|
case TypeCode.SByte:
|
|
switch (tc2)
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
// A conversion to sbyte is better than a conversion to an unsigned type.
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case TypeCode.Int16:
|
|
switch (tc2)
|
|
{
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
// A conversion to short is better than a conversion to an unsigned type.
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case TypeCode.Int32:
|
|
if (tc2 == TypeCode.UInt32 || tc2 == TypeCode.UInt64)
|
|
{
|
|
// A conversion to int is better than a conversion to an unsigned type.
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case TypeCode.Int64:
|
|
if (tc2 == TypeCode.UInt64)
|
|
{
|
|
// A conversion to long is better than a conversion to an unsigned type.
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case TypeCode.Object:
|
|
// it is possible that the types are nullable
|
|
if (ConditionHelper.IsNullableValueType(t1))
|
|
{
|
|
t1 = t1.GetGenericArguments()[0];
|
|
// t2 may already be a value type
|
|
if (ConditionHelper.IsNullableValueType(t2))
|
|
t2 = t2.GetGenericArguments()[0];
|
|
return BetterSignedConversion(t1, t2);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private class CandidateMember
|
|
{
|
|
internal enum Form
|
|
{
|
|
Normal, // no "params" expansion
|
|
Expanded // matched only after "params" expansion
|
|
}
|
|
|
|
internal MemberInfo Member;
|
|
private ParameterInfo[] memberParameters;
|
|
private List<CandidateParameter> signature;
|
|
private Form form;
|
|
private static ParameterInfo[] noParameters = new ParameterInfo[0];
|
|
private static List<CandidateParameter> noSignature = new List<CandidateParameter>();
|
|
|
|
// Constructor for candidate methods with parameters.
|
|
internal CandidateMember(MemberInfo member, ParameterInfo[] parameters, List<CandidateParameter> signature, Form form)
|
|
{
|
|
this.Member = member;
|
|
this.memberParameters = parameters;
|
|
this.signature = signature;
|
|
this.form = form;
|
|
}
|
|
|
|
// Constructor for a candidate method that has no parameters.
|
|
internal CandidateMember(MemberInfo member)
|
|
: this(member, noParameters, noSignature, Form.Normal)
|
|
{
|
|
}
|
|
|
|
internal bool IsExpanded
|
|
{
|
|
get { return form == Form.Expanded; }
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
|
internal int CompareMember(Type targetType, CandidateMember other, List<Argument> arguments, RuleValidation validator)
|
|
{
|
|
int better = 1;
|
|
int worse = -1;
|
|
int equal = 0;
|
|
|
|
// Methods in a base class are not candidates if any method in a derived class
|
|
// is applicable.
|
|
Type thisDeclaringType = this.Member.DeclaringType;
|
|
Type otherDeclaringType = other.Member.DeclaringType;
|
|
if (thisDeclaringType != otherDeclaringType)
|
|
{
|
|
if (TypeProvider.IsAssignable(otherDeclaringType, thisDeclaringType))
|
|
{
|
|
// This declaring type can be converted to the other declaring type,
|
|
// which means this one is more derived.
|
|
return better;
|
|
}
|
|
else if (TypeProvider.IsAssignable(thisDeclaringType, otherDeclaringType))
|
|
{
|
|
// The other declaring type can be converted to this declaring type,
|
|
// which means the other one is more derived.
|
|
return worse;
|
|
}
|
|
}
|
|
|
|
System.Diagnostics.Debug.Assert(arguments.Count == this.signature.Count);
|
|
System.Diagnostics.Debug.Assert(arguments.Count == other.signature.Count);
|
|
|
|
bool hasAtLeastOneBetterConversion = false;
|
|
bool hasAtLeastOneWorseConversion = false;
|
|
bool signaturesAreIdentical = true;
|
|
|
|
// pick non-extension methods over extension methods
|
|
// if both are extension methods, then pick the one in the namespace closest to "this"
|
|
ExtensionMethodInfo thisExtension = this.Member as ExtensionMethodInfo;
|
|
ExtensionMethodInfo otherExtension = other.Member as ExtensionMethodInfo;
|
|
if ((thisExtension == null) && (otherExtension != null))
|
|
return better;
|
|
else if ((thisExtension != null) && (otherExtension == null))
|
|
return worse;
|
|
else if ((thisExtension != null) && (otherExtension != null))
|
|
{
|
|
// we have 2 extension methods, which one is better
|
|
string[] thisNameSpace = thisExtension.DeclaringType.FullName.Split('.');
|
|
string[] otherNameSpace = otherExtension.DeclaringType.FullName.Split('.');
|
|
string[] bestNameSpace = validator.thisType.FullName.Split('.');
|
|
int thisMatch = MatchNameSpace(thisNameSpace, bestNameSpace);
|
|
int otherMatch = MatchNameSpace(otherNameSpace, bestNameSpace);
|
|
if (thisMatch > otherMatch)
|
|
return better;
|
|
else if (thisMatch < otherMatch)
|
|
return worse;
|
|
|
|
// compare arguments, including the "this" argument
|
|
CandidateParameter thisDeclaringParam = new CandidateParameter(thisExtension.AssumedDeclaringType);
|
|
CandidateParameter otherDeclaringParam = new CandidateParameter(otherExtension.AssumedDeclaringType);
|
|
if (!thisDeclaringParam.Equals(otherDeclaringParam))
|
|
{
|
|
signaturesAreIdentical = false;
|
|
int conversionResult = thisDeclaringParam.CompareConversion(otherDeclaringParam, new Argument(targetType));
|
|
if (conversionResult < 0)
|
|
{
|
|
// A conversion was found that was worse, so this candidate is not better.
|
|
hasAtLeastOneWorseConversion = true;
|
|
}
|
|
else if (conversionResult > 0)
|
|
{
|
|
// This candidate had at least one conversion that was better. (But
|
|
// we have to keep looking in case there's one that's worse.)
|
|
hasAtLeastOneBetterConversion = true;
|
|
}
|
|
}
|
|
|
|
// this check compares parameter lists correctly (see below)
|
|
for (int p = 0; p < arguments.Count; ++p)
|
|
{
|
|
CandidateParameter thisParam = this.signature[p];
|
|
CandidateParameter otherParam = other.signature[p];
|
|
|
|
if (!thisParam.Equals(otherParam))
|
|
signaturesAreIdentical = false;
|
|
|
|
int conversionResult = thisParam.CompareConversion(otherParam, arguments[p]);
|
|
if (conversionResult < 0)
|
|
{
|
|
// A conversion was found that was worse, so this candidate is not better.
|
|
hasAtLeastOneWorseConversion = true;
|
|
}
|
|
else if (conversionResult > 0)
|
|
{
|
|
// This candidate had at least one conversion that was better. (But
|
|
// we have to keep looking in case there's one that's worse.)
|
|
hasAtLeastOneBetterConversion = true;
|
|
}
|
|
}
|
|
if (hasAtLeastOneBetterConversion && !hasAtLeastOneWorseConversion)
|
|
{
|
|
// At least one conversion was better than the "other" candidate
|
|
// and no other arguments were worse, so this one is better.
|
|
return better;
|
|
}
|
|
else if (!hasAtLeastOneBetterConversion && hasAtLeastOneWorseConversion)
|
|
{
|
|
// At least one conversion was worse than the "other" candidate
|
|
// and no other arguments were better, so this one is worse.
|
|
return worse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NOTE: this is the original v1 code
|
|
// It doesn't check for worse parameters correctly.
|
|
// However, for backwards compatability, we can't change it
|
|
for (int p = 0; p < arguments.Count; ++p)
|
|
{
|
|
CandidateParameter thisParam = this.signature[p];
|
|
CandidateParameter otherParam = other.signature[p];
|
|
|
|
if (!thisParam.Equals(otherParam))
|
|
signaturesAreIdentical = false;
|
|
|
|
int conversionResult = thisParam.CompareConversion(otherParam, arguments[p]);
|
|
if (conversionResult < 0)
|
|
{
|
|
// A conversion was found that was worse, so this candidate is not better.
|
|
return worse;
|
|
}
|
|
else if (conversionResult > 0)
|
|
{
|
|
// This candidate had at least one conversion that was better. (But
|
|
// we have to keep looking in case there's one that's worse.)
|
|
hasAtLeastOneBetterConversion = true;
|
|
}
|
|
}
|
|
|
|
if (hasAtLeastOneBetterConversion)
|
|
{
|
|
// At least one conversion was better than the "other" candidate, so this one
|
|
// is better.
|
|
return better;
|
|
}
|
|
}
|
|
|
|
if (signaturesAreIdentical)
|
|
{
|
|
// The signatures were "tied". Try some disambiguating rules for expanded signatures
|
|
// vs normal signatures.
|
|
if (this.form == Form.Normal && other.form == Form.Expanded)
|
|
{
|
|
// This candidate matched in its normal form, but the other one matched only after
|
|
// expansion of a params array. This one is better.
|
|
return better;
|
|
}
|
|
else if (this.form == Form.Expanded && other.form == Form.Normal)
|
|
{
|
|
// This candidate matched in its expanded form, but the other one matched in its
|
|
// normal form. The other one was better.
|
|
return worse;
|
|
}
|
|
else if (this.form == Form.Expanded && other.form == Form.Expanded)
|
|
{
|
|
// Both candidates matched in their expanded forms.
|
|
|
|
int thisParameterCount = this.memberParameters.Length;
|
|
int otherParameterCount = other.memberParameters.Length;
|
|
|
|
if (thisParameterCount > otherParameterCount)
|
|
{
|
|
// This candidate had more declared parameters, so it is better.
|
|
return better;
|
|
}
|
|
else if (otherParameterCount > thisParameterCount)
|
|
{
|
|
// The other candidate had more declared parameters, so it was better.
|
|
return worse;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing worked, the two candidates are equally applicable.
|
|
return equal;
|
|
}
|
|
|
|
private static int MatchNameSpace(string[] test, string[] reference)
|
|
{
|
|
// returns the number of strings in test that are the same as reference
|
|
int i;
|
|
int len = Math.Min(test.Length, reference.Length);
|
|
for (i = 0; i < len; ++i)
|
|
{
|
|
if (test[i] != reference[i])
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// Get the candidate target types, ordered from most-derived to least-derived.
|
|
private static List<Type> GetCandidateTargetTypes(Type targetType)
|
|
{
|
|
List<Type> candidateTypes;
|
|
|
|
if (targetType.IsInterface)
|
|
{
|
|
candidateTypes = new List<Type>();
|
|
candidateTypes.Add(targetType);
|
|
|
|
// Add all base interfaces in its hierarchy to the candidate list.
|
|
for (int i = 0; i < candidateTypes.Count; ++i)
|
|
{
|
|
Type currentCandidate = candidateTypes[i];
|
|
candidateTypes.AddRange(currentCandidate.GetInterfaces());
|
|
}
|
|
|
|
// Finally, add "System.Object", since all types intrinsically derive from this.
|
|
candidateTypes.Add(typeof(object));
|
|
}
|
|
else
|
|
{
|
|
// It was a class; just add the one class.
|
|
candidateTypes = new List<Type>(1);
|
|
candidateTypes.Add(targetType);
|
|
}
|
|
|
|
return candidateTypes;
|
|
}
|
|
|
|
private delegate ValidationError BuildArgCountMismatchError(string name, int numArguments);
|
|
|
|
private static void EvaluateCandidate(List<CandidateMember> candidates, MemberInfo candidateMember, ParameterInfo[] parameters, List<Argument> arguments, out ValidationError error, BuildArgCountMismatchError buildArgCountMismatchError)
|
|
{
|
|
error = null;
|
|
|
|
int numArguments = arguments.Count;
|
|
string candidateName = candidateMember.Name;
|
|
|
|
if (parameters == null || parameters.Length == 0)
|
|
{
|
|
// If there were no arguments supplied, and this method has no parameters,
|
|
// then it's a candidate. (It should be the only one.)
|
|
if (numArguments == 0)
|
|
{
|
|
candidates.Add(new CandidateMember(candidateMember));
|
|
}
|
|
else
|
|
{
|
|
error = buildArgCountMismatchError(candidateName, numArguments);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
List<CandidateParameter> signature = new List<CandidateParameter>();
|
|
|
|
int parameterCount = parameters.Length;
|
|
|
|
int fixedParameterCount = parameterCount;
|
|
|
|
// Check to see if the last parameter is (1) an array and (2) has a ParamArrayAttribute
|
|
// (i.e., it is a "params" array).
|
|
ParameterInfo lastParam = parameters[parameterCount - 1];
|
|
if (lastParam.ParameterType.IsArray)
|
|
{
|
|
object[] attrs = lastParam.GetCustomAttributes(typeof(ParamArrayAttribute), false);
|
|
if (attrs != null && attrs.Length > 0)
|
|
fixedParameterCount -= 1;
|
|
}
|
|
|
|
if (numArguments < fixedParameterCount)
|
|
{
|
|
// Not enough arguments were passed for this to be a candidate.
|
|
error = buildArgCountMismatchError(candidateName, numArguments);
|
|
|
|
return;
|
|
}
|
|
else if (fixedParameterCount == parameterCount && numArguments != parameterCount)
|
|
{
|
|
// Too many arguments were passed for this to be a candidate.
|
|
error = buildArgCountMismatchError(candidateName, numArguments);
|
|
|
|
return;
|
|
}
|
|
|
|
// For the fixed part of the method signature, make sure each argument can
|
|
// be implicitly converted to the corresponding parameter.
|
|
int p = 0;
|
|
for (; p < fixedParameterCount; ++p)
|
|
{
|
|
CandidateParameter candidateParam = new CandidateParameter(parameters[p]);
|
|
if (!candidateParam.Match(arguments[p], candidateName, p + 1, out error))
|
|
break; // argument #p didn't match
|
|
|
|
// If we get here, then so far so good.
|
|
signature.Add(candidateParam);
|
|
}
|
|
|
|
if (p != fixedParameterCount)
|
|
{
|
|
// We didn't match all of the fixed part. This method is not a candidate.
|
|
return;
|
|
}
|
|
|
|
if (fixedParameterCount < parameterCount)
|
|
{
|
|
// The last parameter was a "params" array. As long as zero or more arguments
|
|
// are assignable, it's a valid candidate in the expanded form.
|
|
|
|
CandidateMember candidateMethod = null;
|
|
|
|
if (numArguments == fixedParameterCount)
|
|
{
|
|
// Zero arguments were passed as the params array. The method is a candidate
|
|
// in its expanded form.
|
|
candidateMethod = new CandidateMember(candidateMember, parameters, signature, CandidateMember.Form.Expanded);
|
|
}
|
|
else if (numArguments == parameterCount)
|
|
{
|
|
// Special case: one argument was passed as the params array.
|
|
CandidateParameter candidateParam = new CandidateParameter(lastParam);
|
|
if (candidateParam.Match(arguments[p], candidateName, p + 1, out error))
|
|
{
|
|
// It was the same array type as the params array, so the candidate
|
|
// matched in its normal form.
|
|
signature.Add(candidateParam);
|
|
candidateMethod = new CandidateMember(candidateMember, parameters, signature, CandidateMember.Form.Normal);
|
|
}
|
|
}
|
|
|
|
if (candidateMethod == null)
|
|
{
|
|
// One or more arguments were passed as the params array. As long
|
|
// as they match the element type, this method is a candidate.
|
|
CandidateParameter candidateParam = new CandidateParameter(lastParam.ParameterType.GetElementType());
|
|
|
|
for (; p < numArguments; ++p)
|
|
{
|
|
if (!candidateParam.Match(arguments[p], candidateName, p + 1, out error))
|
|
{
|
|
// Not all of the trailing arguments matched the params array's element type;
|
|
// this cannot be a candidate.
|
|
return;
|
|
}
|
|
|
|
// If we get here, then so far so good.
|
|
signature.Add(candidateParam);
|
|
}
|
|
|
|
// All the trailing arguments matched, so this is a candidate in the expanded form.
|
|
candidateMethod = new CandidateMember(candidateMember, parameters, signature, CandidateMember.Form.Expanded);
|
|
}
|
|
|
|
candidates.Add(candidateMethod);
|
|
}
|
|
else
|
|
{
|
|
// The last parameter wasn't "params". This candidate matched in its normal form.
|
|
candidates.Add(new CandidateMember(candidateMember, parameters, signature, CandidateMember.Form.Normal));
|
|
}
|
|
}
|
|
}
|
|
|
|
private CandidateMember FindBestCandidate(Type targetType, List<CandidateMember> candidates, List<Argument> arguments)
|
|
{
|
|
int numCandidates = candidates.Count;
|
|
Debug.Assert(numCandidates > 0, "expected at least one candidate");
|
|
|
|
// Start by assuming the first candidate is the best one.
|
|
List<CandidateMember> bestCandidates = new List<CandidateMember>(1);
|
|
bestCandidates.Add(candidates[0]);
|
|
|
|
// Go through the rest of the candidates and try to find a better one. (If
|
|
// there are no more candidates, then there was only one, and that's the right
|
|
// one.)
|
|
for (int i = 1; i < numCandidates; ++i)
|
|
{
|
|
CandidateMember newCandidate = candidates[i];
|
|
|
|
// Compare this new candidate one if the current "best" ones. (If there
|
|
// is currently more than one best candidate, then so far its ambiguous, which
|
|
// means all the best ones are equally good. Thus if this new candidate
|
|
// is better than one, it's better than all.
|
|
CandidateMember bestCandidate = bestCandidates[0];
|
|
|
|
int comparison = newCandidate.CompareMember(targetType, bestCandidate, arguments, this);
|
|
if (comparison > 0)
|
|
{
|
|
// The new one was better than at least one of the best ones. It
|
|
// becomes the new best one.
|
|
bestCandidates.Clear();
|
|
bestCandidates.Add(newCandidate);
|
|
}
|
|
else if (comparison == 0)
|
|
{
|
|
// The new one was no better, so add it to the list of current best.
|
|
// (Unless we find a better one, it's ambiguous so far.)
|
|
bestCandidates.Add(newCandidate);
|
|
}
|
|
}
|
|
|
|
if (bestCandidates.Count == 1)
|
|
{
|
|
// Good, there was exactly one best match.
|
|
return bestCandidates[0];
|
|
}
|
|
|
|
// Otherwise, it must have been ambiguous.
|
|
return null;
|
|
}
|
|
|
|
internal MethodInfo FindBestCandidate(Type targetType, List<MethodInfo> methods, params Type[] types)
|
|
{
|
|
List<Argument> arguments = new List<Argument>();
|
|
foreach (Type t in types)
|
|
arguments.Add(new Argument(t));
|
|
|
|
List<CandidateMember> candidates = new List<CandidateMember>(methods.Count);
|
|
foreach (MethodInfo method in methods)
|
|
{
|
|
ValidationError tempError = null;
|
|
EvaluateCandidate(candidates, method, method.GetParameters(), arguments, out tempError,
|
|
delegate(string name, int numArguments)
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.MethodArgCountMismatch, name, numArguments);
|
|
return new ValidationError(message, ErrorNumbers.Error_MethodArgCountMismatch);
|
|
});
|
|
}
|
|
if (candidates.Count == 0)
|
|
{
|
|
// nothing looks useful
|
|
return null;
|
|
}
|
|
CandidateMember result = FindBestCandidate(targetType, candidates, arguments);
|
|
return (result != null) ? (MethodInfo)result.Member : null;
|
|
}
|
|
|
|
internal RuleConstructorExpressionInfo ResolveConstructor(Type targetType, BindingFlags constructorBindingFlags, List<CodeExpression> argumentExprs, out ValidationError error)
|
|
{
|
|
string message;
|
|
|
|
List<Argument> arguments = new List<Argument>(argumentExprs.Count);
|
|
foreach (CodeExpression argumentExpr in argumentExprs)
|
|
arguments.Add(new Argument(argumentExpr, this));
|
|
|
|
// Get the candidate types and all candidate methods contained in them.
|
|
List<Type> candidateTypes = GetCandidateTargetTypes(targetType);
|
|
// Get all methods by this name...
|
|
List<ConstructorInfo> constructors = GetConstructors(candidateTypes, constructorBindingFlags);
|
|
if (constructors.Count == 0)
|
|
{
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.UnknownConstructor, RuleDecompiler.DecompileType(targetType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodNotExists);
|
|
return null;
|
|
}
|
|
|
|
// Cull the list of methods to those which match the supplied arguments.
|
|
List<CandidateMember> candidateConstructors = GetCandidateConstructors(constructors, arguments, out error);
|
|
|
|
// If the list is null, then no candidates matched.
|
|
if (candidateConstructors == null)
|
|
return null;
|
|
|
|
// We found candidate methods in this type.
|
|
CandidateMember bestCandidate = FindBestCandidate(targetType, candidateConstructors, arguments);
|
|
|
|
if (bestCandidate == null)
|
|
{
|
|
// It was ambiguous.
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.AmbiguousConstructor, RuleDecompiler.DecompileType(targetType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_CannotResolveMember);
|
|
return null;
|
|
}
|
|
|
|
// We found the best match.
|
|
return new RuleConstructorExpressionInfo((ConstructorInfo)bestCandidate.Member, bestCandidate.IsExpanded);
|
|
}
|
|
|
|
internal RuleMethodInvokeExpressionInfo ResolveMethod(Type targetType, string methodName, BindingFlags methodBindingFlags, List<CodeExpression> argumentExprs, out ValidationError error)
|
|
{
|
|
string message;
|
|
|
|
List<Argument> arguments = new List<Argument>(argumentExprs.Count);
|
|
foreach (CodeExpression argumentExpr in argumentExprs)
|
|
arguments.Add(new Argument(argumentExpr, this));
|
|
|
|
// Get the candidate types and all candidate methods contained in them.
|
|
List<Type> candidateTypes = GetCandidateTargetTypes(targetType);
|
|
// Get all methods by this name...
|
|
List<MethodInfo> methods = GetNamedMethods(candidateTypes, methodName, methodBindingFlags);
|
|
if (methods.Count == 0)
|
|
{
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.UnknownMethod, methodName, RuleDecompiler.DecompileType(targetType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodNotExists);
|
|
return null;
|
|
}
|
|
|
|
// Cull the list of methods to those which match the supplied arguments.
|
|
List<CandidateMember> candidateMethods = GetCandidateMethods(methodName, methods, arguments, out error);
|
|
|
|
// If the list is null, then no candidates matched.
|
|
if (candidateMethods == null)
|
|
return null;
|
|
|
|
// We found candidate methods in this type.
|
|
CandidateMember bestCandidate = FindBestCandidate(targetType, candidateMethods, arguments);
|
|
|
|
if (bestCandidate == null)
|
|
{
|
|
// It was ambiguous.
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.AmbiguousMatch, methodName);
|
|
error = new ValidationError(message, ErrorNumbers.Error_CannotResolveMember);
|
|
|
|
return null;
|
|
}
|
|
|
|
// We found the best match.
|
|
MethodInfo theMethod = (MethodInfo)bestCandidate.Member;
|
|
if (theMethod != null)
|
|
{
|
|
IsAuthorized(theMethod.ReturnType);
|
|
}
|
|
return new RuleMethodInvokeExpressionInfo(theMethod, bestCandidate.IsExpanded);
|
|
}
|
|
|
|
internal static List<ConstructorInfo> GetConstructors(List<Type> targetTypes, BindingFlags constructorBindingFlags)
|
|
{
|
|
List<ConstructorInfo> methods = new List<ConstructorInfo>();
|
|
|
|
for (int t = 0; t < targetTypes.Count; ++t)
|
|
{
|
|
Type targetType = targetTypes[t];
|
|
|
|
// Go through all the constructors on the target type
|
|
ConstructorInfo[] members = targetType.GetConstructors(constructorBindingFlags);
|
|
for (int m = 0; m < members.Length; ++m)
|
|
{
|
|
ConstructorInfo constructor = members[m];
|
|
if (constructor.IsGenericMethod) // skip generic constructors
|
|
continue;
|
|
if (constructor.IsStatic) // skip static constructors
|
|
continue;
|
|
if (constructor.IsPrivate) // skip private constructors
|
|
continue;
|
|
if (constructor.IsFamily) // skip internal constructors
|
|
continue;
|
|
methods.Add(constructor);
|
|
}
|
|
}
|
|
return methods;
|
|
}
|
|
|
|
private List<MethodInfo> GetNamedMethods(List<Type> targetTypes, string methodName, BindingFlags methodBindingFlags)
|
|
{
|
|
List<MethodInfo> methods = new List<MethodInfo>();
|
|
List<ExtensionMethodInfo> currentExtensionMethods = ExtensionMethods;
|
|
for (int t = 0; t < targetTypes.Count; ++t)
|
|
{
|
|
Type targetType = targetTypes[t];
|
|
|
|
// Go through all the methods on the target type that have matching names.
|
|
MemberInfo[] members = targetType.GetMember(methodName, MemberTypes.Method, methodBindingFlags);
|
|
for (int m = 0; m < members.Length; ++m)
|
|
{
|
|
MethodInfo method = (MethodInfo)members[m];
|
|
if (!method.IsGenericMethod) // skip generic methods
|
|
methods.Add(method);
|
|
}
|
|
|
|
// add in any extension methods that match
|
|
foreach (ExtensionMethodInfo extension in currentExtensionMethods)
|
|
{
|
|
// does it have the right name and is the type compatible
|
|
ValidationError error;
|
|
if ((extension.Name == methodName) &&
|
|
TypesAreAssignable(targetType, extension.AssumedDeclaringType, null, out error))
|
|
{
|
|
// possible match
|
|
methods.Add(extension);
|
|
}
|
|
}
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
private List<ExtensionMethodInfo> extensionMethods;
|
|
private List<Assembly> seenAssemblies;
|
|
private const string ExtensionAttributeFullName = "System.Runtime.CompilerServices.ExtensionAttribute, " + AssemblyRef.SystemCore;
|
|
private Type extensionAttribute;
|
|
|
|
private static Type defaultExtensionAttribute = GetDefaultExtensionAttribute();
|
|
|
|
private static Type GetDefaultExtensionAttribute()
|
|
{
|
|
return Type.GetType(ExtensionAttributeFullName, false);
|
|
}
|
|
|
|
// The extensionAttributeType may still be null after calling this method
|
|
// if, for example, we are in a 3.0 SP2 environment.
|
|
private void SetExtensionAttribute()
|
|
{
|
|
// use the TypeProvider first
|
|
extensionAttribute = typeProvider.GetType(ExtensionAttributeFullName, false);
|
|
if (extensionAttribute == null)
|
|
{
|
|
extensionAttribute = defaultExtensionAttribute;
|
|
}
|
|
}
|
|
|
|
internal List<ExtensionMethodInfo> ExtensionMethods
|
|
{
|
|
get
|
|
{
|
|
if (extensionMethods == null)
|
|
DetermineExtensionMethods();
|
|
|
|
return extensionMethods;
|
|
}
|
|
}
|
|
|
|
private void DetermineExtensionMethods()
|
|
{
|
|
extensionMethods = new List<ExtensionMethodInfo>();
|
|
|
|
SetExtensionAttribute();
|
|
if (extensionAttribute != null)
|
|
{
|
|
seenAssemblies = new List<Assembly>();
|
|
Assembly localAssembly = typeProvider.LocalAssembly;
|
|
if (localAssembly != null)
|
|
{
|
|
DetermineExtensionMethods(localAssembly);
|
|
foreach (Assembly a in typeProvider.ReferencedAssemblies)
|
|
DetermineExtensionMethods(a);
|
|
}
|
|
else
|
|
{
|
|
// probably at design-time, nothing compiled yet
|
|
// go through all types it knows about
|
|
DetermineExtensionMethods(typeProvider.GetTypes());
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void DetermineExtensionMethods(Assembly assembly)
|
|
{
|
|
// when this method is called outside of this class, we must have tried
|
|
// getting ExtensionMethods. So we must have tried setting extensionAttributeType.
|
|
|
|
if (extensionAttribute != null)
|
|
{
|
|
if ((assembly != null) && (!seenAssemblies.Contains(assembly)))
|
|
{
|
|
seenAssemblies.Add(assembly);
|
|
if (IsMarkedExtension(assembly))
|
|
{
|
|
Type[] types;
|
|
try
|
|
{
|
|
types = assembly.GetTypes();
|
|
}
|
|
catch (ReflectionTypeLoadException e)
|
|
{
|
|
// problems loading all the types, take what we can get
|
|
// some types will be null
|
|
types = e.Types;
|
|
}
|
|
DetermineExtensionMethods(types);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DetermineExtensionMethods(Type[] types)
|
|
{
|
|
foreach (Type type in types)
|
|
{
|
|
// static classes are defined as "abstract sealed"
|
|
// Note: VB doesn't support static classes, so the modules are only defined as "sealed"
|
|
if ((type != null) && (type.IsPublic || type.IsNestedPublic) && (type.IsSealed) && (IsMarkedExtension(type)))
|
|
{
|
|
// looks like a class containing extension methods, let's find them
|
|
MethodInfo[] staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
|
|
foreach (MethodInfo mi in staticMethods)
|
|
{
|
|
// skip generic methods
|
|
if ((mi.IsStatic) && !(mi.IsGenericMethod) && (IsMarkedExtension(mi)))
|
|
{
|
|
ParameterInfo[] parms = mi.GetParameters();
|
|
if (parms.Length > 0 && parms[0].ParameterType != null)
|
|
{
|
|
extensionMethods.Add(new ExtensionMethodInfo(mi, parms));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsMarkedExtension(Assembly assembly)
|
|
{
|
|
if (extensionAttribute != null)
|
|
{
|
|
object[] objAttrs = assembly.GetCustomAttributes(extensionAttribute, false);
|
|
if (objAttrs != null && objAttrs.Length > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsMarkedExtension(Type type)
|
|
{
|
|
if (extensionAttribute != null)
|
|
{
|
|
object[] objAttrs = type.GetCustomAttributes(extensionAttribute, false);
|
|
if (objAttrs != null && objAttrs.Length > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsMarkedExtension(MethodInfo mi)
|
|
{
|
|
if (extensionAttribute != null)
|
|
{
|
|
object[] objAttrs = mi.GetCustomAttributes(extensionAttribute, false);
|
|
if (objAttrs != null && objAttrs.Length > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static List<CandidateMember> GetCandidateMethods(string methodName, List<MethodInfo> methods, List<Argument> arguments, out ValidationError error)
|
|
{
|
|
List<CandidateMember> candidates = new List<CandidateMember>();
|
|
|
|
error = null;
|
|
|
|
int errorCount = 0;
|
|
foreach (MethodInfo method in methods)
|
|
{
|
|
ValidationError tempError = null;
|
|
EvaluateCandidate(candidates, method, method.GetParameters(), arguments, out tempError,
|
|
delegate(string name, int numArguments)
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.MethodArgCountMismatch, name, numArguments);
|
|
return new ValidationError(message, ErrorNumbers.Error_MethodArgCountMismatch);
|
|
});
|
|
|
|
error = tempError;
|
|
if (tempError != null)
|
|
++errorCount;
|
|
}
|
|
|
|
if (candidates.Count == 0)
|
|
{
|
|
// No candidates were found.
|
|
|
|
if (errorCount > 1)
|
|
{
|
|
// If multiple candidates generated errors, then use a more generic error that says
|
|
// we couldn't find a matching overload.
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.MethodOverloadNotFound, methodName);
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodOverloadNotFound);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
// If there are any candidates, then wipe out any errors left over from any mismatches.
|
|
error = null;
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
static List<CandidateMember> GetCandidateConstructors(List<ConstructorInfo> constructors, List<Argument> arguments, out ValidationError error)
|
|
{
|
|
List<CandidateMember> candidates = new List<CandidateMember>();
|
|
|
|
error = null;
|
|
|
|
int errorCount = 0;
|
|
foreach (ConstructorInfo method in constructors)
|
|
{
|
|
ValidationError tempError = null;
|
|
EvaluateCandidate(candidates, method, method.GetParameters(), arguments, out tempError,
|
|
delegate(string name, int numArguments)
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.MethodArgCountMismatch, name, numArguments);
|
|
return new ValidationError(message, ErrorNumbers.Error_MethodArgCountMismatch);
|
|
});
|
|
|
|
error = tempError;
|
|
if (tempError != null)
|
|
++errorCount;
|
|
}
|
|
|
|
if (candidates.Count == 0)
|
|
{
|
|
// No candidates were found.
|
|
|
|
if (errorCount > 1)
|
|
{
|
|
// If multiple candidates generated errors, then use a more generic error that says
|
|
// we couldn't find a matching overload.
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.ConstructorOverloadNotFound);
|
|
error = new ValidationError(message, ErrorNumbers.Error_MethodOverloadNotFound);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
// If there are any candidates, then wipe out any errors left over from any mismatches.
|
|
error = null;
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
internal RulePropertyExpressionInfo ResolveIndexerProperty(Type targetType, BindingFlags bindingFlags, List<CodeExpression> argumentExprs, out ValidationError error)
|
|
{
|
|
string message;
|
|
|
|
int numArgs = argumentExprs.Count;
|
|
|
|
if (numArgs < 1)
|
|
{
|
|
// Must have at least one indexer!
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.IndexerCountMismatch, numArgs);
|
|
error = new ValidationError(message, ErrorNumbers.Error_IndexerCountMismatch);
|
|
return null;
|
|
}
|
|
|
|
List<Argument> arguments = new List<Argument>(numArgs);
|
|
foreach (CodeExpression argumentExpr in argumentExprs)
|
|
arguments.Add(new Argument(argumentExpr, this));
|
|
|
|
// Get the candidate types and all the candidate indexer properties contained in them.
|
|
List<Type> candidateTypes = GetCandidateTargetTypes(targetType);
|
|
List<PropertyInfo> indexerProperties = GetIndexerProperties(candidateTypes, bindingFlags);
|
|
if (indexerProperties.Count == 0)
|
|
{
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.IndexerNotFound, RuleDecompiler.DecompileType(targetType));
|
|
error = new ValidationError(message, ErrorNumbers.Error_IndexerNotFound);
|
|
return null;
|
|
}
|
|
|
|
List<CandidateMember> candidateIndexers = GetCandidateIndexers(indexerProperties, arguments, out error);
|
|
|
|
// If the list is null, then no candidates matched.
|
|
if (candidateIndexers == null)
|
|
return null;
|
|
|
|
// We found candidate methods in this type.
|
|
CandidateMember bestCandidate = FindBestCandidate(targetType, candidateIndexers, arguments);
|
|
|
|
if (bestCandidate == null)
|
|
{
|
|
// It was ambiguous.
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.AmbiguousIndexerMatch);
|
|
error = new ValidationError(message, ErrorNumbers.Error_CannotResolveMember);
|
|
|
|
return null;
|
|
}
|
|
|
|
// We found the best match.
|
|
PropertyInfo pi = (PropertyInfo)bestCandidate.Member;
|
|
if (pi != null)
|
|
{
|
|
IsAuthorized(pi.PropertyType);
|
|
}
|
|
return new RulePropertyExpressionInfo(pi, pi.PropertyType, bestCandidate.IsExpanded);
|
|
}
|
|
|
|
private static List<PropertyInfo> GetIndexerProperties(List<Type> candidateTypes, BindingFlags bindingFlags)
|
|
{
|
|
List<PropertyInfo> indexerProperties = new List<PropertyInfo>();
|
|
|
|
foreach (Type targetType in candidateTypes)
|
|
{
|
|
object[] attrs = targetType.GetCustomAttributes(typeof(DefaultMemberAttribute), true);
|
|
if (attrs == null || attrs.Length == 0)
|
|
continue;
|
|
|
|
DefaultMemberAttribute[] defaultMemberAttrs = (DefaultMemberAttribute[])attrs;
|
|
|
|
PropertyInfo[] properties = targetType.GetProperties(bindingFlags);
|
|
for (int p = 0; p < properties.Length; ++p)
|
|
{
|
|
PropertyInfo pi = properties[p];
|
|
|
|
// Select only those properties whose name matches the default name.
|
|
bool matchedName = false;
|
|
for (int dm = 0; dm < defaultMemberAttrs.Length; ++dm)
|
|
{
|
|
if (defaultMemberAttrs[dm].MemberName == pi.Name)
|
|
{
|
|
matchedName = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matchedName)
|
|
{
|
|
// We matched the name...
|
|
ParameterInfo[] indexerParameters = pi.GetIndexParameters();
|
|
if (indexerParameters.Length > 0)
|
|
{
|
|
// ... and have indexer parameters; therefore, this is
|
|
// an interesting property.
|
|
indexerProperties.Add(pi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return indexerProperties;
|
|
}
|
|
|
|
private static List<CandidateMember> GetCandidateIndexers(List<PropertyInfo> indexerProperties, List<Argument> arguments, out ValidationError error)
|
|
{
|
|
List<CandidateMember> candidates = new List<CandidateMember>();
|
|
|
|
error = null;
|
|
|
|
int errorCount = 0;
|
|
foreach (PropertyInfo indexerProp in indexerProperties)
|
|
{
|
|
ValidationError tempError = null;
|
|
EvaluateCandidate(candidates, indexerProp, indexerProp.GetIndexParameters(), arguments, out tempError,
|
|
delegate(string propName, int numArguments)
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.IndexerCountMismatch, numArguments);
|
|
return new ValidationError(message, ErrorNumbers.Error_IndexerCountMismatch);
|
|
});
|
|
|
|
error = tempError;
|
|
if (tempError != null)
|
|
++errorCount;
|
|
}
|
|
|
|
if (candidates.Count == 0)
|
|
{
|
|
// No candidates were found.
|
|
|
|
if (errorCount > 1)
|
|
{
|
|
// If multiple candidates generated errors, then use a more generic error that says
|
|
// we couldn't find a matching overload.
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.IndexerOverloadNotFound);
|
|
error = new ValidationError(message, ErrorNumbers.Error_IndexerOverloadNotFound);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
// If there are any candidates, then wipe out any errors left over from any mismatches.
|
|
error = null;
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Type resolution
|
|
|
|
internal void AddTypeReference(CodeTypeReference typeRef, Type type)
|
|
{
|
|
typeRefMap[typeRef] = type;
|
|
}
|
|
|
|
internal Type ResolveType(CodeTypeReference typeRef)
|
|
{
|
|
Type resultType = null;
|
|
|
|
if (!typeRefMap.TryGetValue(typeRef, out resultType))
|
|
{
|
|
string message;
|
|
|
|
resultType = FindType(typeRef.BaseType);
|
|
|
|
if (resultType == null)
|
|
{
|
|
// check if we have a qualifiedname saved, and if we do, use it
|
|
string qualifiedName = typeRef.UserData[RuleUserDataKeys.QualifiedName] as string;
|
|
resultType = ResolveType(qualifiedName);
|
|
if (resultType != null)
|
|
{
|
|
// qualified name returned the complete type, save it and we're done
|
|
typeRefMap.Add(typeRef, resultType);
|
|
return resultType;
|
|
}
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.UnknownType, typeRef.BaseType);
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_UnableToResolveType);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = typeRef;
|
|
Errors.Add(error);
|
|
return null;
|
|
}
|
|
|
|
// Handle generic type arguments.
|
|
if (typeRef.TypeArguments.Count > 0)
|
|
{
|
|
Type[] typeArguments = new Type[typeRef.TypeArguments.Count];
|
|
for (int i = 0; i < typeRef.TypeArguments.Count; ++i)
|
|
{
|
|
// design-time types don't have fully-qualified names, so when they are
|
|
// used in a generic CodeTypeReference constructor leaves them with []
|
|
// surrounding them. Remove the [] if possible
|
|
CodeTypeReference arg = typeRef.TypeArguments[i];
|
|
if (arg.BaseType.StartsWith("[", StringComparison.Ordinal))
|
|
arg.BaseType = arg.BaseType.Substring(1, arg.BaseType.Length - 2);
|
|
|
|
typeArguments[i] = ResolveType(arg);
|
|
if (typeArguments[i] == null)
|
|
return null;
|
|
}
|
|
|
|
resultType = resultType.MakeGenericType(typeArguments);
|
|
if (resultType == null)
|
|
{
|
|
StringBuilder sb = new StringBuilder(typeRef.BaseType);
|
|
string prefix = "<";
|
|
foreach (Type t in typeArguments)
|
|
{
|
|
sb.Append(prefix);
|
|
prefix = ",";
|
|
sb.Append(RuleDecompiler.DecompileType(t));
|
|
}
|
|
sb.Append(">");
|
|
message = string.Format(CultureInfo.CurrentCulture, Messages.UnknownGenericType, sb.ToString());
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_UnableToResolveType);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = typeRef;
|
|
Errors.Add(error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
if (resultType != null)
|
|
{
|
|
CodeTypeReference arrayTypeRef = typeRef;
|
|
if (arrayTypeRef.ArrayRank > 0)
|
|
{
|
|
do
|
|
{
|
|
resultType = (arrayTypeRef.ArrayRank == 1) ? resultType.MakeArrayType() : resultType.MakeArrayType(arrayTypeRef.ArrayRank);
|
|
|
|
arrayTypeRef = arrayTypeRef.ArrayElementType;
|
|
} while (arrayTypeRef.ArrayRank > 0);
|
|
}
|
|
}
|
|
|
|
if (resultType != null)
|
|
{
|
|
typeRefMap.Add(typeRef, resultType);
|
|
|
|
// at runtime we may not have the assembly loaded, so keep the fully qualified name around
|
|
typeRef.UserData[RuleUserDataKeys.QualifiedName] = resultType.AssemblyQualifiedName;
|
|
}
|
|
}
|
|
|
|
return resultType;
|
|
}
|
|
|
|
internal Type ResolveType(string qualifiedName)
|
|
{
|
|
Type resultType = null;
|
|
if (qualifiedName != null)
|
|
{
|
|
resultType = typeProvider.GetType(qualifiedName, false);
|
|
|
|
// if the Typeprovider can't find it, use the framework,
|
|
// since it should be an AssemblyQualifiedName
|
|
if (resultType == null)
|
|
resultType = Type.GetType(qualifiedName, false);
|
|
|
|
}
|
|
return resultType;
|
|
}
|
|
|
|
private Type FindType(string typeName)
|
|
{
|
|
if (typeName == null)
|
|
throw new ArgumentNullException("typeName");
|
|
|
|
Type type = null;
|
|
|
|
// do we know about this type
|
|
if (!typesUsed.TryGetValue(typeName, out type))
|
|
{
|
|
type = typeProvider.GetType(typeName, false);
|
|
|
|
if (type != null)
|
|
{
|
|
typesUsed.Add(typeName, type);
|
|
|
|
IsAuthorized(type);
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
internal void IsAuthorized(Type type)
|
|
{
|
|
Debug.Assert(!type.IsPointer && !type.IsByRef,
|
|
"IsAuthorized should not be called for a type that is a pointer or passed by reference : " + type.AssemblyQualifiedName);
|
|
if (checkStaticType)
|
|
{
|
|
if (authorizedTypes == null)
|
|
{
|
|
ValidationError error = new ValidationError(Messages.Error_ConfigFileMissingOrInvalid, ErrorNumbers.Error_ConfigFileMissingOrInvalid);
|
|
Errors.Add(error);
|
|
}
|
|
else
|
|
{
|
|
while (type.IsArray)
|
|
{
|
|
type = type.GetElementType();
|
|
}
|
|
if (type.IsGenericType)
|
|
{
|
|
IsAuthorizedSimpleType(type.GetGenericTypeDefinition());
|
|
Type[] typeArguments = type.GetGenericArguments();
|
|
foreach (Type t in typeArguments)
|
|
{
|
|
IsAuthorized(t);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IsAuthorizedSimpleType(type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsAuthorizedSimpleType(Type type)
|
|
{
|
|
Debug.Assert((!type.IsGenericType || type.IsGenericTypeDefinition) && !type.HasElementType,
|
|
"IsAuthorizedSimpleType should not be called for a partially specialized generic type or a type that encompasses or refers to another type : " +
|
|
type.AssemblyQualifiedName);
|
|
|
|
string qualifiedName = type.AssemblyQualifiedName;
|
|
|
|
if (!typesUsedAuthorized.ContainsKey(qualifiedName))
|
|
{
|
|
bool authorized = false;
|
|
foreach (AuthorizedType authorizedType in authorizedTypes)
|
|
{
|
|
if (authorizedType.RegularExpression.IsMatch(qualifiedName))
|
|
{
|
|
authorized = (String.Compare(bool.TrueString, authorizedType.Authorized, StringComparison.OrdinalIgnoreCase) == 0);
|
|
if (!authorized)
|
|
break;
|
|
}
|
|
}
|
|
if (!authorized)
|
|
{
|
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.Error_TypeNotAuthorized, type.FullName);
|
|
ValidationError error = new ValidationError(message, ErrorNumbers.Error_TypeNotAuthorized);
|
|
error.UserData[RuleUserDataKeys.ErrorObject] = type;
|
|
Errors.Add(error);
|
|
}
|
|
else
|
|
{
|
|
typesUsedAuthorized.Add(qualifiedName, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|
|
#endregion RuleValidator
|
|
}
|