Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit e52d80f

Browse files
authored
Fix AssemblyName version (#11505)
* Fix AssemblyName.FullName * Use canonicalized version instead of Version.ToString
1 parent 27f4b80 commit e52d80f

File tree

4 files changed

+323
-4
lines changed

4 files changed

+323
-4
lines changed

src/mscorlib/System.Private.CoreLib.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,9 @@
8686
<PropertyGroup Condition="'$(OsEnvironment)' == 'Unix'">
8787
<DebugType>portable</DebugType>
8888
</PropertyGroup>
89-
9089
<PropertyGroup Condition="'$(TargetsOSX)' == 'true'">
9190
<DefineConstants>PLATFORM_OSX;$(DefineConstants)</DefineConstants>
9291
</PropertyGroup>
93-
9492
<!-- Assembly attributes -->
9593
<PropertyGroup>
9694
<AssemblyName>System.Private.CoreLib</AssemblyName>
@@ -456,6 +454,10 @@
456454
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\TypeToken.cs" />
457455
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\XXXOnTypeBuilderInstantiation.cs" />
458456
</ItemGroup>
457+
<ItemGroup>
458+
<Compile Include="$(BclSourcesRoot)\System\Reflection\Runtime\Assemblies\AssemblyNameHelpers.cs" />
459+
<Compile Include="$(BclSourcesRoot)\System\Reflection\Runtime\Assemblies\AssemblyNameLexer.cs" />
460+
</ItemGroup>
459461
<ItemGroup>
460462
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\AssemblyExtensions.cs" />
461463
</ItemGroup>
@@ -731,4 +733,4 @@
731733
<Win32Resource Condition="'$(GenerateNativeVersionInfo)'=='true'">$(IntermediateOutputPath)\System.Private.CoreLib.res</Win32Resource>
732734
</PropertyGroup>
733735
<Import Project="GenerateCompilerResponseFile.targets" />
734-
</Project>
736+
</Project>

src/mscorlib/src/System/Reflection/AssemblyName.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace System.Reflection
1919
using System;
2020
using System.IO;
2121
using System.Configuration.Assemblies;
22+
using System.Reflection.Runtime.Assemblies;
2223
using System.Runtime.CompilerServices;
2324
using CultureInfo = System.Globalization.CultureInfo;
2425
using System.Runtime.Serialization;
@@ -279,7 +280,9 @@ public String FullName
279280
{
280281
get
281282
{
282-
return nToString();
283+
if (this.Name == null)
284+
return string.Empty;
285+
return AssemblyNameHelpers.ComputeDisplayName(this);
283286
}
284287
}
285288

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
/*============================================================
6+
**
7+
Type: AssemblyNameHelpers
8+
**
9+
==============================================================*/
10+
11+
using System;
12+
using System.Globalization;
13+
using System.IO;
14+
using System.Text;
15+
using System.Collections.Generic;
16+
17+
namespace System.Reflection.Runtime.Assemblies
18+
{
19+
internal static class AssemblyNameHelpers
20+
{
21+
private const int PUBLIC_KEY_TOKEN_LEN = 8;
22+
23+
public static String ComputeDisplayName(AssemblyName a)
24+
{
25+
if (a.Name == String.Empty)
26+
throw new FileLoadException();
27+
28+
StringBuilder sb = new StringBuilder();
29+
if (a.Name != null)
30+
{
31+
sb.AppendQuoted(a.Name);
32+
}
33+
34+
if (a.Version != null)
35+
{
36+
Version canonicalizedVersion = a.Version.CanonicalizeVersion();
37+
if (canonicalizedVersion.Major != ushort.MaxValue)
38+
{
39+
sb.Append(", Version=");
40+
sb.Append(canonicalizedVersion.Major);
41+
42+
if(canonicalizedVersion.Minor != ushort.MaxValue)
43+
{
44+
sb.Append('.');
45+
sb.Append(canonicalizedVersion.Minor);
46+
47+
if(canonicalizedVersion.Build != ushort.MaxValue)
48+
{
49+
sb.Append('.');
50+
sb.Append(canonicalizedVersion.Build);
51+
52+
if(canonicalizedVersion.Revision != ushort.MaxValue)
53+
{
54+
sb.Append('.');
55+
sb.Append(canonicalizedVersion.Revision);
56+
}
57+
}
58+
}
59+
}
60+
}
61+
62+
String cultureName = a.CultureName;
63+
if (cultureName != null)
64+
{
65+
if (cultureName == String.Empty)
66+
cultureName = "neutral";
67+
sb.Append(", Culture=");
68+
sb.AppendQuoted(cultureName);
69+
}
70+
71+
byte[] pkt = a.GetPublicKeyToken();
72+
if (pkt != null)
73+
{
74+
if (pkt.Length > PUBLIC_KEY_TOKEN_LEN)
75+
throw new ArgumentException();
76+
77+
sb.Append(", PublicKeyToken=");
78+
if (pkt.Length == 0)
79+
sb.Append("null");
80+
else
81+
{
82+
foreach (byte b in pkt)
83+
{
84+
sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
85+
}
86+
}
87+
}
88+
89+
if (0 != (a.Flags & AssemblyNameFlags.Retargetable))
90+
sb.Append(", Retargetable=Yes");
91+
92+
AssemblyContentType contentType = a.ContentType;
93+
if (contentType == AssemblyContentType.WindowsRuntime)
94+
sb.Append(", ContentType=WindowsRuntime");
95+
96+
// NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture.
97+
98+
return sb.ToString();
99+
}
100+
101+
private static void AppendQuoted(this StringBuilder sb, String s)
102+
{
103+
bool needsQuoting = false;
104+
const char quoteChar = '\"';
105+
106+
//@todo: App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one
107+
// by some algorithm. Rather than guess at it, I'll just use double-quote consistently.
108+
if (s != s.Trim() || s.Contains("\"") || s.Contains("\'"))
109+
needsQuoting = true;
110+
111+
if (needsQuoting)
112+
sb.Append(quoteChar);
113+
114+
for (int i = 0; i < s.Length; i++)
115+
{
116+
bool addedEscape = false;
117+
foreach (KeyValuePair<char, String> kv in AssemblyNameLexer.EscapeSequences)
118+
{
119+
String escapeReplacement = kv.Value;
120+
if (!(s[i] == escapeReplacement[0]))
121+
continue;
122+
if ((s.Length - i) < escapeReplacement.Length)
123+
continue;
124+
String prefix = s.Substring(i, escapeReplacement.Length);
125+
if (prefix == escapeReplacement)
126+
{
127+
sb.Append('\\');
128+
sb.Append(kv.Key);
129+
addedEscape = true;
130+
}
131+
}
132+
133+
if (!addedEscape)
134+
sb.Append(s[i]);
135+
}
136+
137+
if (needsQuoting)
138+
sb.Append(quoteChar);
139+
}
140+
141+
public static Version CanonicalizeVersion(this Version version)
142+
{
143+
ushort major = (ushort)version.Major;
144+
ushort minor = (ushort)version.Minor;
145+
ushort build = (ushort)version.Build;
146+
ushort revision = (ushort)version.Revision;
147+
148+
if (major == version.Major && minor == version.Minor && build == version.Build && revision == version.Revision)
149+
return version;
150+
151+
return new Version(major, minor, build, revision);
152+
}
153+
154+
internal static AssemblyContentType ExtractAssemblyContentType(this AssemblyNameFlags flags)
155+
{
156+
return (AssemblyContentType)((((int)flags) >> 9) & 0x7);
157+
}
158+
159+
internal static ProcessorArchitecture ExtractProcessorArchitecture(this AssemblyNameFlags flags)
160+
{
161+
return (ProcessorArchitecture)((((int)flags) >> 4) & 0x7);
162+
}
163+
164+
public static AssemblyNameFlags ExtractAssemblyNameFlags(this AssemblyNameFlags combinedFlags)
165+
{
166+
return combinedFlags & unchecked((AssemblyNameFlags)0xFFFFF10F);
167+
}
168+
169+
internal static AssemblyNameFlags CombineAssemblyNameFlags(AssemblyNameFlags flags, AssemblyContentType contentType, ProcessorArchitecture processorArchitecture)
170+
{
171+
return (AssemblyNameFlags)(((int)flags) | (((int)contentType) << 9) | ((int)processorArchitecture << 4));
172+
}
173+
}
174+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Text;
8+
using System.Globalization;
9+
using System.Collections.Generic;
10+
using System.Runtime.InteropServices;
11+
12+
namespace System.Reflection.Runtime.Assemblies
13+
{
14+
//
15+
// A simple lexer for assembly display names.
16+
//
17+
internal struct AssemblyNameLexer
18+
{
19+
internal AssemblyNameLexer(String s)
20+
{
21+
// Convert string to char[] with NUL terminator. (An actual NUL terminator in the input string will be treated
22+
// as an actual end of string: this is compatible with desktop behavior.)
23+
char[] chars = new char[s.Length + 1];
24+
s.CopyTo(0, chars, 0, s.Length);
25+
_chars = chars;
26+
_index = 0;
27+
}
28+
29+
//
30+
// Return the next token in assembly name. If you expect the result to be DisplayNameToken.String,
31+
// use GetNext(out String) instead.
32+
//
33+
internal Token GetNext()
34+
{
35+
String ignore;
36+
return GetNext(out ignore);
37+
}
38+
39+
//
40+
// Return the next token in assembly name. If the result is DisplayNameToken.String,
41+
// sets "tokenString" to the tokenized string.
42+
//
43+
internal Token GetNext(out String tokenString)
44+
{
45+
tokenString = null;
46+
while (Char.IsWhiteSpace(_chars[_index]))
47+
_index++;
48+
49+
char c = _chars[_index++];
50+
if (c == 0)
51+
return Token.End;
52+
if (c == ',')
53+
return Token.Comma;
54+
if (c == '=')
55+
return Token.Equals;
56+
57+
StringBuilder sb = new StringBuilder();
58+
59+
char quoteChar = (char)0;
60+
if (c == '\'' || c == '\"')
61+
{
62+
quoteChar = c;
63+
c = _chars[_index++];
64+
}
65+
66+
for (;;)
67+
{
68+
if (c == 0)
69+
{
70+
_index--;
71+
break; // Terminate: End of string (desktop compat: if string was quoted, permitted to terminate without end-quote.)
72+
}
73+
74+
if (quoteChar != 0 && c == quoteChar)
75+
break; // Terminate: Found closing quote of quoted string.
76+
77+
if (quoteChar == 0 && (c == ',' || c == '='))
78+
{
79+
_index--;
80+
break; // Terminate: Found start of a new ',' or '=' token.
81+
}
82+
83+
if (quoteChar == 0 && (c == '\'' || c == '\"'))
84+
throw new FileLoadException(); // Desktop compat: Unescaped quote illegal unless entire string is quoted.
85+
86+
if (c == '\\')
87+
{
88+
c = _chars[_index++];
89+
bool matched = false;
90+
foreach (KeyValuePair<char, String> kv in EscapeSequences)
91+
{
92+
if (c == kv.Key)
93+
{
94+
matched = true;
95+
sb.Append(kv.Value);
96+
break;
97+
}
98+
}
99+
if (!matched)
100+
throw new FileLoadException(); // Unrecognized escape
101+
}
102+
else
103+
{
104+
sb.Append(c);
105+
}
106+
107+
c = _chars[_index++];
108+
}
109+
110+
tokenString = sb.ToString();
111+
if (quoteChar == 0)
112+
tokenString = tokenString.Trim(); // Unless quoted, whitespace at beginning or end doesn't count.
113+
return Token.String;
114+
}
115+
116+
internal static KeyValuePair<char, String>[] EscapeSequences =
117+
{
118+
new KeyValuePair<char, String>('\\', "\\"),
119+
new KeyValuePair<char, String>(',', ","),
120+
new KeyValuePair<char, String>('=', "="),
121+
new KeyValuePair<char, String>('\'', "'"),
122+
new KeyValuePair<char, String>('\"', "\""),
123+
new KeyValuePair<char, String>('n', Environment.NewLine),
124+
new KeyValuePair<char, String>('t', "\t"),
125+
};
126+
127+
// Token categories for display name lexer.
128+
internal enum Token
129+
{
130+
Equals = 1,
131+
Comma = 2,
132+
String = 3,
133+
End = 4,
134+
}
135+
136+
private readonly char[] _chars;
137+
private int _index;
138+
}
139+
}
140+

0 commit comments

Comments
 (0)