[X] more conv exception, bubble to previewer (#5400)
authorStephane Delcroix <stephane@delcroix.org>
Thu, 28 Feb 2019 01:58:58 +0000 (02:58 +0100)
committerSamantha Houts <samhouts@users.noreply.github.com>
Thu, 28 Feb 2019 01:58:58 +0000 (17:58 -0800)
Catch more exception cases, make sure the previewer can recover from them

- fixes #5397

Xamarin.Forms.Core/StyleSheets/Style.cs
Xamarin.Forms.Core/Xaml/TypeConversionExtensions.cs
Xamarin.Forms.Core/Xaml/ValueConverterProvider.cs
Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/CreateValuesVisitor.cs
Xamarin.Forms.Xaml/MarkupExtensionParser.cs
Xamarin.Forms.Xaml/MarkupExtensions/OnIdiomExtension.cs
Xamarin.Forms.Xaml/MarkupExtensions/OnPlatformExtension.cs

index 1f311e6..b2df539 100644 (file)
@@ -103,7 +103,10 @@ namespace Xamarin.Forms.StyleSheets
                                                throw new XamlParseException($"Multiple methods with name '{property.DeclaringType}.Get{property.PropertyName}' found.", serviceProvider, innerException: e);
                                        }
                                };
-                       return value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
+                       var ret = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider, out Exception exception);
+                       if (exception != null)
+                               throw exception;
+                       return ret;
                }
 
                public void UnApply(IStylable styleable)
index 52f42a2..d9a46c4 100644 (file)
@@ -38,7 +38,7 @@ namespace Xamarin.Forms.Xaml
        static class TypeConversionExtensions
        {
                internal static object ConvertTo(this object value, Type toType, Func<ParameterInfo> pinfoRetriever,
-                       IServiceProvider serviceProvider)
+                       IServiceProvider serviceProvider, out Exception exception)
                {
                        Func<TypeConverter> getConverter = () =>
                        {
@@ -53,11 +53,11 @@ namespace Xamarin.Forms.Xaml
                                return (TypeConverter)Activator.CreateInstance(convertertype);
                        };
 
-                       return ConvertTo(value, toType, getConverter, serviceProvider);
+                       return ConvertTo(value, toType, getConverter, serviceProvider, out exception);
                }
 
                internal static object ConvertTo(this object value, Type toType, Func<MemberInfo> minfoRetriever,
-                       IServiceProvider serviceProvider)
+                       IServiceProvider serviceProvider, out Exception exception)
                {
                        Func<object> getConverter = () =>
                        {
@@ -73,7 +73,7 @@ namespace Xamarin.Forms.Xaml
                                return Activator.CreateInstance(convertertype);
                        };
 
-                       return ConvertTo(value, toType, getConverter, serviceProvider);
+                       return ConvertTo(value, toType, getConverter, serviceProvider, out exception);
                }
 
                static string GetTypeConverterTypeName(this IEnumerable<CustomAttributeData> attributes)
