Skip to content

Commit 81abf6c

Browse files
NH-3954: fix and test cases.
1 parent 33f1962 commit 81abf6c

File tree

4 files changed

+246
-3
lines changed

4 files changed

+246
-3
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3954
5+
{
6+
class Entity1
7+
{
8+
}
9+
class Entity1FakeProxy
10+
{
11+
}
12+
class Entity1FakeProxy2
13+
{
14+
}
15+
16+
class Entity2
17+
{
18+
}
19+
class Entity2FakeProxy
20+
{
21+
}
22+
class Entity2FakeProxy2
23+
{
24+
}
25+
26+
class Entity3
27+
{
28+
}
29+
class Entity3FakeProxy
30+
{
31+
}
32+
class Entity3FakeProxy2
33+
{
34+
}
35+
36+
class Entity4
37+
{
38+
}
39+
class Entity4FakeProxy
40+
{
41+
}
42+
class Entity4FakeProxy2
43+
{
44+
}
45+
46+
class Entity5
47+
{
48+
}
49+
class Entity5FakeProxy
50+
{
51+
}
52+
class Entity5FakeProxy2
53+
{
54+
}
55+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
using NHibernate.Linq;
8+
using NHibernate.Proxy;
9+
using NHibernate.Proxy.DynamicProxy;
10+
using NUnit.Framework;
11+
12+
namespace NHibernate.Test.NHSpecificTest.NH3954
13+
{
14+
[TestFixture]
15+
public class Fixture
16+
{
17+
private ProxyCache _cache;
18+
private ConcurrentDictionary<ProxyCacheEntry, System.Type> _internalCache;
19+
private static readonly FieldInfo InternalCacheField =
20+
typeof(ProxyCache).GetField("cache", BindingFlags.Static | BindingFlags.NonPublic);
21+
22+
[SetUp]
23+
public void SetUp()
24+
{
25+
_cache = new ProxyCache();
26+
27+
_internalCache = (ConcurrentDictionary<ProxyCacheEntry, System.Type>)InternalCacheField.GetValue(_cache);
28+
29+
_cache.StoreProxyType(typeof(Entity1FakeProxy), typeof(Entity1));
30+
_cache.StoreProxyType(typeof(Entity2FakeProxy), typeof(Entity2), typeof(INHibernateProxy));
31+
_cache.StoreProxyType(typeof(Entity3FakeProxy), typeof(Entity3));
32+
_cache.StoreProxyType(typeof(Entity4FakeProxy), typeof(Entity4), typeof(IProxy));
33+
_cache.StoreProxyType(typeof(Entity5FakeProxy), typeof(Entity5), typeof(INHibernateProxy), typeof(IProxy));
34+
35+
// Artificially inject other entries with same hashcodes
36+
Inject(new ProxyCacheEntry(typeof(Entity1), null), typeof(Entity1FakeProxy2), typeof(Entity1), typeof(INHibernateProxy));
37+
Inject(new ProxyCacheEntry(typeof(Entity1), null), typeof(Entity3FakeProxy2), typeof(Entity3));
38+
39+
Inject(new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) }), typeof(Entity2FakeProxy2), typeof(Entity2));
40+
Inject(new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) }), typeof(Entity4FakeProxy2), typeof(Entity4), typeof(IProxy));
41+
Inject(new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) }), typeof(Entity5FakeProxy2), typeof(Entity5), typeof(INHibernateProxy), typeof(IProxy));
42+
}
43+
44+
private void Inject(ProxyCacheEntry entryToTweak, System.Type result, System.Type baseType, params System.Type[] baseInterfaces)
45+
{
46+
TweakEntry(entryToTweak, baseType, baseInterfaces);
47+
_internalCache[entryToTweak] = result;
48+
}
49+
50+
private static readonly MethodInfo BaseTypeSetter = typeof(ProxyCacheEntry).GetProperty("BaseType").GetSetMethod(true);
51+
private static readonly MethodInfo InterfacesSetter = typeof(ProxyCacheEntry).GetProperty("Interfaces").GetSetMethod(true);
52+
private static readonly FieldInfo UniqueInterfacesField =
53+
typeof(ProxyCacheEntry).GetField("_uniqueInterfaces", BindingFlags.Instance | BindingFlags.NonPublic);
54+
55+
/// <summary>
56+
/// Transforms the entry to some other one but preserves its hashcode. This allows to reproduce the issue, which otherwise
57+
/// is too unlikely for being tested.
58+
/// </summary>
59+
private void TweakEntry(ProxyCacheEntry entryToTweak, System.Type baseType, params System.Type[] baseInterfaces)
60+
{
61+
BaseTypeSetter.Invoke(entryToTweak, new object[] { baseType });
62+
InterfacesSetter.Invoke(entryToTweak, new object[] { baseInterfaces ?? new System.Type[0] });
63+
UniqueInterfacesField.SetValue(entryToTweak, new HashSet<System.Type>(baseInterfaces ?? new System.Type[0]));
64+
}
65+
66+
[TearDown]
67+
public void TearDown()
68+
{
69+
_cache = null;
70+
_internalCache = null;
71+
}
72+
73+
[Test]
74+
public void ProxyCacheEntity1FakeProxy()
75+
{
76+
var result = _cache.GetProxyType(typeof(Entity1));
77+
Assert.AreEqual(typeof(Entity1FakeProxy), result);
78+
}
79+
80+
[Test]
81+
public void ProxyCacheEntity1FakeProxy2()
82+
{
83+
var entry = new ProxyCacheEntry(typeof(Entity1), null);
84+
TweakEntry(entry, typeof(Entity1), typeof(INHibernateProxy));
85+
var result = _internalCache[entry];
86+
Assert.AreEqual(typeof(Entity1FakeProxy2), result);
87+
}
88+
89+
[Test]
90+
public void ProxyCacheEntity2FakeProxy()
91+
{
92+
var result = _cache.GetProxyType(typeof(Entity2), typeof(INHibernateProxy));
93+
Assert.AreEqual(typeof(Entity2FakeProxy), result);
94+
}
95+
96+
[Test]
97+
public void ProxyCacheEntity2FakeProxy2()
98+
{
99+
var entry = new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) });
100+
TweakEntry(entry, typeof(Entity2));
101+
var result = _internalCache[entry];
102+
Assert.AreEqual(typeof(Entity2FakeProxy2), result);
103+
}
104+
105+
[Test]
106+
public void ProxyCacheEntity3FakeProxy()
107+
{
108+
var result = _cache.GetProxyType(typeof(Entity3));
109+
Assert.AreEqual(typeof(Entity3FakeProxy), result);
110+
}
111+
112+
[Test]
113+
public void ProxyCacheEntity3FakeProxy2()
114+
{
115+
var entry = new ProxyCacheEntry(typeof(Entity1), null);
116+
TweakEntry(entry, typeof(Entity3));
117+
var result = _internalCache[entry];
118+
Assert.AreEqual(typeof(Entity3FakeProxy2), result);
119+
}
120+
121+
[Test]
122+
public void ProxyCacheEntity4FakeProxy()
123+
{
124+
var result = _cache.GetProxyType(typeof(Entity4), typeof(IProxy));
125+
Assert.AreEqual(typeof(Entity4FakeProxy), result);
126+
// Interfaces order voluntarily inverted.
127+
}
128+
129+
[Test]
130+
public void ProxyCacheEntity4FakeProxy2()
131+
{
132+
var entry = new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) });
133+
TweakEntry(entry, typeof(Entity4), typeof(IProxy));
134+
var result = _internalCache[entry];
135+
Assert.AreEqual(typeof(Entity4FakeProxy2), result);
136+
}
137+
138+
[Test]
139+
public void ProxyCacheEntity5FakeProxy()
140+
{
141+
var result = _cache.GetProxyType(typeof(Entity5), typeof(IProxy), typeof(INHibernateProxy));
142+
Assert.AreEqual(typeof(Entity5FakeProxy), result);
143+
}
144+
145+
[Test]
146+
public void ProxyCacheEntity5FakeProxy2()
147+
{
148+
var entry = new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) });
149+
TweakEntry(entry, typeof(Entity5), typeof(IProxy), typeof(INHibernateProxy));
150+
var result = _internalCache[entry];
151+
Assert.AreEqual(typeof(Entity5FakeProxy2), result);
152+
}
153+
154+
[Test]
155+
public void ProxyCacheNone()
156+
{
157+
// Beware not testing the lookup failure of any combination, even tweaked, actually added in cache.
158+
// (Otherwise the test may starts failing unexpectedly sometimes, as the original bug ...)
159+
// This one was not added in anyway.
160+
System.Type result;
161+
Assert.IsFalse(_cache.TryGetProxyType(typeof(Entity2), new[] { typeof(IProxy) }, out result));
162+
}
163+
}
164+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@
737737
<Compile Include="NHSpecificTest\EntityWithUserTypeCanHaveLinqGenerators\Fixture.cs" />
738738
<Compile Include="NHSpecificTest\EntityWithUserTypeCanHaveLinqGenerators\FooExample.cs" />
739739
<Compile Include="NHSpecificTest\EntityWithUserTypeCanHaveLinqGenerators\IExample.cs" />
740+
<Compile Include="NHSpecificTest\NH3954\Entity.cs" />
741+
<Compile Include="NHSpecificTest\NH3954\Fixture.cs" />
740742
<Compile Include="NHSpecificTest\NH3950\Entity.cs" />
741743
<Compile Include="NHSpecificTest\NH3950\Fixture.cs" />
742744
<Compile Include="NHSpecificTest\NH3952\Entity.cs" />

