|
// 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, 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);
}
}
}
}
}
|