@@ -92,35 +92,48 @@ namespace Xamarin.Forms.Xaml
                //Don't change the name or the signature of this, it's used by XamlC
                public static object ConvertTo(this object value, Type toType, Type convertertype, IServiceProvider serviceProvider)
                {
-                       if (convertertype == null)
-                               return value.ConvertTo(toType, (Func<object>)null, serviceProvider);
+                       Exception exception = null;
+                       object ret = null;
+                       if (convertertype == null) {
+                               ret = value.ConvertTo(toType, (Func<object>)null, serviceProvider, out exception);
+                               if (exception != null)
+                                       throw exception;
+                               return ret;
+                       }
                        Func<object> getConverter = () => Activator.CreateInstance(convertertype);
-                       ;
-                       return value.ConvertTo(toType, getConverter, serviceProvider);
+                       ret = value.ConvertTo(toType, getConverter, serviceProvider, out exception);
+                       if (exception != null)
+                               throw exception;
+                       return ret;
                }
 
                internal static object ConvertTo(this object value, Type toType, Func<object> getConverter,
-                       IServiceProvider serviceProvider)
+                       IServiceProvider serviceProvider, out Exception exception)
                {
+                       exception = null;
                        if (value == null)
                                return null;
 
-                       var str = value as string;
-                       if (str != null)
-                       {
+                       if (value is string str) {
                                //If there's a [TypeConverter], use it
-                               object converter = getConverter?.Invoke();
-                               var xfTypeConverter = converter as TypeConverter;
-                               var xfExtendedTypeConverter = xfTypeConverter as IExtendedTypeConverter;
-                               if (xfExtendedTypeConverter != null)
-                                       return value = xfExtendedTypeConverter.ConvertFromInvariantString(str, serviceProvider);
-                               if (xfTypeConverter != null) {
-                                       try {
-                                               return value = xfTypeConverter.ConvertFromInvariantString(str);
-                                       }
-                                       catch (Exception e) {
-                                               throw new XamlParseException("Type conversion failed", serviceProvider, e);
-                                       }
+                               object converter;
+                               try { //minforetriver can fail
+                                       converter = getConverter?.Invoke();
+                               }
+                               catch (Exception e) {
+                                       exception = e;
+                                       return null;
+                               }
+                               try {
+                                       if (converter is IExtendedTypeConverter xfExtendedTypeConverter)
+                                               return xfExtendedTypeConverter.ConvertFromInvariantString(str, serviceProvider);
+                                       if (converter is TypeConverter xfTypeConverter)
+                                               return xfTypeConverter.ConvertFromInvariantString(str);
+                               }
+                               catch (Exception e)
+                               {
+                                       exception = e as XamlParseException ?? new XamlParseException("Type converter failed", serviceProvider, e);
+                                       return null;
                                }
                                var converterType = converter?.GetType();
                                if (converterType != null)
@@ -128,7 +141,13 @@ namespace Xamarin.Forms.Xaml
                                        var convertFromStringInvariant = converterType.GetRuntimeMethod("ConvertFromInvariantString",
                                                new[] { typeof (string) });
                                        if (convertFromStringInvariant != null)
-                                               return value = convertFromStringInvariant.Invoke(converter, new object[] { str });
+                                               try {
+                                                       return convertFromStringInvariant.Invoke(converter, new object[] { str });
+                                               }
+                                               catch (Exception e) {
+                                                       exception = new XamlParseException("Type conversion failed", serviceProvider, e);
+                                                       return null;
+                                               }
                                }
                                var ignoreCase = (serviceProvider?.GetService(typeof(IConverterOptions)) as IConverterOptions)?.IgnoreCase ?? false;
 
@@ -137,45 +156,50 @@ namespace Xamarin.Forms.Xaml
                                        toType = Nullable.GetUnderlyingType(toType);
 
                                //Obvious Built-in conversions
-                               if (toType.GetTypeInfo().IsEnum)
-                                       return Enum.Parse(toType, str, ignoreCase);
-                               if (toType == typeof(SByte))
-                                       return SByte.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(Int16))
-                                       return Int16.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(Int32))
-                                       return Int32.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(Int64))
-                                       return Int64.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(Byte))
-                                       return Byte.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(UInt16))
-                                       return UInt16.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(UInt32))
-                                       return UInt32.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(UInt64))
-                                       return UInt64.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof (Single))
-                                       return Single.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof (Double))
-                                       return Double.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof (Boolean))
-                                       return Boolean.Parse(str);
-                               if (toType == typeof (TimeSpan))
-                                       return TimeSpan.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof (DateTime))
-                                       return DateTime.Parse(str, CultureInfo.InvariantCulture);
-                               if (toType == typeof(Char)) {
-                                       char c = '\0';
-                                       Char.TryParse(str, out c);
-                                       return c;
+                               try {
+                                       if (toType.GetTypeInfo().IsEnum)
+                                               return Enum.Parse(toType, str, ignoreCase);
+                                       if (toType == typeof(SByte))
+                                               return SByte.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Int16))
+                                               return Int16.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Int32))
+                                               return Int32.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Int64))
+                                               return Int64.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Byte))
+                                               return Byte.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(UInt16))
+                                               return UInt16.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(UInt32))
+                                               return UInt32.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(UInt64))
+                                               return UInt64.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Single))
+                                               return Single.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Double))
+                                               return Double.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Boolean))
+                                               return Boolean.Parse(str);
+                                       if (toType == typeof(TimeSpan))
+                                               return TimeSpan.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(DateTime))
+                                               return DateTime.Parse(str, CultureInfo.InvariantCulture);
+                                       if (toType == typeof(Char)) {
+                                               Char.TryParse(str, out var c);
+                                               return c;
+                                       }
+                                       if (toType == typeof(String) && str.StartsWith("{}", StringComparison.Ordinal))
+                                               return str.Substring(2);
+                                       if (toType == typeof(String))
+                                               return value;
+                                       if (toType == typeof(Decimal))
+                                               return Decimal.Parse(str, CultureInfo.InvariantCulture);
+                               }
+                               catch (FormatException fe) {
+                                       exception = fe;
+                                       return null;
                                }
-                               if (toType == typeof (String) && str.StartsWith("{}", StringComparison.Ordinal))
-                                       return str.Substring(2);
-                               if (toType == typeof (String))
-                                       return value;
-                               if (toType == typeof(Decimal))
-                                       return Decimal.Parse(str, CultureInfo.InvariantCulture);
                        }
 
                        //if the value is not assignable and there's an implicit conversion, convert
