From 6cdf94b7ee973e2820caf08cf76892e4414271df Mon Sep 17 00:00:00 2001 From: Kevin Halverson Date: Tue, 10 Feb 2015 01:01:19 -0800 Subject: [PATCH] Substitute generic type arguments for generic frames in the Call Stack Window. This change also ensures that we get non-fatal Watson reports when we're in the continuation of a call to get generic type arguments or argument values from the debugee process. --- .../CSharpExpressionCompiler.csproj | 3 +- .../ExpressionCompiler/CSharpFrameDecoder.cs | 5 +- .../CSharpInstructionDecoder.cs | 42 ++- .../CSharpLanguageInstructionDecoder.cs | 5 +- .../ExpressionCompiler/CompilationContext.cs | 10 +- .../ExpressionCompiler/SymbolExtensions.cs | 18 ++ .../InstructionDecoderTests.cs | 256 ++++++++++++++++-- .../ExpressionEvaluatorFatalError.cs | 20 +- .../Source/ExpressionCompiler/FrameDecoder.cs | 211 ++++++++++----- .../ExpressionCompiler/InstructionDecoder.cs | 63 +++-- .../LanguageInstructionDecoder.cs | 15 +- .../ExpressionCompiler/CompilationContext.vb | 11 +- .../ExpressionCompiler/SymbolExtensions.vb | 13 +- .../VisualBasicFrameDecoder.vb | 8 +- .../VisualBasicInstructionDecoder.vb | 45 ++- .../VisualBasicLanguageInstructionDecoder.vb | 7 +- .../ExpressionCompilerTests.vb | 4 +- .../InstructionDecoderTests.vb | 209 ++++++++++++-- 18 files changed, 750 insertions(+), 195 deletions(-) create mode 100644 src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymbolExtensions.cs diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.csproj b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.csproj index 44f46341125a..bb88c53d9062 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.csproj +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpExpressionCompiler.csproj @@ -79,6 +79,7 @@ + @@ -113,4 +114,4 @@ - + \ No newline at end of file diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpFrameDecoder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpFrameDecoder.cs index 11fa7caa8934..63ffdb55f5eb 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpFrameDecoder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpFrameDecoder.cs @@ -1,12 +1,15 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.CodeAnalysis.ExpressionEvaluator; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { [DkmReportNonFatalWatsonException(ExcludeExceptionType = typeof(NotImplementedException)), DkmContinueCorruptingException] - internal sealed class CSharpFrameDecoder : FrameDecoder + internal sealed class CSharpFrameDecoder : FrameDecoder { public CSharpFrameDecoder() : base(CSharpInstructionDecoder.Instance) diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpInstructionDecoder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpInstructionDecoder.cs index 0c155cc3954c..772ecc5898a1 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpInstructionDecoder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpInstructionDecoder.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; @@ -9,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { - internal sealed class CSharpInstructionDecoder : InstructionDecoder + internal sealed class CSharpInstructionDecoder : InstructionDecoder { // This string was not localized in the old EE. We'll keep it that way // so as not to break consumers who may have been parsing frame names... @@ -28,7 +30,7 @@ private CSharpInstructionDecoder() AddMemberOptions(SymbolDisplayMemberOptions.IncludeParameters). WithParameterOptions(SymbolDisplayParameterOptions.IncludeType); - internal override void AppendFullName(StringBuilder builder, PEMethodSymbol method) + internal override void AppendFullName(StringBuilder builder, MethodSymbol method) { var displayFormat = ((method.MethodKind == MethodKind.PropertyGet) || (method.MethodKind == MethodKind.PropertySet)) ? @@ -86,7 +88,28 @@ internal override void AppendFullName(StringBuilder builder, PEMethodSymbol meth } } - internal override PEMethodSymbol GetMethod(DkmClrInstructionAddress instructionAddress) + internal override MethodSymbol ConstructMethod(MethodSymbol method, ImmutableArray typeParameters, ImmutableArray typeArguments) + { + var methodArity = method.Arity; + var methodArgumentStartIndex = typeParameters.Length - methodArity; + var typeMap = new TypeMap( + ImmutableArray.Create(typeParameters, 0, methodArgumentStartIndex), + ImmutableArray.Create(typeArguments, 0, methodArgumentStartIndex)); + var substitutedType = typeMap.SubstituteNamedType(method.ContainingType); + method = method.AsMember(substitutedType); + if (methodArity > 0) + { + method = method.Construct(ImmutableArray.Create(typeArguments, methodArgumentStartIndex, methodArity)); + } + return method; + } + + internal override ImmutableArray GetAllTypeParameters(MethodSymbol method) + { + return method.GetAllTypeParameters(); + } + + internal override CSharpCompilation GetCompilation(DkmClrInstructionAddress instructionAddress) { var moduleInstance = instructionAddress.ModuleInstance; var appDomain = moduleInstance.AppDomain; @@ -105,7 +128,18 @@ internal override PEMethodSymbol GetMethod(DkmClrInstructionAddress instructionA compilation = dataItem.Compilation; } - return compilation.GetSourceMethod(moduleInstance.Mvid, instructionAddress.MethodId.Token); + return compilation; + } + + internal override MethodSymbol GetMethod(CSharpCompilation compilation, DkmClrInstructionAddress instructionAddress) + { + return compilation.GetSourceMethod(instructionAddress.ModuleInstance.Mvid, instructionAddress.MethodId.Token); + } + + internal override TypeNameDecoder GetTypeNameDecoder(CSharpCompilation compilation, MethodSymbol method) + { + Debug.Assert(method is PEMethodSymbol); + return new EETypeNameDecoder(compilation, (PEModuleSymbol)method.ContainingModule); } } } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpLanguageInstructionDecoder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpLanguageInstructionDecoder.cs index f068b0364d1c..5787513d95eb 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpLanguageInstructionDecoder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CSharpLanguageInstructionDecoder.cs @@ -2,16 +2,17 @@ using System; using Microsoft.CodeAnalysis.ExpressionEvaluator; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { [DkmReportNonFatalWatsonException(ExcludeExceptionType = typeof(NotImplementedException)), DkmContinueCorruptingException] - internal sealed class CSharpLanguageInstructionDecoder : LanguageInstructionDecoder + internal sealed class CSharpLanguageInstructionDecoder : LanguageInstructionDecoder { public CSharpLanguageInstructionDecoder() : base(CSharpInstructionDecoder.Instance) { } } -} +} \ No newline at end of file diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs index 4fd8220a4b1c..26345a6da8a8 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs @@ -245,14 +245,6 @@ internal CommonPEModuleBuilder CompileAssignment( return module; } - private static ImmutableArray GetAllTypeParameters(MethodSymbol method) - { - var builder = ArrayBuilder.GetInstance(); - method.ContainingType.GetAllTypeParameters(builder); - builder.AddRange(method.TypeParameters); - return builder.ToImmutableAndFree(); - } - private static string GetNextMethodName(ArrayBuilder builder) { return string.Format("<>m{0}", builder.Count); @@ -270,7 +262,7 @@ internal CommonPEModuleBuilder CompileGetLocals( DiagnosticBag diagnostics) { var objectType = this.Compilation.GetSpecialType(SpecialType.System_Object); - var allTypeParameters = GetAllTypeParameters(_currentFrame); + var allTypeParameters = _currentFrame.GetAllTypeParameters(); var additionalTypes = ArrayBuilder.GetInstance(); EENamedTypeSymbol typeVariablesType = null; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymbolExtensions.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymbolExtensions.cs new file mode 100644 index 000000000000..d498e56290b1 --- /dev/null +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymbolExtensions.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Symbols; + +namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator +{ + internal static class SymbolExtensions + { + internal static ImmutableArray GetAllTypeParameters(this MethodSymbol method) + { + var builder = ArrayBuilder.GetInstance(); + method.ContainingType.GetAllTypeParameters(builder); + builder.AddRange(method.TypeParameters); + return builder.ToImmutableAndFree(); + } + } +} diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs index 7e33eba50298..26f3a00c48a8 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/InstructionDecoderTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Diagnostics; +using System.Linq; using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.ExpressionEvaluator; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; @@ -14,8 +17,90 @@ namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { public class InstructionDecoderTests : ExpressionCompilerTestBase { + [Fact] + void GetNameGenerics() + { + var source = @" +using System; +class Class1 +{ + void M1(Action a) + { + } + void M2(Action a) + { + } + void M3(Action a) + { + } +}"; + + Assert.Equal( + "Class1.M1(System.Action a)", + GetName(source, "Class1.M1", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)); + + Assert.Equal( + "Class1.M2(System.Action a)", + GetName(source, "Class1.M2", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)); + + Assert.Equal( + "Class1.M3(System.Action a)", + GetName(source, "Class1.M3", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)); + + Assert.Equal( + "Class1.M1(System.Action a)", + GetName(source, "Class1.M1", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) })); + + Assert.Equal( + "Class1.M2(System.Action a)", + GetName(source, "Class1.M2", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) })); + + Assert.Equal( + "Class1.M3(System.Action a)", + GetName(source, "Class1.M3", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) })); + } + + [Fact] + void GetNameNullTypeArguments() + { + var source = @" +using System; +class Class1 +{ + void M(Action a) + { + } +}"; + + Assert.Equal( + "Class1.M(System.Action a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new Type[] { null, null })); + + Assert.Equal( + "Class1.M(System.Action a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { typeof(string), null })); + + Assert.Equal( + "Class1.M(System.Action a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { null, typeof(decimal) })); + } + + [Fact] + void GetNameGenericArgumentTypeNotInReferences() + { + var source = @" +class Class1 +{ +}"; + + var serializedTypeArgumentName = "Class1, " + nameof(InstructionDecoderTests) + ", Culture=neutral, PublicKeyToken=null"; + Assert.Equal( + "System.Collections.Generic.Comparer.Create(System.Comparison comparison)", + GetName(source, "System.Collections.Generic.Comparer.Create", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { serializedTypeArgumentName })); + } + [Fact, WorkItem(1107977)] - public void GetNameGenericAsync() + void GetNameGenericAsync() { var source = @" using System.Threading.Tasks; @@ -29,12 +114,12 @@ static async Task M(T x) }"; Assert.Equal( - "C.M(T x)", - GetName(source, "C.d__0.MoveNext", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)); + "C.M(System.Exception x)", + GetName(source, "C.d__0.MoveNext", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(Exception) })); } [Fact] - public void GetNameLambda() + void GetNameLambda() { var source = @" using System; @@ -52,7 +137,7 @@ void M() } [Fact] - public void GetNameGenericLambda() + void GetNameGenericLambda() { var source = @" using System; @@ -65,12 +150,12 @@ void M() where U : T }"; Assert.Equal( - "C.M.AnonymousMethod__0_0(U u)", - GetName(source, "C.<>c__0.b__0_0", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)); + "C.M.AnonymousMethod__0_0(System.ArgumentException u)", + GetName(source, "C.<>c__0.b__0_0", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(Exception), typeof(ArgumentException) })); } [Fact] - public void GetNameProperties() + void GetNameProperties() { var source = @" class C @@ -101,7 +186,7 @@ int this[object x] } [Fact] - public void GetNameExplicitInterfaceImplementation() + void GetNameExplicitInterfaceImplementation() { var source = @" using System; @@ -116,7 +201,7 @@ void IDisposable.Dispose() { } } [Fact] - public void GetNameExtensionMethod() + void GetNameExtensionMethod() { var source = @" static class Extensions @@ -130,7 +215,7 @@ static void M(this string @this) { } } [Fact] - public void GetNameArgumentFlagsNone() + void GetNameArgumentFlagsNone() { var source = @" static class C @@ -148,41 +233,154 @@ static void M2(int x, int y) { } GetName(source, "C.M2", DkmVariableInfoFlags.None)); } - private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, params string[] argumentValues) + [Fact] + void GetReturnTypeNamePrimitive() + { + var source = @" +static class C +{ + static uint M1() { return 42; } +}"; + + Assert.Equal("uint", GetReturnTypeName(source, "C.M1")); + } + + [Fact] + void GetReturnTypeNameNested() + { + var source = @" +static class C +{ + static N.D.E M1() { return default(N.D.E); } +} +namespace N +{ + class D + { + internal struct E + { + } + } +}"; + + Assert.Equal("N.D.E", GetReturnTypeName(source, "C.M1")); + } + + [Fact] + void GetReturnTypeNameGenericOfPrimitive() + { + var source = @" +using System; +class C +{ + Action M1() { return null; } +}"; + + Assert.Equal("System.Action", GetReturnTypeName(source, "C.M1")); + } + + [Fact] + void GetReturnTypeNameGenericOfNested() + { + var source = @" +using System; +class C +{ + Action M1() { return null; } + class D + { + } +}"; + + Assert.Equal("System.Action", GetReturnTypeName(source, "C.M1")); + } + + [Fact] + void GetReturnTypeNameGenericOfGeneric() + { + var source = @" +using System; +class C +{ + Action> M1() { return null; } +}"; + + Assert.Equal("System.Action>", GetReturnTypeName(source, "C.M1", new[] { typeof(object) })); + } + + private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, Type[] typeArguments = null, string[] argumentValues = null) + { + var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray(); + return GetName(source, methodName, argumentFlags, serializedTypeArgumentNames, argumentValues); + } + + private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, string[] typeArguments, string[] argumentValues = null) { Debug.Assert((argumentFlags & (DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)) == argumentFlags, "Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags); - var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugDll); - var runtime = CreateRuntimeInstance(compilation); - var moduleInstances = runtime.Modules; - var blocks = moduleInstances.SelectAsArray(m => m.MetadataBlock); - compilation = blocks.ToCompilation(); - var frame = (PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName); + var instructionDecoder = CSharpInstructionDecoder.Instance; + var method = GetConstructedMethod(source, methodName, typeArguments, instructionDecoder); - // Once we have the method token, we want to look up the method (again) - // using the same helper as the product code. This helper will also map - // async/iterator "MoveNext" methods to the original source method. - var method = compilation.GetSourceMethod( - ((PEModuleSymbol)frame.ContainingModule).Module.GetModuleVersionIdOrThrow(), - MetadataTokens.GetToken(frame.Handle)); var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types); var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names); ArrayBuilder builder = null; - if (argumentValues.Length > 0) + if (argumentValues != null) { + Assert.InRange(argumentValues.Length, 1, int.MaxValue); builder = ArrayBuilder.GetInstance(); builder.AddRange(argumentValues); } - var frameDecoder = CSharpInstructionDecoder.Instance; - var frameName = frameDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder); + var name = instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder); if (builder != null) { builder.Free(); } - return frameName; + return name; + } + + private string GetReturnTypeName(string source, string methodName, Type[] typeArguments = null) + { + var instructionDecoder = CSharpInstructionDecoder.Instance; + var serializedTypeArgumentNames = typeArguments?.Select(t => (t != null) ? t.AssemblyQualifiedName : null).ToArray(); + var method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder); + + return instructionDecoder.GetReturnTypeName(method); + } + + private MethodSymbol GetConstructedMethod(string source, string methodName, string[] serializedTypeArgumentNames, CSharpInstructionDecoder instructionDecoder) + { + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugDll, assemblyName: nameof(InstructionDecoderTests)); + var runtime = CreateRuntimeInstance(compilation); + var moduleInstances = runtime.Modules; + var blocks = moduleInstances.SelectAsArray(m => m.MetadataBlock); + compilation = blocks.ToCompilation(); + var frame = (PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName); + + // Once we have the method token, we want to look up the method (again) + // using the same helper as the product code. This helper will also map + // async/iterator "MoveNext" methods to the original source method. + MethodSymbol method = compilation.GetSourceMethod( + ((PEModuleSymbol)frame.ContainingModule).Module.GetModuleVersionIdOrThrow(), + MetadataTokens.GetToken(frame.Handle)); + if (serializedTypeArgumentNames != null) + { + Assert.NotEmpty(serializedTypeArgumentNames); + var typeParameters = instructionDecoder.GetAllTypeParameters(method); + Assert.NotEmpty(typeParameters); + var typeNameDecoder = new EETypeNameDecoder(compilation, (PEModuleSymbol)method.ContainingModule); + // Use the same helper method as the FrameDecoder to get the TypeSymbols for the + // generic type arguments (rather than using EETypeNameDecoder directly). + var typeArguments = instructionDecoder.GetTypeSymbols(compilation, method, serializedTypeArgumentNames); + if (!typeArguments.IsEmpty) + { + method = instructionDecoder.ConstructMethod(method, typeParameters, typeArguments); + } + } + + return method; } } -} +} \ No newline at end of file diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs index 30601e88a158..77d27323a45b 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs @@ -4,11 +4,12 @@ using System.Diagnostics; using System.Reflection; using Microsoft.VisualStudio.Debugger; +using Roslyn.Utilities; #if !EXPRESSIONCOMPILER using Microsoft.CodeAnalysis.ErrorReporting; - #endif + namespace Microsoft.CodeAnalysis.ExpressionEvaluator { internal static class ExpressionEvaluatorFatalError @@ -80,5 +81,20 @@ internal static bool CrashIfFailFastEnabled(Exception exception) return FatalError.Report(exception); } + + internal delegate bool NonFatalExceptionHandler(Exception exception, string implementationName); + + internal static bool ReportNonFatalException(Exception exception, NonFatalExceptionHandler handler) + { + if (CrashIfFailFastEnabled(exception)) + { + throw ExceptionUtilities.Unreachable; + } + + // Ignore the return value, because we always want to continue after reporting the Exception. + handler(exception, nameof(ExpressionEvaluatorFatalError)); + + return true; + } } -} +} \ No newline at end of file diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/FrameDecoder.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/FrameDecoder.cs index 0ab210df78e5..f3e1b24a68b8 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/FrameDecoder.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/FrameDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Immutable; using System.Diagnostics; using Microsoft.VisualStudio.Debugger; using Microsoft.VisualStudio.Debugger.CallStack; @@ -19,86 +20,35 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator /// always used C# syntax (but with language-specific "special names"). Since these names are exposed through public /// APIs, we will remain consistent with the old behavior (for consumers who may be parsing the frame names). /// - internal abstract class FrameDecoder : IDkmLanguageFrameDecoder + internal abstract class FrameDecoder : IDkmLanguageFrameDecoder + where TCompilation : Compilation + where TMethodSymbol : class, IMethodSymbol + where TModuleSymbol : class, IModuleSymbol + where TTypeSymbol : class, ITypeSymbol + where TTypeParameterSymbol : class, ITypeParameterSymbol { - private readonly InstructionDecoder _instructionDecoder; + private readonly InstructionDecoder _instructionDecoder; - internal FrameDecoder(InstructionDecoder instructionDecoder) + internal FrameDecoder(InstructionDecoder instructionDecoder) { _instructionDecoder = instructionDecoder; } - void IDkmLanguageFrameDecoder.GetFrameName(DkmInspectionContext inspectionContext, DkmWorkList workList, DkmStackWalkFrame frame, DkmVariableInfoFlags argumentFlags, DkmCompletionRoutine completionRoutine) + void IDkmLanguageFrameDecoder.GetFrameName( + DkmInspectionContext inspectionContext, + DkmWorkList workList, + DkmStackWalkFrame frame, + DkmVariableInfoFlags argumentFlags, + DkmCompletionRoutine completionRoutine) { try { Debug.Assert((argumentFlags & (DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Values)) == argumentFlags, "Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags); - var instructionAddress = (DkmClrInstructionAddress)frame.InstructionAddress; - var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types); - var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names); - - if (argumentFlags.Includes(DkmVariableInfoFlags.Values)) - { - // No need to compute the Expandable bit on - // argument values since that can be expensive. - inspectionContext = DkmInspectionContext.Create( - inspectionContext.InspectionSession, - inspectionContext.RuntimeInstance, - inspectionContext.Thread, - inspectionContext.Timeout, - inspectionContext.EvaluationFlags | DkmEvaluationFlags.NoExpansion, - inspectionContext.FuncEvalFlags, - inspectionContext.Radix, - inspectionContext.Language, - inspectionContext.ReturnValue, - inspectionContext.AdditionalVisualizationData, - inspectionContext.AdditionalVisualizationDataPriority, - inspectionContext.ReturnValues); - - // GetFrameArguments returns an array of formatted argument values. We'll pass - // ourselves (GetFrameName) as the continuation of the GetFrameArguments call. - inspectionContext.GetFrameArguments( - workList, - frame, - result => - { - try - { - var builder = ArrayBuilder.GetInstance(); - foreach (var argument in result.Arguments) - { - var evaluatedArgument = argument as DkmSuccessEvaluationResult; - // Not expecting Expandable bit, at least not from this EE. - Debug.Assert((evaluatedArgument == null) || (evaluatedArgument.Flags & DkmEvaluationResultFlags.Expandable) == 0); - builder.Add((evaluatedArgument != null) ? evaluatedArgument.Value : null); - } - - var frameName = _instructionDecoder.GetName(instructionAddress, includeParameterTypes, includeParameterNames, builder); - builder.Free(); - completionRoutine(new DkmGetFrameNameAsyncResult(frameName)); - } - // TODO: Consider calling DkmComponentManager.ReportCurrentNonFatalException() to - // trigger a non-fatal Watson when this occurs. - catch (Exception e) when (!ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e)) - { - completionRoutine(DkmGetFrameNameAsyncResult.CreateErrorResult(e)); - } - finally - { - foreach (var argument in result.Arguments) - { - argument.Close(); - } - } - }); - } - else - { - var frameName = _instructionDecoder.GetName(instructionAddress, includeParameterTypes, includeParameterNames, null); - completionRoutine(new DkmGetFrameNameAsyncResult(frameName)); - } + GetNameWithGenericTypeArguments(inspectionContext, workList, frame, + onSuccess: method => GetFrameName(inspectionContext, workList, frame, argumentFlags, completionRoutine, method), + onFailure: e => completionRoutine(DkmGetFrameNameAsyncResult.CreateErrorResult(e))); } catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e)) { @@ -106,18 +56,135 @@ void IDkmLanguageFrameDecoder.GetFrameName(DkmInspectionContext inspectionContex } } - void IDkmLanguageFrameDecoder.GetFrameReturnType(DkmInspectionContext inspectionContext, DkmWorkList workList, DkmStackWalkFrame frame, DkmCompletionRoutine completionRoutine) + void IDkmLanguageFrameDecoder.GetFrameReturnType( + DkmInspectionContext inspectionContext, + DkmWorkList workList, + DkmStackWalkFrame frame, + DkmCompletionRoutine completionRoutine) { try { - var returnType = _instructionDecoder.GetReturnType((DkmClrInstructionAddress)frame.InstructionAddress); - var result = new DkmGetFrameReturnTypeAsyncResult(returnType); - completionRoutine(result); + GetNameWithGenericTypeArguments(inspectionContext, workList, frame, + onSuccess: method => completionRoutine(new DkmGetFrameReturnTypeAsyncResult(_instructionDecoder.GetReturnTypeName(method))), + onFailure: e => completionRoutine(DkmGetFrameReturnTypeAsyncResult.CreateErrorResult(e))); } catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e)) { throw ExceptionUtilities.Unreachable; } } + + private void GetNameWithGenericTypeArguments( + DkmInspectionContext inspectionContext, + DkmWorkList workList, + DkmStackWalkFrame frame, + Action onSuccess, + Action onFailure) + { + // NOTE: We could always call GetClrGenericParameters, pass them to GetMethod and have that + // return a constructed method symbol, but it seems unwise to call GetClrGenericParameters + // for all frames (as this call requires a round-trip to the debuggee process). + var instructionAddress = (DkmClrInstructionAddress)frame.InstructionAddress; + var compilation = _instructionDecoder.GetCompilation(instructionAddress); + var method = _instructionDecoder.GetMethod(compilation, instructionAddress); + var typeParameters = _instructionDecoder.GetAllTypeParameters(method); + if (!typeParameters.IsEmpty) + { + frame.GetClrGenericParameters( + workList, + result => + { + try + { + var typeArguments = _instructionDecoder.GetTypeSymbols(compilation, method, result.ParameterTypeNames); + if (!typeArguments.IsEmpty) + { + method = _instructionDecoder.ConstructMethod(method, typeParameters, typeArguments); + } + onSuccess(method); + } + catch (Exception e) when (ExpressionEvaluatorFatalError.ReportNonFatalException(e, DkmComponentManager.ReportCurrentNonFatalException)) + { + onFailure(e); + } + }); + } + else + { + onSuccess(method); + } + } + + private void GetFrameName( + DkmInspectionContext inspectionContext, + DkmWorkList workList, + DkmStackWalkFrame frame, + DkmVariableInfoFlags argumentFlags, + DkmCompletionRoutine completionRoutine, + TMethodSymbol method) + { + var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types); + var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names); + + if (argumentFlags.Includes(DkmVariableInfoFlags.Values)) + { + // No need to compute the Expandable bit on + // argument values since that can be expensive. + inspectionContext = DkmInspectionContext.Create( + inspectionContext.InspectionSession, + inspectionContext.RuntimeInstance, + inspectionContext.Thread, + inspectionContext.Timeout, + inspectionContext.EvaluationFlags | DkmEvaluationFlags.NoExpansion, + inspectionContext.FuncEvalFlags, + inspectionContext.Radix, + inspectionContext.Language, + inspectionContext.ReturnValue, + inspectionContext.AdditionalVisualizationData, + inspectionContext.AdditionalVisualizationDataPriority, + inspectionContext.ReturnValues); + + // GetFrameArguments returns an array of formatted argument values. We'll pass + // ourselves (GetFrameName) as the continuation of the GetFrameArguments call. + inspectionContext.GetFrameArguments( + workList, + frame, + result => + { + var argumentValues = result.Arguments; + try + { + var builder = ArrayBuilder.GetInstance(); + foreach (var argument in argumentValues) + { + var formattedArgument = argument as DkmSuccessEvaluationResult; + // Not expecting Expandable bit, at least not from this EE. + Debug.Assert((formattedArgument == null) || (formattedArgument.Flags & DkmEvaluationResultFlags.Expandable) == 0); + builder.Add(formattedArgument?.Value); + } + + var frameName = _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder); + builder.Free(); + completionRoutine(new DkmGetFrameNameAsyncResult(frameName)); + } + catch (Exception e) when (ExpressionEvaluatorFatalError.ReportNonFatalException(e, DkmComponentManager.ReportCurrentNonFatalException)) + { + completionRoutine(DkmGetFrameNameAsyncResult.CreateErrorResult(e)); + } + finally + { + foreach (var argument in argumentValues) + { + argument.Close(); + } + } + }); + } + else + { + var frameName = _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, null); + completionRoutine(new DkmGetFrameNameAsyncResult(frameName)); + } + } } } diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/InstructionDecoder.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/InstructionDecoder.cs index be947e272e0a..80126b5f5e6b 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/InstructionDecoder.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/InstructionDecoder.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Immutable; using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis.Collections; @@ -7,13 +8,12 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator { - internal abstract class InstructionDecoder - { - internal abstract string GetName(DkmClrInstructionAddress instructionAddress, bool includeParameterTypes, bool includeParameterNames, ArrayBuilder argumentValues); - internal abstract string GetReturnType(DkmClrInstructionAddress instructionAddress); - } - - internal abstract class InstructionDecoder : InstructionDecoder where TMethodSymbol : class, IMethodSymbol + internal abstract class InstructionDecoder + where TCompilation : Compilation + where TMethodSymbol : class, IMethodSymbol + where TModuleSymbol : class, IModuleSymbol + where TTypeSymbol : class, ITypeSymbol + where TTypeParameterSymbol : class, ITypeParameterSymbol { internal static readonly SymbolDisplayFormat DisplayFormat = new SymbolDisplayFormat( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, @@ -21,21 +21,18 @@ internal abstract class InstructionDecoder : InstructionDecoder w memberOptions: SymbolDisplayMemberOptions.IncludeContainingType | SymbolDisplayMemberOptions.IncludeExplicitInterface, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - internal override string GetName(DkmClrInstructionAddress instructionAddress, bool includeParameterTypes, bool includeParameterNames, ArrayBuilder argumentValues) - { - var method = this.GetMethod(instructionAddress); - return this.GetName(method, includeParameterTypes, includeParameterNames, argumentValues); - } + internal abstract void AppendFullName(StringBuilder builder, TMethodSymbol method); - internal override string GetReturnType(DkmClrInstructionAddress instructionAddress) - { - var method = this.GetMethod(instructionAddress); - return method.ReturnType.ToDisplayString(DisplayFormat); - } + /// + /// Constructs a method and any of its generic containing types using the specified . + /// + internal abstract TMethodSymbol ConstructMethod(TMethodSymbol method, ImmutableArray typeParameters, ImmutableArray typeArguments); - internal abstract void AppendFullName(StringBuilder builder, TMethodSymbol method); + internal abstract ImmutableArray GetAllTypeParameters(TMethodSymbol method); + + internal abstract TCompilation GetCompilation(DkmClrInstructionAddress instructionAddress); - internal abstract TMethodSymbol GetMethod(DkmClrInstructionAddress instructionAddress); + internal abstract TMethodSymbol GetMethod(TCompilation compilation, DkmClrInstructionAddress instructionAddress); internal string GetName(TMethodSymbol method, bool includeParameterTypes, bool includeParameterNames, ArrayBuilder argumentValues = null) { @@ -96,5 +93,33 @@ internal string GetName(TMethodSymbol method, bool includeParameterTypes, bool i return pooled.ToStringAndFree(); } + + internal string GetReturnTypeName(TMethodSymbol method) + { + return method.ReturnType.ToDisplayString(DisplayFormat); + } + + internal abstract TypeNameDecoder GetTypeNameDecoder(TCompilation compilation, TMethodSymbol method); + + internal ImmutableArray GetTypeSymbols(TCompilation compilation, TMethodSymbol method, string[] serializedTypeNames) + { + var builder = ArrayBuilder.GetInstance(); + foreach (var name in serializedTypeNames) + { + // The list of type names will include null values if type arguments are not available. + // It seems unlikely that only some type arguments will be missing (and it also seems + // like very little incremental value to include only some of the arguments), so we'll + // keep things simple and omit all type arguments if any are missing. + if (name == null) + { + builder.Free(); + return ImmutableArray.Empty; + } + + var typeNameDecoder = GetTypeNameDecoder(compilation, method); + builder.Add(typeNameDecoder.GetTypeSymbolForSerializedType(name)); + } + return builder.ToImmutableAndFree(); + } } } diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/LanguageInstructionDecoder.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/LanguageInstructionDecoder.cs index d5a7b0a35759..bbed5e608894 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/LanguageInstructionDecoder.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/LanguageInstructionDecoder.cs @@ -17,11 +17,16 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator /// /// This class provides function name information for the Breakpoints window. /// - internal abstract class LanguageInstructionDecoder : IDkmLanguageInstructionDecoder where TMethodSymbol : class, IMethodSymbol + internal abstract class LanguageInstructionDecoder : IDkmLanguageInstructionDecoder + where TCompilation : Compilation + where TMethodSymbol : class, IMethodSymbol + where TModuleSymbol : class, IModuleSymbol + where TTypeSymbol : class, ITypeSymbol + where TTypeParameterSymbol : class, ITypeParameterSymbol { - private readonly InstructionDecoder _instructionDecoder; + private readonly InstructionDecoder _instructionDecoder; - internal LanguageInstructionDecoder(InstructionDecoder instructionDecoder) + internal LanguageInstructionDecoder(InstructionDecoder instructionDecoder) { _instructionDecoder = instructionDecoder; } @@ -37,7 +42,9 @@ string IDkmLanguageInstructionDecoder.GetMethodName(DkmLanguageInstructionAddres Debug.Assert((argumentFlags & (DkmVariableInfoFlags.FullNames | DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)) == argumentFlags, "Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags); - var method = _instructionDecoder.GetMethod((DkmClrInstructionAddress)languageInstructionAddress.Address); + var instructionAddress = (DkmClrInstructionAddress)languageInstructionAddress.Address; + var compilation = _instructionDecoder.GetCompilation(instructionAddress); + var method = _instructionDecoder.GetMethod(compilation, instructionAddress); var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types); var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names); diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/CompilationContext.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/CompilationContext.vb index 1b42c478cc57..1538af4c4605 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/CompilationContext.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/CompilationContext.vb @@ -1,4 +1,6 @@ -Imports System +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System Imports System.Collections.Immutable Imports System.Runtime.InteropServices Imports System.Threading @@ -173,13 +175,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return moduleBuilder End Function - Private Shared Function GetAllTypeParameters(method As MethodSymbol) As ImmutableArray(Of TypeParameterSymbol) - Dim builder = ArrayBuilder(Of TypeParameterSymbol).GetInstance() - method.ContainingType.GetAllTypeParameters(builder) - builder.AddRange(method.TypeParameters) - Return builder.ToImmutableAndFree() - End Function - Private Shared Function GetNextMethodName(builder As ArrayBuilder(Of MethodSymbol)) As String ' NOTE: These names are consumed by Concord, so there's no native precedent. Return String.Format("<>m{0}", builder.Count) diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymbolExtensions.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymbolExtensions.vb index 4c6b96d59311..b2f418a0b827 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymbolExtensions.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymbolExtensions.vb @@ -1,4 +1,7 @@ -Imports System.Runtime.CompilerServices +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.Runtime.CompilerServices Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator @@ -59,5 +62,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Friend Function IsStateMachineType(type As TypeSymbol) As Boolean Return type.Name.StartsWith(StringConstants.StateMachineTypeNamePrefix, StringComparison.Ordinal) End Function + + + Friend Function GetAllTypeParameters(method As MethodSymbol) As ImmutableArray(Of TypeParameterSymbol) + Dim builder = ArrayBuilder(Of TypeParameterSymbol).GetInstance() + method.ContainingType.GetAllTypeParameters(builder) + builder.AddRange(method.TypeParameters) + Return builder.ToImmutableAndFree() + End Function End Module End Namespace diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicFrameDecoder.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicFrameDecoder.vb index fc1aa18494b3..dc71a62a05c4 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicFrameDecoder.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicFrameDecoder.vb @@ -1,9 +1,13 @@ -Imports Microsoft.CodeAnalysis.ExpressionEvaluator +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.ExpressionEvaluator +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator - Friend NotInheritable Class VisualBasicFrameDecoder : Inherits FrameDecoder + Friend NotInheritable Class VisualBasicFrameDecoder : Inherits FrameDecoder(Of VisualBasicCompilation, MethodSymbol, PEModuleSymbol, TypeSymbol, TypeParameterSymbol) Public Sub New() MyBase.New(VisualBasicInstructionDecoder.Instance) diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicInstructionDecoder.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicInstructionDecoder.vb index ba82c0aac338..c4fac085d08f 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicInstructionDecoder.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicInstructionDecoder.vb @@ -1,6 +1,8 @@ -Imports System.Runtime.InteropServices +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis.ExpressionEvaluator +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.VisualStudio.Debugger Imports Microsoft.VisualStudio.Debugger.Clr @@ -8,7 +10,7 @@ Imports System.Text Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator - Friend NotInheritable Class VisualBasicInstructionDecoder : Inherits InstructionDecoder(Of PEMethodSymbol) + Friend NotInheritable Class VisualBasicInstructionDecoder : Inherits InstructionDecoder(Of VisualBasicCompilation, MethodSymbol, PEModuleSymbol, TypeSymbol, TypeParameterSymbol) ' These strings were not localized in the old EE. We'll keep them that way ' so as not to break consumers who may have been parsing frame names... @@ -18,12 +20,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator ''' ''' Singleton instance of (created using default constructor). ''' - Friend Shared ReadOnly Instance as VisualBasicInstructionDecoder = New VisualBasicInstructionDecoder() + Friend Shared ReadOnly Instance As VisualBasicInstructionDecoder = New VisualBasicInstructionDecoder() Private Sub New() End Sub - Friend Overrides Sub AppendFullName(builder As StringBuilder, method As PEMethodSymbol) + Friend Overrides Sub AppendFullName(builder As StringBuilder, method As MethodSymbol) Dim parts = method.ToDisplayParts(DisplayFormat) Dim numParts = parts.Length For i = 0 To numParts - 1 @@ -59,9 +61,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Next End Sub - Friend Overrides Function GetMethod(instructionAddress As DkmClrInstructionAddress) As PEMethodSymbol - Dim moduleInstance = instructionAddress.ModuleInstance - Dim appDomain = moduleInstance.AppDomain + Friend Overrides Function ConstructMethod(method As MethodSymbol, typeParameters As ImmutableArray(Of TypeParameterSymbol), typeArguments As ImmutableArray(Of TypeSymbol)) As MethodSymbol + Dim methodArity = method.Arity + Dim methodArgumentStartIndex = typeParameters.Length - methodArity + Dim typeMap = TypeSubstitution.Create( + method, + ImmutableArray.Create(typeParameters, 0, methodArgumentStartIndex), + ImmutableArray.Create(typeArguments, 0, methodArgumentStartIndex)) + Dim substitutedType = typeMap.SubstituteNamedType(method.ContainingType) + method = method.AsMember(substitutedType) + If methodArity > 0 Then + method = method.Construct(ImmutableArray.Create(typeArguments, methodArgumentStartIndex, methodArity)) + End If + Return method + End Function + + Friend Overrides Function GetAllTypeParameters(method As MethodSymbol) As ImmutableArray(Of TypeParameterSymbol) + Return method.GetAllTypeParameters() + End Function + + Friend Overrides Function GetCompilation(instructionAddress As DkmClrInstructionAddress) As VisualBasicCompilation + Dim appDomain = instructionAddress.ModuleInstance.AppDomain Dim previous = appDomain.GetDataItem(Of VisualBasicMetadataContext)() Dim metadataBlocks = instructionAddress.Process.GetMetadataBlocks(appDomain) @@ -73,7 +93,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator appDomain.SetDataItem(DkmDataCreationDisposition.CreateAlways, New VisualBasicMetadataContext(metadataBlocks)) End If - Return compilation.GetSourceMethod(moduleInstance.Mvid, instructionAddress.MethodId.Token) + Return compilation + End Function + + Friend Overrides Function GetMethod(compilation As VisualBasicCompilation, instructionAddress As DkmClrInstructionAddress) As MethodSymbol + Return compilation.GetSourceMethod(instructionAddress.ModuleInstance.Mvid, instructionAddress.MethodId.Token) + End Function + + Friend Overrides Function GetTypeNameDecoder(compilation As VisualBasicCompilation, method As MethodSymbol) As TypeNameDecoder(Of PEModuleSymbol, TypeSymbol) + Debug.Assert(TypeOf method Is PEMethodSymbol) + Return New EETypeNameDecoder(compilation, DirectCast(method.ContainingModule, PEModuleSymbol)) End Function End Class diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicLanguageInstructionDecoder.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicLanguageInstructionDecoder.vb index cd4ba00673d4..c8fad9add4aa 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicLanguageInstructionDecoder.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/VisualBasicLanguageInstructionDecoder.vb @@ -1,10 +1,13 @@ -Imports Microsoft.CodeAnalysis.ExpressionEvaluator +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.ExpressionEvaluator +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator - Friend NotInheritable Class VisualBasicLanguageInstructionDecoder : Inherits LanguageInstructionDecoder(Of PEMethodSymbol) + Friend NotInheritable Class VisualBasicLanguageInstructionDecoder : Inherits LanguageInstructionDecoder(Of VisualBasicCompilation, MethodSymbol, PEModuleSymbol, TypeSymbol, TypeParameterSymbol) Public Sub New() MyBase.New(VisualBasicInstructionDecoder.Instance) diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb index 5b0f03b1db13..ccc81e7fa618 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb @@ -1,4 +1,6 @@ -Imports System.Collections.Immutable +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable Imports System.Globalization Imports System.Reflection.Metadata Imports System.Threading diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/InstructionDecoderTests.vb b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/InstructionDecoderTests.vb index 1b33fa3b22a4..93796e17f3f7 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/InstructionDecoderTests.vb +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/InstructionDecoderTests.vb @@ -1,4 +1,7 @@ -Imports System.Reflection.Metadata.Ecma335 +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Reflection.Metadata.Ecma335 +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.ExpressionEvaluator Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests @@ -20,11 +23,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator '// TODO: string argument values requiring quotes '// TODO: argument flags == names only, types only, values only '// TODO: params Argument values - '// TODO: GetFrameReturnType primitive types - '// TODO: GetFrameReturnType non-primitive types (nested namespace/class) - '// TODO: GetFrameReturnType generic(Of non-primitive, nested) - '// TODO: GetFrameReturnType generic(Of generic) - '// TODO: GetFrameReturnType generic(Of primitive) + '// TODO: generic class/method with 2 or more type parameters + '// TODO: generic argument type that is not from a referenced assembly Public Class InstructionDecoderTests : Inherits ExpressionCompilerTestBase @@ -85,7 +85,6 @@ Class Class1(Of T) Sub M3(Of U)(a As Action(Of U)) End Sub End Class" - ' TODO: Type parameters should be substituted with type arguments once we have an API to retrieve them. Assert.Equal( "Class1(Of T).M1(Of U)(System.Action(Of Integer) a)", @@ -98,6 +97,52 @@ End Class" Assert.Equal( "Class1(Of T).M3(Of U)(System.Action(Of U) a)", GetName(source, "Class1.M3", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types)) + + Assert.Equal( + "Class1(Of String).M1(Of Decimal)(System.Action(Of Integer) a)", + GetName(source, "Class1.M1", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(String), GetType(Decimal)})) + + Assert.Equal( + "Class1(Of String).M2(Of Decimal)(System.Action(Of String) a)", + GetName(source, "Class1.M2", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(String), GetType(Decimal)})) + + Assert.Equal( + "Class1(Of String).M3(Of Decimal)(System.Action(Of Decimal) a)", + GetName(source, "Class1.M3", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(String), GetType(Decimal)})) + End Sub + + + Sub GetNameNullTypeArguments() + Dim source = " +Imports System +Class Class1(Of T) + Sub M(Of U)(a As Action(Of U)) + End Sub +End Class" + + Assert.Equal( + "Class1(Of T).M(Of U)(System.Action(Of U) a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:=New Type() {Nothing, Nothing})) + + Assert.Equal( + "Class1(Of T).M(Of U)(System.Action(Of U) a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(String), Nothing})) + + Assert.Equal( + "Class1(Of T).M(Of U)(System.Action(Of U) a)", + GetName(source, "Class1.M", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={Nothing, GetType(Decimal)})) + End Sub + + + Sub GetNameGenericArgumentTypeNotInReferences() + Dim source = " +Class Class1 +End Class" + + Dim serializedTypeArgumentName = "Class1, " & NameOf(InstructionDecoderTests) & ", Culture=neutral, PublicKeyToken=null" + Assert.Equal( + "System.Collections.Generic.Comparer(Of Class1).Create(System.Comparison(Of Class1) comparison)", + GetName(source, "System.Collections.Generic.Comparer.Create", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={serializedTypeArgumentName})) End Sub @@ -130,8 +175,8 @@ Class C End Class" Assert.Equal( - "C.M(Of T)(T x)", - GetName(source, "C.VB$StateMachine_1_M.MoveNext", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types)) + "C.M(Of Long)(Long x)", + GetName(source, "C.VB$StateMachine_1_M.MoveNext", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(Long)})) End Sub @@ -175,10 +220,10 @@ Class Class1(Of T) Dim f As Func(Of U, T) = Function(u2 As U) u2 End Sub End Class" - ' TODO: Type parameter $CLS0 should be substituted with a type argument once we have an API to retrieve it. + Assert.Equal( - "Class1(Of T)..($CLS0 u2)", - GetName(source, "Class1._Closure$__1._Lambda$__1-1", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types)) + "Class1(Of System.Exception)..(System.ArgumentException u2)", + GetName(source, "Class1._Closure$__1._Lambda$__1-1", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, typeArguments:={GetType(Exception), GetType(ArgumentException)})) End Sub @@ -196,7 +241,7 @@ End Module" Assert.Equal( "Module1.M(Date d = #6/23/1912#)", - GetName(source, "Module1.M", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, "#6/23/1912#")) + GetName(source, "Module1.M", DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types, argumentValues:={"#6/23/1912#"})) End Sub @@ -284,14 +329,120 @@ End Module" GetName(source, "Module1.M2", DkmVariableInfoFlags.None)) End Sub - Private Function GetName(source As String, methodName As String, argumentFlags As DkmVariableInfoFlags, ParamArray argumentValues() As String) As String + + Sub GetReturnTypeNamePrimitive() + Dim source = " +Class C + Function M1() As UInteger + Return 42 + End Function +End Class" + + Assert.Equal("UInteger", GetReturnTypeName(source, "C.M1")) + End Sub + + + Sub GetReturnTypeNameNested() + Dim source = " +Class C + Function M1() As N.D.E + Return Nothing + End Function +End Class +Namespace N + Class D + Friend Structure E + End Structure + End Class +End Namespace" + + Assert.Equal("N.D.E", GetReturnTypeName(source, "C.M1")) + End Sub + + + Sub GetReturnTypeNameGenericOfPrimitive() + Dim source = " +Imports System +Class C + Function M1() As Action(Of Int32) + Return Nothing + End Function +End Class" + + Assert.Equal("System.Action(Of Integer)", GetReturnTypeName(source, "C.M1")) + End Sub + + + Sub GetReturnTypeNameGenericOfNested() + Dim source = " +Imports System +Class C + Function M1() As Action(Of D) + Return Nothing + End Function + Class D + End Class +End Class" + + Assert.Equal("System.Action(Of C.D)", GetReturnTypeName(source, "C.M1")) + End Sub + + + Sub GetReturnTypeNameGenericOfGeneric() + Dim source = " +Imports System +Class C + Function M1(Of T)() As Action(Of Func(Of T)) + Return Nothing + End Function +End Class" + + Assert.Equal("System.Action(Of System.Func(Of Object))", GetReturnTypeName(source, "C.M1", typeArguments:={GetType(Object)})) + End Sub + + Private Function GetName(source As String, methodName As String, argumentFlags As DkmVariableInfoFlags, Optional typeArguments() As Type = Nothing, Optional argumentValues() As String = Nothing) As String + Dim serializedTypeArgumentNames = typeArguments?.Select(Function(t) t?.AssemblyQualifiedName).ToArray() + Return GetName(source, methodName, argumentFlags, serializedTypeArgumentNames, argumentValues) + End Function + + Private Function GetName(source As String, methodName As String, argumentFlags As DkmVariableInfoFlags, typeArguments() As String, Optional argumentValues() As String = Nothing) As String Debug.Assert((argumentFlags And (DkmVariableInfoFlags.Names Or DkmVariableInfoFlags.Types)) = argumentFlags, "Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags) + Dim instructionDecoder = VisualBasicInstructionDecoder.Instance + Dim method = GetConstructedMethod(source, methodName, typeArguments, instructionDecoder) + + Dim includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types) + Dim includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names) + Dim builder As ArrayBuilder(Of String) = Nothing + If argumentValues IsNot Nothing Then + Assert.InRange(argumentValues.Length, 1, Integer.MaxValue) + builder = ArrayBuilder(Of String).GetInstance() + builder.AddRange(argumentValues) + End If + + Dim name = instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder) + If builder IsNot Nothing Then + builder.Free() + End If + + Return name + End Function + + Private Function GetReturnTypeName(source As String, methodName As String, Optional typeArguments() As Type = Nothing) As String + Dim instructionDecoder = VisualBasicInstructionDecoder.Instance + Dim serializedTypeArgumentNames = typeArguments?.Select(Function(t) t?.AssemblyQualifiedName).ToArray() + Dim method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder) + + Return instructionDecoder.GetReturnTypeName(method) + End Function + + Private Function GetConstructedMethod(source As String, methodName As String, serializedTypeArgumentNames() As String, instructionDecoder As VisualBasicInstructionDecoder) As MethodSymbol Dim compilation = CreateCompilationWithReferences( {VisualBasicSyntaxTree.ParseText(source)}, references:={MscorlibRef_v4_0_30316_17626, MsvbRef_v4_0_30319_17929}, - options:=TestOptions.DebugDll) + options:=TestOptions.DebugDll, + assemblyName:=NameOf(InstructionDecoderTests)) Dim runtime = CreateRuntimeInstance(compilation) Dim moduleInstances = runtime.Modules Dim blocks = moduleInstances.SelectAsArray(Function(m) m.MetadataBlock) @@ -301,26 +452,24 @@ End Module" ' Once we have the method token, we want to look up the method (again) ' using the same helper as the product code. This helper will also map ' async/ iterator "MoveNext" methods to the original source method. - Dim method = compilation.GetSourceMethod( + Dim method As MethodSymbol = compilation.GetSourceMethod( DirectCast(frame.ContainingModule, PEModuleSymbol).Module.GetModuleVersionIdOrThrow(), MetadataTokens.GetToken(frame.Handle)) - Dim includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types) - Dim includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names) - Dim builder As ArrayBuilder(Of String) = Nothing - If argumentValues.Length > 0 Then - builder = ArrayBuilder(Of String).GetInstance() - builder.AddRange(argumentValues) - End If - - Dim frameDecoder = VisualBasicInstructionDecoder.Instance - Dim frameName = frameDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder) - If builder IsNot Nothing Then - builder.Free() + If serializedTypeArgumentNames IsNot Nothing Then + Assert.NotEmpty(serializedTypeArgumentNames) + Dim typeParameters = instructionDecoder.GetAllTypeParameters(method) + Assert.NotEmpty(typeParameters) + Dim typeNameDecoder = New EETypeNameDecoder(compilation, DirectCast(method.ContainingModule, PEModuleSymbol)) + ' Use the same helper method as the FrameDecoder to get the TypeSymbols for the + ' generic type arguments (rather than using EETypeNameDecoder directly). + Dim typeArgumentSymbols = instructionDecoder.GetTypeSymbols(compilation, method, serializedTypeArgumentNames) + If Not typeArgumentSymbols.IsEmpty Then + method = instructionDecoder.ConstructMethod(method, typeParameters, typeArgumentSymbols) + End If End If - Return frameName + Return method End Function - End Class End Namespace