Browse Source

Emit debug info for display class locals, so that the debugger can show the values of captured variables.

pull/1440/head
Daniel Grunwald 6 years ago
parent
commit
763683748b
  1. 26
      ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs
  2. 30
      ICSharpCode.Decompiler/IL/ILVariable.cs
  3. 2
      ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs
  4. 4
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  5. 13
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

26
ICSharpCode.Decompiler/DebugInfo/ScopesGenerator.cs

@ -60,7 +60,8 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -60,7 +60,8 @@ namespace ICSharpCode.Decompiler.DebugInfo
var firstLocalVariable = MetadataTokens.LocalVariableHandle(nextRow);
foreach (var local in localScope.Locals.OrderBy(l => l.Index)) {
metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index.Value, metadata.GetOrAddString(local.Name));
var name = local.Name != null ? metadata.GetOrAddString(local.Name) : default;
metadata.AddLocalVariable(LocalVariableAttributes.None, local.Index.Value, name);
}
metadata.AddLocalScope(localScope.Method, localScope.Import.Handle, firstLocalVariable,
@ -68,14 +69,6 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -68,14 +69,6 @@ namespace ICSharpCode.Decompiler.DebugInfo
}
}
private static bool IsSupported(VariableKind kind)
{
return kind == VariableKind.Local
|| kind == VariableKind.PinnedLocal
|| kind == VariableKind.UsingLocal
|| kind == VariableKind.ForeachLocal;
}
static BlobHandle EncodeImports(MetadataBuilder metadata, ImportScopeInfo scope)
{
var writer = new BlobBuilder();
@ -192,18 +185,9 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -192,18 +185,9 @@ namespace ICSharpCode.Decompiler.DebugInfo
new TypeSystem.GenericContext(function.Method));
foreach (var v in function.Variables) {
if (v.Index == null)
continue;
switch (v.Kind) {
case VariableKind.Local:
case VariableKind.PinnedLocal:
case VariableKind.UsingLocal:
case VariableKind.ForeachLocal:
Debug.Assert(v.Index < types.Length && v.Type.Equals(types[v.Index.Value]));
localVariables.Add(v);
break;
default:
continue;
if (v.Index != null && v.Kind.IsLocal()) {
Debug.Assert(v.Index < types.Length && v.Type.Equals(types[v.Index.Value]));
localVariables.Add(v);
}
}
}

30
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -66,6 +66,28 @@ namespace ICSharpCode.Decompiler.IL @@ -66,6 +66,28 @@ namespace ICSharpCode.Decompiler.IL
/// Variable in BlockKind.CallWithNamedArgs
/// </summary>
NamedArgument,
/// <summary>
/// Local variable that holds the display class used for lambdas within this function.
/// </summary>
DisplayClassLocal,
}
static class VariableKindExtensions
{
public static bool IsLocal(this VariableKind kind)
{
switch (kind) {
case VariableKind.Local:
case VariableKind.ExceptionLocal:
case VariableKind.ForeachLocal:
case VariableKind.UsingLocal:
case VariableKind.PinnedLocal:
case VariableKind.DisplayClassLocal:
return true;
default:
return false;
}
}
}
[DebuggerDisplay("{Name} : {Type}")]
@ -80,6 +102,8 @@ namespace ICSharpCode.Decompiler.IL @@ -80,6 +102,8 @@ namespace ICSharpCode.Decompiler.IL
internal set {
if (kind == VariableKind.Parameter)
throw new InvalidOperationException("Kind=Parameter cannot be changed!");
if (Index != null && value.IsLocal())
Debug.Assert(kind.IsLocal());
kind = value;
}
}
@ -120,6 +144,7 @@ namespace ICSharpCode.Decompiler.IL @@ -120,6 +144,7 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.PinnedLocal:
case VariableKind.UsingLocal:
case VariableKind.ExceptionLocal:
case VariableKind.DisplayClassLocal:
// in range of LocalVariableSignature
Debug.Assert(Index == null || Index >= 0);
break;
@ -302,7 +327,7 @@ namespace ICSharpCode.Decompiler.IL @@ -302,7 +327,7 @@ namespace ICSharpCode.Decompiler.IL
/// Set when the variable is from a 'yield return' or 'async' state machine.
/// </summary>
public IField StateMachineField;
public ILVariable(VariableKind kind, IType type, int? index = null)
{
if (type == null)
@ -367,6 +392,9 @@ namespace ICSharpCode.Decompiler.IL @@ -367,6 +392,9 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.NamedArgument:
output.Write("named_arg ");
break;
case VariableKind.DisplayClassLocal:
output.Write("display_class local ");
break;
default:
throw new ArgumentOutOfRangeException();
}

2
ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs

@ -103,7 +103,7 @@ namespace ICSharpCode.Decompiler.IL @@ -103,7 +103,7 @@ namespace ICSharpCode.Decompiler.IL
for (int i = 0; i < list.Count;) {
var v = list[i];
int deadStoreCount = v.HasInitialValue ? 1 : 0;
if (v.StoreCount == deadStoreCount && v.LoadCount == 0 && v.AddressCount == 0) {
if (v.StoreCount == deadStoreCount && v.LoadCount == 0 && v.AddressCount == 0 && v.Kind != VariableKind.DisplayClassLocal) {
RemoveAt(i);
} else {
i++;

4
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -135,6 +135,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -135,6 +135,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
// remove unused variables before assigning names
function.Variables.RemoveDead();
int numDisplayClassLocals = 0;
foreach (var v in function.Variables) {
switch (v.Kind) {
case VariableKind.Parameter: // ignore
@ -142,6 +143,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -142,6 +143,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case VariableKind.InitializerTarget: // keep generated names
AddExistingName(reservedVariableNames, v.Name);
break;
case VariableKind.DisplayClassLocal:
v.Name = "CS$<>8__locals" + (numDisplayClassLocals++);
break;
default:
if (v.HasGeneratedName || !IsValidName(v.Name) || ConflictWithLocal(v)) {
// don't use the name from the debug symbols if it looks like a generated name

13
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -46,10 +46,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -46,10 +46,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (inst is NewObj call) {
context.StepStartGroup($"TransformDelegateConstruction {call.ILRange}", call);
ILFunction f = TransformDelegateConstruction(call, out ILInstruction target);
if (f != null) {
call.ReplaceWith(f);
if (target is IInstructionWithVariableOperand)
targetsToReplace.Add((IInstructionWithVariableOperand)target);
if (f != null && target is IInstructionWithVariableOperand instWithVar) {
if (instWithVar.Variable.Kind == VariableKind.Local) {
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
}
targetsToReplace.Add(instWithVar);
}
context.StepEndGroup();
}
@ -186,6 +187,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -186,6 +187,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, context.CancellationToken);
function.DelegateType = value.Method.DeclaringType;
function.CheckInvariant(ILPhase.Normal);
// Embed the lambda into the parent function's ILAst, so that "Show steps" can show
// how the lambda body is being transformed.
value.ReplaceWith(function);
var contextPrefix = targetMethod.Name;
foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) {
@ -194,6 +198,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -194,6 +198,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var nestedContext = new ILTransformContext(context, function);
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext);
nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function);
function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter)));
// handle nested lambdas
nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function);

Loading…
Cancel
Save