Skip to content

Commit

Permalink
EE: Support compact name in `IDkmLanguageInstructionDecoder.GetMethod…
Browse files Browse the repository at this point in the history
…Name()` implementation (#75764)
  • Loading branch information
cston authored Nov 15, 2024
1 parent ab8015d commit ad68d4b
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 26 deletions.
4 changes: 2 additions & 2 deletions eng/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@
VS Debugger
-->
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Contracts" Version="17.13.0-beta.24561.1" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Engine-implementation" Version="17.13.1100701-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Metadata-implementation" Version="17.13.1100701-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Engine-implementation" Version="17.13.1110101-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Metadata-implementation" Version="17.13.1110101-preview" />

<!--
VS .NET Runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ private CSharpInstructionDecoder()
AddMemberOptions(SymbolDisplayMemberOptions.IncludeParameters).
WithParameterOptions(SymbolDisplayParameterOptions.IncludeType);

private static readonly SymbolDisplayFormat s_indexerCompactNameFormat = CompactNameFormat.
WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters).
WithParameterOptions(SymbolDisplayParameterOptions.None);

internal override string GetCompactName(MethodSymbol method)
{
var symbol = method.AssociatedSymbol ?? method;
var format = symbol is PropertySymbol { IsIndexer: true } ?
s_indexerCompactNameFormat :
CompactNameFormat;
return symbol.ToDisplayString(format);
}

internal override void AppendFullName(StringBuilder builder, MethodSymbol method)
{
var displayFormat = method.MethodKind is MethodKind.PropertyGet or MethodKind.PropertySet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
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.CSharp.UnitTests;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
Expand Down Expand Up @@ -402,6 +402,144 @@ class C
Assert.Equal("System.Action<System.Func<object>>", GetReturnTypeName(source, "C.M1", [typeof(object)]));
}

