Dnspy/dnSpy/Roslyn/dnSpy.Roslyn/Debugger/ValueNodes/LanguageValueNodeFactory.cs

274 lines
14 KiB
C#
Raw Normal View History

2021-09-20 18:20:01 +02:00
/*
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 <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.ValueNodes;
using dnSpy.Contracts.Debugger.DotNet.Text;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Contracts.Debugger.Text;
using dnSpy.Debugger.DotNet.Metadata;
namespace dnSpy.Roslyn.Debugger.ValueNodes {
abstract class LanguageValueNodeFactory : DbgDotNetValueNodeFactory {
readonly DbgDotNetValueNodeProviderFactory valueNodeProviderFactory;
protected abstract DbgDotNetValueNodeProviderFactory CreateValueNodeProviderFactory();
protected LanguageValueNodeFactory() => valueNodeProviderFactory = CreateValueNodeProviderFactory();
public abstract string GetFieldExpression(string baseExpression, string name, DmdType? castType, bool addParens);
public abstract string GetPropertyExpression(string baseExpression, string name, DmdType? castType, bool addParens);
public abstract string GetExpression(string baseExpression, int index, DmdType? castType, bool addParens);
public abstract string GetExpression(string baseExpression, int[] indexes, DmdType? castType, bool addParens);
protected abstract string EscapeIdentifier(string identifier);
protected abstract bool SupportsModuleTypes { get; }
public DbgDotNetText GetTypeParameterName(DmdType typeParameter) {
bool isMethodParam = typeParameter.DeclaringMethod is not null;
var name = typeParameter.MetadataName ?? string.Empty;
// Added by vbc
const string StateMachineTypeParameterPrefix = "SM$";
if (name.StartsWith(StateMachineTypeParameterPrefix))
name = name.Substring(StateMachineTypeParameterPrefix.Length);
return new DbgDotNetText(new DbgDotNetTextPart(isMethodParam ? DbgTextColor.MethodGenericParameter : DbgTextColor.TypeGenericParameter, EscapeIdentifier(name)));
}
internal DbgDotNetValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValueNodeProvider provider, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, DbgDotNetText valueText) =>
new DbgDotNetValueNodeImpl(this, provider, name, null, expression, imageName, true, false, null, null, null, valueText, formatSpecifiers, null);
internal DbgDotNetValueNode Create(DbgDotNetValueNodeProvider provider, DbgDotNetText name, DbgDotNetValueNodeInfo nodeInfo, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType, DmdType actualType, string? errorMessage, DbgDotNetText valueText, ReadOnlyCollection<string>? formatSpecifiers) =>
new DbgDotNetValueNodeImpl(this, provider, name, nodeInfo, expression, imageName, isReadOnly, causesSideEffects, expectedType, actualType, errorMessage, valueText, formatSpecifiers, null);
DbgDotNetValueNode CreateValue(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType, bool isRootExpression, ColumnFormatter? columnFormatter) {
// Could be a by-ref property, local, or parameter.
if (expectedType.IsByRef) {
if (value.Type.IsByRef) {
var newValue = value.LoadIndirect();
value.Dispose();
if (newValue.HasError)
return CreateError(evalInfo, name, newValue.ErrorMessage!, expression, causesSideEffects);
value = newValue.Value!;
}
expectedType = expectedType.GetElementType()!;
}
options = PredefinedFormatSpecifiers.GetValueNodeEvaluationOptions(formatSpecifiers, options);
var nodeInfo = new DbgDotNetValueNodeInfo(value, expression);
bool addParens = isRootExpression && NeedsParentheses(expression);
DbgDotNetValueNodeProviderResult info;
bool useProvider = false;
var specialViewOptions = (options & ~(DbgValueNodeEvaluationOptions.ResultsView | DbgValueNodeEvaluationOptions.DynamicView));
if ((options & DbgValueNodeEvaluationOptions.ResultsView) != 0) {
info = valueNodeProviderFactory.CreateResultsView(evalInfo, addParens, expectedType, nodeInfo, specialViewOptions);
useProvider = info.ErrorMessage is not null;
}
else if ((options & DbgValueNodeEvaluationOptions.DynamicView) != 0) {
info = valueNodeProviderFactory.CreateDynamicView(evalInfo, addParens, expectedType, nodeInfo, specialViewOptions);
useProvider = true;
}
else
info = valueNodeProviderFactory.Create(evalInfo, addParens, expectedType, nodeInfo, options);
if (useProvider) {
if (info.ErrorMessage is not null)
return new DbgDotNetValueNodeImpl(this, info.Provider, name, nodeInfo, expression, PredefinedDbgValueNodeImageNames.Error, true, false, null, null, info.ErrorMessage, new DbgDotNetText(new DbgDotNetTextPart(DbgTextColor.Error, info.ErrorMessage)), formatSpecifiers, columnFormatter);
Debug2.Assert(info.Provider is not null);
return new DbgDotNetValueNodeImpl(this, info.Provider, name, nodeInfo, expression, info.Provider?.ImageName ?? imageName, true, false, null, null, info.ErrorMessage, info.Provider?.ValueText ?? default, formatSpecifiers, columnFormatter);
}
return new DbgDotNetValueNodeImpl(this, info.Provider, name, nodeInfo, expression, imageName, isReadOnly, causesSideEffects, expectedType, value.Type, info.ErrorMessage, default, formatSpecifiers, columnFormatter);
}
public sealed override DbgDotNetValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType) =>
Create(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, expectedType, true);
internal DbgDotNetValueNode Create(DbgEvaluationInfo evalInfo, DbgDotNetText name, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, string expression, string imageName, bool isReadOnly, bool causesSideEffects, DmdType expectedType, bool isRootExpression) =>
CreateValue(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, expectedType, isRootExpression, null);
public sealed override DbgDotNetValueNode CreateException(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options) {
var output = ObjectCache.AllocDotNetTextOutput();
evalInfo.Context.Language.Formatter.FormatExceptionName(evalInfo.Context, output, id);
var name = ObjectCache.FreeAndToText(ref output);
var expression = name.ToString();
const bool isReadOnly = true;
const bool causesSideEffects = false;
const string imageName = PredefinedDbgValueNodeImageNames.Exception;
return CreateValue(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, value.Type, false, null);
}
public sealed override DbgDotNetValueNode CreateStowedException(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options) {
var output = ObjectCache.AllocDotNetTextOutput();
evalInfo.Context.Language.Formatter.FormatStowedExceptionName(evalInfo.Context, output, id);
var name = ObjectCache.FreeAndToText(ref output);
var expression = name.ToString();
const bool isReadOnly = true;
const bool causesSideEffects = false;
const string imageName = PredefinedDbgValueNodeImageNames.StowedException;
return CreateValue(evalInfo, name, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, value.Type, false, null);
}
public sealed override DbgDotNetValueNode CreateReturnValue(DbgEvaluationInfo evalInfo, uint id, DbgDotNetValue value, ReadOnlyCollection<string>? formatSpecifiers, DbgValueNodeEvaluationOptions options, DmdMethodBase method) {
var columnFormatter = new ReturnValueColumnFormatter(this, method);
var output = ObjectCache.AllocDotNetTextOutput();
evalInfo.Context.Language.Formatter.FormatReturnValueName(evalInfo.Context, output, id);
var expression = ObjectCache.FreeAndToText(ref output).ToString();
const bool isReadOnly = true;
const bool causesSideEffects = false;
var property = PropertyState.TryGetProperty(method);
var imageName = property is not null ? ImageNameUtils.GetImageName(property) : ImageNameUtils.GetImageName(method, SupportsModuleTypes);
return CreateValue(evalInfo, default, value, formatSpecifiers, options, expression, imageName, isReadOnly, causesSideEffects, value.Type, false, columnFormatter);
}
internal void FormatReturnValueMethodName(DbgEvaluationInfo evalInfo, IDbgTextWriter output, DbgValueFormatterOptions options, CultureInfo? cultureInfo, DmdMethodBase method) =>
FormatReturnValueMethodName(evalInfo, output, ToDbgValueFormatterTypeOptions(options), options, cultureInfo, method, PropertyState.TryGetProperty(method));
static DbgValueFormatterTypeOptions ToDbgValueFormatterTypeOptions(DbgValueFormatterOptions options) {
var res = DbgValueFormatterTypeOptions.None;
if ((options & DbgValueFormatterOptions.Decimal) != 0)
res |= DbgValueFormatterTypeOptions.Decimal;
if ((options & DbgValueFormatterOptions.DigitSeparators) != 0)
res |= DbgValueFormatterTypeOptions.DigitSeparators;
if ((options & DbgValueFormatterOptions.Namespaces) != 0)
res |= DbgValueFormatterTypeOptions.Namespaces;
if ((options & DbgValueFormatterOptions.IntrinsicTypeKeywords) != 0)
res |= DbgValueFormatterTypeOptions.IntrinsicTypeKeywords;
if ((options & DbgValueFormatterOptions.Tokens) != 0)
res |= DbgValueFormatterTypeOptions.Tokens;
return res;
}
protected abstract void FormatReturnValueMethodName(DbgEvaluationInfo evalInfo, IDbgTextWriter output, DbgValueFormatterTypeOptions typeOptions, DbgValueFormatterOptions valueOptions, CultureInfo? cultureInfo, DmdMethodBase method, DmdPropertyInfo? property);
public sealed override DbgDotNetValueNode CreateError(DbgEvaluationInfo evalInfo, DbgDotNetText name, string errorMessage, string expression, bool causesSideEffects) =>
new DbgDotNetValueNodeImpl(this, null, name, null, expression, PredefinedDbgValueNodeImageNames.Error, true, causesSideEffects, null, null, errorMessage, default, null, null);
public sealed override DbgDotNetValueNode CreateTypeVariables(DbgEvaluationInfo evalInfo, DbgDotNetTypeVariableInfo[] typeVariableInfos) =>
new DbgDotNetTypeVariablesNode(this, typeVariableInfos);
sealed class PropertyState {
readonly Dictionary<DmdMethodBase, DmdPropertyInfo> toProperty;
PropertyState(DmdType type) {
toProperty = new Dictionary<DmdMethodBase, DmdPropertyInfo>(DmdMemberInfoEqualityComparer.DefaultMember);
foreach (var property in type.DeclaredProperties) {
foreach (var method in property.GetAccessors(DmdGetAccessorOptions.All))
toProperty[method] = property;
}
}
public static DmdPropertyInfo? TryGetProperty(DmdMethodBase method) {
var m = method as DmdMethodInfo;
if (m is null)
return null;
var state = GetState(m.DeclaringType!);
if (state.toProperty.TryGetValue(method, out var property))
return property;
return null;
}
static PropertyState GetState(DmdType type) {
if (type.TryGetData(out PropertyState? state))
return state;
return CreateState(type);
PropertyState CreateState(DmdType type2) {
var state2 = new PropertyState(type2);
return type2.GetOrCreateData(() => state2);
}
}
}
protected void AddParens(StringBuilder sb, string expression, bool forceAddParens) {
if (forceAddParens) {
sb.Append('(');
sb.Append(expression);
sb.Append(')');
}
else
sb.Append(expression);
}
// Roslyn: Microsoft.CodeAnalysis.ExpressionEvaluator.Formatter
bool NeedsParentheses(string expr) {
int parens = 0;
for (int i = 0; i < expr.Length; i++) {
var ch = expr[i];
switch (ch) {
case '(':
// Cast, "(A)b", requires parentheses.
if ((parens == 0) && FollowsCloseParen(expr, i))
return true;
parens++;
break;
case '[':
parens++;
break;
case ')':
case ']':
parens--;
break;
case '.':
break;
default:
if (parens == 0) {
if (IsIdentifierPartCharacter(ch)) {
// Cast, "(A)b", requires parentheses.
if (FollowsCloseParen(expr, i))
return true;
}
else
return true;
}
break;
}
}
return false;
static bool FollowsCloseParen(string expr2, int index2) => (index2 > 0) && (expr2[index2 - 1] == ')');
}
protected abstract bool IsIdentifierPartCharacter(char c);
internal static string RemoveFormatSpecifiers(string expression) {
int i = expression.Length - 1;
int lastComma = -1;
for (;;) {
while ((uint)i < (uint)expression.Length && char.IsWhiteSpace(expression[i]))
i--;
while ((uint)i < (uint)expression.Length && IsFormatSpecifierChar(expression[i]))
i--;
while ((uint)i < (uint)expression.Length && char.IsWhiteSpace(expression[i]))
i--;
if ((uint)i >= (uint)expression.Length || expression[i] != ',')
break;
lastComma = i;
i--;
}
if (lastComma >= 0)
return expression.Substring(0, lastComma);
return expression;
}
static bool IsFormatSpecifierChar(char c) => ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || char.IsDigit(c);
}
}