/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/ExpressionEvaluator/CSharp/Test/ResultProvider/DebuggerDisplayAttributeTests.cs
732 строки
25 KB
Anders Sundheim
Improve debug view of synthesized collection types (#84453)
17 июл 2026, 20:34
Не верифицирован
17 июл 2026, 20:34
3b2e63e
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.ExpressionEvaluator; using Microsoft.VisualStudio.Debugger; using Microsoft.VisualStudio.Debugger.Clr; using Microsoft.VisualStudio.Debugger.Evaluation; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests { public class DebuggerDisplayAttributeTests : CSharpResultProviderTestBase { [Fact] public void WithoutExpressionHoles() { var source = @" using System.Diagnostics; class C0 { } [DebuggerDisplay(""Value"")] class C1 { } [DebuggerDisplay(""Value"", Name=""Name"")] class C2 { } [DebuggerDisplay(""Value"", Type=""Type"")] class C3 { } [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] class C4 { } class Wrapper { C0 c0 = new C0(); C1 c1 = new C1(); C2 c2 = new C2(); C3 c3 = new C3(); C4 c4 = new C4(); } "; var assembly = GetAssembly(source); var type = assembly.GetType("Wrapper"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(GetChildren(FormatResult("w", value)), EvalResult("c0", "{C0}", "C0", "w.c0", DkmEvaluationResultFlags.None | DkmEvaluationResultFlags.CanFavorite), EvalResult("c1", "Value", "C1", "w.c1", DkmEvaluationResultFlags.None | DkmEvaluationResultFlags.CanFavorite), EvalResult("Name", "Value", "C2", "w.c2", DkmEvaluationResultFlags.None), EvalResult("c3", "Value", "Type", "w.c3", DkmEvaluationResultFlags.None | DkmEvaluationResultFlags.CanFavorite), EvalResult("Name", "Value", "Type", "w.c4", DkmEvaluationResultFlags.None)); } [Fact] public void OnlyExpressionHoles() { var source = @" using System.Diagnostics; [DebuggerDisplay(""{value}"", Name=""{name}"", Type=""{type}"")] class C { string name = ""Name""; string value = ""Value""; string type = ""Type""; } class Wrapper { C c = new C(); } "; var assembly = GetAssembly(source); var type = assembly.GetType("Wrapper"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(GetChildren(FormatResult("c", value)), EvalResult("\"Name\"", "\"Value\"", "\"Type\"", "c.c", DkmEvaluationResultFlags.Expandable)); } [Fact] public void FormatStrings() { var source = @" using System.Diagnostics; [DebuggerDisplay(""<{value}>"", Name=""<{name}>"", Type=""<{type}>"")] class C { string name = ""Name""; string value = ""Value""; string type = ""Type""; } class Wrapper { C c = new C(); } "; var assembly = GetAssembly(source); var type = assembly.GetType("Wrapper"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(GetChildren(FormatResult("w", value)), EvalResult("<\"Name\">", "<\"Value\">", "<\"Type\">", "w.c", DkmEvaluationResultFlags.Expandable)); } [Fact] public void BindingError() { var source = @" using System.Diagnostics; [DebuggerDisplay(""<{missing}>"")] class C { } "; const string rootExpr = "c"; // Note that this is the full name in all cases - DebuggerDisplayAttribute does not affect it. var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(FormatResult(rootExpr, value), EvalResult(rootExpr, "<Problem evaluating expression>", "C", rootExpr, DkmEvaluationResultFlags.None)); // Message inlined without quotation marks. } [Fact] public void RecursiveDebuggerDisplay() { var source = @" using System.Diagnostics; [DebuggerDisplay(""{value}"")] class C { C value; C() { this.value = this; } } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); // No stack overflow, since attribute on computed value is ignored. Verify(FormatResult(rootExpr, value), EvalResult(rootExpr, "{C}", "C", rootExpr, DkmEvaluationResultFlags.Expandable)); } [Fact] public void MultipleAttributes() { var source = @" using System.Diagnostics; [DebuggerDisplay(""V1"")] [DebuggerDisplay(""V2"")] class C { } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); // First attribute wins, as in dev12. Verify(FormatResult(rootExpr, value), EvalResult(rootExpr, "V1", "C", rootExpr)); } [Fact] public void NullValues() { var source = @" using System.Diagnostics; [DebuggerDisplay(null, Name=null, Type=null)] class C { } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(FormatResult(rootExpr, value), EvalResult(rootExpr, "{C}", "C", rootExpr)); } [Fact] public void EmptyStringValues() { var source = @" using System.Diagnostics; [DebuggerDisplay("""", Name="""", Type="""")] class C { } class Wrapper { C c = new C(); } "; var assembly = GetAssembly(source); var type = assembly.GetType("Wrapper"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(GetChildren(FormatResult("w", value)), EvalResult("", "", "", "w.c")); } [Fact] public void ConstructedGenericType() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Name"")] class C<T> { } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C`1").MakeGenericType(typeof(int)); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); Verify(FormatResult(rootExpr, value), EvalResult("c", "Name", "C<int>", rootExpr)); } [Fact] public void MemberExpansion() { var source = @" using System.Diagnostics; interface I { D P { get; } } class C : I { D I.P { get { return new D(); } } D Q { get { return new D(); } } } [DebuggerDisplay(""Value"", Name=""Name"")] class D { } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); var root = FormatResult(rootExpr, value); Verify(root, EvalResult(rootExpr, "{C}", "C", rootExpr, DkmEvaluationResultFlags.Expandable)); Verify(GetChildren(root), EvalResult("Name", "Value", "D", "((I)c).P", DkmEvaluationResultFlags.ReadOnly), // Not "I.Name". EvalResult("Name", "Value", "D", "c.Q", DkmEvaluationResultFlags.ReadOnly)); } [Fact] public void PointerDereferenceExpansion_Null() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] unsafe struct Display { Display* DisplayPointer; NoDisplay* NoDisplayPointer; } unsafe struct NoDisplay { Display* DisplayPointer; NoDisplay* NoDisplayPointer; } class Wrapper { Display display = new Display(); } "; var assembly = GetUnsafeAssembly(source); var type = assembly.GetType("Wrapper"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); var root = FormatResult("wrapper", value); Verify(DepthFirstSearch(GetChildren(root).Single(), maxDepth: 3), EvalResult("Name", "Value", "Type", "wrapper.display", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayPointer", PointerToString(IntPtr.Zero), "Display*", "wrapper.display.DisplayPointer"), EvalResult("NoDisplayPointer", PointerToString(IntPtr.Zero), "NoDisplay*", "wrapper.display.NoDisplayPointer")); } [Fact] public void PointerDereferenceExpansion_NonNull() { var source = @" using System; using System.Diagnostics; [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] unsafe struct Display { public Display* DisplayPointer; public NoDisplay* NoDisplayPointer; } unsafe struct NoDisplay { public Display* DisplayPointer; public NoDisplay* NoDisplayPointer; } unsafe class C { Display* DisplayPointer; NoDisplay* NoDisplayPointer; public C(IntPtr d, IntPtr nd) { this.DisplayPointer = (Display*)d; this.NoDisplayPointer = (NoDisplay*)nd; this.DisplayPointer->DisplayPointer = this.DisplayPointer; this.DisplayPointer->NoDisplayPointer = this.NoDisplayPointer; this.NoDisplayPointer->DisplayPointer = this.DisplayPointer; this.NoDisplayPointer->NoDisplayPointer = this.NoDisplayPointer; } } "; var assembly = GetUnsafeAssembly(source); unsafe { var displayType = assembly.GetType("Display"); var displayInstance = displayType.Instantiate(); var displayHandle = GCHandle.Alloc(displayInstance, GCHandleType.Pinned); var displayPtr = displayHandle.AddrOfPinnedObject(); var noDisplayType = assembly.GetType("NoDisplay"); var noDisplayInstance = noDisplayType.Instantiate(); var noDisplayHandle = GCHandle.Alloc(noDisplayInstance, GCHandleType.Pinned); var noDisplayPtr = noDisplayHandle.AddrOfPinnedObject(); var testType = assembly.GetType("C"); var testInstance = ReflectionUtilities.Instantiate(testType, displayPtr, noDisplayPtr); var testValue = CreateDkmClrValue(testInstance, testType, evalFlags: DkmEvaluationResultFlags.None); var displayPtrString = PointerToString(displayPtr); var noDisplayPtrString = PointerToString(noDisplayPtr); Verify(DepthFirstSearch(FormatResult("c", testValue), maxDepth: 3), EvalResult("c", "{C}", "C", "c", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayPointer", displayPtrString, "Display*", "c.DisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("*c.DisplayPointer", "Value", "Type", "*c.DisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayPointer", displayPtrString, "Display*", "(*c.DisplayPointer).DisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayPointer", noDisplayPtrString, "NoDisplay*", "(*c.DisplayPointer).NoDisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayPointer", noDisplayPtrString, "NoDisplay*", "c.NoDisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("*c.NoDisplayPointer", "{NoDisplay}", "NoDisplay", "*c.NoDisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayPointer", displayPtrString, "Display*", "(*c.NoDisplayPointer).DisplayPointer", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayPointer", noDisplayPtrString, "NoDisplay*", "(*c.NoDisplayPointer).NoDisplayPointer", DkmEvaluationResultFlags.Expandable)); displayHandle.Free(); noDisplayHandle.Free(); } } [Fact] public void ArrayExpansion() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] struct Display { public Display[] DisplayArray; public NoDisplay[] NoDisplayArray; } struct NoDisplay { public Display[] DisplayArray; public NoDisplay[] NoDisplayArray; } class C { public Display[] DisplayArray; public NoDisplay[] NoDisplayArray; public C() { this.DisplayArray = new[] { new Display() }; this.NoDisplayArray = new[] { new NoDisplay() }; this.DisplayArray[0].DisplayArray = this.DisplayArray; this.DisplayArray[0].NoDisplayArray = this.NoDisplayArray; this.NoDisplayArray[0].DisplayArray = this.DisplayArray; this.NoDisplayArray[0].NoDisplayArray = this.NoDisplayArray; } } "; var assembly = GetUnsafeAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); var root = FormatResult("c", value); Verify(DepthFirstSearch(root, maxDepth: 4), EvalResult("c", "{C}", "C", "c", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayArray", "{Display[1]}", "Display[]", "c.DisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("Name", "Value", "Type", "c.DisplayArray[0]", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayArray", "{Display[1]}", "Display[]", "c.DisplayArray[0].DisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("Name", "Value", "Type", "c.DisplayArray[0].DisplayArray[0]", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayArray", "{NoDisplay[1]}", "NoDisplay[]", "c.DisplayArray[0].NoDisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("[0]", "{NoDisplay}", "NoDisplay", "c.DisplayArray[0].NoDisplayArray[0]", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayArray", "{NoDisplay[1]}", "NoDisplay[]", "c.NoDisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("[0]", "{NoDisplay}", "NoDisplay", "c.NoDisplayArray[0]", DkmEvaluationResultFlags.Expandable), EvalResult("DisplayArray", "{Display[1]}", "Display[]", "c.NoDisplayArray[0].DisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("Name", "Value", "Type", "c.NoDisplayArray[0].DisplayArray[0]", DkmEvaluationResultFlags.Expandable), EvalResult("NoDisplayArray", "{NoDisplay[1]}", "NoDisplay[]", "c.NoDisplayArray[0].NoDisplayArray", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite), EvalResult("[0]", "{NoDisplay}", "NoDisplay", "c.NoDisplayArray[0].NoDisplayArray[0]", DkmEvaluationResultFlags.Expandable)); } [Fact] public void DebuggerTypeProxyExpansion() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] public struct Display { } public struct NoDisplay { } [DebuggerTypeProxy(typeof(P))] public class C { public Display DisplayC = new Display(); public NoDisplay NoDisplayC = new NoDisplay(); } public class P { public Display DisplayP = new Display(); public NoDisplay NoDisplayP = new NoDisplay(); public P(C c) { } } "; var assembly = GetUnsafeAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); var root = FormatResult("c", value); Verify(DepthFirstSearch(root, maxDepth: 4), EvalResult("c", "{C}", "C", "c", DkmEvaluationResultFlags.Expandable), EvalResult("Name", "Value", "Type", "new P(c).DisplayP"), EvalResult("NoDisplayP", "{NoDisplay}", "NoDisplay", "new P(c).NoDisplayP"), EvalResult("Raw View", null, "", "c, raw", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data), EvalResult("Name", "Value", "Type", "c.DisplayC"), EvalResult("NoDisplayC", "{NoDisplay}", "NoDisplay", "c.NoDisplayC")); } [Fact] public void NullInstance() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Hello"")] class C { } "; const string rootExpr = "c"; var assembly = GetAssembly(source); var type = assembly.GetType("C"); var value = CreateDkmClrValue(null, type, evalFlags: DkmEvaluationResultFlags.None); Verify(FormatResult(rootExpr, value), EvalResult(rootExpr, "null", "C", rootExpr)); } [Fact] public void NonGenericDisplayAttributeOnGenericBase() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Type={GetType()}"")] class A<T> { } class B : A<int> { } "; var assembly = GetAssembly(source); var type = assembly.GetType("B"); var value = CreateDkmClrValue(type.Instantiate(), type, evalFlags: DkmEvaluationResultFlags.None); var result = FormatResult("b", value); Verify(result, EvalResult("b", "Type={B}", "B", "b", DkmEvaluationResultFlags.None)); } [Fact] public void SynthesizedCollections() { // synthesized collection types should be generated with [DebuggerDisplay("Count = {...}")] var source = @" using System.Collections.Generic; class Program { static void Main() { IEnumerable<int> x = [1]; IEnumerable<int> y = [2, 3]; IEnumerable<int> z = [.. x]; } } "; var assembly = GetAssembly(source); var types = assembly.GetTypes(); VerifySynthesizedType(types.First(t => t.Name.Equals("<>z__ReadOnlySingleElementList`1")), 1, 1, "<>z__ReadOnlySingleElementList<int>"); VerifySynthesizedType(types.First(t => t.Name.Equals("<>z__ReadOnlyArray`1")), new int[] { 2, 3 }, 2, "<>z__ReadOnlyArray<int>"); VerifySynthesizedType(types.First(t => t.Name.Equals("<>z__ReadOnlyList`1")), new List<int>() { 1 }, 1, "<>z__ReadOnlyList<int>"); void VerifySynthesizedType<T>(Type genericType, T ctorArgs, int expectedCount, string expectedInstantiatedType) { var constructedType = genericType.MakeGenericType(typeof(int)); var value = CreateDkmClrValue(constructedType.Instantiate(ctorArgs), constructedType, evalFlags: DkmEvaluationResultFlags.None); var result = FormatResult("x", value); Verify(result, EvalResult("x", $"Count = {expectedCount}", expectedInstantiatedType, "x", DkmEvaluationResultFlags.Expandable)); } } [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1016895")] public void RootVersusInternal() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Value"", Name = ""Name"")] class A { } class B { A a; public B(A a) { this.a = a; } } "; var assembly = GetAssembly(source); var typeA = assembly.GetType("A"); var typeB = assembly.GetType("B"); var instanceA = typeA.Instantiate(); var instanceB = typeB.Instantiate(instanceA); var result = FormatResult("a", CreateDkmClrValue(instanceA)); Verify(result, EvalResult("a", "Value", "A", "a", DkmEvaluationResultFlags.None)); result = FormatResult("b", CreateDkmClrValue(instanceB)); Verify(GetChildren(result), EvalResult("Name", "Value", "A", "b.a", DkmEvaluationResultFlags.None)); } [Fact] public void Error() { var source = @"using System.Diagnostics; [DebuggerDisplay(""Value"", Name=""Name"", Type=""Type"")] class A { } class B { bool f; internal A P { get { return new A(); } } internal A Q { get { while(f) { } return new A(); } } } "; DkmClrRuntimeInstance runtime = null; VisualStudio.Debugger.Evaluation.ClrCompilation.DkmClrValue getMemberValue(VisualStudio.Debugger.Evaluation.ClrCompilation.DkmClrValue v, string m) => (m == "Q") ? CreateErrorValue(runtime.GetType("A"), "Function evaluation timed out") : null; runtime = new DkmClrRuntimeInstance(ReflectionUtilities.GetMscorlibAndSystemCore(GetAssembly(source)), getMemberValue: getMemberValue); using (runtime.Load()) { var type = runtime.GetType("B"); var value = type.Instantiate(); var evalResult = FormatResult("o", value); var children = GetChildren(evalResult); Verify(children, EvalResult("Name", "Value", "Type", "o.P", DkmEvaluationResultFlags.ReadOnly), EvalFailedResult("Q", "Function evaluation timed out", "A", "o.Q", DkmEvaluationResultFlags.CanFavorite), EvalResult("f", "false", "bool", "o.f", DkmEvaluationResultFlags.Boolean | DkmEvaluationResultFlags.CanFavorite)); } } [Fact] public void UnhandledException() { var source = @"using System.Diagnostics; [DebuggerDisplay(""Value}"")] class A { internal int Value; } "; var assembly = GetAssembly(source); var typeA = assembly.GetType("A"); var instanceA = typeA.Instantiate(); var result = FormatResult("a", CreateDkmClrValue(instanceA)); Verify(result, EvalFailedResult("a", "Unmatched closing brace in 'Value}'", null, null, DkmEvaluationResultFlags.None)); } [Fact, WorkItem(171123, "https://devdiv.visualstudio.com/DefaultCollection/DevDiv")] public void ExceptionDuringEvaluate() { var source = @" using System.Diagnostics; [DebuggerDisplay(""Make it throw."")] public class Picard { } "; var assembly = GetAssembly(source); var picard = assembly.GetType("Picard"); var jeanLuc = picard.Instantiate(); var result = FormatAsyncResult("says", "says", CreateDkmClrValue(jeanLuc), declaredType: new BadType(picard)); Assert.Equal(BadType.Exception, result.Exception); } private class BadType : DkmClrType { public static readonly Exception Exception = new TargetInvocationException(new DkmException(DkmExceptionCode.E_PROCESS_DESTROYED)); public BadType(System.Type innerType) : base((TypeImpl)innerType) { } public override VisualStudio.Debugger.Metadata.Type GetLmrType() { if (Environment.StackTrace.Contains("Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.GetTypeName")) { throw Exception; } return base.GetLmrType(); } } private IReadOnlyList<DkmEvaluationResult> DepthFirstSearch(DkmEvaluationResult root, int maxDepth) { var builder = ArrayBuilder<DkmEvaluationResult>.GetInstance(); DepthFirstSearchInternal(builder, root, 0, maxDepth); return builder.ToImmutableAndFree(); } private void DepthFirstSearchInternal(ArrayBuilder<DkmEvaluationResult> builder, DkmEvaluationResult curr, int depth, int maxDepth) { Assert.InRange(depth, 0, maxDepth); builder.Add(curr); var childDepth = depth + 1; if (childDepth <= maxDepth) { foreach (var child in GetChildren(curr)) { DepthFirstSearchInternal(builder, child, childDepth, maxDepth); } } } } }