Skip to content

Commit

Permalink
Merge pull request #3114 from ElektroKill/pdb-extra-local-type-info
Browse files Browse the repository at this point in the history
Read and use tuple element names and dynamic type information from PDBs
  • Loading branch information
siegfriedpammer authored Dec 24, 2023
2 parents d2bf239 + eefb466 commit 760e7e0
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 3 deletions.
7 changes: 7 additions & 0 deletions ICSharpCode.Decompiler/DebugInfo/IDebugInfoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ public Variable(int index, string name)
public string Name { get; }
}

public struct PdbExtraTypeInfo
{
public string[] TupleElementNames;
public bool[] DynamicFlags;
}

public interface IDebugInfoProvider
{
string Description { get; }
IList<SequencePoint> GetSequencePoints(MethodDefinitionHandle method);
IList<Variable> GetVariables(MethodDefinitionHandle method);
bool TryGetName(MethodDefinitionHandle method, int index, out string name);
bool TryGetExtraTypeInfo(MethodDefinitionHandle method, int index, out PdbExtraTypeInfo extraTypeInfo);
string SourceFileName { get; }
}
}
1 change: 1 addition & 0 deletions ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\ApplyPdbLocalTypeInfoTypeVisitor.cs" />
<Compile Include="NRTAttributes.cs" />
<Compile Include="PartialTypeInfo.cs" />
<Compile Include="CSharp\ProjectDecompiler\IProjectFileWriter.cs" />
Expand Down
159 changes: 159 additions & 0 deletions ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System;
using System.Collections.Immutable;

using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;

namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Heavily based on <see cref="ApplyAttributeTypeVisitor"/>
/// </summary>
sealed class ApplyPdbLocalTypeInfoTypeVisitor : TypeVisitor
{
private readonly bool[] dynamicData;
private readonly string[] tupleElementNames;
private int dynamicTypeIndex = 0;
private int tupleTypeIndex = 0;

private ApplyPdbLocalTypeInfoTypeVisitor(bool[] dynamicData, string[] tupleElementNames)
{
this.dynamicData = dynamicData;
this.tupleElementNames = tupleElementNames;
}

public static IType Apply(IType type, PdbExtraTypeInfo pdbExtraTypeInfo)
{
if (pdbExtraTypeInfo.DynamicFlags is null && pdbExtraTypeInfo.TupleElementNames is null)
return type;
return type.AcceptVisitor(new ApplyPdbLocalTypeInfoTypeVisitor(pdbExtraTypeInfo.DynamicFlags, pdbExtraTypeInfo.TupleElementNames));
}

public override IType VisitModOpt(ModifiedType type)
{
dynamicTypeIndex++;
return base.VisitModOpt(type);
}

public override IType VisitModReq(ModifiedType type)
{
dynamicTypeIndex++;
return base.VisitModReq(type);
}

public override IType VisitPointerType(PointerType type)
{
dynamicTypeIndex++;
return base.VisitPointerType(type);
}

public override IType VisitArrayType(ArrayType type)
{
dynamicTypeIndex++;
return base.VisitArrayType(type);
}

public override IType VisitByReferenceType(ByReferenceType type)
{
dynamicTypeIndex++;
return base.VisitByReferenceType(type);
}

public override IType VisitTupleType(TupleType type)
{
if (tupleElementNames != null && tupleTypeIndex < tupleElementNames.Length)
{
int tupleCardinality = type.Cardinality;
string[] extractedValues = new string[tupleCardinality];
Array.Copy(tupleElementNames, tupleTypeIndex, extractedValues, 0,
Math.Min(tupleCardinality, tupleElementNames.Length - tupleTypeIndex));
var elementNames = ImmutableArray.CreateRange(extractedValues);
tupleTypeIndex += tupleCardinality;

int level = 0;
var elementTypes = new IType[type.ElementTypes.Length];
for (int i = 0; i < type.ElementTypes.Length; i++)
{
dynamicTypeIndex++;
IType elementType = type.ElementTypes[i];
if (i != 0 && (i - level) % TupleType.RestPosition == 0 && elementType is TupleType tuple)
{
tupleTypeIndex += tuple.Cardinality;
level++;
}
elementTypes[i] = elementType.AcceptVisitor(this);
}

return new TupleType(
type.Compilation,
elementTypes.ToImmutableArray(),
elementNames,
type.GetDefinition()?.ParentModule
);
}
return base.VisitTupleType(type);
}

public override IType VisitParameterizedType(ParameterizedType type)
{
if (TupleType.IsTupleCompatible(type, out var tupleCardinality))
tupleTypeIndex += tupleCardinality;
// Visit generic type and type arguments.
// Like base implementation, except that it increments dynamicTypeIndex.
var genericType = type.GenericType.AcceptVisitor(this);
bool changed = type.GenericType != genericType;
var arguments = new IType[type.TypeArguments.Count];
for (int i = 0; i < type.TypeArguments.Count; i++)
{
dynamicTypeIndex++;
arguments[i] = type.TypeArguments[i].AcceptVisitor(this);
changed = changed || arguments[i] != type.TypeArguments[i];
}
if (!changed)
return type;
return new ParameterizedType(genericType, arguments);
}

public override IType VisitFunctionPointerType(FunctionPointerType type)
{
dynamicTypeIndex++;
if (type.ReturnIsRefReadOnly)
{
dynamicTypeIndex++;
}
var returnType = type.ReturnType.AcceptVisitor(this);
bool changed = type.ReturnType != returnType;
var parameters = new IType[type.ParameterTypes.Length];
for (int i = 0; i < parameters.Length; i++)
{
dynamicTypeIndex += type.ParameterReferenceKinds[i] switch {
ReferenceKind.None => 1,
ReferenceKind.Ref => 1,
ReferenceKind.Out => 2, // in/out also count the modreq
ReferenceKind.In => 2,
_ => throw new NotSupportedException()
};
parameters[i] = type.ParameterTypes[i].AcceptVisitor(this);
changed = changed || parameters[i] != type.ParameterTypes[i];
}
if (!changed)
return type;
return type.WithSignature(returnType, parameters.ToImmutableArray());
}

public override IType VisitTypeDefinition(ITypeDefinition type)
{
IType newType = type;
var ktc = type.KnownTypeCode;
if (ktc == KnownTypeCode.Object && dynamicData is not null)
{
if (dynamicTypeIndex >= dynamicData.Length)
newType = SpecialType.Dynamic;
else if (dynamicData[dynamicTypeIndex])
newType = SpecialType.Dynamic;
}
return newType;
}
}
}
9 changes: 8 additions & 1 deletion ICSharpCode.Decompiler/IL/ILReader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2014 Daniel Grunwald
// Copyright (c) 2014 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
Expand Down Expand Up @@ -304,6 +304,13 @@ ILVariable CreateILVariable(int index, IType type)
{
kind = VariableKind.Local;
}

