From 5e6d159f016201f471b033c42c998f75790f0f75 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 25 Feb 2015 17:41:35 -0800 Subject: [PATCH] Ask for bytes of not-yet-loaded winmds When we try to bind something that is from a not-yet-loaded winmd, we generally see ErrorCode.ERR_DottedTypeNameNotFoundInNS because the namespace Windows.X is only provided by Windows.X.winmd. When this occurs, heuristically ask the debugger for an assembly with name Windows.X and content type AssemblyContentType.WindowsRuntime. The VB equivalent of ERR_DottedTypeNameNotFoundInNS is ERR_NameNotMember2. However, VB also has a second error code to consider: ERR_UndefinedType1, which is only produced in type-only contexts. Since the argument to ERR_UndefinedType1 is the qualified name, as a string, we have to do some extra work to determine the next part that needs to be resolved. Caveat: This doesn't work end-to-end (DevDiv #1130191). --- .../ExpressionCompiler/EvaluationContext.cs | 15 ++ .../MissingAssemblyTests.cs | 82 +++++++++- .../ExpressionCompiler/MetadataUtilities.cs | 6 + .../ExpressionCompiler/EvaluationContext.vb | 55 ++++++- .../MissingAssemblyTests.vb | 153 +++++++++++++++++- 5 files changed, 304 insertions(+), 7 deletions(-) diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs index 69bbfe7b134..2d7f36f29bd 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs @@ -550,6 +550,21 @@ internal static ImmutableArray GetMissingAssemblyIdentitiesHel } } break; + case ErrorCode.ERR_DottedTypeNameNotFoundInNS: + if (arguments.Count == 2) + { + var namespaceName = arguments[0] as string; + var containingNamespace = arguments[1] as NamespaceSymbol; + if (namespaceName != null && (object)containingNamespace != null && + containingNamespace.ConstituentNamespaces.Any(n => n.ContainingAssembly.Identity.IsWindowsAssemblyIdentity())) + { + // This is just a heuristic, but it has the advantage of being portable, particularly + // across different versions of (desktop) windows. + var identity = new AssemblyIdentity($"{containingNamespace.ToDisplayString()}.{namespaceName}", contentType: System.Reflection.AssemblyContentType.WindowsRuntime); + return ImmutableArray.Create(identity); + } + } + break; case ErrorCode.ERR_DynamicAttributeMissing: case ErrorCode.ERR_DynamicRequiredTypesMissing: // MSDN says these might come from System.Dynamic.Runtime diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs index 8910302bc4d..5a9b4bb665e 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs @@ -380,7 +380,87 @@ static void M() Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()); } + [WorkItem(1114866)] + [ConditionalFact(typeof(OSVersionWin8))] + public void NotYetLoadedWinMds() + { + var source = +@"class C +{ + static void M(Windows.Storage.StorageFolder f) + { + } +}"; + var comp = CreateCompilationWithMscorlib(source, WinRtRefs, TestOptions.DebugDll); + var runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.Storage"); + Assert.True(runtimeAssemblies.Any()); + var context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)); + + const string expectedError = "error CS0234: The type or namespace name 'UI' does not exist in the namespace 'Windows' (are you missing an assembly reference?)"; + var expectedMissingAssemblyIdentity = new AssemblyIdentity("Windows.UI", contentType: System.Reflection.AssemblyContentType.WindowsRuntime); + + ResultProperties resultProperties; + string actualError; + ImmutableArray actualMissingAssemblyIdentities; + context.CompileExpression( + InspectionContextFactory.Empty, + "typeof(@Windows.UI.Colors)", + DkmEvaluationFlags.None, + DiagnosticFormatter.Instance, + out resultProperties, + out actualError, + out actualMissingAssemblyIdentities, + EnsureEnglishUICulture.PreferredOrNull, + testData: null); + Assert.Equal(expectedError, actualError); + Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()); + } + + /// + /// Windows.UI.Xaml is the only (win8) winmd with more than two parts. + /// + [WorkItem(1114866)] + [ConditionalFact(typeof(OSVersionWin8))] + public void NotYetLoadedWinMds_MultipleParts() + { + var source = +@"class C +{ + static void M(Windows.UI.Colors c) + { + } +}"; + var comp = CreateCompilationWithMscorlib(source, WinRtRefs, TestOptions.DebugDll); + var runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.UI"); + Assert.True(runtimeAssemblies.Any()); + var context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)); + + const string expectedError = "error CS0234: The type or namespace name 'Xaml' does not exist in the namespace 'Windows.UI' (are you missing an assembly reference?)"; + var expectedMissingAssemblyIdentity = new AssemblyIdentity("Windows.UI.Xaml", contentType: System.Reflection.AssemblyContentType.WindowsRuntime); + + ResultProperties resultProperties; + string actualError; + ImmutableArray actualMissingAssemblyIdentities; + context.CompileExpression( + InspectionContextFactory.Empty, + "typeof(Windows.@UI.Xaml.Application)", + DkmEvaluationFlags.None, + DiagnosticFormatter.Instance, + out resultProperties, + out actualError, + out actualMissingAssemblyIdentities, + EnsureEnglishUICulture.PreferredOrNull, + testData: null); + Assert.Equal(expectedError, actualError); + Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()); + } + private EvaluationContext CreateMethodContextWithReferences(Compilation comp, string methodName, params MetadataReference[] references) + { + return CreateMethodContextWithReferences(comp, methodName, ImmutableArray.CreateRange(references)); + } + + private EvaluationContext CreateMethodContextWithReferences(Compilation comp, string methodName, ImmutableArray references) { byte[] exeBytes; byte[] pdbBytes; @@ -388,7 +468,7 @@ private EvaluationContext CreateMethodContextWithReferences(Compilation comp, st var result = comp.EmitAndGetReferences(out exeBytes, out pdbBytes, out unusedReferences); Assert.True(result); - var runtime = CreateRuntimeInstance(GetUniqueName(), ImmutableArray.CreateRange(references), exeBytes, new SymReader(pdbBytes)); + var runtime = CreateRuntimeInstance(GetUniqueName(), references, exeBytes, new SymReader(pdbBytes)); return CreateMethodContext(runtime, methodName); } diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs index 00410d62ab2..5e29b8b0007 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs @@ -194,6 +194,12 @@ internal static bool IsWindowsAssemblyName(string assemblyName) return assemblyName.Equals("windows", StringComparison.OrdinalIgnoreCase); } + internal static bool IsWindowsAssemblyIdentity(this AssemblyIdentity assemblyIdentity) + { + return IsWindowsAssemblyName(assemblyIdentity.Name) && + assemblyIdentity.ContentType == System.Reflection.AssemblyContentType.WindowsRuntime; + } + internal static LocalInfo GetLocalInfo( this MetadataDecoder metadataDecoder, byte[] signature) diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb index f23844408ad..cdec4a09b61 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb @@ -562,17 +562,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator End Sub Friend Overrides Function GetMissingAssemblyIdentities(diagnostic As Diagnostic) As ImmutableArray(Of AssemblyIdentity) - Return GetMissingAssemblyIdentitiesHelper(CType(diagnostic.Code, ERRID), diagnostic.Arguments) + Return GetMissingAssemblyIdentitiesHelper(CType(diagnostic.Code, ERRID), diagnostic.Arguments, Me.Compilation.GlobalNamespace) End Function ''' ''' Friend for testing. ''' - Friend Shared Function GetMissingAssemblyIdentitiesHelper(code As ERRID, arguments As IReadOnlyList(Of Object)) As ImmutableArray(Of AssemblyIdentity) - Select Case code + Friend Shared Function GetMissingAssemblyIdentitiesHelper(code As ERRID, arguments As IReadOnlyList(Of Object), globalNamespace As NamespaceSymbol) As ImmutableArray(Of AssemblyIdentity) + Select Case code Case ERRID.ERR_UnreferencedAssemblyEvent3, ERRID.ERR_UnreferencedAssembly3 For Each argument As Object In arguments - Dim identity = If(TryCast(argument, AssemblyIdentity), TryCast(argument, AssemblySymbol)?.Identity) + Dim identity = If(TryCast(argument, AssemblyIdentity), TryCast(argument, AssemblySymbol)?.Identity) If IsValidMissingAssemblyIdentity(identity) Then Return ImmutableArray.Create(identity) End If @@ -584,6 +584,49 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return ImmutableArray.Create(identity) End If End If + Case ERRID.ERR_NameNotMember2 + If arguments.Count = 2 Then + Dim namespaceName = TryCast(arguments(0), String) + Dim containingNamespace = TryCast(arguments(1), NamespaceSymbol) + If namespaceName IsNot Nothing AndAlso containingNamespace IsNot Nothing AndAlso HasConstituentFromWindowsAssembly(containingNamespace) Then + ' This is just a heuristic, but it has the advantage of being portable, particularly + ' across different versions of (desktop) windows. + Dim identity = New AssemblyIdentity($"{containingNamespace.ToDisplayString}.{namespaceName}", contentType:=AssemblyContentType.WindowsRuntime) + Return ImmutableArray.Create(identity) + End If + End If + Case ERRID.ERR_UndefinedType1 + If arguments.Count = 1 Then + Dim qualifiedName = TryCast(arguments(0), String) + If Not String.IsNullOrEmpty(qualifiedName) Then + Dim nameParts = qualifiedName.Split("."c) + Dim numParts = nameParts.Length + Dim pos = 0 + If CaseInsensitiveComparison.Comparer.Equals(nameParts(0), "global") Then + pos = 1 + Debug.Assert(pos < numParts) + End If + Dim currNamespace = globalNamespace + While pos < numParts + Dim nextNamespace = currNamespace.GetMembers(nameParts(pos)).OfType(Of NamespaceSymbol).SingleOrDefault() + If nextNamespace Is Nothing Then + Exit While + End If + pos += 1 + currNamespace = nextNamespace + End While + + If currNamespace IsNot globalNamespace AndAlso HasConstituentFromWindowsAssembly(currNamespace) AndAlso pos < numParts Then + Dim nextNamePart = nameParts(pos) + If nextNamePart.All(AddressOf SyntaxFacts.IsIdentifierPartCharacter) Then + ' This is just a heuristic, but it has the advantage of being portable, particularly + ' across different versions of (desktop) windows. + Dim identity = New AssemblyIdentity($"{currNamespace.ToDisplayString}.{nameParts(pos)}", contentType:=AssemblyContentType.WindowsRuntime) + Return ImmutableArray.Create(identity) + End If + End If + End If + End If Case ERRID.ERR_XmlFeaturesNotAvailable Return ImmutableArray.Create(SystemIdentity, SystemCoreIdentity, SystemXmlIdentity, SystemXmlLinqIdentity) Case ERRID.ERR_MissingRuntimeHelper @@ -593,6 +636,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return Nothing End Function + Private Shared Function HasConstituentFromWindowsAssembly(namespaceSymbol As NamespaceSymbol) As Boolean + Return namespaceSymbol.ConstituentNamespaces.Any(Function(n) n.ContainingAssembly.Identity.IsWindowsAssemblyIdentity) + End Function + Private Shared Function IsValidMissingAssemblyIdentity(identity As AssemblyIdentity) As Boolean Return identity IsNot Nothing AndAlso Not identity.Equals(MissingCorLibrarySymbol.Instance.Identity) End Function diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/MissingAssemblyTests.vb b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/MissingAssemblyTests.vb index 42174f806ea..845e557dc30 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/MissingAssemblyTests.vb +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/MissingAssemblyTests.vb @@ -229,6 +229,43 @@ End Class Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()) End Sub + + + Public Sub ERR_UndefinedType1() + Dim source = " +Class C + Sub M() + End Sub +End Class +" + + Dim comp = CreateCompilationWithReferences({VisualBasicSyntaxTree.ParseText(source)}, {MscorlibRef}.Concat(WinRtRefs), TestOptions.DebugDll) + Dim runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.UI") + Assert.True(runtimeAssemblies.Any()) + Dim context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)) + Dim globalNamespace = context.Compilation.GlobalNamespace + + Dim expectedIdentity = New AssemblyIdentity("Windows.Storage", contentType:=Reflection.AssemblyContentType.WindowsRuntime) + + Dim actualIdentity = EvaluationContext.GetMissingAssemblyIdentitiesHelper(ERRID.ERR_UndefinedType1, {"Windows.Storage"}, globalNamespace).Single() + Assert.Equal(expectedIdentity, actualIdentity) + + actualIdentity = EvaluationContext.GetMissingAssemblyIdentitiesHelper(ERRID.ERR_UndefinedType1, {"Global.Windows.Storage"}, globalNamespace).Single() + Assert.Equal(expectedIdentity, actualIdentity) + + actualIdentity = EvaluationContext.GetMissingAssemblyIdentitiesHelper(ERRID.ERR_UndefinedType1, {"Global.Windows.Storage.Additional"}, globalNamespace).Single() + Assert.Equal(expectedIdentity, actualIdentity) + + + expectedIdentity = New AssemblyIdentity("Windows.UI.Xaml", contentType:=Reflection.AssemblyContentType.WindowsRuntime) + + actualIdentity = EvaluationContext.GetMissingAssemblyIdentitiesHelper(ERRID.ERR_UndefinedType1, {"Windows.UI.Xaml"}, globalNamespace).Single() + Assert.Equal(expectedIdentity, actualIdentity) + + + Assert.True(EvaluationContext.GetMissingAssemblyIdentitiesHelper(ERRID.ERR_UndefinedType1, {"Windows.UI.Xaml(Of T)"}, globalNamespace).IsDefault) + End Sub + @@ -264,19 +301,131 @@ End Class Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()) End Sub + + + Public Sub NotYetLoadedWinMds() + Dim source = " +Class C + Shared Sub M(f As Windows.Storage.StorageFolder) + End Sub +End Class +" + + Dim comp = CreateCompilationWithReferences({VisualBasicSyntaxTree.ParseText(source)}, {MscorlibRef}.Concat(WinRtRefs), TestOptions.DebugDll) + Dim runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.Storage") + Assert.True(runtimeAssemblies.Any()) + Dim context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)) + + Const expectedError = "(1,1): error BC30456: 'UI' is not a member of 'Windows'." + Dim expectedMissingAssemblyIdentity = New AssemblyIdentity("Windows.UI", contentType:=System.Reflection.AssemblyContentType.WindowsRuntime) + + Dim resultProperties As ResultProperties = Nothing + Dim actualError As String = Nothing + Dim actualMissingAssemblyIdentities As ImmutableArray(Of AssemblyIdentity) = Nothing + context.CompileExpression( + InspectionContextFactory.Empty, + "Windows.UI.Colors", + DkmEvaluationFlags.None, + DiagnosticFormatter.Instance, + resultProperties, + actualError, + actualMissingAssemblyIdentities, + EnsureEnglishUICulture.PreferredOrNull, + testData:=Nothing) + Assert.Equal(expectedError, actualError) + Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()) + End Sub + + ''' + ''' Windows.UI.Xaml is the only (win8) winmd with more than two parts. + ''' + + + Public Sub NotYetLoadedWinMds_MultipleParts() + Dim source = " +Class C + Shared Sub M(c As Windows.UI.Colors) + End Sub +End Class +" + + Dim comp = CreateCompilationWithReferences({VisualBasicSyntaxTree.ParseText(source)}, {MscorlibRef}.Concat(WinRtRefs), TestOptions.DebugDll) + Dim runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.UI") + Assert.True(runtimeAssemblies.Any()) + Dim context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)) + + Const expectedError = "(1,1): error BC30456: 'Xaml' is not a member of 'Windows.UI'." + Dim expectedMissingAssemblyIdentity = New AssemblyIdentity("Windows.UI.Xaml", contentType:=System.Reflection.AssemblyContentType.WindowsRuntime) + + Dim resultProperties As ResultProperties = Nothing + Dim actualError As String = Nothing + Dim actualMissingAssemblyIdentities As ImmutableArray(Of AssemblyIdentity) = Nothing + context.CompileExpression( + InspectionContextFactory.Empty, + "Windows.[UI].Xaml.Application", + DkmEvaluationFlags.None, + DiagnosticFormatter.Instance, + resultProperties, + actualError, + actualMissingAssemblyIdentities, + EnsureEnglishUICulture.PreferredOrNull, + testData:=Nothing) + Assert.Equal(expectedError, actualError) + Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()) + End Sub + + + + Public Sub NotYetLoadedWinMds_GetType() + Dim source = " +Class C + Shared Sub M(f As Windows.Storage.StorageFolder) + End Sub +End Class +" + + Dim comp = CreateCompilationWithReferences({VisualBasicSyntaxTree.ParseText(source)}, {MscorlibRef}.Concat(WinRtRefs), TestOptions.DebugDll) + Dim runtimeAssemblies = ExpressionCompilerTestHelpers.GetRuntimeWinMds("Windows.Storage") + Assert.True(runtimeAssemblies.Any()) + Dim context = CreateMethodContextWithReferences(comp, "C.M", ImmutableArray.Create(MscorlibRef).Concat(runtimeAssemblies)) + + Const expectedError = "(1,9): error BC30002: Type 'Windows.UI.Colors' is not defined." + Dim expectedMissingAssemblyIdentity = New AssemblyIdentity("Windows.UI", contentType:=System.Reflection.AssemblyContentType.WindowsRuntime) + + Dim resultProperties As ResultProperties = Nothing + Dim actualError As String = Nothing + Dim actualMissingAssemblyIdentities As ImmutableArray(Of AssemblyIdentity) = Nothing + context.CompileExpression( + InspectionContextFactory.Empty, + "GetType([Windows].UI.Colors)", + DkmEvaluationFlags.None, + DiagnosticFormatter.Instance, + resultProperties, + actualError, + actualMissingAssemblyIdentities, + EnsureEnglishUICulture.PreferredOrNull, + testData:=Nothing) + Assert.Equal(expectedError, actualError) + Assert.Equal(expectedMissingAssemblyIdentity, actualMissingAssemblyIdentities.Single()) + End Sub + Private Function CreateMethodContextWithReferences(comp As Compilation, methodName As String, ParamArray references As MetadataReference()) As EvaluationContext + Return CreateMethodContextWithReferences(comp, methodName, ImmutableArray.CreateRange(references)) + End Function + + Private Function CreateMethodContextWithReferences(comp As Compilation, methodName As String, references As ImmutableArray(Of MetadataReference)) As EvaluationContext Dim exeBytes As Byte() = Nothing Dim pdbBytes As Byte() = Nothing Dim unusedReferences As ImmutableArray(Of MetadataReference) = Nothing Dim result = comp.EmitAndGetReferences(exeBytes, pdbBytes, unusedReferences) Assert.True(result) - Dim runtime = CreateRuntimeInstance(GetUniqueName(), ImmutableArray.CreateRange(references), exeBytes, New SymReader(pdbBytes)) + Dim runtime = CreateRuntimeInstance(GetUniqueName(), references, exeBytes, New SymReader(pdbBytes)) Return CreateMethodContext(runtime, methodName) End Function Private Shared Function GetMissingAssemblyIdentities(code As ERRID, ParamArray arguments As Object()) As ImmutableArray(Of AssemblyIdentity) - Return EvaluationContext.GetMissingAssemblyIdentitiesHelper(code, arguments) + Return EvaluationContext.GetMissingAssemblyIdentitiesHelper(code, arguments, globalNamespace:=Nothing) End Function End Class End Namespace \ No newline at end of file -- GitLab