mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
DebugInfoGenerator asserted that every local variable's decompiled type is equivalent to the metadata local-signature type at its slot. That holds at IL-read time but not afterwards: variable splitting gives one slot several typed variables, pinned-region locals are modeled as pointers (int* vs int& pinned), and generic-context type-parameter identity differs. The assertion therefore aborted PDB generation (Debug builds) for ordinary inputs such as any method with a fixed statement. The type is never written to the PDB - only the slot index and name are - so the mismatch cannot affect the debugging experience. The slot index, the only emitted value, is correct by construction: it is the IL ldloc/stloc operand, sourced from the signature slot when the variable is created and copied verbatim by SplitVariables; it is never reassigned. Keep only the index-bound check and document why the type is not verified. Assisted-by: Claude:claude-opus-4-8:Claude Code
275 lines
9.3 KiB
C#
275 lines
9.3 KiB
C#
// 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
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection.Metadata;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
|
|
using ICSharpCode.Decompiler.CSharp;
|
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
using ICSharpCode.Decompiler.IL;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.DebugInfo
|
|
{
|
|
/// <summary>
|
|
/// Visitor that generates debug information.
|
|
///
|
|
/// The intended usage is to create a new instance for each source file,
|
|
/// and call syntaxTree.AcceptVisitor(debugInfoGenerator) to fill the internal debug info tables.
|
|
/// This can happen concurrently for multiple source files.
|
|
/// Then the main thread calls Generate() to write out the results into the PDB.
|
|
/// </summary>
|
|
class DebugInfoGenerator : DepthFirstAstVisitor
|
|
{
|
|
static readonly KeyComparer<ILVariable, int> ILVariableKeyComparer = new KeyComparer<ILVariable, int>(l => l.Index.Value, Comparer<int>.Default, EqualityComparer<int>.Default);
|
|
|
|
IDecompilerTypeSystem typeSystem;
|
|
readonly ImportScopeInfo globalImportScope = new ImportScopeInfo();
|
|
ImportScopeInfo currentImportScope;
|
|
List<ImportScopeInfo> importScopes = new List<ImportScopeInfo>();
|
|
internal List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet<ILVariable> Locals)> LocalScopes { get; } = new List<(MethodDefinitionHandle Method, ImportScopeInfo Import, int Offset, int Length, HashSet<ILVariable> Locals)>();
|
|
List<ILFunction> functions = new List<ILFunction>();
|
|
|
|
/// <summary>
|
|
/// Gets all functions with bodies that were seen by the visitor so far.
|
|
/// </summary>
|
|
public IReadOnlyList<ILFunction> Functions {
|
|
get => functions;
|
|
}
|
|
|
|
public DebugInfoGenerator(IDecompilerTypeSystem typeSystem)
|
|
{
|
|
this.typeSystem = typeSystem ?? throw new ArgumentNullException(nameof(typeSystem));
|
|
this.currentImportScope = globalImportScope;
|
|
}
|
|
|
|
public void GenerateImportScopes(MetadataBuilder metadata, ImportScopeHandle globalImportScope)
|
|
{
|
|
foreach (var scope in importScopes)
|
|
{
|
|
var blob = EncodeImports(metadata, scope);
|
|
scope.Handle = metadata.AddImportScope(scope.Parent == null ? globalImportScope : scope.Parent.Handle, blob);
|
|
}
|
|
}
|
|
|
|
static BlobHandle EncodeImports(MetadataBuilder metadata, ImportScopeInfo scope)
|
|
{
|
|
var writer = new BlobBuilder();
|
|
|
|
foreach (var import in scope.Imports)
|
|
{
|
|
writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace);
|
|
writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(metadata.GetOrAddBlobUTF8(import)));
|
|
}
|
|
|
|
return metadata.GetOrAddBlob(writer);
|
|
}
|
|
|
|
public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration)
|
|
{
|
|
var parentImportScope = currentImportScope;
|
|
currentImportScope = new ImportScopeInfo(parentImportScope);
|
|
importScopes.Add(currentImportScope);
|
|
base.VisitNamespaceDeclaration(namespaceDeclaration);
|
|
currentImportScope = parentImportScope;
|
|
}
|
|
|
|
public override void VisitUsingDeclaration(UsingDeclaration usingDeclaration)
|
|
{
|
|
currentImportScope.Imports.Add(usingDeclaration.Namespace);
|
|
}
|
|
|
|
public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration)
|
|
{
|
|
HandleMethod(methodDeclaration);
|
|
}
|
|
|
|
public override void VisitAccessor(Accessor accessor)
|
|
{
|
|
HandleMethod(accessor);
|
|
}
|
|
|
|
public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration)
|
|
{
|
|
HandleMethod(constructorDeclaration);
|
|
}
|
|
|
|
public override void VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration)
|
|
{
|
|
HandleMethod(destructorDeclaration);
|
|
}
|
|
|
|
public override void VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration)
|
|
{
|
|
HandleMethod(operatorDeclaration);
|
|
}
|
|
|
|
public override void VisitLambdaExpression(LambdaExpression lambdaExpression)
|
|
{
|
|
HandleMethod(lambdaExpression);
|
|
}
|
|
|
|
public override void VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression)
|
|
{
|
|
HandleMethod(anonymousMethodExpression);
|
|
}
|
|
|
|
public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration)
|
|
{
|
|
if (propertyDeclaration.ExpressionBody is not null)
|
|
{
|
|
HandleMethod(propertyDeclaration.ExpressionBody, propertyDeclaration.Annotation<ILFunction>());
|
|
}
|
|
else
|
|
{
|
|
base.VisitPropertyDeclaration(propertyDeclaration);
|
|
}
|
|
}
|
|
|
|
public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration)
|
|
{
|
|
if (indexerDeclaration.ExpressionBody is not null)
|
|
{
|
|
HandleMethod(indexerDeclaration.ExpressionBody, indexerDeclaration.Annotation<ILFunction>());
|
|
}
|
|
else
|
|
{
|
|
base.VisitIndexerDeclaration(indexerDeclaration);
|
|
}
|
|
}
|
|
|
|
public override void VisitQueryFromClause(QueryFromClause queryFromClause)
|
|
{
|
|
if (queryFromClause.Parent.FirstChild != queryFromClause)
|
|
{
|
|
HandleMethod(queryFromClause);
|
|
}
|
|
else
|
|
{
|
|
base.VisitQueryFromClause(queryFromClause);
|
|
}
|
|
}
|
|
|
|
public override void VisitQueryGroupClause(QueryGroupClause queryGroupClause)
|
|
{
|
|
var annotation = queryGroupClause.Annotation<QueryGroupClauseAnnotation>();
|
|
if (annotation == null)
|
|
{
|
|
base.VisitQueryGroupClause(queryGroupClause);
|
|
return;
|
|
}
|
|
HandleMethod(queryGroupClause.Projection, annotation.ProjectionLambda);
|
|
HandleMethod(queryGroupClause.Key, annotation.KeyLambda);
|
|
}
|
|
|
|
public override void VisitQueryJoinClause(QueryJoinClause queryJoinClause)
|
|
{
|
|
var annotation = queryJoinClause.Annotation<QueryJoinClauseAnnotation>();
|
|
if (annotation == null)
|
|
{
|
|
base.VisitQueryJoinClause(queryJoinClause);
|
|
return;
|
|
}
|
|
HandleMethod(queryJoinClause.OnExpression, annotation.OnLambda);
|
|
HandleMethod(queryJoinClause.EqualsExpression, annotation.EqualsLambda);
|
|
}
|
|
|
|
public override void VisitQueryLetClause(QueryLetClause queryLetClause)
|
|
{
|
|
HandleMethod(queryLetClause);
|
|
}
|
|
|
|
public override void VisitQueryOrdering(QueryOrdering queryOrdering)
|
|
{
|
|
HandleMethod(queryOrdering);
|
|
}
|
|
|
|
public override void VisitQuerySelectClause(QuerySelectClause querySelectClause)
|
|
{
|
|
HandleMethod(querySelectClause);
|
|
}
|
|
|
|
public override void VisitQueryWhereClause(QueryWhereClause queryWhereClause)
|
|
{
|
|
HandleMethod(queryWhereClause);
|
|
}
|
|
|
|
void HandleMethod(AstNode node)
|
|
{
|
|
HandleMethod(node, node.Annotation<ILFunction>());
|
|
}
|
|
|
|
void HandleMethod(AstNode node, ILFunction function)
|
|
{
|
|
// Look into method body, e.g. in order to find lambdas
|
|
VisitChildren(node);
|
|
|
|
if (function == null || function.Method == null || function.Method.MetadataToken.IsNil)
|
|
return;
|
|
this.functions.Add(function);
|
|
var method = function.MoveNextMethod ?? function.Method;
|
|
MethodDefinitionHandle handle = (MethodDefinitionHandle)method.MetadataToken;
|
|
var file = typeSystem.MainModule.MetadataFile;
|
|
MethodDefinition md = file.Metadata.GetMethodDefinition(handle);
|
|
if (md.HasBody())
|
|
{
|
|
HandleMethodBody(function, file.GetMethodBody(md.RelativeVirtualAddress));
|
|
}
|
|
}
|
|
|
|
void HandleMethodBody(ILFunction function, MethodBodyBlock methodBody)
|
|
{
|
|
var method = function.MoveNextMethod ?? function.Method;
|
|
var localVariables = new HashSet<ILVariable>(ILVariableKeyComparer);
|
|
|
|
if (!methodBody.LocalSignature.IsNil)
|
|
{
|
|
#if DEBUG
|
|
var types = typeSystem.MainModule.DecodeLocalSignature(methodBody.LocalSignature,
|
|
new TypeSystem.GenericContext(method));
|
|
#endif
|
|
|
|
foreach (var v in function.Variables)
|
|
{
|
|
if (v.Index != null && v.Kind.IsLocal())
|
|
{
|
|
#if DEBUG
|
|
// The index is the IL ldloc/stloc operand, which indexes the local signature
|
|
// directly: ILReader creates one variable per signature slot, and SplitVariables
|
|
// copies that index. It is therefore correct by construction. The variable type
|
|
// is intentionally not checked against the signature - it is never written to the
|
|
// PDB (only the slot index and name are), and it legitimately diverges after
|
|
// variable splitting (one slot, several typed variables), for pinned-region locals
|
|
// modeled as pointers, and through generic-context type-parameter identity.
|
|
Debug.Assert(v.Index < types.Length);
|
|
#endif
|
|
localVariables.Add(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
LocalScopes.Add(((MethodDefinitionHandle)method.MetadataToken, currentImportScope,
|
|
0, methodBody.GetCodeSize(), localVariables));
|
|
}
|
|
}
|
|
}
|