Skip to content

Commit 3d65b93

Browse files
IgorFedchenkoAaronontheweb
authored andcommitted
Fix version tolerance when bad assembly version specified in type manifest (#149)
* Added red test * Ignore assembly version, if specified * Handling assembly loading failure * Fix mscorelib substitution case * Fixed ToQualifiedAssemblyName test * Strip assembly version with regex * Reuse existing assembly version strip regex
1 parent 54e13f0 commit 3d65b93

File tree

5 files changed

+100
-5
lines changed

5 files changed

+100
-5
lines changed

src/Hyperion.Tests/Bugs.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
using System.Collections.Generic;
1212
using System.Collections.Immutable;
1313
using System.IO;
14+
using System.Reflection;
15+
using System.Reflection.Emit;
16+
using System.Text;
17+
using FluentAssertions;
18+
using Hyperion.Extensions;
1419
using Xunit;
1520

1621
namespace Hyperion.Tests
@@ -85,6 +90,69 @@ public void CanSerializeMessageWithByte()
8590
var res = serializer.Deserialize(stream);
8691
}
8792

93+
/// <summary>
94+
/// Fix for https://github.com/akkadotnet/Hyperion/issues/144
95+
/// </summary>
96+
[Fact]
97+
public void CanFindTypeByManifest_WhenManifestContainsUnknownAssemblyVersion()
98+
{
99+
var serializer = new Serializer(new SerializerOptions(versionTolerance: true, preserveObjectReferences: true));
100+
var type = typeof(ByteMessage);
101+
102+
MemoryStream GetStreamForManifest(string manifest)
103+
{
104+
var stream = new MemoryStream();
105+
stream.WriteLengthEncodedByteArray(manifest.ToUtf8Bytes(), serializer.GetSerializerSession());
106+
stream.Position = 0;
107+
return stream;
108+
}
109+
110+
// This is used in serialized manifest, should be something like 'Hyperion.Tests.Bugs+ByteMessage, Hyperion.Tests'
111+
var shortName = type.GetShortAssemblyQualifiedName();
112+
var shortNameStream = GetStreamForManifest(shortName);
113+
// Something like 'Hyperion.Tests.Bugs+ByteMessage, Hyperion.Tests, Version=0.9.11.0, Culture=neutral, PublicKeyToken=null'
114+
var fullName = type.AssemblyQualifiedName;
115+
var fullNameStream = GetStreamForManifest(fullName);
116+
// Set bad assembly version to make deserialization fail
117+
var fullNameWithUnknownVersion = fullName.Remove(fullName.IndexOf(", Version=")) + ", Version=999999, Culture=neutral, PublicKeyToken=null";
118+
var fullNameWithUnknownVersionStream = GetStreamForManifest(fullNameWithUnknownVersion);
119+
120+
this.Invoking(_ => TypeEx.GetTypeFromManifestFull(shortNameStream, serializer.GetDeserializerSession()))
121+
.Should().NotThrow("When assembly short name is specified in manifest, should work");
122+
this.Invoking(_ => TypeEx.GetTypeFromManifestFull(fullNameStream, serializer.GetDeserializerSession()))
123+
.Should().NotThrow("When assembly fully qualified name specified and name is correct, should work even before fix");
124+
// This one was initially failing
125+
this.Invoking(_ => TypeEx.GetTypeFromManifestFull(fullNameWithUnknownVersionStream, serializer.GetDeserializerSession()))
126+
.Should().NotThrow("When assembly fully qualified name specified and unknown/wrong, type should be detected anyway");
127+
}
128+
129+
[Fact]
130+
public void TypeEx_ToQualifiedAssemblyName_should_strip_version_correctly_for_mscorlib_substitution()
131+
{
132+
var version = TypeEx.ToQualifiedAssemblyName(
133+
"System.Collections.Immutable.ImmutableDictionary`2[[System.String, mscorlib,%core%],[System.Int32, mscorlib,%core%]]," +
134+
" System.Collections.Immutable, Version=1.2.1.0, PublicKeyToken=b03f5f7f11d50a3a",
135+
ignoreAssemblyVersion: true);
136+
137+
var coreAssemblyName = typeof(TypeEx).GetField("CoreAssemblyName", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);
138+
if (coreAssemblyName == null)
139+
throw new Exception($"CoreAssemblyName private static field does not exist in {nameof(TypeEx)} class anymore");
140+
141+
version.Should().Be("System.Collections.Immutable.ImmutableDictionary`2" +
142+
$"[[System.String, mscorlib{coreAssemblyName}],[System.Int32, mscorlib{coreAssemblyName}]], System.Collections.Immutable");
143+
}
144+
145+
[Fact]
146+
public void TypeEx_ToQualifiedAssemblyName_should_strip_version_correctly_for_multiple_versions_specified()
147+
{
148+
var version = TypeEx.ToQualifiedAssemblyName(
149+
"System.Collections.Immutable.ImmutableList`1[[Foo.Bar, Foo, Version=2019.12.10.1]], " +
150+
"System.Collections.Immutable, Version=1.2.2.0, PublicKeyToken=b03f5f7f11d50a3a",
151+
ignoreAssemblyVersion: true);
152+
153+
version.Should().Be("System.Collections.Immutable.ImmutableList`1[[Foo.Bar, Foo]], System.Collections.Immutable");
154+
}
155+
88156
[Fact]
89157
public void CanSerialieCustomType_bug()
90158
{

src/Hyperion.Tests/Hyperion.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<PackageReference Include="FluentAssertions" Version="5.10.0" />
1314
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
1415
<PackageReference Include="System.Collections.Immutable" Version="1.7.0" />
1516
<PackageReference Include="xunit" Version="$(XunitVersion)" />

src/Hyperion/Extensions/TypeEx.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,26 @@ private static Type GetTypeFromManifestName(Stream stream, DeserializerSession s
130130
shortName = shortName.Replace("mscorlib,%core%", "System.Private.CoreLib,%core%");
131131
}
132132
#endif
133+
return LoadTypeByName(shortName);
134+
});
135+
}
133136