index ef5edc9..2943001 100644 (file)
@@ -11,7 +11,10 @@ namespace Xamarin.Forms.Xaml
        {
                public object Convert(object value, Type toType, Func<MemberInfo> minfoRetriever, IServiceProvider serviceProvider)
                {
-                       return value.ConvertTo(toType, minfoRetriever, serviceProvider);
+                       var ret = value.ConvertTo(toType, minfoRetriever, serviceProvider, out Exception exception);
+                       if (exception != null)
+                               throw exception;
+                       return ret;
                }
        }
 }
index 4c60675..f3dbfc0 100644 (file)
@@ -534,6 +534,22 @@ namespace Xamarin.Forms.Xaml.UnitTests
                        Assert.DoesNotThrow(() => XamlLoader.Create(xaml, true));
                        Assert.That(exceptions.Count, Is.EqualTo(2));
                }
+
+               [Test]
+               public void IgnoreConverterException()
+               {
+                       var xaml = @"
+                                       <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
+                                               xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
+                                               xmlns:local=""clr-namespace:Xamarin.Forms.Xaml.UnitTests;assembly=Xamarin.Forms.Xaml.UnitTests"">
+                                               <Label BackgroundColor=""AlmostPink"" />
+                                       </ContentPage>";
+
+                       var exceptions = new List<Exception>();
+                       Xamarin.Forms.Internals.ResourceLoader.ExceptionHandler = exceptions.Add;
+                       Assert.DoesNotThrow(() => XamlLoader.Create(xaml, true));
+                       Assert.That(exceptions.Count, Is.EqualTo(1));
+               }
        }
 
        public class InstantiateThrows
index 448646e..a9fbb88 100644 (file)
@@ -446,7 +446,9 @@ namespace Xamarin.Forms.Xaml
                        exception = null;
 
                        var elementType = element.GetType();
-                       var binding = value.ConvertTo(typeof(BindingBase),pinfoRetriever:null,serviceProvider:null) as BindingBase;
+                       var binding = value.ConvertTo(typeof(BindingBase),pinfoRetriever:null,serviceProvider:null, exception:out exception) as BindingBase;
+                       if (exception != null)
+                               return false;
                        var bindable = element as BindableObject;
                        var nativeBindingService = DependencyService.Get<INativeBindingService>();
 
@@ -503,7 +505,9 @@ namespace Xamarin.Forms.Xaml
                                                throw new XamlParseException($"Multiple properties with name '{property.DeclaringType}.{property.PropertyName}' found.", lineInfo, innerException: e);
                                        }
                                };
-                       var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
+                       var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider, out exception);
+                       if (exception != null)
+                               return false;
 
                        if (bindable != null) {
                                //SetValue doesn't throw on mismatching type, so check before to get a chance to try the property setting or the collection adding
@@ -522,7 +526,7 @@ namespace Xamarin.Forms.Xaml
                                }
 
                                // This might be a collection; see if we can add to it
-                               return TryAddValue(bindable, property, value, serviceProvider);
+                               return TryAddValue(bindable, property, value, serviceProvider, out exception);
                        }
 
                        if (nativeBindingService != null && nativeBindingService.TrySetValue(element, property, convertedValue))
@@ -566,8 +570,8 @@ namespace Xamarin.Forms.Xaml
                        if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
                                ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = propertyInfo;
 
-                       object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider);
-                       if (convertedValue != null && !propertyInfo.PropertyType.IsInstanceOfType(convertedValue))
+                       object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider, out exception);
+                       if (exception != null || (convertedValue != null && !propertyInfo.PropertyType.IsInstanceOfType(convertedValue)))
                                return false;
 
                        try {
@@ -654,8 +658,8 @@ namespace Xamarin.Forms.Xaml
                        if (serviceProvider != null)
                                ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = targetProperty;
 
-                       addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
-                       return true;
+                       addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func<TypeConverter>)null, serviceProvider, out exception) });
+                       return exception == null;
                }
 
                static bool TryAddToResourceDictionary(ResourceDictionary resourceDictionary, object value, string xKey, IXmlLineInfo lineInfo, out Exception exception)
@@ -698,23 +702,21 @@ namespace Xamarin.Forms.Xaml
                        };
                }
 
