Skip to content

Commit

Permalink
Merge pull request #229 from dnSpyEx/feature/debugger-custom-type-dis…
Browse files Browse the repository at this point in the history
…play
  • Loading branch information
ElektroKill authored Aug 12, 2023
2 parents 8f808c5 + 99c18d6 commit 63722fd
Show file tree
Hide file tree
Showing 26 changed files with 451 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ You should have received a copy of the GNU General Public License

namespace dnSpy.Debugger.DotNet.Evaluation.Engine {
abstract class DbgDotNetEngineValueNodeFactory {
public abstract DbgEngineValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType);
public abstract DbgEngineValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType, DbgDotNetCustomTypeInfo? customTypeInfo);
public abstract DbgEngineValueNode CreateException(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options);
public abstract DbgEngineValueNode CreateStowedException(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options);
public abstract DbgEngineValueNode CreateReturnValue(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, DmdMethodBase method);
Expand All @@ -53,8 +53,8 @@ public DbgDotNetEngineValueNodeFactoryImpl(DbgDotNetFormatter formatter, DbgDotN

internal DbgEngineValueNode Create(DbgDotNetValueNode node) => new DbgEngineValueNodeImpl(this, node);

public override DbgEngineValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType) =>
new DbgEngineValueNodeImpl(this, factory.Create(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, expectedType));
public override DbgEngineValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType, DbgDotNetCustomTypeInfo? customTypeInfo) =>
new DbgEngineValueNodeImpl(this, factory.Create(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, expectedType, customTypeInfo));