134-
var typename = ToQualifiedAssemblyName(shortName);
137+
public static Type LoadTypeByName(string name)
138+
{
139+
try
140+
{
141+
// Try to load type name using strict version to avoid possible conflicts
142+
// i.e. if there are different version available in GAC and locally
143+
var typename = ToQualifiedAssemblyName(name, ignoreAssemblyVersion: false);
135144
return Type.GetType(typename, true);
136-
});
145+
}
146+
catch (FileLoadException)
147+
{
148+
var typename = ToQualifiedAssemblyName(name, ignoreAssemblyVersion: true);
149+
return Type.GetType(typename, true);
150+
}
137151
}
152+
138153
public static Type GetTypeFromManifestFull(Stream stream, DeserializerSession session)
139154
{
140155
var type = GetTypeFromManifestName(stream, session);
@@ -228,9 +243,16 @@ public static string GetShortAssemblyQualifiedName(this Type self)
228243
return name;
229244
}
230245

231-
public static string ToQualifiedAssemblyName(string shortName)
246+
public static string ToQualifiedAssemblyName(string shortName, bool ignoreAssemblyVersion)
232247
{
248+
// Strip out assembly version, if specified
249+
if (ignoreAssemblyVersion)
250+
{
251+
shortName = cleanAssemblyVersionRegex.Replace(shortName, string.Empty);
252+
}
253+
233254
var res = shortName.Replace(",%core%", CoreAssemblyName);
255+
234256
return res;
235257
}
236258

src/Hyperion/Hyperion.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<Reference Include="Microsoft.CSharp" />
2525
</ItemGroup>
2626

27+
<ItemGroup>
28+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
29+
<_Parameter1>Hyperion.Tests</_Parameter1>
30+
</AssemblyAttribute>
31+
</ItemGroup>
2732

2833
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
2934
<DefineConstants>$(DefineConstants);NETSTANDARD16</DefineConstants>

src/Hyperion/ValueSerializers/TypeSerializer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public override object ReadValue(Stream stream, DeserializerSession session)
6666
if (shortname == null)
6767
return null;
6868

69-
var name = TypeEx.ToQualifiedAssemblyName(shortname);
70-
var type = Type.GetType(name,true);
69+
var type = TypeEx.LoadTypeByName(shortname);
7170

7271
//add the deserialized type to lookup
7372
if (session.Serializer.Options.PreserveObjectReferences)

0 commit comments

Comments
 (0)