src/NHibernate/Proxy/DynamicProxy/ProxyCacheEntry.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#endregion
88

99
using System;
10+
using System.Collections;
1011
using System.Collections.Generic;
1112

1213
namespace NHibernate.Proxy.DynamicProxy
@@ -24,6 +25,7 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
2425
}
2526
BaseType = baseType;
2627
Interfaces = interfaces ?? new System.Type[0];
28+
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
2729

2830
if (Interfaces.Length == 0)
2931
{
@@ -32,7 +34,7 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
3234
}
3335

3436
// duplicated type exclusion
35-
var set = new HashSet<System.Type>(Interfaces) { baseType };
37+
var set = new HashSet<System.Type>(_uniqueInterfaces) { baseType };
3638
hashCode = 59;
3739
foreach (System.Type type in set)
3840
{
@@ -43,15 +45,35 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
4345
public System.Type BaseType { get; private set; }
4446
public System.Type[] Interfaces { get; private set; }
4547

48+
[NonSerialized]
49+
private HashSet<System.Type> _uniqueInterfaces;
50+
4651
public override bool Equals(object obj)
4752
{
4853
var that = obj as ProxyCacheEntry;
49-
if (ReferenceEquals(null, that))
54+
if (ReferenceEquals(null, that) ||
55+
hashCode != that.hashCode ||
56+
BaseType != that.BaseType)
5057
{
5158
return false;
5259
}
5360

54-
return hashCode == that.GetHashCode();
61+
// In case the instance was de-serialized, the _uniqueInterfaces will be null. Re-fetch its _uniqueInterfaces if needed.
62+
if (_uniqueInterfaces == null)
63+
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
64+
if (that._uniqueInterfaces == null)
65+
that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces);
66+
67+
if (_uniqueInterfaces.Count != that._uniqueInterfaces.Count)
68+
return false;
69+
70+
foreach (var @interface in that._uniqueInterfaces)
71+
{
72+
if (!_uniqueInterfaces.Contains(@interface))
73+
return false;
74+
}
75+
76+
return true;
5577
}
5678

5779
public override int GetHashCode()

0 commit comments

Comments
 (0)