[Fact]
public void GetCompactName_Members()
{
var source = """
using System;
namespace System.Runtime.CompilerServices
{
public class IsExternalInit { }
}
class C
{
static C() { }
C(int x) { }
~C() { }
object F() => null;
object P1 { get; }
object P2 { set { } }
object P3 { init { } }
object this[int i] { get { return null; } set { } }
event EventHandler E;
public static C operator+(C c) => c;
public static implicit operator int(C c) => 0;
public static explicit operator string(C c) => "";
}
static class E
{
static void M(this string x) { }
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("C");
VerifyMethodName(compilation, containingType.GetMethod("F"), "C.F()", "F");
VerifyMethodName(compilation, containingType.GetMethod("get_P1"), "C.P1.get()", "P1");
VerifyMethodName(compilation, containingType.GetMethod("set_P2"), "C.P2.set(value)", "P2");
VerifyMethodName(compilation, containingType.GetMethod("set_P3"), "C.P3.init(value)", "P3");
VerifyMethodName(compilation, containingType.GetMethod("get_Item"), "C.this[int].get(i)", "this[]");
VerifyMethodName(compilation, containingType.GetMethod("set_Item"), "C.this[int].set(i, value)", "this[]");
VerifyMethodName(compilation, containingType.GetMethod("add_E"), "C.E.add(value)", "E");
VerifyMethodName(compilation, containingType.GetMethod("remove_E"), "C.E.remove(value)", "E");
VerifyMethodName(compilation, compilation.GlobalNamespace.GetTypeMember("E").GetMethod("M"), "E.M(x)", "M");
VerifyMethodName(compilation, containingType.GetMethod(".cctor"), "C.C()", "C");
VerifyMethodName(compilation, containingType.GetMethod(".ctor"), "C.C(x)", "C");
VerifyMethodName(compilation, containingType.GetMethod("Finalize"), "C.~C()", "~C");
VerifyMethodName(compilation, containingType.GetMethod("op_UnaryPlus"), "C.operator +(c)", "operator +");
VerifyMethodName(compilation, containingType.GetMethod("op_Implicit"), "C.implicit operator int(c)", "implicit operator int");
VerifyMethodName(compilation, containingType.GetMethod("op_Explicit"), "C.explicit operator string(c)", "explicit operator string");
}

[Fact]
public void GetCompactName_GenericMethod()
{
var source = """
using System;
class A<T>
{
public struct B<U>
{
static void M<V>(T t, U u, V v) { }
}
}
""";
var compilation = CreateCompilation(source);
var instructionDecoder = CSharpInstructionDecoder.Instance;
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)compilation.GlobalNamespace.GetTypeMember("A").GetTypeMember("B").GetMethod("M"),
["object", "int", "string"],
instructionDecoder);
var actualName = instructionDecoder.GetName(method, includeParameterTypes: false, includeParameterNames: true);
var actualCompactName = instructionDecoder.GetCompactName(method);
Assert.Equal("A<object>.B<int>.M<string>(t, u, v)", actualName);
Assert.Equal("M", actualCompactName);
}

[Fact]
public void GetCompactName_ExplicitImplementation()
{
var source = """
using System;
interface I
{
object F();
object P { get; }
object this[int i] { get; }
event EventHandler E;
}
class C : I
{
object I.F() => null;
object I.P => null;
object I.this[int i] => null;
event EventHandler I.E { add { } remove { } }
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("C");
VerifyMethodName(compilation, containingType.GetMethod("I.F"), "C.I.F()", "I.F");
VerifyMethodName(compilation, containingType.GetMethod("I.get_P"), "C.I.P.get()", "I.P");
VerifyMethodName(compilation, containingType.GetMethod("I.get_Item"), "C.I.get_Item(i)", "I.get_Item");
VerifyMethodName(compilation, containingType.GetMethod("I.add_E"), "C.I.E.add(value)", "I.E");
}

[Fact]
public void GetCompactName_NestedFunctions()
{
var source = """
using System;
class Program
{
static void Main(string[] args)
{
Func<string> f1 = () => args[0];
string f2() => args[1];
_ = f1();
_ = f2();
}
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("Program").GetTypeMember("<>c__DisplayClass0_0");
VerifyMethodName(compilation, containingType.GetMethod("<Main>b__0"), "Program.Main.AnonymousMethod__0()", "<Main>b__0");
VerifyMethodName(compilation, containingType.GetMethod("<Main>g__f2|1"), "Program.Main.__f2|1()", "<Main>g__f2|1");
}

private void VerifyMethodName(CSharpCompilation compilation, MethodSymbol method, string expectedName, string expectedCompactName)
{
var instructionDecoder = CSharpInstructionDecoder.Instance;
method = GetConstructedMethod(
compilation,
(PEMethodSymbol)method,
null,
instructionDecoder);
var actualName = instructionDecoder.GetName(method, includeParameterTypes: false, includeParameterNames: true);
var actualCompactName = instructionDecoder.GetCompactName(method);
Assert.Equal(expectedName, actualName);
Assert.Equal(expectedCompactName, actualCompactName);
}

private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, Type[] typeArguments = null, string[] argumentValues = null)
{
var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
Expand All @@ -414,7 +552,12 @@ private string GetName(string source, string methodName, DkmVariableInfoFlags ar
"Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags);

var instructionDecoder = CSharpInstructionDecoder.Instance;
var method = GetConstructedMethod(source, methodName, typeArguments, instructionDecoder);
var compilation = CreateCompilation(source);
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName),
typeArguments,
instructionDecoder);

var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);
Expand All @@ -435,20 +578,27 @@ private string GetReturnTypeName(string source, string methodName, Type[] typeAr
{
var instructionDecoder = CSharpInstructionDecoder.Instance;
var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
var method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder);
var compilation = CreateCompilation(source);
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName),
serializedTypeArgumentNames,
instructionDecoder);

return instructionDecoder.GetReturnTypeName(method);
}

private MethodSymbol GetConstructedMethod(string source, string methodName, string[] serializedTypeArgumentNames, CSharpInstructionDecoder instructionDecoder)
private CSharpCompilation CreateCompilation(string source)
{
var compilation = CreateCompilationWithMscorlib461(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(default(Guid), MakeAssemblyReferencesKind.AllAssemblies);
var frame = (PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName);
return blocks.ToCompilation(default(Guid), MakeAssemblyReferencesKind.AllAssemblies);
}

private MethodSymbol GetConstructedMethod(CSharpCompilation compilation, PEMethodSymbol frame, string[] serializedTypeArgumentNames, CSharpInstructionDecoder 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ internal abstract class InstructionDecoder<TCompilation, TMethodSymbol, TModuleS
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType | SymbolDisplayMemberOptions.IncludeExplicitInterface,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

internal static readonly SymbolDisplayFormat CompactNameFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
genericsOptions: SymbolDisplayGenericsOptions.None,
memberOptions: SymbolDisplayMemberOptions.IncludeExplicitInterface,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

private readonly bool _useReferencedAssembliesOnly;

internal InstructionDecoder()
Expand All @@ -36,6 +42,8 @@ internal MakeAssemblyReferencesKind GetMakeAssemblyReferencesKind()
return _useReferencedAssembliesOnly ? MakeAssemblyReferencesKind.AllReferences : MakeAssemblyReferencesKind.AllAssemblies;
}

internal abstract string GetCompactName(TMethodSymbol method);

internal abstract void AppendFullName(StringBuilder builder, TMethodSymbol method);

internal virtual void AppendParameterTypeName(StringBuilder builder, IParameterSymbol parameter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@ string IDkmLanguageInstructionDecoder.GetMethodName(DkmLanguageInstructionAddres
// but it was ignored. Furthermore, it's not clear what FullNames would mean with respect
// to argument names in C# or Visual Basic. For consistency with the old behavior, we'll
// just ignore the flag as well.
Debug.Assert((argumentFlags & (DkmVariableInfoFlags.FullNames | DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)) == argumentFlags,
Debug.Assert((argumentFlags & (DkmVariableInfoFlags.FullNames | DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types | DkmVariableInfoFlags.CompactName)) == argumentFlags,
$"Unexpected argumentFlags '{argumentFlags}'");

var instructionAddress = (DkmClrInstructionAddress)languageInstructionAddress.Address;
var compilation = _instructionDecoder.GetCompilation(instructionAddress.ModuleInstance);
var method = _instructionDecoder.GetMethod(compilation, instructionAddress);
var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);

return _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames);
if (argumentFlags.Includes(DkmVariableInfoFlags.CompactName))
{
return _instructionDecoder.GetCompactName(method);
}
else
{
var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);

return _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames);
}
}
catch (NotImplementedMetadataException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Imports System.Reflection.Metadata.Ecma335
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
Imports System.Text

Expand All @@ -29,6 +28,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Private Sub New()
End Sub

Friend Overrides Function GetCompactName(method As MethodSymbol) As String
Dim symbol = If(method.AssociatedSymbol, method)
Return symbol.ToDisplayString(CompactNameFormat)
End Function

Friend Overrides Sub AppendFullName(builder As StringBuilder, method As MethodSymbol)
Dim parts = method.ToDisplayParts(DisplayFormat)
Dim numParts = parts.Length
Expand Down
Loading

0 comments on commit ad68d4b

Please sign in to comment.