|
// 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
#pragma warning disable CA1825 // Avoid zero-length array allocations.
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.VisualStudio.Debugger;
using Microsoft.VisualStudio.Debugger.CallStack;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.ComponentInterfaces;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
using Microsoft.VisualStudio.Debugger.Metadata;
using Roslyn.Utilities;
using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal delegate void CompletionRoutine();
internal delegate void CompletionRoutine<TResult>(TResult result);
/// <summary>
/// Computes expansion of <see cref="DkmClrValue"/> instances.
/// </summary>
/// <remarks>
/// This class provides implementation for the default ResultProvider component.
/// </remarks>
public abstract class ResultProvider : IDkmClrResultProvider
{
// TODO: There is a potential that these values will conflict with debugger defined flags in future.
// It'd be better if we attached these flags to the DkmClrValue object via data items, however DkmClrValue is currently mutable
// and we can't clone it -- in some cases we might need to attach different flags in different code paths and it wouldn't be possible
// to do so due to mutability.
// See https://github.com/dotnet/roslyn/issues/55676.
internal const DkmEvaluationFlags NotRoot = (DkmEvaluationFlags)(1 << 30);
internal const DkmEvaluationFlags NoResults = (DkmEvaluationFlags)(1 << 31);
// Fields should be removed and replaced with calls through DkmInspectionContext.
// (see https://github.com/dotnet/roslyn/issues/6899).
internal readonly IDkmClrFormatter2 Formatter2;
internal readonly IDkmClrFullNameProvider FullNameProvider;
static ResultProvider()
{
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);
}
internal ResultProvider(IDkmClrFormatter2 formatter2, IDkmClrFullNameProvider fullNameProvider)
{
Formatter2 = formatter2;
FullNameProvider = fullNameProvider;
}
internal abstract string StaticMembersString { get; }
internal abstract bool IsPrimitiveType(Type type);
#nullable enable
/// <summary>
/// Returns true if a generated member of <paramref name="metadataName"/> should be displayed.
/// <paramref name="displayName"/> is set to the unmangled name to be displayed.
/// </summary>
internal abstract bool TryGetGeneratedMemberDisplay(string metadataName, [NotNullWhen(true)] out string? displayName);
#nullable disable
void IDkmClrResultProvider.GetResult(DkmClrValue value, DkmWorkList workList, DkmClrType declaredType, DkmClrCustomTypeInfo declaredTypeInfo, DkmInspectionContext inspectionContext, ReadOnlyCollection<string> formatSpecifiers, string resultName, string resultFullName, DkmCompletionRoutine<DkmEvaluationAsyncResult> completionRoutine)
{
formatSpecifiers ??= Formatter.NoFormatSpecifiers;
if (resultFullName != null)
{
ReadOnlyCollection<string> otherSpecifiers;
resultFullName = FullNameProvider.GetClrExpressionAndFormatSpecifiers(inspectionContext, resultFullName, out otherSpecifiers);
foreach (var formatSpecifier in otherSpecifiers)
{
formatSpecifiers = Formatter.AddFormatSpecifier(formatSpecifiers, formatSpecifier);
}
}
var wl = new WorkList(workList, e => completionRoutine(DkmEvaluationAsyncResult.CreateErrorResult(e)));
wl.ContinueWith(
() => GetRootResultAndContinue(
value,
wl,
declaredType,
declaredTypeInfo,
inspectionContext,
resultName,
resultFullName,
formatSpecifiers,
result => wl.ContinueWith(() => completionRoutine(new DkmEvaluationAsyncResult(result)))));
}
DkmClrValue IDkmClrResultProvider.GetClrValue(DkmSuccessEvaluationResult evaluationResult)
{
try
{
var dataItem = evaluationResult.GetDataItem<EvalResultDataItem>();
if (dataItem == null)
{
// We don't know about this result. Call next implementation
return evaluationResult.GetClrValue();
}
return dataItem.Value;
}
catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e))
{
throw ExceptionUtilities.Unreachable();
}
}
void IDkmClrResultProvider.GetChildren(DkmEvaluationResult evaluationResult, DkmWorkList workList, int initialRequestSize, DkmInspectionContext inspectionContext, DkmCompletionRoutine<DkmGetChildrenAsyncResult> completionRoutine)
{
var dataItem = evaluationResult.GetDataItem<EvalResultDataItem>();
if (dataItem == null)
{
// We don't know about this result. Call next implementation
evaluationResult.GetChildren(workList, initialRequestSize, inspectionContext, completionRoutine);
return;
}
var expansion = dataItem.Expansion;
if (expansion == null)
{
var enumContext = DkmEvaluationResultEnumContext.Create(0, evaluationResult.StackFrame, inspectionContext, new EnumContextDataItem(evaluationResult));
completionRoutine(new DkmGetChildrenAsyncResult(new DkmEvaluationResult[0], enumContext));
return;
}
// Evaluate children with InspectionContext that is not the root.
inspectionContext = inspectionContext.With(NotRoot);
var rows = ArrayBuilder<EvalResult>.GetInstance();
int index = 0;
expansion.GetRows(this, rows, inspectionContext, dataItem, dataItem.Value, 0, initialRequestSize, visitAll: true, index: ref index);
var numRows = rows.Count;
Debug.Assert(index >= numRows);
Debug.Assert(initialRequestSize >= numRows);
var initialChildren = new DkmEvaluationResult[numRows];
void onException(Exception e) => completionRoutine(DkmGetChildrenAsyncResult.CreateErrorResult(e));
var wl = new WorkList(workList, onException);
wl.ContinueWith(() =>
GetEvaluationResultsAndContinue(evaluationResult, rows, initialChildren, 0, numRows, wl, inspectionContext,
() =>
wl.ContinueWith(
() =>
{
var enumContext = DkmEvaluationResultEnumContext.Create(index, evaluationResult.StackFrame, inspectionContext, new EnumContextDataItem(evaluationResult));
completionRoutine(new DkmGetChildrenAsyncResult(initialChildren, enumContext));
rows.Free();
}),
onException));
}
void IDkmClrResultProvider.GetItems(DkmEvaluationResultEnumContext enumContext, DkmWorkList workList, int startIndex, int count, DkmCompletionRoutine<DkmEvaluationEnumAsyncResult> completionRoutine)
{
var enumContextDataItem = enumContext.GetDataItem<EnumContextDataItem>();
if (enumContextDataItem == null)
{
// We don't know about this result. Call next implementation
enumContext.GetItems(workList, startIndex, count, completionRoutine);
return;
}
var evaluationResult = enumContextDataItem.Result;
var dataItem = evaluationResult.GetDataItem<EvalResultDataItem>();
var expansion = dataItem.Expansion;
if (expansion == null)
{
completionRoutine(new DkmEvaluationEnumAsyncResult(new DkmEvaluationResult[0]));
return;
}
var inspectionContext = enumContext.InspectionContext;
var rows = ArrayBuilder<EvalResult>.GetInstance();
int index = 0;
expansion.GetRows(this, rows, inspectionContext, dataItem, dataItem.Value, startIndex, count, visitAll: false, index: ref index);
var numRows = rows.Count;
Debug.Assert(count >= numRows);
var results = new DkmEvaluationResult[numRows];
void onException(Exception e) => completionRoutine(DkmEvaluationEnumAsyncResult.CreateErrorResult(e));
var wl = new WorkList(workList, onException);
wl.ContinueWith(() =>
GetEvaluationResultsAndContinue(evaluationResult, rows, results, 0, numRows, wl, inspectionContext,
() =>
wl.ContinueWith(
() =>
{
completionRoutine(new DkmEvaluationEnumAsyncResult(results));
rows.Free();
}),
onException));
}
string IDkmClrResultProvider.GetUnderlyingString(DkmEvaluationResult result)
{
try
{
var dataItem = result.GetDataItem<EvalResultDataItem>();
if (dataItem == null)
{
// We don't know about this result. Call next implementation
return result.GetUnderlyingString();
}
return dataItem.Value?.GetUnderlyingString(result.InspectionContext);
}
catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e))
{
throw ExceptionUtilities.Unreachable();
}
}
private void GetChild(
DkmEvaluationResult parent,
WorkList workList,
EvalResult row,
DkmCompletionRoutine<DkmEvaluationAsyncResult> completionRoutine)
{
var inspectionContext = row.InspectionContext;
if ((row.Kind != ExpansionKind.Default) || (row.Value == null))
{
CreateEvaluationResultAndContinue(
row,
workList,
row.InspectionContext,
parent.StackFrame,
child => completionRoutine(new DkmEvaluationAsyncResult(child)));
}
else
{
var typeDeclaringMember = row.TypeDeclaringMemberAndInfo;
var name = (typeDeclaringMember.Type == null)
? row.Name
: GetQualifiedMemberName(row.InspectionContext, typeDeclaringMember, row.Name, FullNameProvider);
row.Value.SetDataItem(DkmDataCreationDisposition.CreateAlways, new FavoritesDataItem(row.CanFavorite, row.IsFavorite));
row.Value.GetResult(
workList.InnerWorkList,
row.DeclaredTypeAndInfo.ClrType,
row.DeclaredTypeAndInfo.Info,
row.InspectionContext,
Formatter.NoFormatSpecifiers,
name,
row.FullName,
result => workList.ContinueWith(() => completionRoutine(result)));
}
}
private void CreateEvaluationResultAndContinue(EvalResult result, WorkList workList, DkmInspectionContext inspectionContext, DkmStackWalkFrame stackFrame, CompletionRoutine<DkmEvaluationResult> completionRoutine)
{
switch (result.Kind)
{
case ExpansionKind.Explicit:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: result.DisplayName,
FullName: result.FullName,
Flags: result.Flags,
Value: result.DisplayValue,
EditableValue: result.EditableValue,
Type: result.DisplayType,
Category: DkmEvaluationResultCategory.Data,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.Error:
completionRoutine(DkmFailedEvaluationResult.Create(
inspectionContext,
StackFrame: stackFrame,
Name: result.Name,
FullName: result.FullName,
ErrorMessage: result.DisplayValue,
Flags: DkmEvaluationResultFlags.None,
Type: null,
DataItem: null));
break;
case ExpansionKind.NativeView:
{
var value = result.Value;
var name = Resources.NativeView;
var fullName = result.FullName;
var display = result.Name;
DkmEvaluationResult evalResult;
if (value.IsError())
{
evalResult = DkmFailedEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: name,
FullName: fullName,
ErrorMessage: display,
Flags: result.Flags,
Type: null,
DataItem: result.ToDataItem());
}
else
{
// For Native View, create a DkmIntermediateEvaluationResult.
// This will allow the C++ EE to take over expansion.
var process = inspectionContext.RuntimeInstance.Process;
var cpp = process.EngineSettings.GetLanguage(new DkmCompilerId(DkmVendorId.Microsoft, DkmLanguageId.Cpp));
evalResult = DkmIntermediateEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: name,
FullName: fullName,
Expression: display,
IntermediateLanguage: cpp,
TargetRuntime: process.GetNativeRuntimeInstance(),
DataItem: result.ToDataItem());
}
completionRoutine(evalResult);
}
break;
case ExpansionKind.NonPublicMembers:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: Resources.NonPublicMembers,
FullName: result.FullName,
Flags: result.Flags,
Value: null,
EditableValue: null,
Type: string.Empty,
Category: DkmEvaluationResultCategory.Data,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.StaticMembers:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: StaticMembersString,
FullName: result.FullName,
Flags: result.Flags,
Value: null,
EditableValue: null,
Type: string.Empty,
Category: DkmEvaluationResultCategory.Class,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.RawView:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
Name: Resources.RawView,
FullName: result.FullName,
Flags: result.Flags,
Value: null,
EditableValue: result.EditableValue,
Type: string.Empty,
Category: DkmEvaluationResultCategory.Data,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.DynamicView:
case ExpansionKind.ResultsView:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
result.Name,
result.FullName,
result.Flags,
result.DisplayValue,
EditableValue: null,
Type: string.Empty,
Category: DkmEvaluationResultCategory.Method,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.TypeVariable:
completionRoutine(DkmSuccessEvaluationResult.Create(
inspectionContext,
stackFrame,
result.Name,
result.FullName,
result.Flags,
result.DisplayValue,
EditableValue: null,
Type: result.DisplayValue,
Category: DkmEvaluationResultCategory.Data,
Access: DkmEvaluationResultAccessType.None,
StorageType: DkmEvaluationResultStorageType.None,
TypeModifierFlags: DkmEvaluationResultTypeModifierFlags.None,
Address: result.Value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: result.ToDataItem()));
break;
case ExpansionKind.PointerDereference:
case ExpansionKind.Default:
// This call will evaluate DebuggerDisplayAttributes.
GetResultAndContinue(
result,
workList,
declaredType: result.DeclaredTypeAndInfo.ClrType,
declaredTypeInfo: result.DeclaredTypeAndInfo.Info,
inspectionContext: inspectionContext,
useDebuggerDisplay: result.UseDebuggerDisplay,
completionRoutine: completionRoutine);
break;
default:
throw ExceptionUtilities.UnexpectedValue(result.Kind);
}
}
private static DkmEvaluationResult CreateEvaluationResult(
DkmInspectionContext inspectionContext,
DkmClrValue value,
string name,
string typeName,
string display,
EvalResult result)
{
if (value.IsError())
{
// Evaluation failed
return DkmFailedEvaluationResult.Create(
InspectionContext: inspectionContext,
StackFrame: value.StackFrame,
Name: name,
FullName: result.FullName,
ErrorMessage: display,
Flags: result.Flags,
Type: typeName,
DataItem: result.ToDataItem());
}
else
{
ReadOnlyCollection<DkmCustomUIVisualizerInfo> customUIVisualizers = null;
if (!value.IsNull)
{
DkmCustomUIVisualizerInfo[] customUIVisualizerInfo = value.Type.GetDebuggerCustomUIVisualizerInfo();
if (customUIVisualizerInfo != null)
{
customUIVisualizers = new ReadOnlyCollection<DkmCustomUIVisualizerInfo>(customUIVisualizerInfo);
}
}
// If the EvalResultDataItem doesn't specify a particular category, we'll just propagate DkmClrValue.Category,
// which typically appears to be set to the default value ("Other").
var category = (result.Category != DkmEvaluationResultCategory.Other) ? result.Category : value.Category;
var nullableMemberInfo = value.GetDataItem<NullableMemberInfo>();
// Valid value
return DkmSuccessEvaluationResult.Create(
InspectionContext: inspectionContext,
StackFrame: value.StackFrame,
Name: name,
FullName: result.FullName,
Flags: result.Flags,
Value: display,
EditableValue: result.EditableValue,
Type: typeName,
Category: nullableMemberInfo?.Category ?? category,
Access: nullableMemberInfo?.Access ?? value.Access,
StorageType: nullableMemberInfo?.StorageType ?? value.StorageType,
TypeModifierFlags: nullableMemberInfo?.TypeModifierFlags ?? value.TypeModifierFlags,
Address: value.Address,
CustomUIVisualizers: customUIVisualizers,
ExternalModules: null,
DataItem: result.ToDataItem());
}
}
/// <returns>
/// The qualified name (i.e. including containing types and namespaces) of a named, pointer,
/// or array type followed by the qualified name of the actual runtime type, if provided.
/// </returns>
internal static string GetTypeName(
DkmInspectionContext inspectionContext,
DkmClrValue value,
DkmClrType declaredType,
DkmClrCustomTypeInfo declaredTypeInfo,
bool isPointerDereference)
{
var declaredLmrType = declaredType.GetLmrType();
var runtimeType = value.Type;
var declaredTypeName = inspectionContext.GetTypeName(declaredType, declaredTypeInfo, Formatter.NoFormatSpecifiers);
// Include the runtime type if distinct.
if (!declaredLmrType.IsPointer &&
!isPointerDereference &&
(!declaredLmrType.IsNullable() || value.EvalFlags.Includes(DkmEvaluationResultFlags.ExceptionThrown)))
{
// Generate the declared type name without tuple element names.
var declaredTypeInfoNoTupleElementNames = declaredTypeInfo.WithNoTupleElementNames();
var declaredTypeNameNoTupleElementNames = (declaredTypeInfo == declaredTypeInfoNoTupleElementNames)
? declaredTypeName
: inspectionContext.GetTypeName(declaredType, declaredTypeInfoNoTupleElementNames, Formatter.NoFormatSpecifiers);
// Generate the runtime type name with no tuple element names and no dynamic.
var runtimeTypeName = inspectionContext.GetTypeName(runtimeType, null, FormatSpecifiers: Formatter.NoFormatSpecifiers);
// If the two names are distinct, include both.
if (!string.Equals(declaredTypeNameNoTupleElementNames, runtimeTypeName, StringComparison.Ordinal)) // Names will reflect "dynamic", types will not.
{
return string.Format("{0} {{{1}}}", declaredTypeName, runtimeTypeName);
}
}
return declaredTypeName;
}
internal EvalResult CreateDataItem(
DkmInspectionContext inspectionContext,
string name,
TypeAndCustomInfo typeDeclaringMemberAndInfo,
TypeAndCustomInfo declaredTypeAndInfo,
DkmClrValue value,
bool useDebuggerDisplay,
ExpansionFlags expansionFlags,
bool childShouldParenthesize,
string fullName,
ReadOnlyCollection<string> formatSpecifiers,
DkmEvaluationResultCategory category,
DkmEvaluationResultFlags flags,
DkmEvaluationFlags evalFlags,
bool canFavorite,
bool isFavorite,
bool supportsFavorites)
{
if ((evalFlags & DkmEvaluationFlags.ShowValueRaw) != 0)
{
formatSpecifiers = Formatter.AddFormatSpecifier(formatSpecifiers, "raw");
}
Expansion expansion;
// If the declared type is Nullable<T>, the value should
// have no expansion if null, or be expanded as a T.
var declaredType = declaredTypeAndInfo.Type;
var lmrNullableTypeArg = declaredType.GetNullableTypeArgument();
if (lmrNullableTypeArg != null && !value.HasExceptionThrown())
{
Debug.Assert(value.Type.GetProxyType() == null);
DkmClrValue nullableValue;
if (value.IsError())
{
expansion = null;
}
else if ((nullableValue = value.GetNullableValue(lmrNullableTypeArg, inspectionContext)) == null)
{
Debug.Assert(declaredType.Equals(value.Type.GetLmrType()));
// No expansion of "null".
expansion = null;
}
else
{
// nullableValue is taken from an internal field.
// It may have different category, access, etc comparing the original member.
// For example, the orignal member can be a property not a field.
// Save original member values to restore them later.
if (value != nullableValue)
{
var nullableMemberInfo = new NullableMemberInfo(value.Category, value.Access, value.StorageType, value.TypeModifierFlags);
nullableValue.SetDataItem(DkmDataCreationDisposition.CreateAlways, nullableMemberInfo);
}
value = nullableValue;
Debug.Assert(lmrNullableTypeArg.Equals(value.Type.GetLmrType())); // If this is not the case, add a test for includeRuntimeTypeIfNecessary.
// CONSIDER: The DynamicAttribute for the type argument should just be Skip(1) of the original flag array.
expansion = this.GetTypeExpansion(inspectionContext, new TypeAndCustomInfo(DkmClrType.Create(declaredTypeAndInfo.ClrType.AppDomain, lmrNullableTypeArg)), value, ExpansionFlags.IncludeResultsView, supportsFavorites: supportsFavorites);
}
}
else if (value.IsError() || (inspectionContext.EvaluationFlags & DkmEvaluationFlags.NoExpansion) != 0)
{
expansion = null;
}
else
{
expansion = DebuggerTypeProxyExpansion.CreateExpansion(
this,
inspectionContext,
name,
typeDeclaringMemberAndInfo,
declaredTypeAndInfo,
value,
childShouldParenthesize,
fullName,
flags.Includes(DkmEvaluationResultFlags.ExceptionThrown) ? null : fullName,
formatSpecifiers,
flags,
Formatter2.GetEditableValueString(value, inspectionContext, declaredTypeAndInfo.Info));
expansion ??= value.HasExceptionThrown()
? this.GetTypeExpansion(inspectionContext, new TypeAndCustomInfo(value.Type), value, expansionFlags, supportsFavorites: false)
: this.GetTypeExpansion(inspectionContext, declaredTypeAndInfo, value, expansionFlags, supportsFavorites: supportsFavorites);
}
return new EvalResult(
ExpansionKind.Default,
name,
typeDeclaringMemberAndInfo,
declaredTypeAndInfo,
useDebuggerDisplay: useDebuggerDisplay,
value: value,
displayValue: null,
expansion: expansion,
childShouldParenthesize: childShouldParenthesize,
fullName: fullName,
childFullNamePrefixOpt: flags.Includes(DkmEvaluationResultFlags.ExceptionThrown) ? null : fullName,
formatSpecifiers: formatSpecifiers,
category: category,
flags: flags,
editableValue: Formatter2.GetEditableValueString(value, inspectionContext, declaredTypeAndInfo.Info),
inspectionContext: inspectionContext,
canFavorite: canFavorite,
isFavorite: isFavorite);
}
private void GetRootResultAndContinue(
DkmClrValue value,
WorkList workList,
DkmClrType declaredType,
DkmClrCustomTypeInfo declaredTypeInfo,
DkmInspectionContext inspectionContext,
string name,
string fullName,
ReadOnlyCollection<string> formatSpecifiers,
CompletionRoutine<DkmEvaluationResult> completionRoutine)
{
Debug.Assert(formatSpecifiers != null);
var type = value.Type.GetLmrType();
if (type.IsTypeVariables())
{
Debug.Assert(type.Equals(declaredType.GetLmrType()));
var declaredTypeAndInfo = new TypeAndCustomInfo(declaredType, declaredTypeInfo);
var expansion = new TypeVariablesExpansion(declaredTypeAndInfo);
var dataItem = new EvalResult(
ExpansionKind.Default,
name,
typeDeclaringMemberAndInfo: default(TypeAndCustomInfo),
declaredTypeAndInfo: declaredTypeAndInfo,
useDebuggerDisplay: false,
value: value,
displayValue: null,
expansion: expansion,
childShouldParenthesize: false,
fullName: null,
childFullNamePrefixOpt: null,
formatSpecifiers: Formatter.NoFormatSpecifiers,
category: DkmEvaluationResultCategory.Data,
flags: DkmEvaluationResultFlags.ReadOnly,
editableValue: null,
inspectionContext: inspectionContext);
Debug.Assert(dataItem.Flags == (DkmEvaluationResultFlags.ReadOnly | DkmEvaluationResultFlags.Expandable));
// Note: We're not including value.EvalFlags in Flags parameter
// below (there shouldn't be a reason to do so).
completionRoutine(DkmSuccessEvaluationResult.Create(
InspectionContext: inspectionContext,
StackFrame: value.StackFrame,
Name: Resources.TypeVariablesName,
FullName: dataItem.FullName,
Flags: dataItem.Flags,
Value: "",
EditableValue: null,
Type: "",
Category: dataItem.Category,
Access: value.Access,
StorageType: value.StorageType,
TypeModifierFlags: value.TypeModifierFlags,
Address: value.Address,
CustomUIVisualizers: null,
ExternalModules: null,
DataItem: dataItem.ToDataItem()));
}
else if ((inspectionContext.EvaluationFlags & DkmEvaluationFlags.ResultsOnly) != 0)
{
var dataItem = ResultsViewExpansion.CreateResultsOnlyRow(
inspectionContext,
name,
fullName,
formatSpecifiers,
declaredType,
declaredTypeInfo,
value,
this);
CreateEvaluationResultAndContinue(
dataItem,
workList,
inspectionContext,
value.StackFrame,
completionRoutine);
}
else if ((inspectionContext.EvaluationFlags & DkmEvaluationFlags.DynamicView) != 0)
{
var dataItem = DynamicViewExpansion.CreateMembersOnlyRow(
inspectionContext,
name,
value,
this);
CreateEvaluationResultAndContinue(
dataItem,
workList,
inspectionContext,
value.StackFrame,
completionRoutine);
}
else
{
var dataItem = ResultsViewExpansion.CreateResultsOnlyRowIfSynthesizedEnumerable(
inspectionContext,
name,
fullName,
formatSpecifiers,
declaredType,
declaredTypeInfo,
value,
this);
if (dataItem != null)
{
CreateEvaluationResultAndContinue(
dataItem,
workList,
inspectionContext,
value.StackFrame,
completionRoutine);
}
else
{
var useDebuggerDisplay = (inspectionContext.EvaluationFlags & NotRoot) != 0;
var expansionFlags = (inspectionContext.EvaluationFlags & NoResults) != 0
? ExpansionFlags.IncludeBaseMembers
: ExpansionFlags.All;
var favortiesDataItem = value.GetDataItem<FavoritesDataItem>();
dataItem = CreateDataItem(
inspectionContext,
name,
typeDeclaringMemberAndInfo: default(TypeAndCustomInfo),
declaredTypeAndInfo: new TypeAndCustomInfo(declaredType, declaredTypeInfo),
value: value,
useDebuggerDisplay: useDebuggerDisplay,
expansionFlags: expansionFlags,
childShouldParenthesize: (fullName == null) ? false : FullNameProvider.ClrExpressionMayRequireParentheses(inspectionContext, fullName),
fullName: fullName,
formatSpecifiers: formatSpecifiers,
category: DkmEvaluationResultCategory.Other,
flags: value.EvalFlags,
evalFlags: inspectionContext.EvaluationFlags,
canFavorite: favortiesDataItem?.CanFavorite ?? false,
isFavorite: favortiesDataItem?.IsFavorite ?? false,
supportsFavorites: true);
GetResultAndContinue(dataItem, workList, declaredType, declaredTypeInfo, inspectionContext, useDebuggerDisplay, completionRoutine);
}
}
}
private void GetResultAndContinue(
EvalResult result,
WorkList workList,
DkmClrType declaredType,
DkmClrCustomTypeInfo declaredTypeInfo,
DkmInspectionContext inspectionContext,
bool useDebuggerDisplay,
CompletionRoutine<DkmEvaluationResult> completionRoutine)
{
var value = result.Value; // Value may have been replaced (specifically, for Nullable<T>).
if (value.TryGetDebuggerDisplayInfo(out DebuggerDisplayInfo displayInfo))
{
void onException(Exception e) => completionRoutine(CreateEvaluationResultFromException(e, result, inspectionContext));
if (displayInfo.Name != null)
{
// Favorites currently dependes on the name matching the member name
result = result.WithDisableCanAddFavorite();
}
var innerWorkList = workList.InnerWorkList;
EvaluateDebuggerDisplayStringAndContinue(value, innerWorkList, inspectionContext, displayInfo.Name,
displayName => EvaluateDebuggerDisplayStringAndContinue(value, innerWorkList, inspectionContext, displayInfo.GetValue(inspectionContext),
displayValue => EvaluateDebuggerDisplayStringAndContinue(value, innerWorkList, inspectionContext, displayInfo.TypeName,
displayType =>
workList.ContinueWith(() =>
completionRoutine(GetResult(inspectionContext, result, declaredType, declaredTypeInfo, displayName.Result, displayValue.Result, displayType.Result, useDebuggerDisplay))),
onException),
onException),
onException);
}
else
{
completionRoutine(GetResult(inspectionContext, result, declaredType, declaredTypeInfo, displayName: null, displayValue: null, displayType: null, useDebuggerDisplay: false));
}
}
private static void EvaluateDebuggerDisplayStringAndContinue(
DkmClrValue value,
DkmWorkList workList,
DkmInspectionContext inspectionContext,
DebuggerDisplayItemInfo displayInfo,
CompletionRoutine<DkmEvaluateDebuggerDisplayStringAsyncResult> onCompleted,
CompletionRoutine<Exception> onException)
{
void completionRoutine(DkmEvaluateDebuggerDisplayStringAsyncResult result)
{
try
{
onCompleted(result);
}
catch (Exception e)
{
onException(e);
}
}
if (displayInfo == null)
{
completionRoutine(default(DkmEvaluateDebuggerDisplayStringAsyncResult));
}
else
{
value.EvaluateDebuggerDisplayString(workList, inspectionContext, displayInfo.TargetType, displayInfo.Value, completionRoutine);
}
}
private DkmEvaluationResult GetResult(
DkmInspectionContext inspectionContext,
EvalResult result,
DkmClrType declaredType,
DkmClrCustomTypeInfo declaredTypeInfo,
string displayName,
string displayValue,
string displayType,
bool useDebuggerDisplay)
{
var name = result.Name;
Debug.Assert(name != null);
var typeDeclaringMemberAndInfo = result.TypeDeclaringMemberAndInfo;
// Note: Don't respect the debugger display name on the root element:
// 1) In the Watch window, that's where the user's text goes.
// 2) In the Locals window, that's where the local name goes.
// Note: Dev12 respects the debugger display name in the Locals window,
// but not in the Watch window, but we can't distinguish and this
// behavior seems reasonable.
if (displayName != null && useDebuggerDisplay)
{
name = displayName;
}
else if (typeDeclaringMemberAndInfo.Type != null)
{
name = GetQualifiedMemberName(inspectionContext, typeDeclaringMemberAndInfo, name, FullNameProvider);
}
var value = result.Value;
string display;
if (value.HasExceptionThrown())
{
display = result.DisplayValue ?? value.GetExceptionMessage(inspectionContext, result.FullNameWithoutFormatSpecifiers ?? result.Name);
}
else if (displayValue != null)
{
display = value.IncludeObjectId(displayValue);
}
else
{
display = value.GetValueString(inspectionContext, Formatter.NoFormatSpecifiers);
}
var typeName = displayType ?? GetTypeName(inspectionContext, value, declaredType, declaredTypeInfo, result.Kind == ExpansionKind.PointerDereference);
return CreateEvaluationResult(inspectionContext, value, name, typeName, display, result);
}
private void GetEvaluationResultsAndContinue(
DkmEvaluationResult parent,
ArrayBuilder<EvalResult> rows,
DkmEvaluationResult[] results,
int index,
int numRows,
WorkList workList,
DkmInspectionContext inspectionContext,
CompletionRoutine onCompleted,
CompletionRoutine<Exception> onException)
{
void completionRoutine(DkmEvaluationAsyncResult result)
{
try
{
results[index] = result.Result;
GetEvaluationResultsAndContinue(parent, rows, results, index + 1, numRows, workList, inspectionContext, onCompleted, onException);
}
catch (Exception e)
{
onException(e);
}
}
if (index < numRows)
{
GetChild(
parent,
workList,
rows[index],
child => workList.ContinueWith(() => completionRoutine(child)));
}
else
{
onCompleted();
}
}
internal Expansion GetTypeExpansion(
DkmInspectionContext inspectionContext,
TypeAndCustomInfo declaredTypeAndInfo,
DkmClrValue value,
ExpansionFlags flags,
bool supportsFavorites)
{
var declaredType = declaredTypeAndInfo.Type;
Debug.Assert(!declaredType.IsTypeVariables());
if ((inspectionContext.EvaluationFlags & DkmEvaluationFlags.NoExpansion) != 0)
{
return null;
}
var runtimeType = value.Type.GetLmrType();
// If the value is an array, expand the array elements.
if (runtimeType.IsArray)
{
var sizes = value.ArrayDimensions;
if (sizes == null)
{
// Null array. No expansion.
return null;
}
var lowerBounds = value.ArrayLowerBounds;
Type elementType;
DkmClrCustomTypeInfo elementTypeInfo;
if (declaredType.IsArray)
{
elementType = declaredType.GetElementType();
elementTypeInfo = CustomTypeInfo.SkipOne(declaredTypeAndInfo.Info);
}
else
{
elementType = runtimeType.GetElementType();
elementTypeInfo = null;
}
return ArrayExpansion.CreateExpansion(new TypeAndCustomInfo(DkmClrType.Create(declaredTypeAndInfo.ClrType.AppDomain, elementType), elementTypeInfo), sizes, lowerBounds);
}
if (this.IsPrimitiveType(runtimeType))
{
return null;
}
if (declaredType.IsFunctionPointer())
{
// Function pointers have no expansion
return null;
}
if (declaredType.IsPointer)
{
// If this assert fails, the element type info is just .SkipOne().
Debug.Assert(declaredTypeAndInfo.Info?.PayloadTypeId != CustomTypeInfo.PayloadTypeId);
var elementType = declaredType.GetElementType();
return value.IsNull || elementType.IsVoid()
? null
: new PointerDereferenceExpansion(new TypeAndCustomInfo(DkmClrType.Create(declaredTypeAndInfo.ClrType.AppDomain, elementType)));
}
if (value.EvalFlags.Includes(DkmEvaluationResultFlags.ExceptionThrown) &&
runtimeType.IsEmptyResultsViewException())
{
// The value is an exception thrown expanding an empty
// IEnumerable. Use the runtime type of the exception and
// skip base types. (This matches the native EE behavior
// to expose a single property from the exception.)
flags &= ~ExpansionFlags.IncludeBaseMembers;
}
int cardinality;
if (runtimeType.IsTupleCompatible(out cardinality))
{
return TupleExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, cardinality);
}
return MemberExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, flags, TypeHelpers.IsVisibleMember, this, isProxyType: false, supportsFavorites);
}
private static DkmEvaluationResult CreateEvaluationResultFromException(Exception e, EvalResult result, DkmInspectionContext inspectionContext)
{
return DkmFailedEvaluationResult.Create(
inspectionContext,
result.Value.StackFrame,
Name: result.Name,
FullName: null,
ErrorMessage: e.Message,
Flags: DkmEvaluationResultFlags.None,
Type: null,
DataItem: null);
}
private static string GetQualifiedMemberName(
DkmInspectionContext inspectionContext,
TypeAndCustomInfo typeDeclaringMember,
string memberName,
IDkmClrFullNameProvider fullNameProvider)
{
var typeName = fullNameProvider.GetClrTypeName(inspectionContext, typeDeclaringMember.ClrType, typeDeclaringMember.Info) ??
inspectionContext.GetTypeName(typeDeclaringMember.ClrType, typeDeclaringMember.Info, Formatter.NoFormatSpecifiers);
return typeDeclaringMember.Type.IsInterface
? $"{typeName}.{memberName}"
: $"{memberName} ({typeName})";
}
// Track remaining evaluations so that each subsequent evaluation
// is executed at the entry point from the host rather than on the
// callstack of the previous evaluation.
private sealed class WorkList
{
private enum State { Initialized, Executing, Executed }
internal readonly DkmWorkList InnerWorkList;
private readonly CompletionRoutine<Exception> _onException;
private CompletionRoutine _completionRoutine;
private State _state;
internal WorkList(DkmWorkList workList, CompletionRoutine<Exception> onException)
{
InnerWorkList = workList;
_onException = onException;
_state = State.Initialized;
}
/// <summary>
/// Run the continuation synchronously if there is no current
/// continuation. Otherwise hold on to the continuation for
/// the current execution to complete.
/// </summary>
internal void ContinueWith(CompletionRoutine completionRoutine)
{
Debug.Assert(_completionRoutine == null);
_completionRoutine = completionRoutine;
if (_state != State.Executing)
{
Execute();
}
}
private void Execute()
{
Debug.Assert(_state != State.Executing);
_state = State.Executing;
while (_completionRoutine != null)
{
var completionRoutine = _completionRoutine;
_completionRoutine = null;
try
{
completionRoutine();
}
catch (Exception e)
{
_onException(e);
}
}
_state = State.Executed;
}
}
private class NullableMemberInfo : DkmDataItem
{
public readonly DkmEvaluationResultCategory Category;
public readonly DkmEvaluationResultAccessType Access;
public readonly DkmEvaluationResultStorageType StorageType;
public readonly DkmEvaluationResultTypeModifierFlags TypeModifierFlags;
public NullableMemberInfo(DkmEvaluationResultCategory category, DkmEvaluationResultAccessType access, DkmEvaluationResultStorageType storageType, DkmEvaluationResultTypeModifierFlags typeModifierFlags)
{
Category = category;
Access = access;
StorageType = storageType;
TypeModifierFlags = typeModifierFlags;
}
}
}
}
|