Skip to content

Commit 51587ec

Browse files
committed
CSHARP-1296: Add delayed lookup of child serializers to prevent stack overflows when there are cyclic types.
1 parent fac1e0e commit 51587ec

22 files changed

+396
-210
lines changed

src/MongoDB.Bson.Tests/MongoDB.Bson.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
<Compile Include="Serialization\IdGenerators\AscendingGuidGeneratorTests.cs" />
140140
<Compile Include="Serialization\IdGenerators\CombGuidGeneratorTests.cs" />
141141
<Compile Include="Serialization\Options\RepresentationConverterTests.cs" />
142+
<Compile Include="Serialization\Serializers\EnumerableInterfaceImplementerSerializerTests.cs" />
142143
<Compile Include="Serialization\Serializers\ExpandoObjectSerializerTests.cs" />
143144
<Compile Include="Serialization\Serializers\ExtraElementsTests.cs" />
144145
<Compile Include="Serialization\Serializers\KeyValuePairSerializerTests.cs" />
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* Copyright 2015 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections;
17+
using System.Collections.Generic;
18+
using System.IO;
19+
using FluentAssertions;
20+
using MongoDB.Bson.IO;
21+
using MongoDB.Bson.Serialization;
22+
using MongoDB.Bson.Serialization.Serializers;
23+
using NUnit.Framework;
24+
25+
namespace MongoDB.Bson.Tests.Serialization.Serializers
26+
{
27+
[TestFixture]
28+
public class EnumerableInterfaceImplementerSerializerTests
29+
{
30+
public class C : IEnumerable<C>
31+
{
32+
public int Id;
33+
public List<C> Children;
34+
35+
public IEnumerator<C> GetEnumerator()
36+
{
37+
return Children.GetEnumerator();
38+
}
39+
40+
IEnumerator IEnumerable.GetEnumerator()
41+
{
42+
return GetEnumerator();
43+
}
44+
}
45+
46+
[Test]
47+
public void LookupSerializer_should_not_throw_StackOverflowException()
48+
{
49+
var serializer = BsonSerializer.LookupSerializer<C>();
50+
51+
serializer.Should().BeOfType<EnumerableInterfaceImplementerSerializer<C, C>>();
52+
var itemSerializer = ((EnumerableInterfaceImplementerSerializer<C, C>)serializer).ItemSerializer;
53+
itemSerializer.Should().BeSameAs(serializer);
54+
}
55+
56+
[Test]
57+
public void Serialize_should_return_expected_result()
58+
{
59+
var subject = CreateSubject();
60+
61+
using (var stringWriter = new StringWriter())
62+
using (var jsonWriter = new JsonWriter(stringWriter))
63+
{
64+
var context = BsonSerializationContext.CreateRoot(jsonWriter);
65+
var value = new C { Id = 1, Children = new List<C> { new C { Id = 2, Children = new List<C>() } } };
66+
67+
subject.Serialize(context, value);
68+
69+
var json = stringWriter.ToString();
70+
json.Should().Be("[[]]");
71+
}
72+
}
73+
74+
private IBsonSerializer<C> CreateSubject()
75+
{
76+
// create subject without using the global serializer registry
77+
var serializerRegistry = new BsonSerializerRegistry();
78+
var subject = new EnumerableInterfaceImplementerSerializer<C, C>(serializerRegistry);
79+
serializerRegistry.RegisterSerializer(typeof(C), subject);
80+
return subject;
81+
}
82+
}
83+
}

