diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 56f6b0ce..d65fa6e2 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -233,6 +233,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 55c3494a..8baace34 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -238,6 +238,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 1055878b..3da48f52 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -227,6 +227,7 @@ + diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index f55c8252..082bbaba 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -55,7 +55,7 @@ namespace Newtonsoft.Json.Utilities ILGenerator generator = dynamicMethod.GetILGenerator(); GenerateCreateMethodCallIL(method, generator, 0); - + return (ObjectConstructor)dynamicMethod.CreateDelegate(typeof(ObjectConstructor)); } @@ -90,7 +90,9 @@ namespace Newtonsoft.Json.Utilities generator.PushInstance(method.DeclaringType); } - int localVariableCount = 0; + LocalBuilder localConvertible = generator.DeclareLocal(typeof(IConvertible)); + LocalBuilder localObject = generator.DeclareLocal(typeof(object)); + int localVariableCount = 2; for (int i = 0; i < args.Length; i++) { @@ -125,7 +127,7 @@ namespace Newtonsoft.Json.Utilities generator.MarkLabel(skipSettingDefault); generator.PushArrayInstance(argsIndex, i); generator.UnboxIfNeeded(parameterType); - generator.Emit(OpCodes.Stloc, localVariableCount); + generator.Emit(OpCodes.Stloc_S, localVariable); // parameter finished, we out! generator.MarkLabel(finishedProcessingParameter); @@ -133,7 +135,7 @@ namespace Newtonsoft.Json.Utilities else { generator.UnboxIfNeeded(parameterType); - generator.Emit(OpCodes.Stloc, localVariableCount); + generator.Emit(OpCodes.Stloc_S, localVariable); } } @@ -142,8 +144,9 @@ namespace Newtonsoft.Json.Utilities localVariableCount++; } else if (parameterType.IsValueType()) - { + { generator.PushArrayInstance(argsIndex, i); + generator.Emit(OpCodes.Stloc_S, localObject); // have to check that value type parameters aren't null // otherwise they will error when unboxed @@ -151,34 +154,59 @@ namespace Newtonsoft.Json.Utilities Label finishedProcessingParameter = generator.DefineLabel(); // check if parameter is not null + generator.Emit(OpCodes.Ldloc_S, localObject); generator.Emit(OpCodes.Brtrue_S, skipSettingDefault); // parameter has no value, initialize to default LocalBuilder localVariable = generator.DeclareLocal(parameterType); generator.Emit(OpCodes.Ldloca_S, localVariable); generator.Emit(OpCodes.Initobj, parameterType); - generator.Emit(OpCodes.Ldloc, localVariableCount); + generator.Emit(OpCodes.Ldloc_S, localVariable); generator.Emit(OpCodes.Br_S, finishedProcessingParameter); - // parameter has value, get value from array again and unbox + // argument has value, try to convert it to parameter type generator.MarkLabel(skipSettingDefault); - generator.PushArrayInstance(argsIndex, i); - + if (parameterType.IsPrimitive()) { - // for primitive types we need to call Convert.ChangeType() to correctly - // convert value from other compatible primitive types (e.g. short -> int) + // for primitive types we need to handle type widening (e.g. short -> int) - generator.Emit(OpCodes.Ldtoken, parameterType); - generator.Emit(OpCodes.Call, typeof(Type).GetMethod( - "GetTypeFromHandle", new[] { typeof(RuntimeTypeHandle) })); - - generator.Emit(OpCodes.Call, typeof(Convert).GetMethod( - "ChangeType", new[] { typeof(object), typeof(Type) })); + MethodInfo toParameterTypeMethod = typeof(IConvertible) + .GetMethod("To" + parameterType.Name, new[] { typeof(IFormatProvider) }); + + if (toParameterTypeMethod != null) + { + Label skipConvertible = generator.DefineLabel(); + + // check if argument type is an exact match for parameter type + // in this case we may use cheap unboxing instead + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.Emit(OpCodes.Isinst, parameterType); + generator.Emit(OpCodes.Brtrue_S, skipConvertible); + + // types don't match, check if argument implements IConvertible + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.Emit(OpCodes.Isinst, typeof(IConvertible)); + generator.Emit(OpCodes.Stloc_S, localConvertible); + generator.Emit(OpCodes.Ldloc_S, localConvertible); + generator.Emit(OpCodes.Brfalse_S, skipConvertible); + + // convert argument to parameter type + generator.Emit(OpCodes.Ldloc_S, localConvertible); + generator.Emit(OpCodes.Ldnull); + generator.Emit(OpCodes.Callvirt, toParameterTypeMethod); + generator.Emit(OpCodes.Br_S, finishedProcessingParameter); + + generator.MarkLabel(skipConvertible); + } } - generator.UnboxIfNeeded(parameterType); + // we got here because either argument type matches parameter (conversion will succeed), + // or argument type doesn't match parameter, but we're out of options (conversion will fail) + generator.Emit(OpCodes.Ldloc_S, localObject); + generator.UnboxIfNeeded(parameterType); + // parameter finished, we out! generator.MarkLabel(finishedProcessingParameter); localVariableCount++; diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index b6235e1a..f8fc8082 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -362,9 +362,13 @@ namespace Newtonsoft.Json.Utilities if (targetType.IsPrimitive()) { - expression = Expression.Call( - typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }), - expression, Expression.Constant(targetType)); + MethodInfo toTargetTypeMethod = typeof(Convert) + .GetMethod("To" + targetType.Name, new[] { typeof(object) }); + + if (toTargetTypeMethod != null) + { + expression = Expression.Call(toTargetTypeMethod, expression); + } } return Expression.Convert(expression, targetType); diff --git a/Src/Newtonsoft.Json/Utilities/MethodBinder.cs b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs new file mode 100644 index 00000000..202321da --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/MethodBinder.cs @@ -0,0 +1,333 @@ +namespace Newtonsoft.Json.Utilities +{ +#if PORTABLE + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + internal static class MethodBinder + { + + /// + /// List of primitive types which can be widened. + /// + private static readonly Type[] PrimitiveTypes = new Type[] + { + typeof(bool), typeof(char), typeof(sbyte), typeof(byte), + typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(float), typeof(double) + }; + + /// + /// Widening masks for primitive types above. + /// Index of the value in this array defines a type we're widening, + /// while the bits in mask define types it can be widened to (including itself). + /// + /// For example, value at index 0 defines a bool type, and it only has bit 0 set, + /// i.e. bool values can be assigned only to bool. + /// + private static readonly int[] WideningMasks = new int[] + { + 0x0001, 0x0FE2, 0x0D54, 0x0FFA, + 0x0D50, 0x0FE2, 0x0D40, 0x0F80, + 0x0D00, 0x0E00, 0x0C00, 0x0800 + }; + + /// + /// Checks if value of primitive type can be + /// assigned to parameter of primitive type . + /// + /// Source primitive type + /// Target primitive type + /// True if source type can be widened to target type, false otherwise + internal static bool CanConvertPrimitive(Type from, Type to) + { + if (from == to) + { + // same type + return true; + } + + int fromMask = 0, toMask = 0; + + for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) + { + if (PrimitiveTypes[i] == from) + { + fromMask = WideningMasks[i]; + } + else if (PrimitiveTypes[i] == to) + { + toMask = 1 << i; + } + } + + return (fromMask & toMask) != 0; + } + + /// + /// Checks if a set of values with given can be used + /// to invoke a method with specified . + /// + /// Method parameters + /// Argument types + /// Try to pack extra arguments into ParamArray + /// True if method can be called with given arguments, false otherwise + private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + if (parameters.Length == 0) + { + // fast check for parameterless methods + return types.Count == 0; + } + if (parameters.Length > types.Count) + { + // not all declared parameters were specified (optional parameters are not supported) + return false; + } + + // check if the last parameter is ParamArray + Type paramArrayType = null; + + if (enableParamArray) + { + ParameterInfo lastParam = parameters[parameters.Length - 1]; + if (lastParam.ParameterType.IsArray && lastParam.IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType = lastParam.ParameterType.GetElementType(); + } + } + + if (paramArrayType == null && parameters.Length != types.Count) + { + // when there's no ParamArray, number of parameters should match + return false; + } + + for (int i = 0; i < types.Count; i++) + { + Type paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType; + + if (paramType == types[i]) + { + // exact match with provided type + continue; + } + + if (paramType == typeof(object)) + { + // parameter of type object matches anything + continue; + } + + if (paramType.IsPrimitive()) + { + if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType)) + { + // primitive parameter can only be assigned from compatible primitive type + return false; + } + } + else + { + if (!paramType.IsAssignableFrom(types[i])) + { + return false; + } + } + } + + return true; + } + + /// + /// Compares two sets of parameters to determine + /// which one suits better for given argument types. + /// + private class ParametersMatchComparer : IComparer + { + private readonly IList _types; + private readonly bool _enableParamArray; + + public ParametersMatchComparer(IList types, bool enableParamArray) + { + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + + _types = types; + _enableParamArray = enableParamArray; + } + + public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) + { + if (parameters1 == null) + { + throw new ArgumentNullException(nameof(parameters1)); + } + if (parameters2 == null) + { + throw new ArgumentNullException(nameof(parameters2)); + } + + // parameterless method wins + if (parameters1.Length == 0) + { + return -1; + } + if (parameters2.Length == 0) + { + return 1; + } + + Type paramArrayType1 = null, paramArrayType2 = null; + + if (_enableParamArray) + { + ParameterInfo lastParam1 = parameters1[parameters1.Length - 1]; + if (lastParam1.ParameterType.IsArray && lastParam1.IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType1 = lastParam1.ParameterType.GetElementType(); + } + + ParameterInfo lastParam2 = parameters2[parameters2.Length - 1]; + if (lastParam2.ParameterType.IsArray && lastParam2.IsDefined(typeof(ParamArrayAttribute))) + { + paramArrayType2 = lastParam2.ParameterType.GetElementType(); + } + + // A method using params always loses to one not using params + if (paramArrayType1 != null && paramArrayType2 == null) + { + return 1; + } + if (paramArrayType2 != null && paramArrayType1 == null) + { + return -1; + } + } + + for (int i = 0; i < _types.Count; i++) + { + Type type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType; + Type type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType; + + if (type1 == type2) + { + // exact match between parameter types doesn't change score + continue; + } + + // exact match with source type decides winner immediately + if (type1 == _types[i]) + { + return -1; + } + if (type2 == _types[i]) + { + return 1; + } + + int r = ChooseMorePreciseType(type1, type2); + if (r != 0) + { + // winner decided + return r; + } + } + + return 0; + } + + private static int ChooseMorePreciseType(Type type1, Type type2) + { + if (type1.IsByRef || type2.IsByRef) + { + if (type1.IsByRef && type2.IsByRef) + { + type1 = type1.GetElementType(); + type2 = type2.GetElementType(); + } + else if (type1.IsByRef) + { + type1 = type1.GetElementType(); + if (type1 == type2) + { + return 1; + } + } + else + { + type2 = type2.GetElementType(); + if (type2 == type1) + { + return -1; + } + } + } + + bool c1FromC2, c2FromC1; + + if (type1.IsPrimitive() && type2.IsPrimitive()) + { + c1FromC2 = CanConvertPrimitive(type2, type1); + c2FromC1 = CanConvertPrimitive(type1, type2); + } + else + { + c1FromC2 = type1.IsAssignableFrom(type2); + c2FromC1 = type2.IsAssignableFrom(type1); + } + + if (c1FromC2 == c2FromC1) + { + return 0; + } + + return c1FromC2 ? 1 : -1; + } + + } + + /// + /// Retunrs a best method overload for given argument . + /// + /// List of method candidates + /// Argument types + /// Best method overload, or null if none matched + public static TMethod SelectMethod(IEnumerable candidates, IList types) where TMethod : MethodBase + { + if (candidates == null) + { + throw new ArgumentNullException(nameof(candidates)); + } + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + + // ParamArrays are not supported by ReflectionDelegateFactory + // They will be treated like ordinary array arguments + const bool enableParamArray = false; + + return candidates + .Where(m => FilterParameters(m.GetParameters(), types, enableParamArray)) + .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray)) + .FirstOrDefault(); + } + + } + +#endif +} diff --git a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs index 79878c90..f4554dd0 100644 --- a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs +++ b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs @@ -274,225 +274,6 @@ namespace Newtonsoft.Json.Utilities #if !DOTNET - private static readonly Type[] PrimitiveTypes = new Type[] - { - typeof(bool), typeof(char), typeof(sbyte), typeof(byte), - typeof(short), typeof(ushort), typeof(int), typeof(uint), - typeof(long), typeof(ulong), typeof(float), typeof(double) - }; - - private static readonly int[] WideningMasks = new int[] - { - 0x0001, 0x0FE2, 0x0D54, 0x0FFA, - 0x0D50, 0x0FE2, 0x0D40, 0x0F80, - 0x0D00, 0x0E00, 0x0C00, 0x0800 - }; - - internal static bool CanConvertPrimitive(Type from, Type to) - { - if (from == to) return true; - - int fromMask = 0, toMask = 0; - - for (int i = 0; i < PrimitiveTypes.Length && (fromMask == 0 || toMask == 0); i++) - { - if (PrimitiveTypes[i] == from) - { - fromMask = WideningMasks[i]; - } - else if (PrimitiveTypes[i] == to) - { - toMask = 1 << i; - } - } - - return (fromMask & toMask) != 0; - } - - private static bool FilterParameters(ParameterInfo[] parameters, IList types, bool enableParamArray) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - if (types == null) - throw new ArgumentNullException(nameof(types)); - - if (parameters.Length == 0) - { - // fast check for parameterless methods - return types.Count == 0; - } - - if (parameters.Length > types.Count) - { - // not all declared parameters were specified (optional parameters are not supported) - return false; - } - - // check if the last parameter is ParamArray - Type paramArrayType = null; - - if (enableParamArray && - parameters[parameters.Length - 1].ParameterType.IsArray && - parameters[parameters.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType = parameters[parameters.Length - 1].ParameterType.GetElementType(); - } - - if (paramArrayType == null && parameters.Length != types.Count) - { - // when there's no ParamArray, number of parameters should match - return false; - } - - for (int i = 0; i < types.Count; i++) - { - var paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType; - - // exact match with provided type - if (paramType == types[i]) continue; - - // parameter of type object matches anything - if (paramType == typeof(object)) continue; - - if (paramType.IsPrimitive()) - { - // primitive parameter can only be assigned from compatible primitive type - if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType)) - return false; - } - else - { - if (!paramType.IsAssignableFrom(types[i])) - return false; - } - } - - return true; - } - - private class ParametersMatchComparer : IComparer - { - private readonly IList _types; - private readonly bool _enableParamArray; - - public ParametersMatchComparer(IList types, bool enableParamArray) - { - if (types == null) - throw new ArgumentNullException(nameof(types)); - - _types = types; - _enableParamArray = enableParamArray; - } - - public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2) - { - if (parameters1 == null) - throw new ArgumentNullException(nameof(parameters1)); - if (parameters2 == null) - throw new ArgumentNullException(nameof(parameters2)); - - // parameterless method wins - if (parameters1.Length == 0) return -1; - if (parameters2.Length == 0) return 1; - - Type paramArrayType1 = null, paramArrayType2 = null; - - if (_enableParamArray) - { - if (parameters1[parameters1.Length - 1].ParameterType.IsArray && - parameters1[parameters1.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType1 = parameters1[parameters1.Length - 1].ParameterType.GetElementType(); - } - - if (parameters2[parameters2.Length - 1].ParameterType.IsArray && - parameters2[parameters2.Length - 1].IsDefined(typeof(ParamArrayAttribute))) - { - paramArrayType2 = parameters2[parameters2.Length - 1].ParameterType.GetElementType(); - } - - // A method using params always loses to one not using params - if (paramArrayType1 != null && paramArrayType2 == null) return 1; - if (paramArrayType2 != null && paramArrayType1 == null) return -1; - } - - for (int i = 0; i < _types.Count; i++) - { - var type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType; - var type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType; - - // exact match between parameter types doesn't change score - if (type1 == type2) continue; - - // exact match with source type decides winner immediately - if (type1 == _types[i]) return -1; - if (type2 == _types[i]) return 1; - - var r = ChooseMorePreciseType(type1, type2); - if (r != 0) return r; - } - - return 0; - } - - private static int ChooseMorePreciseType(Type type1, Type type2) - { - if (type1.IsByRef || type2.IsByRef) - { - if (type1.IsByRef && type2.IsByRef) - { - type1 = type1.GetElementType(); - type2 = type2.GetElementType(); - } - else if (type1.IsByRef) - { - type1 = type1.GetElementType(); - if (type1 == type2) return 1; - } - else - { - type2 = type2.GetElementType(); - if (type2 == type1) return -1; - } - } - - bool c1FromC2, c2FromC1; - - if (type1.IsPrimitive() && type2.IsPrimitive()) - { - c1FromC2 = CanConvertPrimitive(type2, type1); - c2FromC1 = CanConvertPrimitive(type1, type2); - } - else - { - c1FromC2 = type1.IsAssignableFrom(type2); - c2FromC1 = type2.IsAssignableFrom(type1); - } - - if (c1FromC2 == c2FromC1) return 0; - - return c1FromC2 ? 1 : -1; - } - - } - - private static MethodBase SelectMethod(IEnumerable candidates, BindingFlags bindingFlags, IList types) - { - if (candidates == null) - throw new ArgumentNullException(nameof(candidates)); - if (types == null) - throw new ArgumentNullException(nameof(types)); - - // ParamArrays are not supported by ReflectionDelegateFactory - // They will be treated like ordinary array arguments - const bool enableParamArray = false; - - return candidates - .Where(m => TestAccessibility(m, bindingFlags) && FilterParameters(m.GetParameters(), types, enableParamArray)) - .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray)) - .FirstOrDefault(); - } - public static MethodInfo GetMethod(this Type type, string name) { return type.GetMethod(name, DefaultFlags); @@ -515,7 +296,7 @@ namespace Newtonsoft.Json.Utilities public static MethodInfo GetMethod(this Type type, string name, BindingFlags bindingFlags, object placeHolder1, IList parameterTypes, object placeHolder2) { - return (MethodInfo)SelectMethod(type.GetTypeInfo().DeclaredMethods.Where(m => name == null || m.Name == name), bindingFlags, parameterTypes); + return MethodBinder.SelectMethod(type.GetTypeInfo().DeclaredMethods.Where(m => (name == null || m.Name == name) && TestAccessibility(m, bindingFlags)), parameterTypes); } public static IEnumerable GetConstructors(this Type type) @@ -535,7 +316,7 @@ namespace Newtonsoft.Json.Utilities public static ConstructorInfo GetConstructor(this Type type, BindingFlags bindingFlags, object placeholder1, IList parameterTypes, object placeholder2) { - return (ConstructorInfo)SelectMethod(type.GetTypeInfo().DeclaredConstructors, bindingFlags, parameterTypes); + return MethodBinder.SelectMethod(type.GetConstructors(bindingFlags), parameterTypes); } public static MemberInfo[] GetMember(this Type type, string member)