/*
Copyright (C) 2014-2019 de4dot@gmail.com
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 .
*/
using System;
using System.Diagnostics;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Debugger.DotNet.Metadata;
namespace dnSpy.Debugger.DotNet.Steppers.Engine {
static class TaskEvalUtils {
///
/// Gets the builder instance. It's assumed to be stored in a field in the current 'this' instance.
///
/// The decompiler should already know the field. If that info isn't available, we'll try to find
/// the field by name, and if that fails, by field type.
///
/// null is returned if we couldn't find the field or if we failed to read the field.
///
/// Evaluation info
/// Module of builder field or null if unknown
/// Token of builder field or 0 if unknown
///
public static DbgDotNetValue? TryGetBuilder(DbgEvaluationInfo evalInfo, DmdModule? builderFieldModule, uint builderFieldToken) {
DbgDotNetValueResult thisArg = default;
DbgDotNetValueResult tmpResult = default;
try {
var runtime = evalInfo.Runtime.GetDotNetRuntime();
thisArg = runtime.GetParameterValue(evalInfo, 0);
if (!thisArg.IsNormalResult || thisArg.Value!.IsNull)
return null;
if (thisArg.Value.Type.IsByRef) {
tmpResult = thisArg.Value.LoadIndirect();
if (!tmpResult.IsNormalResult || tmpResult.Value!.IsNull)
return null;
thisArg.Value?.Dispose();
thisArg = tmpResult;
tmpResult = default;
}
DmdFieldInfo? builderField = null;
if (builderFieldModule is not null && builderFieldToken != 0)
builderField = thisArg.Value.Type.GetField(builderFieldModule, (int)builderFieldToken);
if (builderField is null)
builderField = TryGetBuilderField(thisArg.Value.Type);
if (builderField is null)
return null;
Debug.Assert((object)builderField == TryGetBuilderFieldByType(thisArg.Value.Type));
Debug2.Assert(TryGetBuilderFieldByname(thisArg.Value.Type) is null ||
(object?)TryGetBuilderFieldByname(thisArg.Value.Type) == TryGetBuilderFieldByType(thisArg.Value.Type));
tmpResult = runtime.LoadField(evalInfo, thisArg.Value, builderField);
if (!tmpResult.IsNormalResult || tmpResult.Value!.IsNull)
return null;
var fieldValue = tmpResult.Value;
tmpResult = default;
return fieldValue;
}
finally {
thisArg.Value?.Dispose();
tmpResult.Value?.Dispose();
}
}
static DmdFieldInfo? TryGetBuilderField(DmdType type) =>
TryGetBuilderFieldByname(type) ?? TryGetBuilderFieldByType(type);
static DmdFieldInfo? TryGetBuilderFieldByname(DmdType type) {
foreach (var name in KnownMemberNames.builderFieldNames) {
const DmdBindingFlags flags = DmdBindingFlags.Instance | DmdBindingFlags.Public | DmdBindingFlags.NonPublic;
if (type.GetField(name, flags) is DmdFieldInfo field)
return field;
}
return null;
}
static DmdFieldInfo? TryGetBuilderFieldByType(DmdType type) {
DmdFieldInfo? builderField = null;
foreach (var field in type.Fields) {
var fieldType = field.FieldType;
if (fieldType.IsNested)
continue;
if (fieldType.IsConstructedGenericType)
fieldType = fieldType.GetGenericTypeDefinition();
foreach (var info in builderWellKnownTypeNames) {
if (fieldType.MetadataNamespace == info.@namespace && fieldType.MetadataName == info.name)
return field;
}
if (builderField is null && fieldType.MetadataName is not null &&
(fieldType.MetadataName.EndsWith("MethodBuilder", StringComparison.Ordinal) ||
fieldType.MetadataName.EndsWith("MethodBuilder`1", StringComparison.Ordinal))) {
builderField = field;
}
}
return builderField;
}
static readonly (string @namespace, string name)[] builderWellKnownTypeNames = new(string, string)[] {
("System.Runtime.CompilerServices", "AsyncTaskMethodBuilder"),
("System.Runtime.CompilerServices", "AsyncTaskMethodBuilder`1"),
("System.Runtime.CompilerServices", "AsyncVoidMethodBuilder"),
("System.Runtime.CompilerServices", "AsyncValueTaskMethodBuilder"),
("System.Runtime.CompilerServices", "AsyncValueTaskMethodBuilder`1"),
};
///
/// Gets the task's object id or null on failure
///
/// Evaluation info
/// Builder value, see
///
public static DbgDotNetValue? TryGetTaskObjectId(DbgEvaluationInfo evalInfo, DbgDotNetValue builderValue) {
var result =
TryGetTaskObjectId_FrameworkBuilder(evalInfo, builderValue) ??
TryGetTaskObjectId_ObjectIdForDebugger(evalInfo, builderValue) ??
TryGetTaskObjectId_TaskProperty(evalInfo, builderValue);
Debug2.Assert(result is null || !result.IsNull);
return result;
}
static DbgDotNetValue? TryGetTaskObjectId_FrameworkBuilder(DbgEvaluationInfo evalInfo, DbgDotNetValue builderValue) {
DbgDotNetValueResult fieldResult1 = default;
DbgDotNetValueResult fieldResult2 = default;
DbgDotNetValue? resultValue = null;
try {
var runtime = evalInfo.Runtime.GetDotNetRuntime();
DmdFieldInfo? field;
var currInst = builderValue;
field = currInst.Type.GetField(KnownMemberNames.AsyncTaskMethodBuilder_Builder_FieldName, DmdBindingFlags.Instance | DmdBindingFlags.Public | DmdBindingFlags.NonPublic);
if (field is not null) {
fieldResult1 = runtime.LoadField(evalInfo, currInst, field);
if (fieldResult1.IsNormalResult)
currInst = fieldResult1.Value!;
}
field = currInst.Type.GetField(KnownMemberNames.Builder_Task_FieldName, DmdBindingFlags.Instance | DmdBindingFlags.Public | DmdBindingFlags.NonPublic);
if (field is not null) {
fieldResult2 = runtime.LoadField(evalInfo, currInst, field);
if (fieldResult2.IsNormalResult && !fieldResult2.Value!.IsNull)
return resultValue = fieldResult2.Value;
}
return null;
}
finally {
if (fieldResult1.Value != resultValue)
fieldResult1.Value?.Dispose();
if (fieldResult2.Value != resultValue)
fieldResult2.Value?.Dispose();
}
}
static DbgDotNetValue? TryGetTaskObjectId_ObjectIdForDebugger(DbgEvaluationInfo evalInfo, DbgDotNetValue builderValue) {
DbgDotNetValueResult getObjectIdTaskResult = default;
DbgDotNetValue? resultValue = null;
try {
var runtime = evalInfo.Runtime.GetDotNetRuntime();
var prop = builderValue.Type.GetProperty(KnownMemberNames.Builder_ObjectIdForDebugger_PropertyName, DmdBindingFlags.Instance | DmdBindingFlags.Public | DmdBindingFlags.NonPublic);
var getMethod = prop?.GetGetMethod(DmdGetAccessorOptions.All);
if (getMethod is not null && getMethod.GetMethodSignature().GetParameterTypes().Count == 0) {
getObjectIdTaskResult = runtime.Call(evalInfo, builderValue, getMethod, Array.Empty