Skip to content

Commit

Permalink
CSHARP-1296: Add delayed lookup of child serializers to prevent stack…
Browse files Browse the repository at this point in the history
… overflows when there are cyclic types.
  • Loading branch information
rstam committed May 25, 2015
1 parent fac1e0e commit 51587ec
Show file tree
Hide file tree
Showing 22 changed files with 396 additions and 210 deletions.
1 change: 1 addition & 0 deletions src/MongoDB.Bson.Tests/MongoDB.Bson.Tests.csproj
Expand Up @@ -139,6 +139,7 @@
<Compile Include="Serialization\IdGenerators\AscendingGuidGeneratorTests.cs" />
<Compile Include="Serialization\IdGenerators\CombGuidGeneratorTests.cs" />
<Compile Include="Serialization\Options\RepresentationConverterTests.cs" />
<Compile Include="Serialization\Serializers\EnumerableInterfaceImplementerSerializerTests.cs" />
<Compile Include="Serialization\Serializers\ExpandoObjectSerializerTests.cs" />
<Compile Include="Serialization\Serializers\ExtraElementsTests.cs" />
<Compile Include="Serialization\Serializers\KeyValuePairSerializerTests.cs" />
Expand Down
@@ -0,0 +1,83 @@
/* Copyright 2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Collections;
using System.Collections.Generic;
using System.IO;
using FluentAssertions;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using NUnit.Framework;

namespace MongoDB.Bson.Tests.Serialization.Serializers
{
[TestFixture]
public class EnumerableInterfaceImplementerSerializerTests
{
public class C : IEnumerable<C>
{
public int Id;
public List<C> Children;

public IEnumerator<C> GetEnumerator()
{
return Children.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

[Test]
public void LookupSerializer_should_not_throw_StackOverflowException()
{
var serializer = BsonSerializer.LookupSerializer<C>();

serializer.Should().BeOfType<EnumerableInterfaceImplementerSerializer<C, C>>();
var itemSerializer = ((EnumerableInterfaceImplementerSerializer<C, C>)serializer).ItemSerializer;
itemSerializer.Should().BeSameAs(serializer);
}

[Test]
public void Serialize_should_return_expected_result()
{
var subject = CreateSubject();

using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonWriter(stringWriter))
{
var context = BsonSerializationContext.CreateRoot(jsonWriter);
var value = new C { Id = 1, Children = new List<C> { new C { Id = 2, Children = new List<C>() } } };

subject.Serialize(context, value);

var json = stringWriter.ToString();
json.Should().Be("[[]]");
}
}

private IBsonSerializer<C> CreateSubject()
{
// create subject without using the global serializer registry
var serializerRegistry = new BsonSerializerRegistry();
var subject = new EnumerableInterfaceImplementerSerializer<C, C>(serializerRegistry);
serializerRegistry.RegisterSerializer(typeof(C), subject);
return subject;
}
}
}
@@ -1,4 +1,4 @@
/* Copyright 2010-2014 MongoDB Inc.
/* Copyright 2010-2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,14 +23,8 @@ namespace MongoDB.Bson.Serialization
/// </summary>
public class AttributedSerializationProvider : BsonSerializationProviderBase
{
/// <summary>
/// Gets a serializer for a type that has been annotated with a <see cref="BsonSerializerAttribute"/> specifying which serializer to use.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// A serializer.
/// </returns>
public override IBsonSerializer GetSerializer(Type type)
/// <inheritdoc/>
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
{
if (type == null)
{
Expand Down
@@ -1,4 +1,4 @@
/* Copyright 2010-2014 MongoDB Inc.
/* Copyright 2010-2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,14 +20,10 @@ namespace MongoDB.Bson.Serialization
/// <summary>
/// Represents the class map serialization provider.
/// </summary>
internal class BsonClassMapSerializationProvider : IBsonSerializationProvider
internal class BsonClassMapSerializationProvider : BsonSerializationProviderBase
{
/// <summary>
/// Gets the serializer for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The serializer.</returns>
public IBsonSerializer GetSerializer(Type type)
/// <inheritdoc/>
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
{
if (type == null)
{
Expand Down
@@ -1,4 +1,4 @@
/* Copyright 2010-2014 MongoDB Inc.
/* Copyright 2010-2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,7 +22,7 @@ namespace MongoDB.Bson.Serialization
/// <summary>
/// Provides serializers for BsonValue and its derivations.
/// </summary>
public class BsonObjectModelSerializationProvider : IBsonSerializationProvider
public class BsonObjectModelSerializationProvider : BsonSerializationProviderBase
{
private static readonly Dictionary<Type, IBsonSerializer> __serializers;

Expand Down Expand Up @@ -54,14 +54,8 @@ static BsonObjectModelSerializationProvider()
};
}

/// <summary>
/// Gets a serializer for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// A serializer.
/// </returns>
public IBsonSerializer GetSerializer(Type type)
/// <inheritdoc/>
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
{
if (type == null)
{
Expand Down
65 changes: 53 additions & 12 deletions src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs
@@ -1,4 +1,4 @@
/* Copyright 2010-2014 MongoDB Inc.
/* Copyright 2010-2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,16 +20,16 @@ namespace MongoDB.Bson.Serialization
/// <summary>
/// Base class for serialization providers.
/// </summary>
public abstract class BsonSerializationProviderBase : IBsonSerializationProvider
public abstract class BsonSerializationProviderBase : IRegistryAwareBsonSerializationProvider
{
/// <summary>
/// Gets a serializer for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// A serializer.
/// </returns>
public abstract IBsonSerializer GetSerializer(Type type);
/// <inheritdoc/>
public virtual IBsonSerializer GetSerializer(Type type)
{
return GetSerializer(type, BsonSerializer.SerializerRegistry);
}

/// <inheritdoc/>
public abstract IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry);

/// <summary>
/// Creates the serializer from a serializer type definition and type arguments.
Expand All @@ -38,9 +38,23 @@ public abstract class BsonSerializationProviderBase : IBsonSerializationProvider
/// <param name="typeArguments">The type arguments.</param>
/// <returns>A serializer.</returns>
protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDefinition, params Type[] typeArguments)
{
return CreateGenericSerializer(serializerTypeDefinition, typeArguments, BsonSerializer.SerializerRegistry);
}

/// <summary>
/// Creates the serializer from a serializer type definition and type arguments.
/// </summary>
/// <param name="serializerTypeDefinition">The serializer type definition.</param>
/// <param name="typeArguments">The type arguments.</param>
/// <param name="serializerRegistry">The serializer registry.</param>
/// <returns>
/// A serializer.
/// </returns>
protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDefinition, Type[] typeArguments, IBsonSerializerRegistry serializerRegistry)
{
var serializerType = serializerTypeDefinition.MakeGenericType(typeArguments);
return CreateSerializer(serializerType);
return CreateSerializer(serializerType, serializerRegistry);
}

/// <summary>
Expand All @@ -50,7 +64,34 @@ protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDef
/// <returns>A serializer.</returns>
protected virtual IBsonSerializer CreateSerializer(Type serializerType)
{
return (IBsonSerializer)Activator.CreateInstance(serializerType);
return CreateSerializer(serializerType, BsonSerializer.SerializerRegistry);
}

/// <summary>
/// Creates the serializer.
/// </summary>
/// <param name="serializerType">The serializer type.</param>
/// <param name="serializerRegistry">The serializer registry.</param>
/// <returns>
/// A serializer.
/// </returns>
protected virtual IBsonSerializer CreateSerializer(Type serializerType, IBsonSerializerRegistry serializerRegistry)
{
var constructorInfo = serializerType.GetConstructor(new[] { typeof(IBsonSerializerRegistry) });
if (constructorInfo != null)
{
return (IBsonSerializer)constructorInfo.Invoke(new object[] { serializerRegistry });
}

constructorInfo = serializerType.GetConstructor(new Type[0]);
if (constructorInfo != null)
{
return (IBsonSerializer)constructorInfo.Invoke(new object[0]);
}

throw new MissingMethodException(string.Format(
"No suitable constructor found for serializer type: '{0}'.",
serializerType.FullName));
}
}
}
15 changes: 13 additions & 2 deletions src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs
@@ -1,4 +1,4 @@
/* Copyright 2010-2014 MongoDB Inc.
/* Copyright 2010-2015 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -125,7 +125,18 @@ private IBsonSerializer CreateSerializer(Type type)
{
foreach (var serializationProvider in _serializationProviders)
{
var serializer = serializationProvider.GetSerializer(type);
IBsonSerializer serializer;

var registryAwareSerializationProvider = serializationProvider as IRegistryAwareBsonSerializationProvider;
if (registryAwareSerializationProvider != null)
{
serializer = registryAwareSerializationProvider.GetSerializer(type, this);
}
else
{
serializer = serializationProvider.GetSerializer(type);
}

if (serializer != null)
{
return serializer;
Expand Down

0 comments on commit 51587ec

Please sign in to comment.