diff --git a/ICSharpCode.Decompiler/DebugInfo/IDebugInfoProvider.cs b/ICSharpCode.Decompiler/DebugInfo/IDebugInfoProvider.cs index 5c148df25..bab259b26 100644 --- a/ICSharpCode.Decompiler/DebugInfo/IDebugInfoProvider.cs +++ b/ICSharpCode.Decompiler/DebugInfo/IDebugInfoProvider.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.Decompiler.DebugInfo IList GetSequencePoints(MethodDefinitionHandle method); IList GetVariables(MethodDefinitionHandle method); bool TryGetName(MethodDefinitionHandle method, int index, out string name); + bool TryGetExtraTypeInfo(MethodDefinitionHandle method, int index, out string[] tupleElementNames, out bool[] dynamicFlags); string SourceFileName { get; } } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a742391f7..0a897cfa2 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -95,6 +95,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs b/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs new file mode 100644 index 000000000..a6b629f87 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Immutable; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; + +namespace ICSharpCode.Decompiler.IL +{ + /// + /// Heavily based on + /// + sealed class ApplyPdbLocalTypeInfoTypeVisitor : TypeVisitor + { + private readonly bool[] dynamicData; + private readonly string[] tupleElementNames; + private int dynamicTypeIndex = 0; + private int tupleTypeIndex = 0; + + public ApplyPdbLocalTypeInfoTypeVisitor(bool[] dynamicData, string[] tupleElementNames) + { + this.dynamicData = dynamicData; + this.tupleElementNames = 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; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 5aa7edb74..ae85ec79c 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -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 @@ -304,6 +304,14 @@ namespace ICSharpCode.Decompiler.IL { kind = VariableKind.Local; } + + if (UseDebugSymbols && DebugInfo is not null && + DebugInfo.TryGetExtraTypeInfo((MethodDefinitionHandle)method.MetadataToken, index, + out string[] tupleElementNames, out bool[] dynamicFlags)) + { + type = type.AcceptVisitor(new ApplyPdbLocalTypeInfoTypeVisitor(dynamicFlags, tupleElementNames)); + } + ILVariable ilVar = new ILVariable(kind, type, index); if (!UseDebugSymbols || DebugInfo == null || !DebugInfo.TryGetName((MethodDefinitionHandle)method.MetadataToken, index, out string name)) { diff --git a/ICSharpCode.ILSpyX/PdbProvider/MonoCecilDebugInfoProvider.cs b/ICSharpCode.ILSpyX/PdbProvider/MonoCecilDebugInfoProvider.cs index c7e09d305..78f468a7e 100644 --- a/ICSharpCode.ILSpyX/PdbProvider/MonoCecilDebugInfoProvider.cs +++ b/ICSharpCode.ILSpyX/PdbProvider/MonoCecilDebugInfoProvider.cs @@ -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 @@ -135,5 +135,15 @@ namespace ICSharpCode.ILSpyX.PdbProvider name = variable.Name; return name != null; } + + public bool TryGetExtraTypeInfo(SRM.MethodDefinitionHandle method, int index, + [NotNullWhen(true)] out string[]? tupleElementNames, [NotNullWhen(true)] out bool[]? dynamicFlags) + { + // Mono.Cecil's WindowsPDB reader is unable to read tuple element names + // and dynamic flags custom debug information. + tupleElementNames = null; + dynamicFlags = null; + return false; + } } } diff --git a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs index 0f64997ea..06cf9967f 100644 --- a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs +++ b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs @@ -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 @@ -161,5 +161,76 @@ namespace ICSharpCode.ILSpyX.PdbProvider } return false; } + + public bool TryGetExtraTypeInfo(MethodDefinitionHandle method, int index, + [NotNullWhen(true)] out string?[]? tupleElementNames, [NotNullWhen(true)] out bool[]? dynamicFlags) + { + var metadata = GetMetadataReader(); + tupleElementNames = null; + dynamicFlags = null; + + 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 reader = metadata.GetBlobReader(cdi.Value); + var kind = metadata.GetGuid(cdi.Kind); + if (kind == KnownGuids.TupleElementNames && tupleElementNames is null) + { + var list = new List(); + while (reader.RemainingBytes > 0) + { + int length = reader.IndexOf(0); + string s = reader.ReadUTF8(length); + reader.ReadByte(); + list.Add(string.IsNullOrWhiteSpace(s) ? null : s); + } + + tupleElementNames = list.ToArray(); + } + else if (kind == KnownGuids.DynamicLocalVariables && dynamicFlags is null) + { + dynamicFlags = new bool[reader.Length * 8]; + int j = 0; + while (reader.Offset < reader.Length) + { + int b = reader.ReadByte(); + for (int i = 1; i < 0x100; i <<= 1) + dynamicFlags[j++] = (b & i) != 0; + } + } + + if (tupleElementNames != null && dynamicFlags != null) + break; + } + + return tupleElementNames != null || dynamicFlags != null; + } } }