if (UseDebugSymbols && DebugInfo is not null &&
DebugInfo.TryGetExtraTypeInfo((MethodDefinitionHandle)method.MetadataToken, index, out var pdbExtraTypeInfo))
{
type = ApplyPdbLocalTypeInfoTypeVisitor.Apply(type, pdbExtraTypeInfo);
}

ILVariable ilVar = new ILVariable(kind, type, index);
if (!UseDebugSymbols || DebugInfo == null || !DebugInfo.TryGetName((MethodDefinitionHandle)method.MetadataToken, index, out string name))
{
Expand Down
10 changes: 9 additions & 1 deletion ICSharpCode.ILSpyX/PdbProvider/MonoCecilDebugInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018 Siegfried Pammer
// Copyright (c) 2018 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
Expand Down Expand Up @@ -135,5 +135,13 @@ public bool TryGetName(SRM.MethodDefinitionHandle handle, int index, [NotNullWhe
name = variable.Name;
return name != null;
}

public bool TryGetExtraTypeInfo(SRM.MethodDefinitionHandle method, int index, out PdbExtraTypeInfo extraTypeInfo)
{
// Mono.Cecil's WindowsPDB reader is unable to read tuple element names
// and dynamic flags custom debug information.
extraTypeInfo = default;
return false;
}
}
}
74 changes: 73 additions & 1 deletion ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018 Siegfried Pammer
// Copyright (c) 2018 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
Expand Down Expand Up @@ -161,5 +161,77 @@ public bool TryGetName(MethodDefinitionHandle method, int index, [NotNullWhen(tr
}
return false;
}

public bool TryGetExtraTypeInfo(MethodDefinitionHandle method, int index, out PdbExtraTypeInfo extraTypeInfo)
{
var metadata = GetMetadataReader();
extraTypeInfo = default;

if (metadata == null)
return false;

LocalVariableHandle localVariableHandle = default;
foreach (var h in metadata.GetLocalScopes(method))
{
var scope = metadata.GetLocalScope(h);
foreach (var v in scope.GetLocalVariables())
{
var var = metadata.GetLocalVariable(v);
if (var.Index == index)
{
localVariableHandle = v;
break;
}
}

if (!localVariableHandle.IsNil)
break;
}

foreach (var h in metadata.CustomDebugInformation)
{
var cdi = metadata.GetCustomDebugInformation(h);
if (cdi.Parent.IsNil || cdi.Parent.Kind != HandleKind.LocalVariable)
continue;
if (localVariableHandle != (LocalVariableHandle)cdi.Parent)
continue;
if (cdi.Value.IsNil || cdi.Kind.IsNil)
continue;
var kind = metadata.GetGuid(cdi.Kind);
if (kind == KnownGuids.TupleElementNames && extraTypeInfo.TupleElementNames is null)
{
var reader = metadata.GetBlobReader(cdi.Value);
var list = new List<string?>();
while (reader.RemainingBytes > 0)
{
// Read a UTF8 null-terminated string
int length = reader.IndexOf(0);
string s = reader.ReadUTF8(length);
// Skip null terminator
reader.ReadByte();
list.Add(string.IsNullOrWhiteSpace(s) ? null : s);
}

extraTypeInfo.TupleElementNames = list.ToArray();
}
else if (kind == KnownGuids.DynamicLocalVariables && extraTypeInfo.DynamicFlags is null)
{
var reader = metadata.GetBlobReader(cdi.Value);
extraTypeInfo.DynamicFlags = new bool[reader.Length * 8];
int j = 0;
while (reader.RemainingBytes > 0)
{
int b = reader.ReadByte();
for (int i = 1; i < 0x100; i <<= 1)
extraTypeInfo.DynamicFlags[j++] = (b & i) != 0;
}
}

if (extraTypeInfo.TupleElementNames != null && extraTypeInfo.DynamicFlags != null)
break;
}

return extraTypeInfo.TupleElementNames != null || extraTypeInfo.DynamicFlags != null;
}
}
}

0 comments on commit 760e7e0

Please sign in to comment.