diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs index 8174ed03594f72e9056e881fbbc78e5b6ebdec58..f293da8dca7649828d1706980fb145661d39f763 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs @@ -1984,7 +1984,7 @@ private void DecodeTypeForwardedToAttribute(ref DecodeWellKnownAttributeArgument if (lazyInternalsVisibleToMap == null) { Interlocked.CompareExchange(ref lazyInternalsVisibleToMap, - new ConcurrentDictionary, Tuple>>(), null); + new ConcurrentDictionary, Tuple>>(StringComparer.OrdinalIgnoreCase), null); } //later, once the identity is established we confirm that if the assembly being diff --git a/Src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs b/Src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs index 490156a01926ca6ebaa9c4efe6427ef72c7901fd..0938d0fb2e3a5a6cc90f518af5bd746ae1fc9a77 100644 --- a/Src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs +++ b/Src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs @@ -1761,6 +1761,57 @@ public A GetA() CompileAndVerify(cb, verify:false).Diagnostics.Verify(); } + [Fact, WorkItem(1072350, "DevDiv")] + public void Bug1072350() + { + const string sourceA = @" +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""X "")] +internal class A +{ + internal static int I = 42; +}"; + + const string sourceB = @" +class B +{ + static void Main() + { + System.Console.Write(A.I); + } +}"; + + var ca = CreateCompilationWithMscorlib(sourceA, options: TestOptions.ReleaseDll, assemblyName: "ClassLibrary2"); + CompileAndVerify(ca); + + var cb = CreateCompilationWithMscorlib(sourceB, options: TestOptions.ReleaseExe, assemblyName: "X", references: new[] { new CSharpCompilationReference(ca)}); + CompileAndVerify(cb, expectedOutput: "42", emitOptions: TestEmitters.CCI).Diagnostics.Verify(); + } + + [Fact, WorkItem(1072339, "DevDiv")] + public void Bug1072339() + { + const string sourceA = @" +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""x"")] +internal class A +{ + internal static int I = 42; +}"; + + const string sourceB = @" +class B +{ + static void Main() + { + System.Console.Write(A.I); + } +}"; + + var ca = CreateCompilationWithMscorlib(sourceA, options: TestOptions.ReleaseDll, assemblyName: "ClassLibrary2"); + CompileAndVerify(ca); + + var cb = CreateCompilationWithMscorlib(sourceB, options: TestOptions.ReleaseExe, assemblyName: "X", references: new[] { new CSharpCompilationReference(ca)}); + CompileAndVerify(cb, expectedOutput: "42", emitOptions: TestEmitters.CCI).Diagnostics.Verify(); + } #endregion } diff --git a/Src/Compilers/Core/CodeAnalysisTest/MetadataReferences/AssemblyIdentityDisplayNameTests.cs b/Src/Compilers/Core/CodeAnalysisTest/MetadataReferences/AssemblyIdentityDisplayNameTests.cs index 50b44931a7a11e04406e0149a5d24d86fd720de8..522b30cd83f7155d042e063821ed2e0e0177f484 100644 --- a/Src/Compilers/Core/CodeAnalysisTest/MetadataReferences/AssemblyIdentityDisplayNameTests.cs +++ b/Src/Compilers/Core/CodeAnalysisTest/MetadataReferences/AssemblyIdentityDisplayNameTests.cs @@ -184,6 +184,7 @@ public void TryParseDisplayName_QuotingAndEscaping() TestParseSimpleName("a\\", expected: null); // double quotes (unescaped can't be in the middle): + TestParseSimpleName("\"a\"", expected: "a"); TestParseSimpleName("\"a'a\", Version=1.0.0.0", expected: "a'a"); TestParseSimpleName("\\\"aa\\\", Version=1.0.0.0", expected: "\"aa\""); TestParseSimpleName("\\\"a'a\\\", Version=1.0.0.0", expected: null); @@ -206,6 +207,7 @@ public void TryParseDisplayName_QuotingAndEscaping() TestParseSimpleName("\"\"a\"\", Version=1.0.0.0", expected: null); // single quotes (unescaped can't be in the middle): + TestParseSimpleName("'a'", expected: "a"); TestParseSimpleName("'a\"a', Version=1.0.0.0", expected: "a\"a"); TestParseSimpleName("\\'aa\\', Version=1.0.0.0", expected: "'aa'"); TestParseSimpleName("\\'a\"a\\', Version=1.0.0.0", expected: null); @@ -227,6 +229,24 @@ public void TryParseDisplayName_QuotingAndEscaping() TestParseSimpleName("'', Version=1.0.0.0", expected: null); TestParseSimpleName("''a'', Version=1.0.0.0", expected: null); + // Unicode quotes + TestParseSimpleName("\u201ca\u201d", expected: "\u201ca\u201d"); + TestParseSimpleName("\\u201c;a\\u201d;", expected: "\u201ca\u201d"); + TestParseSimpleName("\u201ca", expected: "\u201ca"); + TestParseSimpleName("\\u201c;a", expected: "\u201ca"); + TestParseSimpleName("a\u201d", expected: "a\u201d"); + TestParseSimpleName("a\\u201d;", expected: "a\u201d"); + TestParseSimpleName("\u201ca\u201d ", expected: "\u201ca\u201d"); + TestParseSimpleName("\\u201c;a\\u201d; ", expected: "\u201ca\u201d"); + TestParseSimpleName("\u2018a\u2019", expected: "\u2018a\u2019"); + TestParseSimpleName("\\u2018;a\\u2019;", expected: "\u2018a\u2019"); + TestParseSimpleName("\u2018a", expected: "\u2018a"); + TestParseSimpleName("\\u2018;a", expected: "\u2018a"); + TestParseSimpleName("a\u2019", expected: "a\u2019"); + TestParseSimpleName("a\\u2019;", expected: "a\u2019"); + TestParseSimpleName("\u2018a\u2019 ", expected: "\u2018a\u2019"); + TestParseSimpleName("\\u2018;a\\u2019; ", expected: "\u2018a\u2019"); + // NUL characters in the name: TestParseSimpleName(" \0 , Version=1.0.0.0", expected: null); TestParseSimpleName("zzz, Version=1.0.0\0.0", null); @@ -239,11 +259,29 @@ public void TryParseDisplayName_QuotingAndEscaping() TestParseSimpleName(" , Version=1.0.0.0", expected: null); // single or double quote if name starts or ends with whitespace: + TestParseSimpleName("\" a \"", expected: " a "); + TestParseSimpleName("' a '", expected: " a "); TestParseSimpleName("'\r\t\n', Version=1.0.0.0", expected: "\r\t\n"); TestParseSimpleName("\"\r\t\n\", Version=1.0.0.0", expected: "\r\t\n"); TestParseSimpleName("x\n\t\nx, Version=1.0.0.0", expected: "x\n\t\nx"); + // Missing parts + TestParseSimpleName("=", null); + TestParseSimpleName(",", null); + TestParseSimpleName("a,", null); + TestParseSimpleName("a ,", null); + TestParseSimpleName("\"a\"=", expected: null); + TestParseSimpleName("\"a\" =", expected: null); + TestParseSimpleName("\"a\",", expected: null); + TestParseSimpleName("\"a\" ,", expected: null); + TestParseSimpleName("'a'=", expected: null); + TestParseSimpleName("'a' =", expected: null); + TestParseSimpleName("'a',", expected: null); + TestParseSimpleName("'a' ,", expected: null); + // skips initial and trailing whitespace characters (' ', \t, \r, \n): + TestParseSimpleName(" \"a\" ", expected: "a"); + TestParseSimpleName(" 'a' ", expected: "a"); TestParseSimpleName(" x, Version=1.0.0.0", expected: "x"); TestParseSimpleName(" x\t\r\n , Version=1.0.0.0", expected: "x"); TestParseSimpleName("\u0008x, Version=1.0.0.0", expected: "\u0008x"); @@ -255,6 +293,12 @@ public void TryParseDisplayName_QuotingAndEscaping() TestParseSimpleName(" \"aa\" x , Version=1.0.0.0", expected: null); TestParseSimpleName(" \"aa\" \"\" , Version=1.0.0.0", expected: null); TestParseSimpleName(" \"aa\" \'\' , Version=1.0.0.0", expected: null); + TestParseSimpleName(" A", "A"); + TestParseSimpleName("A ", "A"); + TestParseSimpleName(" A ", "A"); + TestParseSimpleName(" A, Version=1.0.0.0", "A"); + TestParseSimpleName("A , Version=1.0.0.0", "A"); + TestParseSimpleName( "A , Version=1.0.0.0", "A"); // invalid characters: foreach (var c in ClrInvalidCharacters) diff --git a/Src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs b/Src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs index a952a48a5743270e74b9b08148442889f8b3f9f6..e5ed499915f66383574fa1c26f66fddc6a06e6e3 100644 --- a/Src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs +++ b/Src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs @@ -173,8 +173,8 @@ public static bool TryParseDisplayName(string displayName, out AssemblyIdentity } int position = 0; - string simpleName = TryParseNameToken(displayName, ',', ref position); - if (simpleName == null) + string simpleName; + if (!TryParseNameToken(displayName, ref position, out simpleName)) { return false; } @@ -191,18 +191,34 @@ public static bool TryParseDisplayName(string displayName, out AssemblyIdentity while (position < displayName.Length) { - string propertyName = TryParseNameToken(displayName, '=', ref position); - if (propertyName == null) + // Parse ',' name '=' value + if (displayName[position] != ',') { return false; } - string propertyValue = TryParseNameToken(displayName, ',', ref position); - if (propertyValue == null) + position++; + + string propertyName; + if (!TryParseNameToken(displayName, ref position, out propertyName)) + { + return false; + } + + if (position >= displayName.Length || displayName[position] != '=') + { + return false; + } + + position++; + + string propertyValue; + if (!TryParseNameToken(displayName, ref position, out propertyValue)) { return false; } + // Process property if (string.Equals(propertyName, "Version", StringComparison.OrdinalIgnoreCase)) { if ((seen & AssemblyIdentityParts.Version) != 0) @@ -381,21 +397,26 @@ public static bool TryParseDisplayName(string displayName, out AssemblyIdentity return true; } - private static string TryParseNameToken(string displayName, char terminator, ref int position) + private static bool TryParseNameToken(string displayName, ref int position, out string value) { Debug.Assert(displayName.IndexOf('\0') == -1); int i = position; // skip leading whitespace: - while (i < displayName.Length && IsWhiteSpace(displayName[i])) + while (true) { - i++; - } + if (i == displayName.Length) + { + value = null; + return false; + } + else if (!IsWhiteSpace(displayName[i])) + { + break; + } - if (i == displayName.Length) - { - return null; + i++; } char quote; @@ -410,75 +431,94 @@ private static string TryParseNameToken(string displayName, char terminator, ref int valueStart = i; int valueEnd = displayName.Length; - int escapeCount = 0; + bool containsEscapes = false; - while (i < displayName.Length) + while (true) { + if (i >= displayName.Length) + { + i = displayName.Length; + break; + } + char c = displayName[i]; if (c == '\\') { - escapeCount++; + containsEscapes = true; i += 2; continue; } - if (quote == 0) + if (quote == '\0') { - if (c == terminator) + if (IsNameTokenTerminator(c)) { - int j = i - 1; - while (j >= valueStart && IsWhiteSpace(displayName[j])) - { - j--; - } - - valueEnd = j + 1; break; } - - if (IsQuote(c) || IsNameTokenTerminator(c)) + else if (IsQuote(c)) { - return null; + value = null; + return false; } } else if (c == quote) { valueEnd = i; i++; + break; + } - // skip any whitespace following the quote - while (i < displayName.Length && IsWhiteSpace(displayName[i])) - { - i++; - } + i++; + } - if (i < displayName.Length && displayName[i] != terminator) + if (quote == '\0') + { + int j = i - 1; + while (j >= valueStart && IsWhiteSpace(displayName[j])) + { + j--; + } + + valueEnd = j + 1; + } + else + { + // skip any whitespace following the quote and check for the terminator + while (i < displayName.Length) + { + char c = displayName[i]; + if (!IsWhiteSpace(c)) { - return null; + if (!IsNameTokenTerminator(c)) + { + value = null; + return false; + } + break; } - break; + i++; } - - i++; } - Debug.Assert(i >= displayName.Length || IsNameTokenTerminator(displayName[i])); - position = (i >= displayName.Length) ? displayName.Length : i + 1; + Debug.Assert(i == displayName.Length || IsNameTokenTerminator(displayName[i])); + position = i; // empty if (valueEnd == valueStart) { - return null; + value = null; + return false; } - if (escapeCount == 0) + if (!containsEscapes) { - return displayName.Substring(valueStart, valueEnd - valueStart); + value = displayName.Substring(valueStart, valueEnd - valueStart); + return true; } else { - return Unescape(displayName, valueStart, valueEnd); + return TryUnescape(displayName, valueStart, valueEnd, out value); } } @@ -687,7 +727,7 @@ private static bool CanBeEscaped(char c) } } - private static string Unescape(string str, int start, int end) + private static bool TryUnescape(string str, int start, int end, out string value) { var sb = PooledStringBuilder.GetInstance(); @@ -697,10 +737,10 @@ private static string Unescape(string str, int start, int end) char c = str[i++]; if (c == '\\') { - Debug.Assert(CanBeEscaped(c)); if (!Unescape(sb.Builder, str, ref i)) { - return null; + value = null; + return false; } } else @@ -709,7 +749,8 @@ private static string Unescape(string str, int start, int end) } } - return sb.ToStringAndFree(); + value = sb.ToStringAndFree(); + return true; } private static bool Unescape(StringBuilder sb, string str, ref int i) @@ -775,4 +816,4 @@ private static bool Unescape(StringBuilder sb, string str, ref int i) } } } -} \ No newline at end of file +} diff --git a/Src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb b/Src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb index 1faef097c8750e0fe8ccfbd221595e8213c6b381..c69216af64e052cc8d1c742d80757fdf31137655 100644 --- a/Src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb +++ b/Src/Compilers/VisualBasic/Portable/Symbols/Source/SourceAssemblySymbol.vb @@ -935,7 +935,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols If m_lazyInternalsVisibleToMap Is Nothing Then Interlocked.CompareExchange(m_lazyInternalsVisibleToMap, - New ConcurrentDictionary(Of String, ConcurrentDictionary(Of ImmutableArray(Of Byte), Tuple(Of Location, String))), Nothing) + New ConcurrentDictionary(Of String, ConcurrentDictionary(Of ImmutableArray(Of Byte), Tuple(Of Location, String)))(StringComparer.OrdinalIgnoreCase), Nothing) End If 'later, once the identity is established we confirm that if the assembly being diff --git a/Src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb b/Src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb index 4367a18cedc9cbd088c85ed77350b823d2b2f2c1..8d141c9103313ca29fb32ffaaa4c4f62df5d52a4 100644 --- a/Src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb +++ b/Src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.IO Imports System.Reflection.Metadata Imports System.Reflection.PortableExecutable +Imports System.Xml.Linq Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.Emit @@ -1555,4 +1556,64 @@ End Class CompileAndVerify(cb, verify:=False).Diagnostics.Verify() End Sub + + Public Sub Bug1072350() + Dim sourceA As XElement = _ + + +Friend Class A + Friend Shared I As Integer = 42 +End Class]]> + + + + Dim sourceB As XElement = _ + + + + + + Dim ca = CreateCompilationWithMscorlib(sourceA, options:=TestOptions.ReleaseDll) + CompileAndVerify(ca) + + Dim cb = CreateCompilationWithMscorlib(sourceB, options:=TestOptions.ReleaseExe, references:={ new VisualBasicCompilationReference(ca) }) + CompileAndVerify(cb, expectedOutput:="42", emitOptions:=TestEmitters.CCI).Diagnostics.Verify() + End Sub + + + Public Sub Bug1072339() + Dim sourceA As XElement = _ + + +Friend Class A + Friend Shared I As Integer = 42 +End Class]]> + + + + Dim sourceB As XElement = _ + + + + + + Dim ca = CreateCompilationWithMscorlib(sourceA, options:=TestOptions.ReleaseDll) + CompileAndVerify(ca) + + Dim cb = CreateCompilationWithMscorlib(sourceB, options:=TestOptions.ReleaseExe, references:={ new VisualBasicCompilationReference(ca) }) + CompileAndVerify(cb, expectedOutput:="42", emitOptions:=TestEmitters.CCI).Diagnostics.Verify() + End Sub + End Class