// 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
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.DebugSteps;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.Instrumentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
using SRM = System.Reflection.Metadata;
#nullable enable
namespace ICSharpCode.Decompiler.CSharp
{
///
/// Main class of the C# decompiler engine.
///
///
/// Instances of this class are not thread-safe. Use separate instances to decompile multiple members in parallel.
/// (in particular, the transform instances are not thread-safe)
///
public class CSharpDecompiler
{
readonly IDecompilerTypeSystem typeSystem;
readonly MetadataModule module;
readonly MetadataReader metadata;
readonly DecompilerSettings settings;
SyntaxTree? syntaxTree;
List ilTransforms = GetILTransforms();
///
/// Pre-yield/await transforms.
///
internal static List EarlyILTransforms(bool aggressivelyDuplicateReturnBlocks = false)
{
return new List {
new ControlFlowSimplification {
aggressivelyDuplicateReturnBlocks = aggressivelyDuplicateReturnBlocks
},
new SplitVariables(),
new ILInlining(),
};
}
///
/// Returns all built-in transforms of the ILAst pipeline.
///
public static List GetILTransforms()
{
return new List {
new ControlFlowSimplification(),
// Run SplitVariables only after ControlFlowSimplification duplicates return blocks,
// so that the return variable is split and can be inlined.
new SplitVariables(),
new ILInlining(),
new InlineReturnTransform(), // must run before DetectPinnedRegions
new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
new DetectExitPoints(),
new LdLocaDupInitObjTransform(),
new EarlyExpressionTransforms(),
new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements
// RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...)
// is already collapsed into stloc(V, ...).
new RemoveDeadVariableInit(),
new ControlFlowSimplification(), //split variables may enable new branch to leave inlining
new DynamicCallSiteTransform(),
new SwitchDetection(),
new SwitchOnStringTransform(),
new SwitchOnNullableTransform(),
new SplitVariables(), // split variables once again, because SwitchOnNullableTransform eliminates ldloca
new IntroduceRefReadOnlyModifierOnLocals(),
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
// Even though it's a post-order block-transform as most other transforms,
// let's keep LoopDetection separate for now until there's a compelling
// reason to combine it with the other block transforms.
// If we ran loop detection after some if structures are already detected,
// we might make our life introducing good exit points more difficult.
new LoopDetection()
}
},
// re-run DetectExitPoints after loop detection
new DetectExitPoints(),
new PatternMatchingTransform(), // must run after LoopDetection and before ConditionDetection
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
new ConditionDetection(),
new LockTransform(),
new UsingTransform(),
// CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform
// and must run before NullCoalescingTransform
new CachedDelegateInitialization(),
new CachedReadOnlySpanInitialization(),
new StatementTransform(
// per-block transforms that depend on each other, and thus need to
// run interleaved (statement by statement).
// Pretty much all transforms that open up new expression inlining
// opportunities belong in this category.
new ILInlining() { options = InliningOptions.AllowInliningOfLdloca },
// Inlining must be first, because it doesn't trigger re-runs.
// Any other transform that opens up new inlining opportunities should call RequestRerun().
new ExpressionTransforms(),
new DynamicIsEventAssignmentTransform(),
new TransformAssignment(), // inline and compound assignments
new NullCoalescingTransform(),
new NullableLiftingStatementTransform(),
new NullPropagationStatementTransform(),
new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new IndexRangeTransform(),
new DeconstructionTransform(),
new NamedArgumentTransform(),
new RemoveUnconstrainedGenericReferenceTypeCheck(),
new UserDefinedLogicTransform(),
new InterpolatedStringTransform()
),
}
},
new ProxyCallReplacer(),
new FixRemainingIncrements(),
new CopyPropagation(),
new DelegateConstruction(),
new LocalFunctionDecompiler(),
new TransformDisplayClassUsage(),
new HighLevelLoopTransform(),
new ReduceNestingTransform(),
new RemoveRedundantReturn(),
new IntroduceDynamicTypeOnLocals(),
new IntroduceNativeIntTypeOnLocals(),
new AssignVariableNames(),
};
}
///
/// Decompiles the body of to ILAst for structural analysis,
/// e.g. for recognizing compiler-generated code (see RecordDecompiler, AutoEventDecompiler).
/// Runs the IL transform pipeline with a fixed set of decompiler settings, so the
/// resulting shape is independent of the user-visible settings, and stops before the
/// late transforms (variable naming etc.) that are only needed for code output.
///
internal static Block? DecompileBodyForAnalysis(IMethod method, IDecompilerTypeSystem typeSystem, CancellationToken cancellationToken)
{
if (method.MetadataToken.IsNil)
return null;
var module = typeSystem.MainModule;
var metadata = module.metadata;
var methodDefHandle = (MethodDefinitionHandle)method.MetadataToken;
var methodDef = metadata.GetMethodDefinition(methodDefHandle);
if (!methodDef.HasBody())
return null;
var genericContext = new GenericContext(
classTypeParameters: method.DeclaringTypeDefinition?.TypeParameters,
methodTypeParameters: null);
var body = module.MetadataFile.GetMethodBody(methodDef.RelativeVirtualAddress);
var ilReader = new ILReader(module);
var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken);
var settings = new DecompilerSettings(LanguageVersion.CSharp1);
var transforms = GetILTransforms();
// Remove the last couple transforms -- we don't need variable names etc. here
int lastBlockTransform = transforms.FindLastIndex(t => t is BlockILTransform);
transforms.RemoveRange(lastBlockTransform + 1, transforms.Count - (lastBlockTransform + 1));
// Use CombineExitsTransform so that "return other != null && ...;" is a single statement even in release builds
transforms.Add(new CombineExitsTransform());
il.RunTransforms(transforms,
new ILTransformContext(il, typeSystem, debugInfo: null, settings) {
CancellationToken = cancellationToken
});
if (il.Body is BlockContainer container)
{
return container.EntryPoint;
}
else if (il.Body is Block block)
{
return block;
}
else
{
return null;
}
}
List astTransforms = GetAstTransforms();
public Stepper Stepper { get; set; } = new Stepper();
///
/// Returns all built-in transforms of the C# AST pipeline.
///
public static List GetAstTransforms()
{
return new List {
new PatternStatementTransform(),
new ReplaceMethodCallsWithOperators(), // must run before DeclareVariables.EnsureExpressionStatementsAreValid
new IntroduceUnsafeModifier(),
new AddCheckedBlocks(),
new DeclareVariables(), // should run after most transforms that modify statements
new TransformFieldAndConstructorInitializers(), // must run after DeclareVariables
new PrettifyAssignments(), // must run after DeclareVariables
new IntroduceUsingDeclarations(),
new IntroduceExtensionMethods(), // must run after IntroduceUsingDeclarations
new IntroduceQueryExpressions(), // must run after IntroduceExtensionMethods
new CombineQueryExpressions(),
new NormalizeBlockStatements(),
new FlattenSwitchBlocks(),
new RenameVisualBasicAnonymousTypes(), // must run before FixNameCollisions
new FixNameCollisions(),
new AddXmlDocumentationTransform(),
};
}
///
/// Token to check for requested cancellation of the decompilation.
///
public CancellationToken CancellationToken { get; set; }
///
/// The type system created from the main module and referenced modules.
///
public IDecompilerTypeSystem TypeSystem => typeSystem;
///
/// Gets or sets the optional provider for debug info.
///
public IDebugInfoProvider? DebugInfoProvider { get; set; }
///
/// Gets or sets the optional provider for XML documentation strings.
///
public IDocumentationProvider? DocumentationProvider { get; set; }
///
/// IL transforms.
///
public IList ILTransforms {
get { return ilTransforms; }
}
///
/// C# AST transforms.
///
public IList AstTransforms {
get { return astTransforms; }
}
///
/// Method bodies that could not be decompiled. Instead of aborting the surrounding type,
/// such a member is emitted with the error text in place of its body (see
/// ) and the exception is collected here, so callers
/// decompiling many members - the project exporter above all - can tell the user how many
/// members are affected.
///
public IReadOnlyList Errors => errors;
readonly List errors = new List();
///
/// Where users are asked to report decompilation failures; part of the error text emitted
/// into the output, because a failure nobody reports is a failure nobody fixes.
///
public const string DecompilationErrorReportUrl = "https://github.com/icsharpcode/ILSpy/issues/new";
///
/// The headline a front end puts above the list of failures it recovered from. Shared so the
/// UI, the command line and any other consumer say the same thing and point at the same URL.
///
public static IEnumerable GetErrorSummaryLines(int errorCount)
{
yield return $"{errorCount} error(s) occurred; the affected code was replaced by the error text in the output.";
yield return $"Please report them at {DecompilationErrorReportUrl}:";
}
///
/// The one-line description of a single failure, so the UI and the command line name it the
/// same way.
///
public static string GetErrorHeadline(DecompilerException error)
{
if (error == null)
throw new ArgumentNullException(nameof(error));
return error.InnerException == null ? error.Message : $"{error.Message}: {error.InnerException.Message}";
}
///
/// Renders as the lines of a comment block: an explanation, the
/// request to report it, and the full exception including its stack trace, which is what
/// makes such a report actionable.
///
internal static IEnumerable GetErrorCommentLines(Exception error)
{
yield return "ILSpy could not decompile this. Please report the exception below,";
yield return "along with the assembly it came from, at " + DecompilationErrorReportUrl;
foreach (string line in error.ToString().Split('\n'))
{
yield return line.TrimEnd('\r');
}
}
///
/// Creates a new instance from the given using the given .
///
public CSharpDecompiler(string fileName, DecompilerSettings settings)
: this(CreateTypeSystemFromFile(fileName, settings), settings)
{
}
///
/// Creates a new instance from the given using the given and .
///
public CSharpDecompiler(string fileName, IAssemblyResolver assemblyResolver, DecompilerSettings settings)
: this(LoadPEFile(fileName, settings), assemblyResolver, settings)
{
}
///
/// Creates a new instance from the given using the given and .
///
public CSharpDecompiler(MetadataFile module, IAssemblyResolver assemblyResolver, DecompilerSettings settings)
: this(new DecompilerTypeSystem(module, assemblyResolver, settings), settings)
{
}
///
/// Creates a new instance from the given and the given .
///
public CSharpDecompiler(IDecompilerTypeSystem typeSystem, DecompilerSettings settings)
{
this.typeSystem = typeSystem ?? throw new ArgumentNullException(nameof(typeSystem));
this.settings = settings;
this.module = typeSystem.MainModule;
this.metadata = module.MetadataFile.Metadata;
if (module.TypeSystemOptions.HasFlag(TypeSystemOptions.Uncached))
throw new ArgumentException("Cannot use an uncached type system in the decompiler.");
}
#region MemberIsHidden
///
/// Determines whether a should be hidden from the decompiled code. This is used to exclude compiler-generated code that is handled by transforms from the output.
///
/// The module containing the member.
/// The metadata token/handle of the member. Can be a TypeDef, MethodDef or FieldDef.
/// The settings used to determine whether code should be hidden. E.g. if async methods are not transformed, async state machines are included in the decompiled code.
public static bool MemberIsHidden(MetadataFile? module, EntityHandle member, DecompilerSettings settings)
{
if (module == null || member.IsNil)
return false;
var metadata = module.Metadata;
string name;
switch (member.Kind)
{
case HandleKind.MethodDefinition:
var methodHandle = (MethodDefinitionHandle)member;
var method = metadata.GetMethodDefinition(methodHandle);
var methodSemantics = module.MethodSemanticsLookup.GetSemantics(methodHandle).Item2;
if (methodSemantics != 0 && methodSemantics != System.Reflection.MethodSemanticsAttributes.Other)
return true;
name = metadata.GetString(method.Name);
if (name == ".ctor" && method.RelativeVirtualAddress == 0 && metadata.GetTypeDefinition(method.GetDeclaringType()).Attributes.HasFlag(System.Reflection.TypeAttributes.Import))
return true;
if (module is PEFile m && IsAccessorInterfaceImplementationRuntimeHelper(m, methodHandle))
return true;
if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle))
return true;
if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata))
return name != "$";
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, methodHandle))
return true;
return false;
case HandleKind.TypeDefinition:
var typeHandle = (TypeDefinitionHandle)member;
var type = metadata.GetTypeDefinition(typeHandle);
name = metadata.GetString(type.Name);
if (!type.GetDeclaringType().IsNil)
{
if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionDisplayClass(module, typeHandle))
return true;
if (settings.AnonymousMethods && IsClosureType(type, metadata))
return true;
if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(typeHandle, metadata))
return true;
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(typeHandle, metadata))
return true;
if (settings.AsyncEnumerator && AsyncAwaitDecompiler.IsCompilerGeneratorAsyncEnumerator(typeHandle, metadata))
return true;
if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer"))
return true;
if (settings.InlineArrays && name.StartsWith("<>y__InlineArray", StringComparison.Ordinal) && name.EndsWith("`1", StringComparison.Ordinal))
return true;
if (settings.ExtensionMembers && (name.StartsWith("<>E__", StringComparison.Ordinal) || name.StartsWith("$", StringComparison.Ordinal)))
return true;
}
else if (type.IsCompilerGenerated(metadata))
{
if (settings.ArrayInitializers && name.StartsWith("", StringComparison.Ordinal))
return true;
if (settings.AnonymousTypes && type.IsAnonymousType(metadata))
return true;
if (settings.Dynamic && type.IsDelegate(metadata) && (name.StartsWith("<>A", StringComparison.Ordinal) || name.StartsWith("<>F", StringComparison.Ordinal)))
return true;
}
if (settings.ArrayInitializers && settings.SwitchStatementOnString && name.StartsWith("", StringComparison.Ordinal))
return true;
return false;
case HandleKind.FieldDefinition:
var fieldHandle = (FieldDefinitionHandle)member;
var field = metadata.GetFieldDefinition(fieldHandle);
name = metadata.GetString(field.Name);
if (field.IsCompilerGenerated(metadata))
{
if (settings.AnonymousMethods && IsAnonymousMethodCacheField(field, metadata))
return true;
if (settings.UsePrimaryConstructorSyntaxForNonRecordTypes && IsPrimaryConstructorParameterBackingField(field, metadata))
return true;
if ((settings.AutomaticProperties || settings.FieldKeyword)
&& module.PropertyAndEventBackingFieldLookup.IsPropertyBackingField(fieldHandle, out var propertyHandle))
{
// GetterOnlyAutomaticProperties exists so output stays compilable on
// toolchains that predate C# 6 getter-only auto-properties. Switching it off
// is a stronger statement than leaving FieldKeyword at its default, and it
// wins: accessors needing the C# 14 field keyword would not compile on such
// a toolchain either.
if (!settings.GetterOnlyAutomaticProperties)
{
PropertyAccessors accessors = metadata.GetPropertyDefinition(propertyHandle).GetAccessors();
if (!accessors.Getter.IsNil && accessors.Setter.IsNil)
return false;
}
return true;
}
if (settings.SwitchStatementOnString && IsSwitchOnStringCache(field, metadata))
return true;
}
// event-fields are not [CompilerGenerated]
if (settings.AutomaticEvents && module.PropertyAndEventBackingFieldLookup.IsEventBackingField(fieldHandle, out _))
{
return true;
}
if (settings.ArrayInitializers && metadata.GetString(metadata.GetTypeDefinition(field.GetDeclaringType()).Name).StartsWith("", StringComparison.Ordinal))
{
// only hide fields starting with '__StaticArrayInit'
if (name.StartsWith("__StaticArrayInit", StringComparison.Ordinal))
return true;
// hide fields starting with '$$method'
if (name.StartsWith("$$method", StringComparison.Ordinal))
return true;
if (field.DecodeSignature(new Metadata.FullTypeNameSignatureDecoder(metadata), default).ToString().StartsWith("__StaticArrayInit", StringComparison.Ordinal))
return true;
}
return false;
}
return false;
}
static bool IsPrimaryConstructorParameterBackingField(SRM.FieldDefinition field, MetadataReader metadata)
{
var name = metadata.GetString(field.Name);
return name.StartsWith("<", StringComparison.Ordinal) && name.EndsWith(">P", StringComparison.Ordinal);
}
static bool IsAccessorInterfaceImplementationRuntimeHelper(PEFile module, MethodDefinitionHandle handle)
{
var metadata = module.Metadata;
var method = metadata.GetMethodDefinition(handle);
if ((method.Attributes & System.Reflection.MethodAttributes.Static) != 0)
return false;
string rawName = metadata.GetString(method.Name);
int dot = rawName.LastIndexOf('.');
if (dot < 0)
return false;
string name = rawName.Substring(dot + 1);
if (handle.GetMethodImplementations(metadata).Length == 0)
return false;
if (method.RelativeVirtualAddress == 0)
return false;
if (!name.StartsWith("get_", StringComparison.Ordinal) &&
!name.StartsWith("set_", StringComparison.Ordinal) &&
!name.StartsWith("add_", StringComparison.Ordinal) &&
!name.StartsWith("remove_", StringComparison.Ordinal) &&
!name.StartsWith("raise_", StringComparison.Ordinal))
{
return false;
}
var signature = metadata.GetBlobReader(method.Signature);
(int genericParameterCount, int parameterCount) = SignatureBlobComparer.ReadParameterCount(ref signature);
if (genericParameterCount == -1 || parameterCount == -1)
return false;
signature.Reset();
// The shortest possible forwarding stub loads every argument with a one-byte
// ldarg.N; the longest uses the four-byte ldarg form. Both end in call + ret.
int minimumMethodSize = 1 * (parameterCount + 1) + 5 + 1;
int maximumMethodSize = 4 * (parameterCount + 1) + 5 + 1;
var body = module.Reader.GetMethodBody(method.RelativeVirtualAddress);
var reader = body.GetILReader();
// Reference assemblies keep the method RVA but strip the body, so a body far
// too short for the stub must be rejected before any of the reads below.
if (reader.RemainingBytes < minimumMethodSize || reader.RemainingBytes > maximumMethodSize)
return false;
for (int i = 0; i < parameterCount + 1; i++)
{
int index;
// The long ldarg forms are wider than the one byte per argument that the
// minimum size accounts for, so the body can still run out mid-loop.
if (reader.RemainingBytes < 1)
return false;
switch (reader.DecodeOpCode())
{
case ILOpCode.Ldarg:
if (reader.RemainingBytes < 2)
return false;
index = reader.ReadUInt16();
if (index != i)
return false;
break;
case ILOpCode.Ldarg_s:
if (reader.RemainingBytes < 1)
return false;
index = reader.ReadByte();
if (index != i)
return false;
break;
case ILOpCode.Ldarg_0:
if (i != 0)
return false;
break;
case ILOpCode.Ldarg_1:
if (i != 1)
return false;
break;
case ILOpCode.Ldarg_2:
if (i != 2)
return false;
break;
case ILOpCode.Ldarg_3:
if (i != 3)
return false;
break;
default:
return false;
}
}
// call <4-byte token> + ret
if (reader.RemainingBytes < 6)
return false;
if (reader.DecodeOpCode() != ILOpCode.Call)
return false;
EntityHandle targetHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
if (targetHandle.IsNil)
return false;
if (reader.DecodeOpCode() != ILOpCode.Ret)
return false;
if (reader.RemainingBytes != 0)
return false;
BlobReader signature2;
string otherName;
switch (targetHandle.Kind)
{
case HandleKind.MethodDefinition:
if (genericParameterCount != 0)
return false;
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)targetHandle);
signature2 = metadata.GetBlobReader(methodDef.Signature);
otherName = metadata.GetString(methodDef.Name);
break;
case HandleKind.MethodSpecification:
if (genericParameterCount == 0)
return false;
var methodSpec = metadata.GetMethodSpecification((MethodSpecificationHandle)targetHandle);
var instantiationBlob = metadata.GetBlobReader(methodSpec.Signature);
if (!IsIdentityInstantiation(ref instantiationBlob, genericParameterCount))
return false;
switch (methodSpec.Method.Kind)
{
case HandleKind.MethodDefinition:
var methodSpecDef = metadata.GetMethodDefinition((MethodDefinitionHandle)methodSpec.Method);
signature2 = metadata.GetBlobReader(methodSpecDef.Signature);
otherName = metadata.GetString(methodSpecDef.Name);
break;
case HandleKind.MemberReference:
var methodSpecRef = metadata.GetMemberReference((MemberReferenceHandle)methodSpec.Method);
if (methodSpecRef.GetKind() != MemberReferenceKind.Method)
return false;
signature2 = metadata.GetBlobReader(methodSpecRef.Signature);
otherName = metadata.GetString(methodSpecRef.Name);
break;
default:
return false;
}
break;
case HandleKind.MemberReference:
if (genericParameterCount != 0)
return false;
var methodRef = metadata.GetMemberReference((MemberReferenceHandle)targetHandle);
if (methodRef.GetKind() != MemberReferenceKind.Method)
return false;
signature2 = metadata.GetBlobReader(methodRef.Signature);
otherName = metadata.GetString(methodRef.Name);
break;
default:
return false;
}
if (otherName != name)
return false;
return SignatureBlobComparer.EqualsMethodSignature(signature, signature2, metadata, metadata, skipModifiers: true);
static bool IsIdentityInstantiation(ref BlobReader reader, int expectedCount)
{
// Format: GENRICINST count type1 type2 ...
if (reader.ReadByte() != 0x0A) // GENERICINST
return false;
if (!reader.TryReadCompressedInteger(out int count) || count != expectedCount)
return false;
for (int i = 0; i < count; i++)
{
if (reader.ReadByte() != 0x1E) // ELEMENT_TYPE_MVAR
return false;
if (!reader.TryReadCompressedInteger(out int index) || index != i)
return false;
}
return true;
}
}
static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata)
{
return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal);
}
static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata)
{
var name = metadata.GetString(field.Name);
return name.StartsWith("CS$<>", StringComparison.Ordinal) || name.StartsWith("<>f__am", StringComparison.Ordinal) || name.StartsWith("<>f__mg", StringComparison.Ordinal);
}
static bool IsClosureType(SRM.TypeDefinition type, MetadataReader metadata)
{
var name = metadata.GetString(type.Name);
if (!type.Name.IsGeneratedName(metadata) || !type.IsCompilerGenerated(metadata))
return false;
if (name.Contains("DisplayClass") || name.Contains("AnonStorey") || name.Contains("Closure$"))
return true;
return type.BaseType.IsKnownType(metadata, KnownTypeCode.Object) && !type.GetInterfaceImplementations().Any();
}
internal static bool IsTransparentIdentifier(string identifier)
{
if (identifier.StartsWith("<>", StringComparison.Ordinal))
{
return identifier.Contains("TransparentIdentifier") || identifier.Contains("TranspIdent");
}
// The VB compiler names the carriers of its query range variables
// $VB$It, $VB$It1, $VB$It2 and $VB$ItAnonymous.
return identifier.StartsWith("$VB$It", StringComparison.Ordinal);
}
#endregion
#region NativeOrdering
///
/// Determines whether a given type requires that its methods be ordered precisely as they were originally defined.
///
/// The type whose members may need native ordering.
internal bool RequiresNativeOrdering(ITypeDefinition typeDef)
{
// The main scenario for requiring the native method ordering is COM interop, where the V-table is fixed by the ABI
return ComHelper.IsComImport(typeDef);
}
///
/// Compare handles with the method definition ordering intact by using the underlying method's MetadataToken,
/// which is defined as the index into a given metadata table. This should equate to the original order that
/// methods and properties were defined by the author.
///
/// The type whose members to order using their method's MetadataToken
/// A sequence of all members ordered by MetadataToken
internal IEnumerable GetMembersWithNativeOrdering(ITypeDefinition typeDef)
{
EntityHandle GetOrderingHandle(IMember member)
{
// Note! Technically COM interfaces could define property getters and setters out of order or interleaved with other
// methods, but C# doesn't support this so we can't define it that way.
if (member is IMethod)
return member.MetadataToken;
else if (member is IProperty property)
return property.Getter?.MetadataToken ?? property.Setter?.MetadataToken ?? property.MetadataToken;
else if (member is IEvent @event)
return @event.AddAccessor?.MetadataToken ?? @event.RemoveAccessor?.MetadataToken ?? @event.InvokeAccessor?.MetadataToken ?? @event.MetadataToken;
else
return member.MetadataToken;
}
return typeDef.Fields.Concat(typeDef.Properties).Concat(typeDef.Methods).Concat(typeDef.Events).OrderBy((member) => GetOrderingHandle(member), HandleComparer.Default);
}
#endregion
static PEFile LoadPEFile(string fileName, DecompilerSettings settings)
{
settings.LoadInMemory = true;
return new PEFile(
fileName,
new FileStream(fileName, FileMode.Open, FileAccess.Read),
streamOptions: PEStreamOptions.PrefetchEntireImage,
metadataOptions: settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None
);
}
static DecompilerTypeSystem CreateTypeSystemFromFile(string fileName, DecompilerSettings settings)
{
settings.LoadInMemory = true;
var file = LoadPEFile(fileName, settings);
var resolver = new UniversalAssemblyResolver(fileName, settings.ThrowOnAssemblyResolveErrors,
file.DetectTargetFrameworkId(), file.DetectRuntimePack(),
settings.LoadInMemory ? PEStreamOptions.PrefetchMetadata : PEStreamOptions.Default,
settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None);
return new DecompilerTypeSystem(file, resolver, settings);
}
static TypeSystemAstBuilder CreateAstBuilder(DecompilerSettings settings)
{
var typeSystemAstBuilder = new TypeSystemAstBuilder();
typeSystemAstBuilder.ShowAttributes = true;
typeSystemAstBuilder.UsePrivateProtectedAccessibility = settings.IntroducePrivateProtectedAccessibility;
typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes;
typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors;
typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses;
typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs;
typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift;
typeSystemAstBuilder.SupportOperatorChecked = settings.CheckedOperators;
typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal;
typeSystemAstBuilder.SupportExtensionDeclarations = settings.ExtensionMembers;
return typeSystemAstBuilder;
}
IDocumentationProvider? CreateDefaultDocumentationProvider()
{
try
{
return XmlDocLoader.LoadDocumentation(module.MetadataFile);
}
catch (System.Xml.XmlException)
{
return null;
}
}
DecompileRun CreateDecompileRun(HashSet namespaces)
{
// Every public Decompile* entry point starts here, so this is where the failures of the
// previous one stop counting - otherwise a reused instance reports them again against
// members that decompiled cleanly.
errors.Clear();
List resolvedNamespaces = new List();
foreach (var ns in namespaces)
{
var resolvedNamespace = typeSystem.GetNamespaceByFullName(ns);
if (resolvedNamespace != null)
{
resolvedNamespaces.Add(resolvedNamespace);
}
}
UsingScope usingScope = new UsingScope(
new CSharpTypeResolveContext(typeSystem.MainModule),
typeSystem.RootNamespace,
resolvedNamespaces.ToImmutableArray()
);
return new DecompileRun(settings, usingScope) {
DocumentationProvider = DocumentationProvider ?? CreateDefaultDocumentationProvider(),
CancellationToken = CancellationToken,
Namespaces = namespaces
};
}
void RunTransforms(AstNode rootNode, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
{
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
var context = new TransformContext(typeSystem, decompileRun, decompilationContext, typeSystemAstBuilder) {
Stepper = Stepper
};
// The tree handed to the pipeline must already be well-formed; check it once up front so a
// malformed builder output is caught here rather than blamed on the first transform (DEBUG only).
rootNode.CheckInvariant();
bool traceTransforms = DecompilerEventSource.Log.IsTransformTracingEnabled();
try
{
foreach (var transform in astTransforms)
{
CancellationToken.ThrowIfCancellationRequested();
context.StepStartGroup(transform.GetType().Name);
long traceStart = traceTransforms ? Stopwatch.GetTimestamp() : 0;
transform.Run(rootNode, context);
if (traceTransforms)
DecompilerEventSource.Log.AstTransformExecuted(transform, traceStart);
// Verify the slot structure survived the transform (DEBUG only); mirrors the IL
// pipeline's per-transform ILInstruction.CheckInvariant.
rootNode.CheckInvariant();
context.StepEndGroup(keepIfEmpty: true);
}
}
catch (StepLimitReachedException)
{
}
CancellationToken.ThrowIfCancellationRequested();
rootNode.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true });
CancellationToken.ThrowIfCancellationRequested();
GenericGrammarAmbiguityVisitor.ResolveAmbiguities(rootNode);
}
string SyntaxTreeToString(SyntaxTree syntaxTree)
{
StringWriter w = new StringWriter();
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions));
return w.ToString();
}
///
/// Decompile assembly and module attributes.
///
public SyntaxTree DecompileModuleAndAssemblyAttributes()
{
var decompilationContext = new SimpleTypeResolveContext(typeSystem.MainModule);
var namespaces = new HashSet();
syntaxTree = new SyntaxTree();
RequiredNamespaceCollector.CollectAttributeNamespaces(module, namespaces);
DecompileRun decompileRun = CreateDecompileRun(namespaces);
DoDecompileModuleAndAssemblyAttributes(decompileRun, decompilationContext, syntaxTree);
RunTransforms(syntaxTree, decompileRun, decompilationContext);
return syntaxTree;
}
///
/// Decompile assembly and module attributes.
///
public string DecompileModuleAndAssemblyAttributesToString()
{
return SyntaxTreeToString(DecompileModuleAndAssemblyAttributes());
}
void DoDecompileModuleAndAssemblyAttributes(DecompileRun decompileRun, ITypeResolveContext decompilationContext, SyntaxTree syntaxTree)
{
try
{
foreach (var a in typeSystem.MainModule.GetAssemblyAttributes())
{
var astBuilder = CreateAstBuilder(decompileRun.Settings);
var attrSection = new AttributeSection(astBuilder.ConvertAttribute(a));
attrSection.AttributeTarget = "assembly";
syntaxTree.Members.Add(attrSection);
}
foreach (var a in typeSystem.MainModule.GetModuleAttributes())
{
var astBuilder = CreateAstBuilder(decompileRun.Settings);
var attrSection = new AttributeSection(astBuilder.ConvertAttribute(a));
attrSection.AttributeTarget = "module";
syntaxTree.Members.Add(attrSection);
}
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
{
throw new DecompilerException(module, null, innerException, "Error decompiling module and assembly attributes of " + module.AssemblyName);
}
}
void DoDecompileTypes(IEnumerable types, DecompileRun decompileRun, ITypeResolveContext decompilationContext, SyntaxTree syntaxTree)
{
string? currentNamespace = null;
AstNode? groupNode = null;
foreach (var typeDefHandle in types)
{
var typeDef = module.GetDefinition(typeDefHandle);
if (typeDef.Name == "" && typeDef.Members.Count == 0)
continue;
if (MemberIsHidden(module.MetadataFile, typeDefHandle, settings))
continue;
if (string.IsNullOrEmpty(typeDef.Namespace))
{
groupNode = syntaxTree;
}
else
{
if (currentNamespace != typeDef.Namespace)
{
groupNode = new NamespaceDeclaration(typeDef.Namespace);
syntaxTree.Members.Add(groupNode);
}
}
currentNamespace = typeDef.Namespace;
var typeDecl = DoDecompile(typeDef, decompileRun, decompilationContext.WithCurrentTypeDefinition(typeDef));
groupNode!.AddChild(typeDecl, Slots.Member);
}
}
///
/// Decompiles the whole module into a single syntax tree.
///
public SyntaxTree DecompileWholeModuleAsSingleFile()
{
return DecompileWholeModuleAsSingleFile(false);
}
///
/// Decompiles the whole module into a single syntax tree.
///
/// If true, top-level-types are emitted sorted by namespace/name.
/// If false, types are emitted in metadata order.
public SyntaxTree DecompileWholeModuleAsSingleFile(bool sortTypes)
{
var decompilationContext = new SimpleTypeResolveContext(typeSystem.MainModule);
syntaxTree = new SyntaxTree();
var namespaces = new HashSet();
RequiredNamespaceCollector.CollectNamespaces(module, namespaces);
var decompileRun = CreateDecompileRun(namespaces);
DoDecompileModuleAndAssemblyAttributes(decompileRun, decompilationContext, syntaxTree);
var typeDefs = metadata.GetTopLevelTypeDefinitions();
if (sortTypes)
{
typeDefs = typeDefs.OrderBy(td => {
var typeDef = module.metadata.GetTypeDefinition(td);
return (module.metadata.GetString(typeDef.Namespace), module.metadata.GetString(typeDef.Name));
});
}
DoDecompileTypes(typeDefs, decompileRun, decompilationContext, syntaxTree);
RunTransforms(syntaxTree, decompileRun, decompilationContext);
return syntaxTree;
}
///
/// Creates an for the given .
///
public ILTransformContext CreateILTransformContext(ILFunction function)
{
var namespaces = new HashSet();
RequiredNamespaceCollector.CollectNamespaces(function.Method, module, namespaces);
var decompileRun = CreateDecompileRun(namespaces);
return new ILTransformContext(function, typeSystem, DebugInfoProvider, settings) {
CancellationToken = CancellationToken,
DecompileRun = decompileRun
};
}
///
/// Determines the "code-mappings" for a given TypeDef or MethodDef. See for more information.
///
public static CodeMappingInfo GetCodeMappingInfo(MetadataFile module, EntityHandle member)
{
var declaringType = (TypeDefinitionHandle)member.GetDeclaringType(module.Metadata);
if (declaringType.IsNil && member.Kind == HandleKind.TypeDefinition)
{
declaringType = (TypeDefinitionHandle)member;
}
var info = new CodeMappingInfo(module, declaringType);
var td = module.Metadata.GetTypeDefinition(declaringType);
foreach (var method in td.GetMethods())
{
var parent = method;
var part = method;
var connectedMethods = new Queue();
var processedMethods = new HashSet();
var processedNestedTypes = new HashSet();
connectedMethods.Enqueue(part);
while (connectedMethods.Count > 0)
{
part = connectedMethods.Dequeue();
if (!processedMethods.Add(part))
continue;
try
{
if (TryGetExtensionImplementation(module.Metadata, part, out var impl))
{
connectedMethods.Enqueue(impl);
}
ReadCodeMappingInfo(module, info, parent, part, connectedMethods, processedNestedTypes);
}
catch (BadImageFormatException)
{
// ignore invalid IL
}
}
}
return info;
}
private static void ReadCodeMappingInfo(MetadataFile module, CodeMappingInfo info, MethodDefinitionHandle parent, MethodDefinitionHandle part, Queue connectedMethods, HashSet processedNestedTypes)
{
var md = module.Metadata.GetMethodDefinition(part);
if (!md.HasBody())
{
info.AddMapping(parent, part);
return;
}
var declaringType = md.GetDeclaringType();
var blob = module.GetMethodBody(md.RelativeVirtualAddress).GetILReader();
while (blob.RemainingBytes > 0)
{
var code = blob.DecodeOpCode();
switch (code)
{
case ILOpCode.Newobj:
case ILOpCode.Stfld:
// async and yield fsms:
var token = MetadataTokenHelpers.EntityHandleOrNil(blob.ReadInt32());
if (token.IsNil)
continue;
TypeDefinitionHandle fsmTypeDef;
switch (token.Kind)
{
case HandleKind.MethodDefinition:
var fsmMethod = module.Metadata.GetMethodDefinition((MethodDefinitionHandle)token);
fsmTypeDef = fsmMethod.GetDeclaringType();
break;
case HandleKind.FieldDefinition:
var fsmField = module.Metadata.GetFieldDefinition((FieldDefinitionHandle)token);
fsmTypeDef = fsmField.GetDeclaringType();
break;
case HandleKind.MemberReference:
var memberRef = module.Metadata.GetMemberReference((MemberReferenceHandle)token);
fsmTypeDef = ExtractDeclaringType(memberRef);
break;
default:
continue;
}
if (!fsmTypeDef.IsNil)
{
var fsmType = module.Metadata.GetTypeDefinition(fsmTypeDef);
// Must be a nested type of the containing type.
if (fsmType.GetDeclaringType() != declaringType)
break;
if (YieldReturnDecompiler.IsCompilerGeneratorEnumerator(fsmTypeDef, module.Metadata)
|| AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(fsmTypeDef, module.Metadata))
{
if (!processedNestedTypes.Add(fsmTypeDef))
break;
foreach (var h in fsmType.GetMethods())
{
if (module.MethodSemanticsLookup.GetSemantics(h).Item2 != 0)
continue;
var otherMethod = module.Metadata.GetMethodDefinition(h);
if (!otherMethod.GetCustomAttributes().HasKnownAttribute(module.Metadata, KnownAttribute.DebuggerHidden))
{
connectedMethods.Enqueue(h);
}
}
}
}
break;
case ILOpCode.Ldftn:
// deal with ldftn instructions, i.e., lambdas
token = MetadataTokenHelpers.EntityHandleOrNil(blob.ReadInt32());
if (token.IsNil)
continue;
TypeDefinitionHandle closureTypeHandle;
switch (token.Kind)
{
case HandleKind.MethodDefinition:
if (((MethodDefinitionHandle)token).IsCompilerGeneratedOrIsInCompilerGeneratedClass(module.Metadata))
{
connectedMethods.Enqueue((MethodDefinitionHandle)token);
}
continue;
case HandleKind.MemberReference:
var memberRef = module.Metadata.GetMemberReference((MemberReferenceHandle)token);
if (memberRef.GetKind() != MemberReferenceKind.Method)
continue;
closureTypeHandle = ExtractDeclaringType(memberRef);
if (!closureTypeHandle.IsNil)
{
var closureType = module.Metadata.GetTypeDefinition(closureTypeHandle);
if (closureTypeHandle != declaringType)
{
// Must be a nested type of the containing type.
if (closureType.GetDeclaringType() != declaringType)
break;
if (!processedNestedTypes.Add(closureTypeHandle))
break;
foreach (var m in closureType.GetMethods())
{
connectedMethods.Enqueue(m);
}
}
else
{
// Delegate body is declared in the same type
foreach (var m in closureType.GetMethods())
{
var methodDef = module.Metadata.GetMethodDefinition(m);
if (methodDef.Name == memberRef.Name && m.IsCompilerGeneratedOrIsInCompilerGeneratedClass(module.Metadata))
connectedMethods.Enqueue(m);
}
}
break;
}
break;
default:
continue;
}
break;
case ILOpCode.Call:
case ILOpCode.Callvirt:
// deal with call/callvirt instructions, i.e., local function invocations
token = MetadataTokenHelpers.EntityHandleOrNil(blob.ReadInt32());
if (token.IsNil)
continue;
switch (token.Kind)
{
case HandleKind.MethodDefinition:
break;
case HandleKind.MethodSpecification:
var methodSpec = module.Metadata.GetMethodSpecification((MethodSpecificationHandle)token);
if (methodSpec.Method.IsNil || methodSpec.Method.Kind != HandleKind.MethodDefinition)
continue;
token = methodSpec.Method;
break;
default:
continue;
}
if (LocalFunctionDecompiler.IsLocalFunctionMethod(module, (MethodDefinitionHandle)token))
{
connectedMethods.Enqueue((MethodDefinitionHandle)token);
}
break;
default:
blob.SkipOperand(code);
break;
}
}
info.AddMapping(parent, part);
TypeDefinitionHandle ExtractDeclaringType(MemberReference memberRef)
{
switch (memberRef.Parent.Kind)
{
case HandleKind.TypeReference:
// This should never happen in normal code, because we are looking at nested types
// If it's not a nested type, it can't be a reference to the state machine or lambda anyway, and
// those should be either TypeDef or TypeSpec.
return default;
case HandleKind.TypeDefinition:
return (TypeDefinitionHandle)memberRef.Parent;
case HandleKind.TypeSpecification:
var ts = module.Metadata.GetTypeSpecification((TypeSpecificationHandle)memberRef.Parent);
// Only read the generic type, ignore the type arguments
var genericType = ts.GetGenericType(module.Metadata);
// Again, we assume this is a type def, because we are only looking at nested types
if (genericType.Kind != HandleKind.TypeDefinition)
return default;
return (TypeDefinitionHandle)genericType;
}
return default;
}
}
private static bool TryGetExtensionImplementation(MetadataReader metadata, MethodDefinitionHandle definitionPart, out MethodDefinitionHandle implementationPart)
{
implementationPart = default;
var def = metadata.GetMethodDefinition(definitionPart);
var declTypeHandle = def.GetDeclaringType();
var declType = metadata.GetTypeDefinition(declTypeHandle);
var name = metadata.GetString(def.Name);
var containerHandle = declType.GetDeclaringType();
if (containerHandle.IsNil)
return false;
if (metadata.StringComparer.StartsWith(declType.Name, "<>E__") || metadata.StringComparer.StartsWith(declType.Name, "$"))
{
implementationPart = FindImplementations(metadata.GetTypeDefinition(containerHandle).GetMethods());
}
else if (metadata.StringComparer.StartsWith(declType.Name, "$"))
{
var container = metadata.GetTypeDefinition(containerHandle);
var groupHandle = container.GetDeclaringType();
if (groupHandle.IsNil)
return false;
implementationPart = FindImplementations(metadata.GetTypeDefinition(groupHandle).GetMethods());
}
else
{
return false;
}
return !implementationPart.IsNil;
MethodDefinitionHandle FindImplementations(MethodDefinitionHandleCollection methods)
{
foreach (var h in methods)
{
var m = metadata.GetMethodDefinition(h);
if (!metadata.StringComparer.Equals(m.Name, name))
continue;
// TODO : use SignatureBlobComparer to ensure that the correct method is resolved
return h;
}
return default;
}
}
///
/// Decompiles the whole module into a single string.
///
public string DecompileWholeModuleAsString()
{
return SyntaxTreeToString(DecompileWholeModuleAsSingleFile());
}
///
/// Decompile the given types.
///
///
/// Unlike Decompile(IMemberDefinition[]), this method will add namespace declarations around the type definitions.
///
public SyntaxTree DecompileTypes(IEnumerable types)
{
if (types == null)
throw new ArgumentNullException(nameof(types));
var decompilationContext = new SimpleTypeResolveContext(typeSystem.MainModule);
syntaxTree = new SyntaxTree();
var namespaces = new HashSet();
foreach (var type in types)
{
CancellationToken.ThrowIfCancellationRequested();
if (type.IsNil)
throw new ArgumentException("types contains null element");
RequiredNamespaceCollector.CollectNamespaces(type, module, namespaces);
}
var decompileRun = CreateDecompileRun(namespaces);
DoDecompileTypes(types, decompileRun, decompilationContext, syntaxTree);
RunTransforms(syntaxTree, decompileRun, decompilationContext);
return syntaxTree;
}
///
/// Decompile the given types.
///
///
/// Unlike Decompile(IMemberDefinition[]), this method will add namespace declarations around the type definitions.
///
public string DecompileTypesAsString(IEnumerable types)
{
return SyntaxTreeToString(DecompileTypes(types));
}
///
/// Decompile the given type.
///
///
/// Unlike Decompile(IMemberDefinition[]), this method will add namespace declarations around the type definition.
/// Note that decompiling types from modules other than the main module is not supported.
///
public SyntaxTree DecompileType(FullTypeName fullTypeName)
{
var type = typeSystem.FindType(fullTypeName.TopLevelTypeName).GetDefinition();
if (type == null)
throw new InvalidOperationException($"Could not find type definition {fullTypeName} in type system.");
if (type.ParentModule != typeSystem.MainModule)
throw new NotSupportedException($"Type {fullTypeName} was not found in the module being decompiled, but only in {type.ParentModule!.Name}");
var decompilationContext = new SimpleTypeResolveContext(typeSystem.MainModule);
var namespaces = new HashSet();
syntaxTree = new SyntaxTree();
RequiredNamespaceCollector.CollectNamespaces(type.MetadataToken, module, namespaces);
var decompileRun = CreateDecompileRun(namespaces);
DoDecompileTypes(new[] { (TypeDefinitionHandle)type.MetadataToken }, decompileRun, decompilationContext, syntaxTree);
RunTransforms(syntaxTree, decompileRun, decompilationContext);
return syntaxTree;
}
///
/// Decompile the given type.
///
///
/// Unlike Decompile(IMemberDefinition[]), this method will add namespace declarations around the type definition.
///
public string DecompileTypeAsString(FullTypeName fullTypeName)
{
return SyntaxTreeToString(DecompileType(fullTypeName));
}
///
/// Decompile the specified types and/or members.
///
public SyntaxTree Decompile(params EntityHandle[] definitions)
{
return Decompile((IEnumerable)definitions);
}
///
/// Decompile the specified types and/or members.
///
public SyntaxTree Decompile(IEnumerable definitions)
{
if (definitions == null)
throw new ArgumentNullException(nameof(definitions));
syntaxTree = new SyntaxTree();
var namespaces = new HashSet();
foreach (var entity in definitions)
{
if (entity.IsNil)
throw new ArgumentException("definitions contains null element");
RequiredNamespaceCollector.CollectNamespaces(entity, module, namespaces);
}
var decompileRun = CreateDecompileRun(namespaces);
bool first = true;
ITypeDefinition? parentTypeDef = null;
foreach (var entity in definitions)
{
switch (entity.Kind)
{
case HandleKind.TypeDefinition:
ITypeDefinition typeDef = module.GetDefinition((TypeDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(typeDef, decompileRun, new SimpleTypeResolveContext(typeDef)));
if (first)
{
parentTypeDef = typeDef.DeclaringTypeDefinition;
}
else if (parentTypeDef != null)
{
parentTypeDef = FindCommonDeclaringTypeDefinition(parentTypeDef, typeDef.DeclaringTypeDefinition);
}
break;
case HandleKind.MethodDefinition:
IMethod method = module.GetDefinition((MethodDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), method.ResolveExtensionInfo()));
if (first)
{
parentTypeDef = method.DeclaringTypeDefinition;
}
else if (parentTypeDef != null)
{
parentTypeDef = FindCommonDeclaringTypeDefinition(parentTypeDef, method.DeclaringTypeDefinition);
}
break;
case HandleKind.FieldDefinition:
IField field = module.GetDefinition((FieldDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(field, decompileRun, new SimpleTypeResolveContext(field)));
parentTypeDef = field.DeclaringTypeDefinition;
break;
case HandleKind.PropertyDefinition:
IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity);
var propertyExtensionInfo = property.ResolveExtensionInfo();
if (property.IsParameterizedProperty())
{
syntaxTree.Members.AddRange(DecompileParameterizedProperty(property, decompileRun, new SimpleTypeResolveContext(property), propertyExtensionInfo));
}
else
{
syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), propertyExtensionInfo));
}
if (first)
{
parentTypeDef = property.DeclaringTypeDefinition;
}
else if (parentTypeDef != null)
{
parentTypeDef = FindCommonDeclaringTypeDefinition(parentTypeDef, property.DeclaringTypeDefinition);
}
break;
case HandleKind.EventDefinition:
IEvent ev = module.GetDefinition((EventDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(ev, decompileRun, new SimpleTypeResolveContext(ev)));
if (first)
{
parentTypeDef = ev.DeclaringTypeDefinition;
}
else if (parentTypeDef != null)
{
parentTypeDef = FindCommonDeclaringTypeDefinition(parentTypeDef, ev.DeclaringTypeDefinition);
}
break;
default:
throw new NotSupportedException(entity.Kind.ToString());
}
first = false;
}
RunTransforms(syntaxTree, decompileRun, parentTypeDef != null ? new SimpleTypeResolveContext(parentTypeDef) : new SimpleTypeResolveContext(typeSystem.MainModule));
return syntaxTree;
}
public SyntaxTree DecompileExtension(EntityHandle handle)
{
if (handle.IsNil)
throw new ArgumentNullException(nameof(handle));
syntaxTree = new SyntaxTree();
var namespaces = new HashSet();
RequiredNamespaceCollector.CollectNamespaces(handle, module, namespaces);
var decompileRun = CreateDecompileRun(namespaces);
switch (handle.Kind)
{
case HandleKind.TypeDefinition:
ITypeDefinition typeDef = module.GetDefinition((TypeDefinitionHandle)handle);
syntaxTree.Members.Add(DoDecompile(typeDef, decompileRun, new SimpleTypeResolveContext(typeDef), asExtension: true));
RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(typeDef));
break;
case HandleKind.MethodDefinition:
IMethod methodDef = module.GetDefinition((MethodDefinitionHandle)handle);
var extensionInfo = methodDef.ResolveExtensionInfo();
Debug.Assert(extensionInfo != null);
var memberInfo = extensionInfo.InfoOfExtensionMember((IMethod)methodDef.MemberDefinition).GetValueOrDefault();
var subst = new TypeParameterSubstitution(memberInfo.ExtensionGroupingTypeParameters, null);
methodDef = methodDef.Specialize(subst);
EntityDeclaration entity = DoDecompile(methodDef, decompileRun, new SimpleTypeResolveContext(methodDef), extensionInfo);
syntaxTree.Members.Add(entity);
RemoveAttribute(entity, KnownAttribute.ExtensionMarker);
RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(methodDef.DeclaringTypeDefinition));
break;
case HandleKind.PropertyDefinition:
IProperty propDef = module.GetDefinition((PropertyDefinitionHandle)handle);
extensionInfo = propDef.ResolveExtensionInfo();
Debug.Assert(extensionInfo != null);
var accessor = propDef.Getter ?? propDef.Setter;
memberInfo = extensionInfo.InfoOfExtensionMember((IMethod)accessor!.MemberDefinition).GetValueOrDefault();
subst = new TypeParameterSubstitution(memberInfo.ExtensionGroupingTypeParameters, null);
propDef = (IProperty)propDef.Specialize(subst);
EntityDeclaration prop = DoDecompile(propDef, decompileRun, new SimpleTypeResolveContext(propDef), extensionInfo);
syntaxTree.Members.Add(prop);
RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
if (propDef.Getter != null)
{
RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker);
}
if (propDef.Setter != null)
{
RemoveAttribute(prop.GetChild(Slots.Setter)!, KnownAttribute.ExtensionMarker);
}
RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(propDef.DeclaringTypeDefinition));
break;
default:
throw new NotSupportedException($"HandleKind {handle.Kind} is not supported!");
}
return syntaxTree;
}
ITypeDefinition? FindCommonDeclaringTypeDefinition(ITypeDefinition? a, ITypeDefinition? b)
{
if (a == null || b == null)
return null;
var declaringTypes = a.GetDeclaringTypeDefinitions();
var set = new HashSet(b.GetDeclaringTypeDefinitions());
return declaringTypes.FirstOrDefault(set.Contains);
}
///
/// Decompile the specified types and/or members.
///
public string DecompileAsString(params EntityHandle[] definitions)
{
return SyntaxTreeToString(Decompile(definitions));
}
///
/// Decompile the specified types and/or members.
///
public string DecompileAsString(IEnumerable definitions)
{
return SyntaxTreeToString(Decompile(definitions));
}
readonly Dictionary partialTypes = new();
public void AddPartialTypeDefinition(PartialTypeInfo info)
{
if (!partialTypes.TryGetValue(info.DeclaringTypeDefinitionHandle, out var existingInfo))
{
partialTypes.Add(info.DeclaringTypeDefinitionHandle, info);
}
else
{
existingInfo.AddDeclaredMembers(info);
}
}
IEnumerable AddInterfaceImplHelpers(
EntityDeclaration memberDecl, IMethod method,
TypeSystemAstBuilder astBuilder)
{
if (memberDecl.GetChild(Slots.PrivateImplementationType) is not null)
{
yield break; // cannot create forwarder for existing explicit interface impl
}
if (method.IsStatic)
{
yield break; // cannot create forwarder for static interface impl
}
if (memberDecl.HasModifier(Modifiers.Extern))
{
yield break; // cannot create forwarder for extern method
}
var genericContext = new Decompiler.TypeSystem.GenericContext(method);
var methodHandle = (MethodDefinitionHandle)method.MetadataToken;
foreach (var h in methodHandle.GetMethodImplementations(metadata))
{
var mi = metadata.GetMethodImplementation(h);
IMethod m = module.ResolveMethod(mi.MethodDeclaration, genericContext);
if (m == null || m.DeclaringType.Kind != TypeKind.Interface)
continue;
var methodDecl = new MethodDeclaration();
// EntityDeclaration.ReturnType is typed non-null but its getter yields null when the Type
// slot is empty; leave the forwarder's (already empty) return-type slot untouched in that case.
if (memberDecl.ReturnType is { } memberReturnType)
methodDecl.ReturnType = memberReturnType.Clone();
methodDecl.PrivateImplementationType = astBuilder.ConvertType(m.DeclaringType.GetInterfaceAsImplementedBy(method.DeclaringType));
methodDecl.Name = m.Name;
methodDecl.TypeParameters.AddRange(memberDecl.GetChildren(Slots.TypeParameter)
.Select(n => (TypeParameterDeclaration)n.Clone()));
methodDecl.Parameters.AddRange(memberDecl.GetChildren(Slots.Parameter).Select(n => n.Clone()));
// Constraints are not copied because explicit interface implementations cannot have constraints. CS0460
methodDecl.Body = new BlockStatement();
var commentStatement = new EmptyStatement();
commentStatement.AddTrailingTrivia(new Comment(
"ILSpy generated this explicit interface implementation from .override directive in " + memberDecl.Name));
methodDecl.Body.Add(commentStatement);
var forwardingCall = new InvocationExpression(new MemberReferenceExpression(new ThisReferenceExpression(), memberDecl.Name,
methodDecl.TypeParameters.Select(tp => new SimpleType(tp.Name))),
methodDecl.Parameters.Select(ForwardParameter)
);
if (m.ReturnType.IsKnownType(KnownTypeCode.Void))
{
methodDecl.Body.Add(new ExpressionStatement(forwardingCall));
}
else
{
methodDecl.Body.Add(new ReturnStatement(forwardingCall));
}
yield return methodDecl;
}
}
Expression ForwardParameter(ParameterDeclaration p)
{
switch (p.ParameterModifier)
{
case ReferenceKind.None:
return new IdentifierExpression(p.Name!);
case ReferenceKind.Ref:
case ReferenceKind.RefReadOnly:
return new DirectionExpression(FieldDirection.Ref, new IdentifierExpression(p.Name!));
case ReferenceKind.Out:
return new DirectionExpression(FieldDirection.Out, new IdentifierExpression(p.Name!));
case ReferenceKind.In:
return new DirectionExpression(FieldDirection.In, new IdentifierExpression(p.Name!));
default:
throw new NotSupportedException();
}
}
///
/// Sets new modifier if the member hides some other member from a base type.
///
/// The node of the member which new modifier state should be determined.
void SetNewModifier(EntityDeclaration member)
{
if (member is ExtensionDeclaration)
return;
var entity = (IEntity)member.GetSymbol()!;
var lookup = new MemberLookup(entity.DeclaringTypeDefinition, entity.ParentModule);
var baseTypes = entity.DeclaringType.GetNonInterfaceBaseTypes().Where(t => entity.DeclaringType != t).ToList();
// A constant, field, property, event, or type introduced in a class or struct hides all base class members with the same name.
bool hideBasedOnSignature = !(entity is ITypeDefinition
|| entity.SymbolKind == SymbolKind.Field
|| entity.SymbolKind == SymbolKind.Property
|| entity.SymbolKind == SymbolKind.Event);
const GetMemberOptions options = GetMemberOptions.IgnoreInheritedMembers | GetMemberOptions.ReturnMemberDefinitions;
if (HidesMemberOrTypeOfBaseType())
member.Modifiers |= Modifiers.New;
bool HidesMemberOrTypeOfBaseType()
{
var parameterListComparer = ParameterListComparer.WithOptions(includeModifiers: true);
foreach (IType baseType in baseTypes)
{
if (!hideBasedOnSignature)
{
if (baseType.GetNestedTypes(t => t.Name == entity.Name && lookup.IsAccessible(t, true), options).Any())
return true;
if (baseType.GetMembers(m => m.Name == entity.Name && m.SymbolKind != SymbolKind.Indexer && lookup.IsAccessible(m, true), options).Any())
return true;
}
else
{
if (entity.SymbolKind == SymbolKind.Indexer)
{
// An indexer introduced in a class or struct hides all base class indexers with the same signature (parameter count and types).
if (baseType.GetProperties(p => p.SymbolKind == SymbolKind.Indexer && lookup.IsAccessible(p, true))
.Any(p => parameterListComparer.Equals(((IProperty)entity).Parameters, p.Parameters)))
{
return true;
}
}
else if (entity.SymbolKind == SymbolKind.Method)
{
// A method introduced in a class or struct hides all non-method base class members with the same name, and all
// base class methods with the same signature (method name and parameter count, modifiers, and types).
if (baseType.GetMembers(m => m.SymbolKind != SymbolKind.Indexer
&& m.SymbolKind != SymbolKind.Constructor
&& m.SymbolKind != SymbolKind.Destructor
&& m.Name == entity.Name && lookup.IsAccessible(m, true))
.Any(m => m.SymbolKind != SymbolKind.Method ||
(((IMethod)entity).TypeParameters.Count == ((IMethod)m).TypeParameters.Count
&& parameterListComparer.Equals(((IMethod)entity).Parameters, ((IMethod)m).Parameters))))
{
return true;
}
}
}
}
return false;
}
}
///
/// Gets whether the method is the managed entry point of the module, or - for an async
/// top-level program - the method holding the top-level statements, which the
/// compiler-generated entry point only awaits.
///
bool IsEntryPoint(IMethod method)
{
var corHeader = module.MetadataFile.CorHeader;
if (corHeader == null)
return false;
// the entry point of a multi-module assembly is in another module, and the token is
// then a File token instead of a method definition
var entryPoint = MetadataTokenHelpers.EntityHandleOrNil(corHeader.EntryPointTokenOrRelativeVirtualAddress);
if (entryPoint.IsNil || entryPoint.Kind != HandleKind.MethodDefinition)
return false;
if (method.MetadataToken == entryPoint)
return true;
// An async top-level program compiles to '$' holding the statements plus a
// '' entry point that awaits it. The latter is hidden, so the name has to be
// given to the former; without AsyncAwait it stays visible and keeps the name.
if (!settings.AsyncAwait
|| !AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module.MetadataFile, (MethodDefinitionHandle)entryPoint))
{
return false;
}
return method.Name == "$"
&& method.DeclaringTypeDefinition?.MetadataToken == metadata.GetMethodDefinition((MethodDefinitionHandle)entryPoint).GetDeclaringType();
}
void FixParameterNames(EntityDeclaration entity)
{
int i = 0;
foreach (var parameter in entity.GetChildren(Slots.Parameter))
{
if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList())
{
// needs to be consistent with logic in ILReader.CreateILVariable
parameter.Name = "P_" + i;
}
i++;
}
}
EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun, ITypeResolveContext decompilationContext, bool asExtension = false)
{
Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef);
DecompilerEventSource.Log.DecompileTypeStart(typeDef);
var entityMap = new MultiDictionary();
var workList = new Queue();
TypeSystemAstBuilder typeSystemAstBuilder;
try
{
typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
EntityDeclaration entityDecl;
if (asExtension)
{
var extensionInfo = typeDef.DeclaringTypeDefinition?.ExtensionInfo ?? typeDef.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
Debug.Assert(extensionInfo != null);
extensionInfo.IsExtensionMarkerType(typeDef, out var extensionGroup);
entityDecl = typeSystemAstBuilder.ConvertExtension(extensionGroup);
}
else
{
entityDecl = typeSystemAstBuilder.ConvertEntity(typeDef);
}
if (entityDecl is DelegateDeclaration delegateDeclaration)
{
// Fix empty parameter names in delegate declarations
FixParameterNames(delegateDeclaration);
}
if (entityDecl is not TypeDeclaration typeDecl)
{
if (entityDecl is ExtensionDeclaration ext && settings.ExtensionMembers)
{
var extensionInfo = typeDef.DeclaringTypeDefinition!.ExtensionInfo ?? typeDef.DeclaringTypeDefinition.DeclaringTypeDefinition!.ExtensionInfo;
extensionInfo!.IsExtensionMarkerType(typeDef, out var group);
DoDecompileExtensionMembers(ext, group.Marker, extensionInfo);
}
// e.g. DelegateDeclaration
return entityDecl;
}
bool isRecord = typeDef.Kind switch {
TypeKind.Class => settings.RecordClasses && typeDef.IsRecord,
TypeKind.Struct => settings.RecordStructs && typeDef.IsRecord,
_ => false,
};
RecordDecompiler? recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
if (recordDecompiler != null)
decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler);
// With C# 9 records, the relative order of fields and properties matters:
IEnumerable fieldsAndProperties = isRecord
? recordDecompiler!.FieldsAndProperties
: typeDef.Fields.Concat(typeDef.Properties);
// For COM interop scenarios, the relative order of virtual functions/properties matters:
IEnumerable allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) :
fieldsAndProperties.Concat(typeDef.Events).Concat(typeDef.Methods);
var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers).ToArray();
if (!partialTypes.TryGetValue((TypeDefinitionHandle)typeDef.MetadataToken, out var partialTypeInfo))
{
partialTypeInfo = null;
}
if (settings.ExtensionMembers)
{
foreach (var group in typeDef.ExtensionInfo?.ExtensionGroups ?? [])
{
var ext = (ExtensionDeclaration)typeSystemAstBuilder.ConvertExtension(group);
DoDecompileExtensionMembers(ext, group.Marker, typeDef.ExtensionInfo!);
typeDecl.Members.Add(ext);
}
}
// Decompile members that are not compiler-generated.
foreach (var entity in allOrderedEntities)
{
if (entity.MetadataToken.IsNil)
{
continue;
}
if (MemberIsHidden(module.MetadataFile, entity.MetadataToken, settings)
&& !IsBackingFieldOfNonAutomaticEvent(entity))
{
continue;
}
DoDecompileMember(entity, recordDecompiler, partialTypeInfo, typeDef.ExtensionInfo);
}
// Decompile compiler-generated members that are still needed.
while (workList.Count > 0)
{
var entity = workList.Dequeue();
if (entityMap.Contains(entity) || entity.MetadataToken.IsNil)
{
// Member is already decompiled.
continue;
}
DoDecompileMember(entity, recordDecompiler, partialTypeInfo, typeDef.ExtensionInfo);
}
// Add all decompiled members to syntax tree in the correct order.
foreach (var member in allOrderedEntities)
{
typeDecl.Members.AddRange(entityMap[member]);
}
if (typeDecl.Members.OfType().Any(idx => idx.PrivateImplementationType is null))
{
// Remove the [DefaultMember] attribute if the class contains indexers
RemoveAttribute(typeDecl, KnownAttribute.DefaultMember);
}
if (partialTypeInfo != null)
{
typeDecl.Modifiers |= Modifiers.Partial;
}
if (settings.IntroduceRefModifiersOnStructs)
{
RemoveObsoleteAttribute(typeDecl, "Types with embedded references are not supported in this version of your compiler.");
RemoveCompilerFeatureRequiredAttribute(typeDecl, "RefStructs");
}
if (settings.RequiredMembers)
{
RemoveAttribute(typeDecl, KnownAttribute.Required);
}
if (typeDecl.ClassType == ClassType.Enum)
{
Debug.Assert(typeDef.Kind == TypeKind.Enum);
EnumValueDisplayMode displayMode = DetectBestEnumValueDisplayMode(typeDef, module.MetadataFile);
switch (displayMode)
{
case EnumValueDisplayMode.FirstOnly:
foreach (var enumMember in typeDecl.Members.OfType().Skip(1))
{
enumMember.Initializer = null;
}
break;
case EnumValueDisplayMode.None:
foreach (var enumMember in typeDecl.Members.OfType())
{
enumMember.Initializer = null;
if (enumMember.GetSymbol() is IField f && f.GetConstantValue() == null)
{
enumMember.AddLeadingTrivia(new Comment(" error: enumerator has no value"));
}
}
break;
case EnumValueDisplayMode.All:
// nothing needs to be changed.
break;
case EnumValueDisplayMode.AllHex:
foreach (var enumMember in typeDecl.Members.OfType())
{
var constantValue = (enumMember.GetSymbol() as IField)!.GetConstantValue();
if (constantValue == null || enumMember.Initializer is not PrimitiveExpression pe)
{
continue;
}
long initValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false);
if (initValue >= 10)
{
pe.Format = LiteralFormat.HexadecimalNumber;
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
foreach (var item in typeDecl.Members)
{
if (item is not EnumMemberDeclaration)
{
item.AddLeadingTrivia(new Comment(" error: nested types are not permitted in C#."));
}
}
}
return typeDecl;
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
{
throw new DecompilerException(module, typeDef, innerException);
}
finally
{
DecompilerEventSource.Log.DecompileTypeStop(typeDef);
}
// MemberIsHidden identifies event backing fields from the metadata name association
// alone. When the event's accessors turn out not to be compiler-generated, the event
// is decompiled with explicit accessors and no field-like declaration takes the
// field's place, so the field must stay in the output even if no decompiled body
// references it (referenced hidden members are re-added via the work list).
bool IsBackingFieldOfNonAutomaticEvent(IEntity entity)
{
if (entity is not IField field || !settings.AutomaticEvents)
return false;
if (!module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle))
return false;
if (AutoEventDecompiler.IsAutomaticEvent(typeSystem, module.GetDefinition(eventHandle), decompileRun, CancellationToken, out _))
return false;
// The field may be hidden for an unrelated reason as well; keep it hidden then.
var settingsWithoutAutomaticEvents = settings.Clone();
settingsWithoutAutomaticEvents.AutomaticEvents = false;
return !MemberIsHidden(module.MetadataFile, field.MetadataToken, settingsWithoutAutomaticEvents);
}
void DoDecompileMember(IEntity entity, RecordDecompiler? recordDecompiler, PartialTypeInfo? partialType, ExtensionInfo? extensionInfo)
{
if (partialType != null && partialType.IsDeclaredMember(entity.MetadataToken))
{
return;
}
if (settings.ExtensionMembers && extensionInfo != null)
{
switch (entity)
{
case ITypeDefinition td when extensionInfo.IsExtensionGroupType(td) || extensionInfo.IsExtensionMarkerType(td, out _):
return;
case IMethod m when extensionInfo.InfoOfImplementationMember(m).HasValue:
return;
}
}
EntityDeclaration entityDecl;
switch (entity)
{
case IField field:
if (typeDef.Kind == TypeKind.Enum && !field.IsConst)
{
return;
}
if (TransformFieldAndConstructorInitializers.IsGeneratedPrimaryConstructorBackingField(field))
{
return;
}
entityDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field));
entityMap.Add(field, entityDecl);
break;
case IProperty property:
if (recordDecompiler?.PropertyIsGenerated(property) == true)
{
return;
}
if (property.IsParameterizedProperty())
{
foreach (var accessorDecl in DecompileParameterizedProperty(property, decompileRun, decompilationContext, null))
{
entityMap.Add(property, accessorDecl);
EnqueueReferencedMembers(accessorDecl);
}
return;
}
entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property), null);
entityMap.Add(property, entityDecl);
break;
case IMethod method:
if (recordDecompiler?.MethodIsGenerated(method) == true)
{
return;
}
entityDecl = DoDecompile(method, decompileRun, decompilationContext.WithCurrentMember(method), null);
entityMap.Add(method, entityDecl);
foreach (var helper in AddInterfaceImplHelpers(entityDecl, method, typeSystemAstBuilder))
{
entityMap.Add(method, helper);
}
break;
case IEvent @event:
entityDecl = DoDecompile(@event, decompileRun, decompilationContext.WithCurrentMember(@event));
entityMap.Add(@event, entityDecl);
break;
case ITypeDefinition type:
entityDecl = DoDecompile(type, decompileRun, decompilationContext.WithCurrentTypeDefinition(type));
SetNewModifier(entityDecl);
entityMap.Add(type, entityDecl);
break;
default:
throw new ArgumentOutOfRangeException("Unexpected member type");
}
EnqueueReferencedMembers(entityDecl);
void EnqueueReferencedMembers(EntityDeclaration decl)
{
foreach (var node in decl.Descendants)
{
var rr = node.GetResolveResult();
if (rr is MemberResolveResult mrr
&& mrr.Member.DeclaringTypeDefinition == typeDef
&& !(mrr.Member is IMethod { IsLocalFunction: true }))
{
// In generic types the reference is to a member specialized by the type's
// own type parameters, but entityMap and the dequeue dedupe are keyed by
// the definition; enqueueing the specialized member would decompile the
// member under a key the output pass never looks up.
workList.Enqueue(mrr.Member.MemberDefinition);
}
else if (rr is TypeResolveResult trr
&& trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef)
{
workList.Enqueue(trr.Type.GetDefinition()!);
}
}
}
}
void DoDecompileExtensionMembers(ExtensionDeclaration ext, IMethod marker, ExtensionInfo extensionInfo)
{
foreach (var member in extensionInfo.GetMembersOfGroup(marker))
{
var extMember = member;
if (entityMap.Contains(extMember) || extMember.MetadataToken.IsNil)
{
// Member is already decompiled.
continue;
}
EntityDeclaration extMemberDecl;
switch (extMember)
{
case IProperty p:
var prop = DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), extensionInfo);
RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
if (p.Getter != null)
{
RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker);
}
if (p.Setter != null)
{
RemoveAttribute(prop.GetChild(Slots.Setter)!, KnownAttribute.ExtensionMarker);
}
extMemberDecl = prop;
break;
case IMethod m:
var meth = DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), extensionInfo);
RemoveAttribute(meth, KnownAttribute.ExtensionMarker);
extMemberDecl = meth;
break;
default:
throw new NotSupportedException($"Extension member {extMember} is not supported for decompilation.");
}
ext.Members.Add(extMemberDecl);
entityMap.Add(extMember, extMemberDecl);
}
}
}
EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, MetadataFile module)
{
if (typeDef.HasAttribute(KnownAttribute.Flags))
return EnumValueDisplayMode.AllHex;
bool first = true;
long firstValue = 0, previousValue = 0;
bool allPowersOfTwo = true;
bool allConsecutive = true;
foreach (var field in typeDef.Fields)
{
if (MemberIsHidden(module, field.MetadataToken, settings))
continue;
object? constantValue = field.GetConstantValue();
if (constantValue == null)
continue;
long currentValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false);
allConsecutive = allConsecutive && (first || previousValue + 1 == currentValue);
// N & (N - 1) == 0, iff N is a power of 2, for all N != 0.
// We define that 0 is a power of 2 in the context of enum values.
allPowersOfTwo = allPowersOfTwo && unchecked(currentValue & (currentValue - 1)) == 0;
if (first)
{
firstValue = currentValue;
first = false;
}
else if (currentValue <= previousValue)
{
// If the values are out of order, we fallback to displaying all values.
return EnumValueDisplayMode.All;
}
else if (!allConsecutive && !allPowersOfTwo)
{
// We already know that the values are neither consecutive nor all powers of 2,
// so we can abort, and just display all values as-is.
return EnumValueDisplayMode.All;
}
previousValue = currentValue;
}
if (allPowersOfTwo)
{
if (previousValue > 8)
{
// If all values are powers of 2 and greater 8, display all enum values, but use hex.
return EnumValueDisplayMode.AllHex;
}
else if (!allConsecutive)
{
// If all values are powers of 2, display all enum values.
return EnumValueDisplayMode.All;
}
}
if (settings.AlwaysShowEnumMemberValues)
{
// The user always wants to see all enum values, but we know hex is not necessary.
return EnumValueDisplayMode.All;
}
// We know that all values are consecutive, so if the first value is not 0
// display the first enum value only.
return firstValue == 0 ? EnumValueDisplayMode.None : EnumValueDisplayMode.FirstOnly;
}
EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
{
Debug.Assert(decompilationContext.CurrentMember == method);
DecompilerEventSource.Log.DecompileMemberStart(method, DecompiledMemberKind.Method);
try
{
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
var methodDecl = typeSystemAstBuilder.ConvertEntity(method);
int lastDot = method.Name.LastIndexOf('.');
if (methodDecl is not OperatorDeclaration && method.IsExplicitInterfaceImplementation && lastDot >= 0)
{
methodDecl.Name = method.Name.Substring(lastDot + 1);
}
if (method.HasGeneratedName() && IsEntryPoint(method))
{
// Roslyn names the entry point of a top-level program '$', which cannot be
// declared in C#. Only a method called 'Main' is accepted as an entry point, so
// without this the decompiled program does not compile (CS5001).
methodDecl.Name = "Main";
}
FixParameterNames(methodDecl);
var methodDefinition = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
if (!settings.LocalFunctions && LocalFunctionDecompiler.LocalFunctionNeedsAccessibilityChange(method.ParentModule!.MetadataFile, (MethodDefinitionHandle)method.MetadataToken))
{
// if local functions are not active and we're dealing with a local function,
// reduce the visibility of the method to private,
// otherwise this leads to compile errors because the display classes have lesser accessibility.
// Note: removing and then adding the static modifier again is necessary to set the private modifier before all other modifiers.
methodDecl.Modifiers &= ~(Modifiers.Internal | Modifiers.Static);
methodDecl.Modifiers |= Modifiers.Private | (method.IsStatic ? Modifiers.Static : 0);
}
if (methodDefinition.HasBody())
{
DecompileBody(method, methodDecl, decompileRun, decompilationContext, extensionInfo);
}
else if (!method.IsAbstract && method.DeclaringType.Kind != TypeKind.Interface)
{
methodDecl.Modifiers |= Modifiers.Extern;
}
// Accessors qualify only when they are emitted as ordinary methods (parameterized
// properties); an Accessor node cannot carry the 'new' modifier.
if ((method.SymbolKind == SymbolKind.Method || (method.SymbolKind == SymbolKind.Accessor && methodDecl is MethodDeclaration))
&& !method.IsExplicitInterfaceImplementation
&& methodDefinition.HasFlag(System.Reflection.MethodAttributes.Virtual) == methodDefinition.HasFlag(System.Reflection.MethodAttributes.NewSlot))
{
SetNewModifier(methodDecl);
}
else if (!method.IsStatic && !method.IsExplicitInterfaceImplementation
&& !method.IsVirtual && method.IsOverride
&& InheritanceHelper.GetBaseMember(method) == null && IsTypeHierarchyKnown(method.DeclaringType))
{
methodDecl.Modifiers &= ~Modifiers.Override;
if (!method.DeclaringTypeDefinition!.IsSealed)
{
methodDecl.Modifiers |= Modifiers.Virtual;
}
}
if (IsCovariantReturnOverride(method))
{
RemoveAttribute(methodDecl, KnownAttribute.PreserveBaseOverrides);
methodDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual);
methodDecl.Modifiers |= Modifiers.Override;
}
if (method.IsConstructor && settings.RequiredMembers && RemoveCompilerFeatureRequiredAttribute(methodDecl, "RequiredMembers"))
{
RemoveObsoleteAttribute(methodDecl, "Constructors of types with required members are not supported in this version of your compiler.");
}
return methodDecl;
bool IsTypeHierarchyKnown(IType type)
{
var definition = type.GetDefinition();
if (definition == null)
{
return false;
}
if (decompileRun.TypeHierarchyIsKnown.TryGetValue(definition, out var value))
return value;
value = method.DeclaringType.GetNonInterfaceBaseTypes().All(t => t.Kind != TypeKind.Unknown);
decompileRun.TypeHierarchyIsKnown.Add(definition, value);
return value;
}
}
finally
{
DecompilerEventSource.Log.DecompileMemberStop(method, DecompiledMemberKind.Method);
}
}
private bool IsCovariantReturnOverride(IEntity entity)
{
if (!settings.CovariantReturns)
return false;
if (!entity.HasAttribute(KnownAttribute.PreserveBaseOverrides))
return false;
return true;
}
internal static bool IsWindowsFormsInitializeComponentMethod(IMethod method)
{
return method.ReturnType.Kind == TypeKind.Void && method.Name == "InitializeComponent" && method.DeclaringTypeDefinition!.GetNonInterfaceBaseTypes().Any(t => t.FullName == "System.Windows.Forms.Control");
}
void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
{
try
{
var ilReader = new ILReader(typeSystem.MainModule) {
UseDebugSymbols = settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider
};
int parameterOffset = 0;
if (extensionInfo != null)
{
if (!method.IsStatic)
parameterOffset = 1; // implementation method has an additional receiver parameter
method = extensionInfo.InfoOfExtensionMember((IMethod)method.MemberDefinition)!.Value.ImplementationMethod;
}
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
BlockStatement body = new BlockStatement();
MethodBodyBlock methodBody;
try
{
methodBody = module.MetadataFile.GetMethodBody(methodDef.RelativeVirtualAddress);
}
catch (BadImageFormatException ex)
{
body = new BlockStatement();
var commentStatement = new EmptyStatement();
commentStatement.AddTrailingTrivia(new Comment("Invalid MethodBodyBlock: " + ex.Message));
body.Statements.Add(commentStatement);
entityDecl.AddChild(body, Slots.Body);
return;
}
var function = ilReader.ReadIL((MethodDefinitionHandle)method.MetadataToken, methodBody, cancellationToken: CancellationToken);
function.CheckInvariant(ILPhase.Normal);
AddAnnotationsToDeclaration(method, entityDecl, function, parameterOffset);
var localSettings = settings.Clone();
if (IsWindowsFormsInitializeComponentMethod(method))
{
localSettings.UseImplicitMethodGroupConversion = false;
localSettings.UsingDeclarations = false;
localSettings.AlwaysCastTargetsOfExplicitInterfaceImplementationCalls = true;
localSettings.NamedArguments = false;
localSettings.AlwaysQualifyMemberReferences = true;
}
var context = new ILTransformContext(function, typeSystem, DebugInfoProvider, localSettings) {
CancellationToken = CancellationToken,
DecompileRun = decompileRun
};
foreach (var transform in ilTransforms)
{
CancellationToken.ThrowIfCancellationRequested();
transform.Run(function, context);
function.CheckInvariant(ILPhase.Normal);
// When decompiling definitions only, we can cancel decompilation of all steps
// after yield and async detection, because only those are needed to properly set
// IsAsync/IsIterator flags on ILFunction.
if (!localSettings.DecompileMemberBodies && transform is AsyncAwaitDecompiler)
break;
}
// Generate C# AST only if bodies should be displayed.
if (localSettings.DecompileMemberBodies)
{
AddDefinesForConditionalAttributes(function, decompileRun);
var statementBuilder = new StatementBuilder(
typeSystem,
decompilationContext,
function,
localSettings,
decompileRun,
CancellationToken
);
body = statementBuilder.ConvertAsBlock(function.Body);
var warningAnchor = body.Statements.FirstOrDefault();
foreach (string warning in function.Warnings)
{
var warningStatement = new EmptyStatement();
warningStatement.AddTrailingTrivia(new Comment(warning));
if (warningAnchor != null)
body.Statements.InsertBefore(warningAnchor, warningStatement);
else
body.Statements.Add(warningStatement);
}
entityDecl.AddChild(body, Slots.Body);
}
CleanUpMethodDeclaration(entityDecl, body, function, localSettings.DecompileMemberBodies);
}
catch (Exception innerException) when (!(innerException is OperationCanceledException))
{
// One method the decompiler cannot handle must not cost the user the type or, when
// exporting a project, the assembly around it: keep the signature, put the error in
// front of it, and let the remaining members decompile.
errors.Add(innerException as DecompilerException ?? new DecompilerException(module, method, innerException));
entityDecl.GetChild(Slots.Body)?.Remove();
if (settings.DecompileMemberBodies)
{
// The error goes where the code would have been, the same way a warning about the
// code does - and the body keeps the member's shape intact.
var errorBody = new BlockStatement();
var errorStatement = new EmptyStatement();
foreach (string line in GetErrorCommentLines(innerException))
{
errorStatement.AddTrailingTrivia(new Comment(" " + line));
}
errorBody.Statements.Add(errorStatement);
entityDecl.AddChild(errorBody, Slots.Body);
}
else
{
// Definitions-only output has no body to put the error in.
foreach (string line in GetErrorCommentLines(innerException))
{
entityDecl.AddLeadingTrivia(new Comment(" " + line));
}
}
}
}
internal static void AddAnnotationsToDeclaration(IMethod method, EntityDeclaration entityDecl, ILFunction function, int parameterOffset = 0)
{
int i = parameterOffset;
var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index!.Value);
foreach (var parameter in entityDecl.GetChildren(Slots.Parameter))
{
if (parameters.TryGetValue(i, out var v))
parameter.AddAnnotation(new ILVariableResolveResult(v, method.Parameters[i].Type));
i++;
}
entityDecl.AddAnnotation(function);
}
internal static void CleanUpMethodDeclaration(EntityDeclaration entityDecl, BlockStatement? body, ILFunction function, bool decompileBody = true)
{
if (function.IsIterator)
{
if (decompileBody && !body!.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement))
{
body.Add(new YieldBreakStatement());
}
if (function.IsAsync)
{
RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine);
}
else
{
RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
}
if (function.StateMachineCompiledWithMono)
{
RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden);
}
if (function.StateMachineCompiledWithLegacyVisualBasic)
{
RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough);
if (function.Method?.IsAccessor == true && entityDecl.Parent is EntityDeclaration parentDecl)
{
RemoveAttribute(parentDecl, KnownAttribute.DebuggerStepThrough);
}
}
}
if (function.IsAsync)
{
entityDecl.Modifiers |= Modifiers.Async;
RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine);
RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough);
}
}
internal static bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType)
{
bool found = false;
foreach (var section in entityDecl.Attributes)
{
foreach (var attr in section.Attributes)
{
var symbol = attr.Type.GetSymbol();
if (symbol is ITypeDefinition td && td.FullTypeName == attributeType.GetTypeName())
{
attr.Remove();
found = true;
}
}
if (section.Attributes.Count == 0)
{
section.Remove();
}
}
return found;
}
internal static bool RemoveCompilerFeatureRequiredAttribute(EntityDeclaration entityDecl, string feature)
{
bool found = false;
foreach (var section in entityDecl.Attributes)
{
foreach (var attr in section.Attributes)
{
var symbol = attr.Type.GetSymbol();
if (symbol is ITypeDefinition td && td.FullTypeName == KnownAttribute.CompilerFeatureRequired.GetTypeName()
&& attr.Arguments.Count == 1 && attr.Arguments.SingleOrDefault() is PrimitiveExpression pe
&& pe.Value is string s && s == feature)
{
attr.Remove();
found = true;
}
}
if (section.Attributes.Count == 0)
{
section.Remove();
}
}
return found;
}
internal static bool RemoveObsoleteAttribute(EntityDeclaration entityDecl, string message)
{
bool found = false;
foreach (var section in entityDecl.Attributes)
{
foreach (var attr in section.Attributes)
{
var symbol = attr.Type.GetSymbol();
if (symbol is ITypeDefinition td && td.FullTypeName == KnownAttribute.Obsolete.GetTypeName()
&& attr.Arguments.Count >= 1 && attr.Arguments.First() is PrimitiveExpression pe
&& pe.Value is string s && s == message)
{
attr.Remove();
found = true;
}
}
if (section.Attributes.Count == 0)
{
section.Remove();
}
}
return found;
}
bool FindAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType, [NotNullWhen(true)] out Syntax.Attribute? attribute)
{
attribute = null;
foreach (var section in entityDecl.Attributes)
{
foreach (var attr in section.Attributes)
{
var symbol = attr.Type.GetSymbol();
if (symbol is ITypeDefinition td && td.FullTypeName == attributeType.GetTypeName())
{
attribute = attr;
return true;
}
}
}
return false;
}
void AddDefinesForConditionalAttributes(ILFunction function, DecompileRun decompileRun)
{
foreach (var call in function.Descendants.OfType())
{
var attr = call.Method.GetAttribute(KnownAttribute.Conditional, inherit: true);
var symbolName = attr?.FixedArguments.FirstOrDefault().Value as string;
if (symbolName == null || !decompileRun.DefinedSymbols.Add(symbolName))
continue;
syntaxTree!.AddLeadingTrivia(new PreProcessorDirective(PreProcessorDirectiveType.Define, symbolName));
}
}
EntityDeclaration DoDecompile(IField field, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
{
Debug.Assert(decompilationContext.CurrentMember == field);
DecompilerEventSource.Log.DecompileMemberStart(field, DecompiledMemberKind.Field);
try
{
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
if (decompilationContext.CurrentTypeDefinition!.Kind == TypeKind.Enum && field.IsConst)
{
var enumDec = new EnumMemberDeclaration { Name = field.Name };
object? constantValue = field.GetConstantValue();
if (constantValue != null)
{
TypeCode underlyingTypeCode = ReflectionHelper.GetTypeCode(decompilationContext.CurrentTypeDefinition.EnumUnderlyingType);
if (underlyingTypeCode is >= TypeCode.SByte and <= TypeCode.UInt64)
{
long initValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constantValue, false);
enumDec.Initializer = typeSystemAstBuilder.ConvertEnumValue(decompilationContext.CurrentTypeDefinition, initValue, field);
}
else
{
// Unusual underlying types (bool, native int, ...) cannot be losslessly
// squeezed through the long-based member-reference beautification.
enumDec.Initializer = typeSystemAstBuilder.ConvertConstantValue(decompilationContext.CurrentTypeDefinition.EnumUnderlyingType!, constantValue);
}
}
enumDec.Attributes.AddRange(field.GetAttributes().Select(a => new AttributeSection(typeSystemAstBuilder.ConvertAttribute(a))));
enumDec.AddAnnotation(new MemberResolveResult(null, field));
return enumDec;
}
bool isMathPIOrE = ((field.Name == "PI" || field.Name == "E") && (field.DeclaringType.FullName == "System.Math" || field.DeclaringType.FullName == "System.MathF"));
typeSystemAstBuilder.UseSpecialConstants = !(field.DeclaringType.Equals(field.ReturnType) || isMathPIOrE);
var fieldDecl = typeSystemAstBuilder.ConvertEntity(field);
SetNewModifier(fieldDecl);
if (settings.RequiredMembers && RemoveAttribute(fieldDecl, KnownAttribute.Required))
{
fieldDecl.Modifiers |= Modifiers.Required;
}
if (settings.FixedBuffers && IsFixedField(field, out var elementType, out var elementCount))
{
var fixedFieldDecl = new FixedFieldDeclaration();
fieldDecl.Attributes.MoveTo(fixedFieldDecl.Attributes);
fixedFieldDecl.Modifiers = fieldDecl.Modifiers;
fixedFieldDecl.ReturnType = typeSystemAstBuilder.ConvertType(elementType);
fixedFieldDecl.Variables.Add(new FixedVariableInitializer(field.Name, new PrimitiveExpression(elementCount)));
fixedFieldDecl.Variables.Single().CopyAnnotationsFrom(((FieldDeclaration)fieldDecl).Variables.Single());
fixedFieldDecl.CopyAnnotationsFrom(fieldDecl);
RemoveAttribute(fixedFieldDecl, KnownAttribute.FixedBuffer);
return fixedFieldDecl;
}
var fieldDefinition = metadata.GetFieldDefinition((FieldDefinitionHandle)field.MetadataToken);
if (fieldDefinition.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
{
// Field data as specified in II.16.3.1 of ECMA-335 6th edition:
// .data I_X = int32(123)
// .field public static int32 _x at I_X
string message;
try
{
var initVal = fieldDefinition.GetInitialValue(module.MetadataFile, TypeSystem);
message = string.Format(" Not supported: data({0}) ", BitConverter.ToString(initVal.ReadBytes(initVal.RemainingBytes)).Replace('-', ' '));
}
catch (BadImageFormatException ex)
{
message = ex.Message;
}
((FieldDeclaration)fieldDecl).Variables.Single().AddTrailingTrivia(new Comment(message, CommentType.MultiLine));
}
return fieldDecl;
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
{
throw new DecompilerException(module, field, innerException);
}
finally
{
DecompilerEventSource.Log.DecompileMemberStop(field, DecompiledMemberKind.Field);
}
}
internal static bool IsFixedField(IField field, [NotNullWhen(true)] out IType? type, out int elementCount)
{
type = null;
elementCount = 0;
IAttribute? attr = field.GetAttribute(KnownAttribute.FixedBuffer);
if (attr != null && attr.FixedArguments.Length == 2)
{
if (attr.FixedArguments[0].Value is IType trr && attr.FixedArguments[1].Value is int length)
{
type = trr;
elementCount = length;
return true;
}
}
return false;
}
///
/// Decompiles a parameterized property (a named property with parameters, e.g. from
/// VB.NET) into declarations of its accessor methods, because C# has no syntax for
/// such properties. The property-level attributes are placed on the first accessor
/// under the 'property:' attribute target, which is not valid for methods and is
/// therefore ignored by the C# compiler (CS0657): recompilation neither loses the
/// attributes from the source nor misapplies them to the accessor method.
///
List DecompileParameterizedProperty(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
{
var result = new List(2);
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
foreach (var accessor in new[] { property.Getter, property.Setter })
{
if (accessor == null)
continue;
var accessorDecl = DoDecompile(accessor, decompileRun, decompilationContext.WithCurrentMember(accessor), extensionInfo);
if (result.Count == 0)
{
accessorDecl.AddLeadingTrivia(new Comment($" C# has no syntax for parameterized property '{property.Name}'."));
var attributes = property.GetAttributes().Select(typeSystemAstBuilder.ConvertAttribute).ToList();
if (attributes.Count > 0)
{
var attrSection = new AttributeSection { AttributeTarget = "property" };
attrSection.Attributes.AddRange(attributes);
accessorDecl.Attributes.InsertAfter(null, attrSection);
accessorDecl.AddLeadingTrivia(new Comment(" Its 'property:' attributes below are ignored by the compiler (CS0657)."));
}
}
result.Add(accessorDecl);
result.AddRange(AddInterfaceImplHelpers(accessorDecl, accessor, typeSystemAstBuilder));
}
return result;
}
EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
{
Debug.Assert(decompilationContext.CurrentMember == property);
DecompilerEventSource.Log.DecompileMemberStart(property, DecompiledMemberKind.Property);
try
{
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
EntityDeclaration propertyDecl = typeSystemAstBuilder.ConvertEntity(property);
if (property.IsExplicitInterfaceImplementation && !property.IsIndexer)
{
int lastDot = property.Name.LastIndexOf('.');
propertyDecl.Name = property.Name.Substring(lastDot + 1);
}
FixParameterNames(propertyDecl);
Accessor? getter, setter;
if (propertyDecl is PropertyDeclaration)
{
getter = ((PropertyDeclaration)propertyDecl).Getter;
setter = ((PropertyDeclaration)propertyDecl).Setter;
}
else
{
getter = ((IndexerDeclaration)propertyDecl).Getter;
setter = ((IndexerDeclaration)propertyDecl).Setter;
}
bool getterHasBody = property.CanGet && property.Getter!.HasBody;
bool setterHasBody = property.CanSet && property.Setter!.HasBody;
if (getterHasBody)
{
DecompileBody(property.Getter!, getter!, decompileRun, decompilationContext, extensionInfo);
}
if (setterHasBody)
{
DecompileBody(property.Setter!, setter!, decompileRun, decompilationContext, extensionInfo);
}
if (!getterHasBody && !setterHasBody && !property.IsAbstract && property.DeclaringType.Kind != TypeKind.Interface)
{
propertyDecl.Modifiers |= Modifiers.Extern;
}
var accessorHandle = (MethodDefinitionHandle)(property.Getter ?? property.Setter)!.MetadataToken;
var accessor = metadata.GetMethodDefinition(accessorHandle);
if (!accessorHandle.GetMethodImplementations(metadata).Any() && accessor.HasFlag(System.Reflection.MethodAttributes.Virtual) == accessor.HasFlag(System.Reflection.MethodAttributes.NewSlot))
{
SetNewModifier(propertyDecl);
}
if (property.CanGet && IsCovariantReturnOverride(property.Getter!))
{
RemoveAttribute(getter!, KnownAttribute.PreserveBaseOverrides);
propertyDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual);
propertyDecl.Modifiers |= Modifiers.Override;
}
if (settings.RequiredMembers && RemoveAttribute(propertyDecl, KnownAttribute.Required))
{
propertyDecl.Modifiers |= Modifiers.Required;
}
return propertyDecl;
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
{
throw new DecompilerException(module, property, innerException);
}
finally
{
DecompilerEventSource.Log.DecompileMemberStop(property, DecompiledMemberKind.Property);
}
}
EntityDeclaration DoDecompile(IEvent ev, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
{
Debug.Assert(decompilationContext.CurrentMember == ev);
DecompilerEventSource.Log.DecompileMemberStart(ev, DecompiledMemberKind.Event);
try
{
bool adderHasBody = ev.CanAdd && ev.AddAccessor!.HasBody;
bool removerHasBody = ev.CanRemove && ev.RemoveAccessor!.HasBody;
var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
IField? backingField = null;
bool isAutomaticEvent = adderHasBody && removerHasBody && decompileRun.Settings.AutomaticEvents
&& AutoEventDecompiler.IsAutomaticEvent(typeSystem, ev, decompileRun, CancellationToken, out backingField);
// A recognized automatic event is built in field-like form directly; its
// compiler-generated accessor bodies are never decompiled. Accessors without
// bodies (abstract, extern, interface members) cannot be expressed as custom
// accessors in C#, so only the field-like form is valid for them as well.
typeSystemAstBuilder.UseCustomEvents = !isAutomaticEvent
&& (ev.IsExplicitInterfaceImplementation
|| adderHasBody
|| removerHasBody);
var eventDecl = typeSystemAstBuilder.ConvertEntity(ev);
int lastDot = ev.Name.LastIndexOf('.');
if (ev.IsExplicitInterfaceImplementation)
{
eventDecl.Name = ev.Name.Substring(lastDot + 1);
}
if (isAutomaticEvent)
{
AutoEventDecompiler.AddFieldLikeEventAttributes((EventDeclaration)eventDecl, typeSystemAstBuilder, ev, backingField!);
}
else
{
if (adderHasBody)
{
DecompileBody(ev.AddAccessor!, ((CustomEventDeclaration)eventDecl).AddAccessor!, decompileRun, decompilationContext, null);
}
if (removerHasBody)
{
DecompileBody(ev.RemoveAccessor!, ((CustomEventDeclaration)eventDecl).RemoveAccessor!, decompileRun, decompilationContext, null);
}
if (!adderHasBody && !removerHasBody && !ev.IsAbstract && ev.DeclaringType.Kind != TypeKind.Interface)
{
eventDecl.Modifiers |= Modifiers.Extern;
}
}
var accessor = metadata.GetMethodDefinition((MethodDefinitionHandle)(ev.AddAccessor ?? ev.RemoveAccessor)!.MetadataToken);
if (accessor.HasFlag(System.Reflection.MethodAttributes.Virtual) == accessor.HasFlag(System.Reflection.MethodAttributes.NewSlot))
{
SetNewModifier(eventDecl);
}
return eventDecl;
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
{
throw new DecompilerException(module, ev, innerException);
}
finally
{
DecompilerEventSource.Log.DecompileMemberStop(ev, DecompiledMemberKind.Event);
}
}
#region Sequence Points
///
/// Creates sequence points for the given syntax tree.
///
/// This only works correctly when the nodes in the syntax tree have line/column information.
///
public Dictionary> CreateSequencePoints(SyntaxTree syntaxTree)
{
SequencePointBuilder spb = new SequencePointBuilder();
syntaxTree.AcceptVisitor(spb);
return spb.GetSequencePoints();
}
#endregion
}
}