Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read and use tuple element names and dynamic type information from PDBs #3114

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -95,6 +95,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);
siegfriedpammer marked this conversation as resolved.
Show resolved Hide resolved
}

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;
}
}
}
Loading