src/MongoDB.Bson/Serialization/AttributedSerializationProvider.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2014 MongoDB Inc.
1+
/* Copyright 2010-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -23,14 +23,8 @@ namespace MongoDB.Bson.Serialization
2323
/// </summary>
2424
public class AttributedSerializationProvider : BsonSerializationProviderBase
2525
{
26-
/// <summary>
27-
/// Gets a serializer for a type that has been annotated with a <see cref="BsonSerializerAttribute"/> specifying which serializer to use.
28-
/// </summary>
29-
/// <param name="type">The type.</param>
30-
/// <returns>
31-
/// A serializer.
32-
/// </returns>
33-
public override IBsonSerializer GetSerializer(Type type)
26+
/// <inheritdoc/>
27+
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
3428
{
3529
if (type == null)
3630
{

src/MongoDB.Bson/Serialization/BsonClassMapSerializationProvider.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2014 MongoDB Inc.
1+
/* Copyright 2010-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -20,14 +20,10 @@ namespace MongoDB.Bson.Serialization
2020
/// <summary>
2121
/// Represents the class map serialization provider.
2222
/// </summary>
23-
internal class BsonClassMapSerializationProvider : IBsonSerializationProvider
23+
internal class BsonClassMapSerializationProvider : BsonSerializationProviderBase
2424
{
25-
/// <summary>
26-
/// Gets the serializer for a type.
27-
/// </summary>
28-
/// <param name="type">The type.</param>
29-
/// <returns>The serializer.</returns>
30-
public IBsonSerializer GetSerializer(Type type)
25+
/// <inheritdoc/>
26+
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
3127
{
3228
if (type == null)
3329
{

src/MongoDB.Bson/Serialization/BsonObjectModelSerializationProvider.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2014 MongoDB Inc.
1+
/* Copyright 2010-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ namespace MongoDB.Bson.Serialization
2222
/// <summary>
2323
/// Provides serializers for BsonValue and its derivations.
2424
/// </summary>
25-
public class BsonObjectModelSerializationProvider : IBsonSerializationProvider
25+
public class BsonObjectModelSerializationProvider : BsonSerializationProviderBase
2626
{
2727
private static readonly Dictionary<Type, IBsonSerializer> __serializers;
2828

@@ -54,14 +54,8 @@ static BsonObjectModelSerializationProvider()
5454
};
5555
}
5656

57-
/// <summary>
58-
/// Gets a serializer for a type.
59-
/// </summary>
60-
/// <param name="type">The type.</param>
61-
/// <returns>
62-
/// A serializer.
63-
/// </returns>
64-
public IBsonSerializer GetSerializer(Type type)
57+
/// <inheritdoc/>
58+
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
6559
{
6660
if (type == null)
6761
{

src/MongoDB.Bson/Serialization/BsonSerializationProviderBase.cs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2014 MongoDB Inc.
1+
/* Copyright 2010-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -20,16 +20,16 @@ namespace MongoDB.Bson.Serialization
2020
/// <summary>
2121
/// Base class for serialization providers.
2222
/// </summary>
23-
public abstract class BsonSerializationProviderBase : IBsonSerializationProvider
23+
public abstract class BsonSerializationProviderBase : IRegistryAwareBsonSerializationProvider
2424
{
25-
/// <summary>
26-
/// Gets a serializer for a type.
27-
/// </summary>
28-
/// <param name="type">The type.</param>
29-
/// <returns>
30-
/// A serializer.
31-
/// </returns>
32-
public abstract IBsonSerializer GetSerializer(Type type);
25+
/// <inheritdoc/>
26+
public virtual IBsonSerializer GetSerializer(Type type)
27+
{
28+
return GetSerializer(type, BsonSerializer.SerializerRegistry);
29+
}
30+
31+
/// <inheritdoc/>
32+
public abstract IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry);
3333

3434
/// <summary>
3535
/// Creates the serializer from a serializer type definition and type arguments.
@@ -38,9 +38,23 @@ public abstract class BsonSerializationProviderBase : IBsonSerializationProvider
3838
/// <param name="typeArguments">The type arguments.</param>
3939
/// <returns>A serializer.</returns>
4040
protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDefinition, params Type[] typeArguments)
41+
{
42+
return CreateGenericSerializer(serializerTypeDefinition, typeArguments, BsonSerializer.SerializerRegistry);
43+
}
44+
45+
/// <summary>
46+
/// Creates the serializer from a serializer type definition and type arguments.
47+
/// </summary>
48+
/// <param name="serializerTypeDefinition">The serializer type definition.</param>
49+
/// <param name="typeArguments">The type arguments.</param>
50+
/// <param name="serializerRegistry">The serializer registry.</param>
51+
/// <returns>
52+
/// A serializer.
53+
/// </returns>
54+
protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDefinition, Type[] typeArguments, IBsonSerializerRegistry serializerRegistry)
4155
{
4256
var serializerType = serializerTypeDefinition.MakeGenericType(typeArguments);
43-
return CreateSerializer(serializerType);
57+
return CreateSerializer(serializerType, serializerRegistry);
4458
}
4559

4660
/// <summary>
@@ -50,7 +64,34 @@ protected virtual IBsonSerializer CreateGenericSerializer(Type serializerTypeDef
5064
/// <returns>A serializer.</returns>
5165
protected virtual IBsonSerializer CreateSerializer(Type serializerType)
5266
{
53-
return (IBsonSerializer)Activator.CreateInstance(serializerType);
67+
return CreateSerializer(serializerType, BsonSerializer.SerializerRegistry);
68+
}
69+
70+
/// <summary>
71+
/// Creates the serializer.
72+
/// </summary>
73+
/// <param name="serializerType">The serializer type.</param>
74+
/// <param name="serializerRegistry">The serializer registry.</param>
75+
/// <returns>
76+
/// A serializer.
77+
/// </returns>
78+
protected virtual IBsonSerializer CreateSerializer(Type serializerType, IBsonSerializerRegistry serializerRegistry)
79+
{
80+
var constructorInfo = serializerType.GetConstructor(new[] { typeof(IBsonSerializerRegistry) });
81+
if (constructorInfo != null)
82+
{
83+
return (IBsonSerializer)constructorInfo.Invoke(new object[] { serializerRegistry });
84+
}
85+
86+
constructorInfo = serializerType.GetConstructor(new Type[0]);
87+
if (constructorInfo != null)
88+
{
89+
return (IBsonSerializer)constructorInfo.Invoke(new object[0]);
90+
}
91+
92+
throw new MissingMethodException(string.Format(
93+
"No suitable constructor found for serializer type: '{0}'.",
94+
serializerType.FullName));
5495
}
5596
}
5697
}

src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2010-2014 MongoDB Inc.
1+
/* Copyright 2010-2015 MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -125,7 +125,18 @@ private IBsonSerializer CreateSerializer(Type type)
125125
{
126126
foreach (var serializationProvider in _serializationProviders)
127127
{
128-
var serializer = serializationProvider.GetSerializer(type);
128+
IBsonSerializer serializer;
129+
130+
var registryAwareSerializationProvider = serializationProvider as IRegistryAwareBsonSerializationProvider;
131+
if (registryAwareSerializationProvider != null)
132+
{
133+
serializer = registryAwareSerializationProvider.GetSerializer(type, this);
134+
}
135+
else
136+
{
137+
serializer = serializationProvider.GetSerializer(type);
138+
}
139+
129140
if (serializer != null)
130141
{
131142
return serializer;

0 commit comments

Comments
 (0)