Skip to content

Commit bc91d45

Browse files
committed
The call to ApiClientBuilder.EnableBackingStoreForParseNodeFactory would not check to see if the supplied ParseNodeFactoryRegistry was already a BackingStoreParseNodeFactory. This means that if it was called in a loop it would add multiple layers of Backing Storage and the call to GetRootParseNode could end up making lots of calls. In dotnet 4.8 projects with Prefer 32 Bit Turned off, the large call stack was causing StackOverflow Exceptions.
1 parent 5155bd7 commit bc91d45

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

src/abstractions/ApiClientBuilder.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public static ISerializationWriterFactory EnableBackingStoreForSerializationWrit
5050
if(registry != SerializationWriterFactoryRegistry.DefaultInstance)// if the registry is the default instance, we already enabled it above. No need to do it twice
5151
EnableBackingStoreForSerializationRegistry(SerializationWriterFactoryRegistry.DefaultInstance);
5252
}
53+
if(result is BackingStoreSerializationWriterProxyFactory)
54+
//We are already enabled so use it.
55+
return result;
5356
else
5457
result = new BackingStoreSerializationWriterProxyFactory(original);
5558

@@ -69,6 +72,9 @@ public static IParseNodeFactory EnableBackingStoreForParseNodeFactory(IParseNode
6972
if(registry != ParseNodeFactoryRegistry.DefaultInstance)// if the registry is the default instance, we already enabled it above. No need to do it twice
7073
EnableBackingStoreForParseNodeRegistry(ParseNodeFactoryRegistry.DefaultInstance);
7174
}
75+
if(result is BackingStoreParseNodeFactory)
76+
//We are already enabled so use it.
77+
return result;
7278
else
7379
result = new BackingStoreParseNodeFactory(original);
7480

@@ -80,7 +86,7 @@ private static void EnableBackingStoreForParseNodeRegistry(ParseNodeFactoryRegis
8086
var keysToUpdate = new List<string>();
8187
foreach(var entry in registry.ContentTypeAssociatedFactories)
8288
{
83-
if(entry.Value is not (BackingStoreSerializationWriterProxyFactory or SerializationWriterFactoryRegistry))
89+
if(entry.Value is not BackingStoreParseNodeFactory)
8490
{
8591
keysToUpdate.Add(entry.Key);
8692
}
@@ -97,7 +103,7 @@ private static void EnableBackingStoreForSerializationRegistry(SerializationWrit
97103
var keysToUpdate = new List<string>();
98104
foreach(var entry in registry.ContentTypeAssociatedFactories)
99105
{
100-
if(entry.Value is not (BackingStoreSerializationWriterProxyFactory or SerializationWriterFactoryRegistry))
106+
if(entry.Value is not BackingStoreSerializationWriterProxyFactory)
101107
{
102108
keysToUpdate.Add(entry.Key);
103109
}

tests/abstractions/ApiClientBuilderTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,45 @@ public void EnableBackingStoreForParseNodeFactory()
6262
Assert.IsType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);
6363
}
6464

65+
[Fact]
66+
public void EnableBackingStoreForParseNodeFactoryMultipleCallsDoesNotDoubleWrap()
67+
{
68+
// Arrange
69+
//it is not normal to test a private field, but it is the purpose of the test
70+
var concreteFieldInfo = typeof(ParseNodeProxyFactory)
71+
.GetField("_concrete", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
72+
73+
74+
var parseNodeRegistry = new ParseNodeFactoryRegistry();
75+
var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
76+
parseNodeRegistry.ContentTypeAssociatedFactories.TryAdd(StreamContentType, mockParseNodeFactory.Object);
77+
78+
Assert.IsNotType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);
79+
80+
// Act
81+
var firstResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(parseNodeRegistry);
82+
var secondResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(firstResult);
83+
var thirdResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(secondResult);
84+
85+
86+
//make sure the original was not modifed
87+
Assert.IsNotType<BackingStoreParseNodeFactory>(parseNodeRegistry);
88+
89+
// Assert the type has changed due to backing store enabling
90+
Assert.IsType<BackingStoreParseNodeFactory>(firstResult);
91+
92+
//make sure the second call returned the original wrapper
93+
Assert.Equal(firstResult, secondResult);
94+
Assert.Equal(firstResult, thirdResult);
95+
96+
//make sure what is in the registry is a BackingStore
97+
var factory = parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType];
98+
Assert.IsType<BackingStoreParseNodeFactory>(factory);
99+
100+
//make sure the concrete version of the factory is the same as the orginal
101+
Assert.Equal(mockParseNodeFactory.Object, concreteFieldInfo!.GetValue(factory));
102+
}
103+
65104
[Fact]
66105
public void EnableBackingStoreForParseNodeFactoryAlsoEnablesForDefaultInstance()
67106
{
@@ -80,5 +119,38 @@ public void EnableBackingStoreForParseNodeFactoryAlsoEnablesForDefaultInstance()
80119
Assert.IsType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);
81120
Assert.IsType<BackingStoreParseNodeFactory>(ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[StreamContentType]);
82121
}
122+
123+
[Fact]
124+
public void EnableBackingStoreForParseNodeFactoryAlsoEnablesForDefaultInstanceMultipleCallsDoesNotDoubleWrap()
125+
{
126+
// Arrange
127+
var parseNodeRegistry = new ParseNodeFactoryRegistry();
128+
var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
129+
parseNodeRegistry.ContentTypeAssociatedFactories.TryAdd(StreamContentType, mockParseNodeFactory.Object);
130+
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories.TryAdd(StreamContentType, mockParseNodeFactory.Object);
131+
132+
Assert.IsNotType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);
133+
134+
// Act
135+
var firstResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(parseNodeRegistry);
136+
var secondResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(firstResult);
137+
var thirdResult = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(secondResult);
138+
139+
140+
//make sure the original was not modifed
141+
Assert.IsNotType<BackingStoreParseNodeFactory>(parseNodeRegistry);
142+
143+
// Assert the type has changed due to backing store enabling
144+
Assert.IsType<BackingStoreParseNodeFactory>(firstResult);
145+
146+
//make sure the second call returned the original wrapper
147+
Assert.Equal(firstResult, secondResult);
148+
Assert.Equal(firstResult, thirdResult);
149+
150+
//make sure what is in the registry is a BackingStore, it will be a new object so we can only check the type
151+
var factory = ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[StreamContentType];
152+
Assert.IsType<BackingStoreParseNodeFactory>(factory);
153+
154+
}
83155
}
84156
}

0 commit comments

Comments
 (0)