diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 742a246d3..981b3b9a4 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -28,15 +28,16 @@ using System.Dynamic; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Text.RegularExpressions; using Xunit; using YamlDotNet.Core; using YamlDotNet.Core.Events; +using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.ObjectFactories; +using YamlDotNet.Serialization.TypeInspectors; namespace YamlDotNet.Test.Serialization { @@ -1369,5 +1370,93 @@ public void DeserializationOfInt64Succeeds(long value) long parsed = new Deserializer().Deserialize(yaml); Assert.Equal(value, parsed); } + + [Fact] + public void SerializeExceptionWithStackTrace() + { + var ex = GetExceptionWithStackTrace(); + var serializer = new SerializerBuilder() + .WithTypeConverter(new MethodInfoConverter()) + .Build(); + string yaml = serializer.Serialize(ex); + Assert.Contains("GetExceptionWithStackTrace", yaml); + } + + private class MethodInfoConverter : IYamlTypeConverter + { + public bool Accepts(Type type) + { + return typeof(MethodInfo).IsAssignableFrom(type); + } + + public object ReadYaml(IParser parser, Type type) + { + throw new NotImplementedException(); + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var method = (MethodInfo)value; + emitter.Emit(new Scalar(string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name))); + } + } + + static Exception GetExceptionWithStackTrace() + { + try + { + throw new ArgumentNullException("foo"); + } + catch (Exception ex) + { + return ex; + } + } + + [Fact] + public void RegisteringATypeConverterPreventsTheTypeFromBeingVisited() + { + var serializer = new SerializerBuilder() + .WithTypeConverter(new SystemTypeTypeConverter()) + .Build(); + + var yaml = serializer.Serialize(new TypeContainer + { + Type = typeof(string), + }); + + var deserializer = new DeserializerBuilder() + .WithTypeConverter(new SystemTypeTypeConverter()) + .Build(); + + var result = deserializer.Deserialize(yaml); + + Assert.Equal(typeof(string), result.Type); + } + + public class TypeContainer + { + public Type Type { get; set; } + } + + public class SystemTypeTypeConverter : IYamlTypeConverter + { + public bool Accepts(Type type) + { + return typeof(Type).IsAssignableFrom(type); + } + + public object ReadYaml(IParser parser, Type type) + { + var scalar = parser.Expect(); + return Type.GetType(scalar.Value); + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var typeName = ((Type)value).AssemblyQualifiedName; + emitter.Emit(new Scalar(typeName)); + } + } } } diff --git a/YamlDotNet/Serialization/BuilderSkeleton.cs b/YamlDotNet/Serialization/BuilderSkeleton.cs index e99ec78ac..7f0c76ce0 100644 --- a/YamlDotNet/Serialization/BuilderSkeleton.cs +++ b/YamlDotNet/Serialization/BuilderSkeleton.cs @@ -136,8 +136,6 @@ Action> where throw new ArgumentNullException("where"); } - typeConvertersCache = null; - where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), _ => typeConverter)); return Self; } @@ -177,15 +175,9 @@ Action> where return Self; } - private IEnumerable typeConvertersCache; - protected IEnumerable BuildTypeConverters() { - if (typeConvertersCache == null) - { - typeConvertersCache = typeConverterFactories.BuildComponentList(); - } - return typeConvertersCache; + return typeConverterFactories.BuildComponentList(); } } } \ No newline at end of file diff --git a/YamlDotNet/Serialization/LazyComponentRegistrationListExtensions.cs b/YamlDotNet/Serialization/LazyComponentRegistrationListExtensions.cs index f5e42a278..a35111cd2 100644 --- a/YamlDotNet/Serialization/LazyComponentRegistrationListExtensions.cs +++ b/YamlDotNet/Serialization/LazyComponentRegistrationListExtensions.cs @@ -53,5 +53,12 @@ public static List BuildComponentList(this LazyComponent .Select(factory => factory(null)) .ToList(); } + + public static List BuildComponentList(this LazyComponentRegistrationList registrations, TArgument argument) + { + return registrations + .Select(factory => factory(argument)) + .ToList(); + } } } \ No newline at end of file diff --git a/YamlDotNet/Serialization/ObjectGraphVisitors/AnchorAssigner.cs b/YamlDotNet/Serialization/ObjectGraphVisitors/AnchorAssigner.cs index 1f5b51789..f32667ce6 100644 --- a/YamlDotNet/Serialization/ObjectGraphVisitors/AnchorAssigner.cs +++ b/YamlDotNet/Serialization/ObjectGraphVisitors/AnchorAssigner.cs @@ -25,7 +25,7 @@ namespace YamlDotNet.Serialization.ObjectGraphVisitors { - public sealed class AnchorAssigner : IObjectGraphVisitor, IAliasProvider + public sealed class AnchorAssigner : PreProcessingPhaseObjectGraphVisitorSkeleton, IAliasProvider { private class AnchorAssignment { @@ -35,7 +35,12 @@ private class AnchorAssignment private readonly IDictionary assignments = new Dictionary(); private uint nextId; - bool IObjectGraphVisitor.Enter(IObjectDescriptor value, Nothing context) + public AnchorAssigner(IEnumerable typeConverters) + : base(typeConverters) + { + } + + protected override bool Enter(IObjectDescriptor value) { AnchorAssignment assignment; if (value.Value != null && assignments.TryGetValue(value.Value, out assignment)) @@ -51,34 +56,34 @@ bool IObjectGraphVisitor.Enter(IObjectDescriptor value, Nothing context return true; } - bool IObjectGraphVisitor.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, Nothing context) + protected override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value) { return true; } - bool IObjectGraphVisitor.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, Nothing context) + protected override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value) { return true; } - void IObjectGraphVisitor.VisitScalar(IObjectDescriptor scalar, Nothing context) + protected override void VisitScalar(IObjectDescriptor scalar) { // Do not assign anchors to scalars } - void IObjectGraphVisitor.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, Nothing context) + protected override void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType) { VisitObject(mapping); } - void IObjectGraphVisitor.VisitMappingEnd(IObjectDescriptor mapping, Nothing context) { } + protected override void VisitMappingEnd(IObjectDescriptor mapping) { } - void IObjectGraphVisitor.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, Nothing context) + protected override void VisitSequenceStart(IObjectDescriptor sequence, Type elementType) { VisitObject(sequence); } - void IObjectGraphVisitor.VisitSequenceEnd(IObjectDescriptor sequence, Nothing context) { } + protected override void VisitSequenceEnd(IObjectDescriptor sequence) { } private void VisitObject(IObjectDescriptor value) { diff --git a/YamlDotNet/Serialization/ObjectGraphVisitors/PreProcessingPhaseObjectGraphVisitorSkeleton.cs b/YamlDotNet/Serialization/ObjectGraphVisitors/PreProcessingPhaseObjectGraphVisitorSkeleton.cs new file mode 100644 index 000000000..76c4e08b3 --- /dev/null +++ b/YamlDotNet/Serialization/ObjectGraphVisitors/PreProcessingPhaseObjectGraphVisitorSkeleton.cs @@ -0,0 +1,111 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace YamlDotNet.Serialization.ObjectGraphVisitors +{ + /// + /// A base class that simplifies the correct implementation of . + /// + public abstract class PreProcessingPhaseObjectGraphVisitorSkeleton : IObjectGraphVisitor + { + protected readonly IEnumerable typeConverters; + + public PreProcessingPhaseObjectGraphVisitorSkeleton(IEnumerable typeConverters) + { + this.typeConverters = typeConverters != null + ? typeConverters.ToList() + : Enumerable.Empty(); + } + + bool IObjectGraphVisitor.Enter(IObjectDescriptor value, Nothing context) + { + var typeConverter = typeConverters.FirstOrDefault(t => t.Accepts(value.Type)); + if (typeConverter != null) + { + return false; + } + + var convertible = value.Value as IYamlConvertible; + if (convertible != null) + { + return false; + } + +#pragma warning disable 0618 // IYamlSerializable is obsolete + var serializable = value.Value as IYamlSerializable; + if (serializable != null) + { + return false; + } +#pragma warning restore + + return Enter(value); + } + + bool IObjectGraphVisitor.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, Nothing context) + { + return EnterMapping(key, value); + } + + bool IObjectGraphVisitor.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, Nothing context) + { + return EnterMapping(key, value); + } + + void IObjectGraphVisitor.VisitMappingEnd(IObjectDescriptor mapping, Nothing context) + { + VisitMappingEnd(mapping); + } + + void IObjectGraphVisitor.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, Nothing context) + { + VisitMappingStart(mapping, keyType, valueType); + } + + void IObjectGraphVisitor.VisitScalar(IObjectDescriptor scalar, Nothing context) + { + VisitScalar(scalar); + } + + void IObjectGraphVisitor.VisitSequenceEnd(IObjectDescriptor sequence, Nothing context) + { + VisitSequenceEnd(sequence); + } + + void IObjectGraphVisitor.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, Nothing context) + { + VisitSequenceStart(sequence, elementType); + } + + protected abstract bool Enter(IObjectDescriptor value); + protected abstract bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value); + protected abstract bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value); + protected abstract void VisitMappingEnd(IObjectDescriptor mapping); + protected abstract void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType); + protected abstract void VisitScalar(IObjectDescriptor scalar); + protected abstract void VisitSequenceEnd(IObjectDescriptor sequence); + protected abstract void VisitSequenceStart(IObjectDescriptor sequence, Type elementType); + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/Serializer.cs b/YamlDotNet/Serialization/Serializer.cs index ca294094e..2095299b9 100644 --- a/YamlDotNet/Serialization/Serializer.cs +++ b/YamlDotNet/Serialization/Serializer.cs @@ -76,7 +76,7 @@ private IObjectGraphVisitor CreateEmittingVisitor(IEmitter emitter, IO if (!IsOptionSet(SerializationOptions.DisableAliases)) { - var anchorAssigner = new AnchorAssigner(); + var anchorAssigner = new AnchorAssigner(Converters); traversalStrategy.Traverse(graph, anchorAssigner, null); emittingVisitor = new AnchorAssigningObjectGraphVisitor(emittingVisitor, eventEmitter, anchorAssigner); diff --git a/YamlDotNet/Serialization/SerializerBuilder.cs b/YamlDotNet/Serialization/SerializerBuilder.cs index c3556a1c6..dd9c0639e 100644 --- a/YamlDotNet/Serialization/SerializerBuilder.cs +++ b/YamlDotNet/Serialization/SerializerBuilder.cs @@ -40,8 +40,8 @@ namespace YamlDotNet.Serialization /// public sealed class SerializerBuilder : BuilderSkeleton { - private Func objectGraphTraversalStrategyFactory; - private readonly LazyComponentRegistrationList> preProcessingPhaseObjectGraphVisitorFactories; + private Func, IObjectGraphTraversalStrategy> objectGraphTraversalStrategyFactory; + private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList eventEmitterFactories; @@ -52,8 +52,8 @@ public SerializerBuilder() typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), inner => new YamlAttributesTypeInspector(inner)); typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), inner => overrides != null ? new YamlAttributeOverridesInspector(inner, overrides.Clone()) : inner); - preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList>(); - preProcessingPhaseObjectGraphVisitorFactories.Add(typeof(AnchorAssigner), _ => new AnchorAssigner()); + preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList, IObjectGraphVisitor>(); + preProcessingPhaseObjectGraphVisitorFactories.Add(typeof(AnchorAssigner), typeConverters => new AnchorAssigner(typeConverters)); emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList>(); emissionPhaseObjectGraphVisitorFactories.Add(typeof(CustomSerializationObjectGraphVisitor), @@ -68,7 +68,7 @@ public SerializerBuilder() eventEmitterFactories = new LazyComponentRegistrationList(); eventEmitterFactories.Add(typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false)); - objectGraphTraversalStrategyFactory = (typeInspector, typeResolver) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, 50, namingConvention ?? new NullNamingConvention()); + objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, 50, namingConvention ?? new NullNamingConvention()); WithTypeResolver(new DynamicTypeResolver()); } @@ -116,8 +116,8 @@ Action> where /// public SerializerBuilder EnsureRoundtrip() { - objectGraphTraversalStrategyFactory = (typeInspector, typeResolver) => new RoundtripObjectGraphTraversalStrategy( - BuildTypeConverters(), + objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters) => new RoundtripObjectGraphTraversalStrategy( + typeConverters, typeInspector, typeResolver, 50 @@ -261,10 +261,15 @@ public Serializer Build() /// public IValueSerializer BuildValueSerializer() { + var typeConverters = BuildTypeConverters(); + var typeInspector = BuildTypeInspector(); + var traversalStrategy = objectGraphTraversalStrategyFactory(typeInspector, typeResolver, typeConverters); + var eventEmitter = eventEmitterFactories.BuildComponentChain(new WriterEventEmitter()); + return new ValueSerializer( - CreateTraversalStrategy(), - CreateEventEmitter(), - BuildTypeConverters(), + traversalStrategy, + eventEmitter, + typeConverters, preProcessingPhaseObjectGraphVisitorFactories.Clone(), emissionPhaseObjectGraphVisitorFactories.Clone() ); @@ -275,14 +280,14 @@ private class ValueSerializer : IValueSerializer private readonly IObjectGraphTraversalStrategy traversalStrategy; private readonly IEventEmitter eventEmitter; private readonly IEnumerable typeConverters; - private readonly LazyComponentRegistrationList> preProcessingPhaseObjectGraphVisitorFactories; + private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; public ValueSerializer( IObjectGraphTraversalStrategy traversalStrategy, IEventEmitter eventEmitter, IEnumerable typeConverters, - LazyComponentRegistrationList> preProcessingPhaseObjectGraphVisitorFactories, + LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories, LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories ) { @@ -300,7 +305,7 @@ public void SerializeValue(IEmitter emitter, object value, Type type) var graph = new ObjectDescriptor(value, actualType, staticType); - var preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(); + var preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); foreach (var visitor in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, visitor, null); @@ -316,18 +321,5 @@ public void SerializeValue(IEmitter emitter, object value, Type type) traversalStrategy.Traverse(graph, emittingVisitor, emitter); } } - - private IEventEmitter CreateEventEmitter() - { - return eventEmitterFactories.BuildComponentChain( - new WriterEventEmitter() - ); - } - - private IObjectGraphTraversalStrategy CreateTraversalStrategy() - { - var typeInspector = BuildTypeInspector(); - return objectGraphTraversalStrategyFactory(typeInspector, typeResolver); - } } } \ No newline at end of file diff --git a/YamlDotNet/YamlDotNet.csproj b/YamlDotNet/YamlDotNet.csproj index 0ba73da5b..73d9ad8d2 100644 --- a/YamlDotNet/YamlDotNet.csproj +++ b/YamlDotNet/YamlDotNet.csproj @@ -298,6 +298,7 @@ +