public override DbgEngineValueNode CreateException(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options) =>
new DbgEngineValueNodeImpl(this, factory.CreateException(evalInfo, id, value, formatSpecifiers, options));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public DbgEngineValueNode CreateValueNode(ref DbgDotNetILInterpreterState? ilInt
if (res.ErrorMessage is not null)
return valueNodeFactory.CreateError(evalInfo, compExprInfo.Name, res.ErrorMessage, compExprInfo.Expression, (compExprInfo.Flags & DbgEvaluationResultFlags.SideEffects) != 0);
//TODO: Pass in compExprInfo.CustomTypeInfo, or attach it to the DbgDotNetValueNode
return valueNodeFactory.Create(evalInfo, compExprInfo.Name, res.Value!, compExprInfo.FormatSpecifiers, nodeOptions, compExprInfo.Expression, compExprInfo.ImageName, (compExprInfo.Flags & DbgEvaluationResultFlags.ReadOnly) != 0, (compExprInfo.Flags & DbgEvaluationResultFlags.SideEffects) != 0, expectedType);
return valueNodeFactory.Create(evalInfo, compExprInfo.Name, res.Value!, compExprInfo.FormatSpecifiers, nodeOptions, compExprInfo.Expression, compExprInfo.ImageName, (compExprInfo.Flags & DbgEvaluationResultFlags.ReadOnly) != 0, (compExprInfo.Flags & DbgEvaluationResultFlags.SideEffects) != 0, expectedType, compExprInfo.CustomTypeInfo);
}
catch {
res.Value?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public override void InitializeContext(DbgEvaluationContext context, DbgCodeLoca
Debug2.Assert(context.Runtime.GetDotNetRuntime() is not null);

IDebuggerDisplayAttributeEvaluatorUtils.Initialize(context, debuggerDisplayAttributeEvaluator);
// Needed by DebuggerRuntimeImpl (calls expressionCompiler.TryGetAliasInfo()) and DbgCorDebugInternalRuntimeImpl (calls expressionCompiler.CreateCustomTypeInfo())
// Needed by DebuggerRuntimeImpl (calls expressionCompiler.TryGetAliasInfo()), DbgCorDebugInternalRuntimeImpl and DbgEngineStaticFieldsProviderImpl (calls expressionCompiler.CreateCustomTypeInfo())
context.GetOrCreateData(() => expressionCompiler);

if ((context.Options & DbgEvaluationContextOptions.NoMethodBody) == 0 && location is IDbgDotNetCodeLocation loc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
using dnlib.DotNet.Emit;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.ExpressionCompiler;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.Formatters;
using dnSpy.Contracts.Debugger.Engine.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
Expand Down Expand Up @@ -88,8 +89,10 @@ DbgEngineValueNode[] GetNodesCore(DbgEvaluationInfo evalInfo, DbgValueNodeEvalua

if (fieldVal.HasError)
valueNodes[j++] = valueNodeFactory.CreateError(evalInfo, fieldExpression, fieldVal.ErrorMessage!, fieldExpression.ToString(), false);
else
valueNodes[j++] = valueNodeFactory.Create(evalInfo, fieldExpression, fieldVal.Value!, null, options, fieldExpression.ToString(), GetFieldImageName(field), false, false, field.FieldType);
else {
evalInfo.Context.TryGetData(out DbgDotNetExpressionCompiler? expressionCompiler);
valueNodes[j++] = valueNodeFactory.Create(evalInfo, fieldExpression, fieldVal.Value!, null, options, fieldExpression.ToString(), GetFieldImageName(field), false, false, field.FieldType, expressionCompiler?.CreateCustomTypeInfo(field));
}
}
ObjectCache.Free(ref output);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ DbgEngineValueNode[] CreateCore(DbgEvaluationInfo evalInfo, DbgExpressionEvaluat
newNode = valueNodeFactory.CreateError(evalInfo, evalRes.Name, evalRes.Error, info.Expression, causesSideEffects);
else {
bool isReadOnly = (evalRes.Flags & DbgEvaluationResultFlags.ReadOnly) != 0;
newNode = valueNodeFactory.Create(evalInfo, evalRes.Name, evalRes.Value!, evalRes.FormatSpecifiers, info.NodeOptions, info.Expression, evalRes.ImageName, isReadOnly, causesSideEffects, evalRes.Type!);
newNode = valueNodeFactory.Create(evalInfo, evalRes.Name, evalRes.Value!, evalRes.FormatSpecifiers, info.NodeOptions, info.Expression, evalRes.ImageName, isReadOnly, causesSideEffects, evalRes.Type!, evalRes.CustomTypeInfo);
}
res[i] = newNode;
}
Expand Down Expand Up @@ -109,7 +109,7 @@ DbgEngineValueNode[] CreateCore(DbgEvaluationInfo evalInfo, DbgEngineObjectId[]
if (objectIdValue is null)
res[i] = valueNodeFactory.CreateError(evalInfo, name, "Could not get Object ID value", expression, false);
else
res[i] = valueNodeFactory.Create(evalInfo, name, objectIdValue, null, options, expression, PredefinedDbgValueNodeImageNames.ObjectId, true, false, objectIdValue.Type);
res[i] = valueNodeFactory.Create(evalInfo, name, objectIdValue, null, options, expression, PredefinedDbgValueNodeImageNames.ObjectId, true, false, objectIdValue.Type, null);
}
ObjectCache.Free(ref output);
return res;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright (C) 2023 ElektroKill
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/

namespace dnSpy.Roslyn.Debugger.Formatters {
struct AdditionalTypeInfoState {
internal readonly IAdditionalTypeInfoProvider? TypeInfoProvider;

internal int DynamicTypeIndex;
internal int NativeIntTypeIndex;
internal int TupleNameIndex;

public AdditionalTypeInfoState(IAdditionalTypeInfoProvider? typeInfoProvider) => TypeInfoProvider = typeInfoProvider;

public override bool Equals(object? obj) {
if (obj is not AdditionalTypeInfoState other)
return false;
if (!Equals(TypeInfoProvider, other.TypeInfoProvider))
return false;
if (DynamicTypeIndex != other.DynamicTypeIndex)
return false;
if (NativeIntTypeIndex != other.NativeIntTypeIndex)
return false;
if (TupleNameIndex != other.TupleNameIndex)
return false;
return true;
}

public override int GetHashCode() {
unchecked {
int hashCode = TypeInfoProvider is not null ? TypeInfoProvider.GetHashCode() : 0;
hashCode = hashCode * 397 ^ DynamicTypeIndex;
hashCode = hashCode * 397 ^ NativeIntTypeIndex;
hashCode = hashCode * 397 ^ TupleNameIndex;
return hashCode;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ You should have received a copy of the GNU General Public License
namespace dnSpy.Roslyn.Debugger.Formatters.CSharp {
[ExportDbgDotNetFormatter(DbgDotNetLanguageGuids.CSharp)]
sealed class CSharpFormatter : LanguageFormatter {
public override void FormatType(DbgEvaluationInfo evalInfo, IDbgTextWriter output, DmdType type, DbgDotNetValue? value, DbgValueFormatterTypeOptions options, CultureInfo? cultureInfo) =>
new CSharpTypeFormatter(output, options.ToTypeFormatterOptions(), cultureInfo).Format(type, value);
public override void FormatType(DbgEvaluationInfo evalInfo, IDbgTextWriter output, DmdType type, AdditionalTypeInfoState additionalTypeInfo, DbgDotNetValue? value, DbgValueFormatterTypeOptions options, CultureInfo? cultureInfo) =>
new CSharpTypeFormatter(output, options.ToTypeFormatterOptions(), cultureInfo).Format(type, additionalTypeInfo, value);

public override void FormatValue(DbgEvaluationInfo evalInfo, IDbgTextWriter output, DbgDotNetValue value, DbgValueFormatterOptions options, CultureInfo? cultureInfo) =>
new CSharpValueFormatter(output, evalInfo, this, options.ToValueFormatterOptions(), cultureInfo).Format(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,19 @@ internal static string GetFormattedIdentifier(string? id) {

void WriteIdentifier(string? id, DbgTextColor color) => OutputWrite(GetFormattedIdentifier(id), color);

struct TypeFormatterState {
public int DynamicTypeIndex;
public int NativeIntTypeIndex;
public int TupleNameIndex;
}
public void Format(DmdType type, DbgDotNetValue? value = null, IAdditionalTypeInfoProvider? additionalTypeInfoProvider = null, DmdParameterInfo? pd = null, bool forceReadOnly = false) =>
Format(type, new AdditionalTypeInfoState(additionalTypeInfoProvider), value, pd, forceReadOnly);

public void Format(DmdType type, DbgDotNetValue? value = null, IAdditionalTypeInfoProvider? additionalTypeInfoProvider = null, DmdParameterInfo? pd = null, bool forceReadOnly = false) {
public void Format(DmdType type, AdditionalTypeInfoState state, DbgDotNetValue? value = null, DmdParameterInfo? pd = null, bool forceReadOnly = false) {
WriteRefIfByRef(type, pd, forceReadOnly);
TypeFormatterState state = default;
if (type.IsByRef) {
type = type.GetElementType()!;
state.DynamicTypeIndex++;
}
FormatCore(type, value, additionalTypeInfoProvider, ref state);
FormatCore(type, value, ref state);
}

void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider? additionalTypeInfoProvider, ref TypeFormatterState state) {
void FormatCore(DmdType type, DbgDotNetValue? value, ref AdditionalTypeInfoState state) {
if (type is null)
throw new ArgumentNullException(nameof(type));

Expand All @@ -154,7 +150,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
state.DynamicTypeIndex++;
} while (type.IsArray);
var t = arrayTypesList[arrayTypesList.Count - 1];
FormatCore(t.type.GetElementType()!, null, additionalTypeInfoProvider, ref state);
FormatCore(t.type.GetElementType()!, null, ref state);
foreach (var tuple in arrayTypesList) {
var aryType = tuple.type;
var aryValue = tuple.value;
Expand Down Expand Up @@ -221,15 +217,15 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider

case DmdTypeSignatureKind.Pointer:
state.DynamicTypeIndex++;
FormatCore(type.GetElementType()!, null, additionalTypeInfoProvider, ref state);
FormatCore(type.GetElementType()!, null, ref state);
OutputWrite("*", DbgTextColor.Operator);
break;

case DmdTypeSignatureKind.ByRef:
state.DynamicTypeIndex++;
OutputWrite(BYREF_KEYWORD, DbgTextColor.Keyword);
WriteSpace();
FormatCore(type.GetElementType()!, disposeThisValue = value?.LoadIndirect().Value, additionalTypeInfoProvider, ref state);
FormatCore(type.GetElementType()!, disposeThisValue = value?.LoadIndirect().Value, ref state);
break;

case DmdTypeSignatureKind.TypeGenericParameter:
Expand All @@ -244,7 +240,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
case DmdTypeSignatureKind.GenericInstance:
if (type.IsNullable) {
state.DynamicTypeIndex++;
FormatCore(type.GetNullableElementType(), null, additionalTypeInfoProvider, ref state);
FormatCore(type.GetNullableElementType(), null, ref state);
OutputWrite("?", DbgTextColor.Operator);
break;
}
Expand All @@ -255,7 +251,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
OutputWrite(TUPLE_OPEN_PAREN, DbgTextColor.Punctuation);
var tupleType = type;
for (;;) {
tupleType = WriteTupleFields(tupleType, ref tupleIndex, additionalTypeInfoProvider, ref state);
tupleType = WriteTupleFields(tupleType, ref tupleIndex, ref state);
if (tupleType is not null) {
WriteCommaSpace();
state.DynamicTypeIndex++;
Expand All @@ -273,24 +269,24 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
KeywordType keywordType;
if (type.DeclaringType is null) {
keywordType = GetKeywordType(type);
if (additionalTypeInfoProvider is not null) {
if (keywordType == KeywordType.Object && additionalTypeInfoProvider.IsDynamicType(state.DynamicTypeIndex)) {
if (state.TypeInfoProvider is not null) {
if (keywordType == KeywordType.Object && state.TypeInfoProvider.IsDynamicType(state.DynamicTypeIndex)) {
OutputWrite("dynamic", DbgTextColor.Keyword);
break;
}
if (type == type.AppDomain.System_IntPtr && additionalTypeInfoProvider.IsNativeIntegerType(state.NativeIntTypeIndex++)) {
if (type == type.AppDomain.System_IntPtr && state.TypeInfoProvider.IsNativeIntegerType(state.NativeIntTypeIndex++)) {
OutputWrite("nint", DbgTextColor.Keyword);
break;
}
if (type == type.AppDomain.System_UIntPtr && additionalTypeInfoProvider.IsNativeIntegerType(state.NativeIntTypeIndex++)) {
if (type == type.AppDomain.System_UIntPtr && state.TypeInfoProvider.IsNativeIntegerType(state.NativeIntTypeIndex++)) {
OutputWrite("nuint", DbgTextColor.Keyword);
break;
}
}
if (keywordType == KeywordType.NoKeyword)
WriteNamespace(type);
WriteTypeName(type, keywordType);
WriteGenericArguments(type, genericArgs, ref genericArgsIndex, additionalTypeInfoProvider, ref state);
WriteGenericArguments(type, genericArgs, ref genericArgsIndex, ref state);
}
else {
var typesList = new List<DmdType>();
Expand All @@ -304,7 +300,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
WriteNamespace(type);
for (int i = typesList.Count - 1; i >= 0; i--) {
WriteTypeName(typesList[i], i == 0 ? keywordType : KeywordType.NoKeyword);
WriteGenericArguments(typesList[i], genericArgs, ref genericArgsIndex, additionalTypeInfoProvider, ref state);
WriteGenericArguments(typesList[i], genericArgs, ref genericArgsIndex, ref state);
if (i != 0)
OutputWrite(".", DbgTextColor.Operator);
}
Expand All @@ -314,15 +310,15 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
case DmdTypeSignatureKind.FunctionPointer:
var sig = type.GetFunctionPointerMethodSignature();
state.DynamicTypeIndex++;
FormatCore(sig.ReturnType, null, additionalTypeInfoProvider, ref state);
FormatCore(sig.ReturnType, null, ref state);
WriteSpace();
OutputWrite(METHOD_OPEN_PAREN, DbgTextColor.Punctuation);
var types = sig.GetParameterTypes();
for (int i = 0; i < types.Count; i++) {
if (i > 0)
WriteCommaSpace();
state.DynamicTypeIndex++;
FormatCore(types[i], null, additionalTypeInfoProvider, ref state);
FormatCore(types[i], null, ref state);
}
types = sig.GetVarArgsParameterTypes();
if (types.Count > 0) {
Expand All @@ -331,7 +327,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
OutputWrite("...", DbgTextColor.Punctuation);
for (int i = 0; i < types.Count; i++) {
WriteCommaSpace();
FormatCore(types[i], null, additionalTypeInfoProvider, ref state);
FormatCore(types[i], null, ref state);
}
}
OutputWrite(METHOD_CLOSE_PAREN, DbgTextColor.Punctuation);
Expand All @@ -353,7 +349,7 @@ void FormatCore(DmdType type, DbgDotNetValue? value, IAdditionalTypeInfoProvider
}
}

void WriteGenericArguments(DmdType type, IList<DmdType> genericArgs, ref int genericArgsIndex, IAdditionalTypeInfoProvider? additionalTypeInfoProvider, ref TypeFormatterState state) {
void WriteGenericArguments(DmdType type, IList<DmdType> genericArgs, ref int genericArgsIndex, ref AdditionalTypeInfoState state) {
var gas = type.GetGenericArguments();
if (genericArgsIndex < genericArgs.Count && genericArgsIndex < gas.Count) {
OutputWrite(GENERICS_OPEN_PAREN, DbgTextColor.Punctuation);
Expand All @@ -362,13 +358,13 @@ void WriteGenericArguments(DmdType type, IList<DmdType> genericArgs, ref int gen
if (j > startIndex)
WriteCommaSpace();
state.DynamicTypeIndex++;
FormatCore(genericArgs[j], null, additionalTypeInfoProvider, ref state);
FormatCore(genericArgs[j], null, ref state);
}
OutputWrite(GENERICS_CLOSE_PAREN, DbgTextColor.Punctuation);
}
}

DmdType? WriteTupleFields(DmdType type, ref int index, IAdditionalTypeInfoProvider? additionalTypeInfoProvider, ref TypeFormatterState state) {
DmdType? WriteTupleFields(DmdType type, ref int index, ref AdditionalTypeInfoState state) {
var args = type.GetGenericArguments();
Debug.Assert(0 < args.Count && args.Count <= TypeFormatterUtils.MAX_TUPLE_ARITY);
if (args.Count > TypeFormatterUtils.MAX_TUPLE_ARITY) {
Expand All @@ -379,8 +375,8 @@ void WriteGenericArguments(DmdType type, IList<DmdType> genericArgs, ref int gen
if (i > 0)
WriteCommaSpace();
state.DynamicTypeIndex++;
FormatCore(args[i], null, additionalTypeInfoProvider, ref state);
string? fieldName = additionalTypeInfoProvider?.GetTupleElementName(index++);
FormatCore(args[i], null, ref state);
string? fieldName = state.TypeInfoProvider?.GetTupleElementName(index++);
if (fieldName is not null) {
WriteSpace();
OutputWrite(fieldName, DbgTextColor.InstanceField);
Expand Down
Loading

0 comments on commit 63722fd

Please sign in to comment.