-               static bool TryAddValue(BindableObject bindable, BindableProperty property, object value, XamlServiceProvider serviceProvider)
+               static bool TryAddValue(BindableObject bindable, BindableProperty property, object value, XamlServiceProvider serviceProvider, out Exception exception)
                {
-                       if(property?.ReturnTypeInfo?.GenericTypeArguments == null){
+                       exception = null;
+
+                       if (property?.ReturnTypeInfo?.GenericTypeArguments == null)
                                return false;
-                       }
 
-                       if(property.ReturnType == null){
+                       if (property.ReturnType == null)
                                return false;
-                       }
 
-                       if (property.ReturnTypeInfo.GenericTypeArguments.Length != 1 ||
-                               !property.ReturnTypeInfo.GenericTypeArguments[0].IsInstanceOfType(value))
+                       if (property.ReturnTypeInfo.GenericTypeArguments.Length != 1 || !property.ReturnTypeInfo.GenericTypeArguments[0].IsInstanceOfType(value))
                                return false;
 
                        // This might be a collection we can add to; see if we can find an Add method
-                       var addMethod = GetAllRuntimeMethods(property.ReturnType)
-                               .FirstOrDefault(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
+                       var addMethod = GetAllRuntimeMethods(property.ReturnType).FirstOrDefault(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
                        if (addMethod == null)
                                return false;
 
@@ -722,8 +724,8 @@ namespace Xamarin.Forms.Xaml
                        var collection = bindable.GetValue(property);
                        
                        // And add the new value to it
-                       addMethod.Invoke(collection, new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
-                       return true;
+                       addMethod.Invoke(collection, new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func<TypeConverter>)null, serviceProvider, out exception) });
+                       return exception == null;
                }
 
                static IEnumerable<MethodInfo> GetAllRuntimeMethods(Type type)
index 838a417..9927e6c 100644 (file)
@@ -79,7 +79,14 @@ namespace Xamarin.Forms.Xaml
                                        if (value == null && node.CollectionItems.Any() && node.CollectionItems.First() is ValueNode) {
                                                var serviceProvider = new XamlServiceProvider(node, Context);
                                                var converted = ((ValueNode)node.CollectionItems.First()).Value.ConvertTo(type, () => type.GetTypeInfo(),
-                                                       serviceProvider);
+                                                       serviceProvider, out Exception exception);
+                                               if (exception != null) {
+                                                       if (Context.ExceptionHandler != null) {
+                                                               Context.ExceptionHandler(exception);
+                                                               return;
+                                                       }
+                                                       throw exception;
+                                               } 
                                                if (converted != null && converted.GetType() == type)
                                                        value = converted;
                                        }
@@ -294,7 +301,9 @@ namespace Xamarin.Forms.Xaml
                                        enode.SkipProperties.Add(name);
                                var value = Context.Values[node];
                                var serviceProvider = new XamlServiceProvider(enode, Context);
-                               var convertedValue = value.ConvertTo(parameter.ParameterType, () => parameter, serviceProvider);
+                               var convertedValue = value.ConvertTo(parameter.ParameterType, () => parameter, serviceProvider, out Exception e);
+                               if (e != null)
+                                       throw e;
                                array[i] = convertedValue;
                        }
 
index 39684b2..439c859 100644 (file)
@@ -82,7 +82,9 @@ namespace Xamarin.Forms.Xaml
                        if (value == null && strValue != null) {
                                try {
                                        value = strValue.ConvertTo(_markupExtension.GetType().GetRuntimeProperty(prop).PropertyType,
-                                               (Func<TypeConverter>)null, serviceProvider);
+                                               (Func<TypeConverter>)null, serviceProvider, out Exception converterException);
+                                       if (converterException != null)
+                                               throw converterException;
                                }
                                catch (AmbiguousMatchException e) {
                                        throw new XamlParseException($"Multiple properties with name  '{_markupExtension.GetType()}.{prop}' found.", serviceProvider, innerException: e);
index 516afbe..fd2a957 100644 (file)
@@ -85,7 +85,10 @@ namespace Xamarin.Forms.Xaml
                        if (converterProvider != null)
                                return converterProvider.Convert(value, propertyType, () => pi, serviceProvider);
 
-                       return value.ConvertTo(propertyType, () => pi, serviceProvider);
+                       var ret = value.ConvertTo(propertyType, () => pi, serviceProvider, out Exception exception);
+                       if (exception != null)
+                               throw exception;
+                       return ret;
                }
 
                object GetValue()
index 1b9431f..6aa0f43 100644 (file)
@@ -85,7 +85,10 @@ namespace Xamarin.Forms.Xaml
 
                                return converterProvider.Convert(value, propertyType, minforetriever, serviceProvider);
                        }
-                       return value.ConvertTo(propertyType, () => pi, serviceProvider);
+                       var ret = value.ConvertTo(propertyType, () => pi, serviceProvider, out Exception exception);
+                       if (exception != null)
+                               throw exception;
+                       return ret;
                }
 
                object GetValue()