Browse Source

Sync Language Decompile methods with WPF; add Warning icon

Assisted-by: Claude:claude-opus-4-7:Claude Code

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
c4673cd006
  1. 1
      ILSpy/Assets/Icons/Warning.svg
  2. 10
      ILSpy/DecompilationOptions.cs
  3. 1
      ILSpy/Images.cs
  4. 375
      ILSpy/Languages/CSharpLanguage.cs
  5. 151
      ILSpy/Languages/ILLanguage.cs
  6. 117
      ILSpy/Languages/Language.cs

1
ILSpy/Assets/Icons/Warning.svg

@ -0,0 +1 @@ @@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-yellow{fill:#fc0}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M9 0l7 14-2 2H2l-2-2L7 0h2z" id="outline"/><path class="icon-vs-yellow" d="M8.382 1h-.764L1.217 13.803 2.5 15h11l1.283-1.197L8.382 1zM9 13H7v-2h2v2zm0-3H7V5h2v5z" id="iconBg"/><path class="icon-black" d="M9 10H7V5h2v5zm0 1H7v2h2v-2z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 530 B

10
ILSpy/DecompilationOptions.cs

@ -32,6 +32,16 @@ namespace ILSpy @@ -32,6 +32,16 @@ namespace ILSpy
public DecompilerSettings DecompilerSettings { get; }
public CancellationToken CancellationToken { get; set; }
/// <summary>Mirrors WPF: full module decompilation rather than just the selected member.</summary>
public bool FullDecompilation { get; set; }
/// <summary>Mirrors WPF: target directory for project export. Always null in the
/// Avalonia host today (no save dialog wired up); kept for signature parity.</summary>
public string? SaveAsProjectDirectory { get; set; }
/// <summary>Mirrors WPF: escape invalid C# identifiers so the output compiles.</summary>
public bool EscapeInvalidIdentifiers { get; set; }
public DecompilationOptions(DecompilerSettings settings)
{
DecompilerSettings = settings;

1
ILSpy/Images.cs

@ -61,6 +61,7 @@ namespace ILSpy.Images @@ -61,6 +61,7 @@ namespace ILSpy.Images
public static readonly IImage Assembly = LoadSvg(nameof(Assembly));
public static readonly IImage AssemblyLoading = LoadSvg(nameof(AssemblyLoading));
public static readonly IImage AssemblyWarning = LoadSvg(nameof(AssemblyWarning));
public static readonly IImage Warning = LoadSvg(nameof(Warning));
public static readonly IImage FindAssembly = LoadSvg(nameof(FindAssembly));
public static readonly IImage Library = LoadSvg(nameof(Library));
public static readonly IImage NuGet = LoadPng(nameof(NuGet));

375
ILSpy/Languages/CSharpLanguage.cs

@ -19,10 +19,10 @@ @@ -19,10 +19,10 @@
using System;
using System.Collections.Generic;
using System.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using AvaloniaEdit.Highlighting;
@ -31,9 +31,12 @@ using ICSharpCode.Decompiler.CSharp; @@ -31,9 +31,12 @@ using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX;
using ConversionFlags = ICSharpCode.Decompiler.Output.ConversionFlags;
@ -77,10 +80,6 @@ namespace ILSpy.Languages @@ -77,10 +80,6 @@ namespace ILSpy.Languages
public override RichText GetRichTextTooltip(IEntity entity)
{
// Mirrors ICSharpCode.ILSpy.Languages.CSharpLanguage.GetRichTextTooltip: hand the
// entity to a CSharpAmbience whose token sink is wrapped in CSharpHighlightingTokenWriter
// so we get semantic colours alongside the text. Body and trailing-return-type bits
// are stripped — the tooltip is a one-line signature.
ArgumentNullException.ThrowIfNull(entity);
var flags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
var output = new StringWriter();
@ -92,66 +91,302 @@ namespace ILSpy.Languages @@ -92,66 +91,302 @@ namespace ILSpy.Languages
return new RichText(output.ToString(), writer.HighlightingModel);
}
public override void DecompileType(ITypeDefinition type, ITextOutput output, DecompilationOptions options)
CSharpDecompiler CreateDecompiler(MetadataFile module, DecompilationOptions options)
{
Debug.Assert(type.ParentModule?.MetadataFile != null);
DecompileEntities(type.ParentModule.MetadataFile, new[] { type.MetadataToken }, output, options);
var decompiler = new CSharpDecompiler(module, module.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences), options.DecompilerSettings) {
CancellationToken = options.CancellationToken,
DebugInfoProvider = module.GetDebugInfoOrNull(),
};
if (options.EscapeInvalidIdentifiers)
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
return decompiler;
}
public override void DecompileMethod(IMethod method, ITextOutput output, DecompilationOptions options)
{
Debug.Assert(method.ParentModule?.MetadataFile != null);
DecompileEntities(method.ParentModule.MetadataFile, new[] { method.MetadataToken }, output, options);
MetadataFile assembly = method.ParentModule!.MetadataFile!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, TypeToString(method.DeclaringType));
var methodDefinition = decompiler.TypeSystem.MainModule.ResolveEntity(method.MetadataToken) as IMethod;
if (methodDefinition!.IsConstructor && methodDefinition.DeclaringType.IsReferenceType != false)
{
var members = CollectFieldsAndCtors(methodDefinition.DeclaringTypeDefinition!, methodDefinition.IsStatic);
decompiler.AstTransforms.Add(new SelectCtorTransform(methodDefinition));
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(members), decompiler.TypeSystem);
}
else
{
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(method.MetadataToken), decompiler.TypeSystem);
}
}
public override void DecompileField(IField field, ITextOutput output, DecompilationOptions options)
public override void DecompileProperty(IProperty property, ITextOutput output, DecompilationOptions options)
{
Debug.Assert(field.ParentModule?.MetadataFile != null);
DecompileEntities(field.ParentModule.MetadataFile, new[] { field.MetadataToken }, output, options);
MetadataFile assembly = property.ParentModule!.MetadataFile!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, TypeToString(property.DeclaringType));
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(property.MetadataToken), decompiler.TypeSystem);
}
public override void DecompileProperty(IProperty property, ITextOutput output, DecompilationOptions options)
public override void DecompileField(IField field, ITextOutput output, DecompilationOptions options)
{
Debug.Assert(property.ParentModule?.MetadataFile != null);
DecompileEntities(property.ParentModule.MetadataFile, new[] { property.MetadataToken }, output, options);
MetadataFile assembly = field.ParentModule!.MetadataFile!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, TypeToString(field.DeclaringType));
if (field.IsConst)
{
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(field.MetadataToken), decompiler.TypeSystem);
}
else
{
var members = CollectFieldsAndCtors(field.DeclaringTypeDefinition!, field.IsStatic);
var resolvedField = decompiler.TypeSystem.MainModule.GetDefinition((FieldDefinitionHandle)field.MetadataToken);
decompiler.AstTransforms.Add(new SelectFieldTransform(resolvedField));
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(members), decompiler.TypeSystem);
}
}
public override void DecompileEvent(IEvent ev, ITextOutput output, DecompilationOptions options)
{
Debug.Assert(ev.ParentModule?.MetadataFile != null);
DecompileEntities(ev.ParentModule.MetadataFile, new[] { ev.MetadataToken }, output, options);
MetadataFile assembly = ev.ParentModule!.MetadataFile!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, TypeToString(ev.DeclaringType));
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(ev.MetadataToken), decompiler.TypeSystem);
}
public override void DecompileType(ITypeDefinition type, ITextOutput output, DecompilationOptions options)
{
MetadataFile assembly = type.ParentModule!.MetadataFile!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, TypeToString(type, ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames));
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(type.MetadataToken), decompiler.TypeSystem);
}
public override void DecompileNamespace(string nameSpace, IEnumerable<ITypeDefinition> types, ITextOutput output, DecompilationOptions options)
{
var typesByModule = types.GroupBy(t => {
Debug.Assert(t.ParentModule?.MetadataFile != null);
return t.ParentModule.MetadataFile;
});
var typesByModule = types.GroupBy(t => t.ParentModule!.MetadataFile);
bool first = true;
foreach (var group in typesByModule)
{
if (!first)
output.WriteLine();
first = false;
DecompileEntities(group.Key, group.Select(t => t.MetadataToken), output, options);
MetadataFile assembly = group.Key!;
CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
AddReferenceAssemblyWarningMessage(assembly, output);
AddReferenceWarningMessage(assembly, output);
WriteCommentLine(output, assembly.FullName);
WriteCommentLine(output, nameSpace);
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(group.Select(t => t.MetadataToken)), decompiler.TypeSystem);
}
}
void DecompileEntities(MetadataFile module, IEnumerable<EntityHandle> handles, ITextOutput output, DecompilationOptions options)
public override ProjectId? DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
var module = assembly.GetMetadataFileOrNull();
if (module == null)
return null;
if (options.FullDecompilation && options.SaveAsProjectDirectory != null)
throw new NotSupportedException($"Language '{Name}' does not support exporting assemblies as projects in the Avalonia host yet.");
AddReferenceAssemblyWarningMessage(module, output);
AddReferenceWarningMessage(module, output);
output.WriteLine();
base.DecompileAssembly(assembly, output, options);
var assemblyResolver = assembly.GetAssemblyResolver(loadOnDemand: options.FullDecompilation && options.DecompilerSettings.AutoLoadAssemblyReferences);
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver, options.DecompilerSettings);
var globalType = typeSystem.MainModule.TypeDefinitions.FirstOrDefault();
if (globalType != null)
{
WriteCommentLine(output, "(metadata file unavailable)");
return;
output.Write("// Global type: ");
output.WriteReference(globalType, ILAmbience.EscapeName(globalType.FullName));
output.WriteLine();
}
var metadata = module.Metadata;
var corHeader = module.CorHeader;
if (module is PEFile peFile && corHeader != null)
{
var entrypointHandle = MetadataTokenHelpers.EntityHandleOrNil(corHeader.EntryPointTokenOrRelativeVirtualAddress);
if (!entrypointHandle.IsNil && entrypointHandle.Kind == HandleKind.MethodDefinition)
{
var entrypoint = typeSystem.MainModule.ResolveMethod(entrypointHandle, new GenericContext());
if (entrypoint != null)
{
output.Write("// Entry point: ");
output.WriteReference(entrypoint, ILAmbience.EscapeName(entrypoint.DeclaringType.FullName + "." + entrypoint.Name));
output.WriteLine();
}
}
output.WriteLine("// Architecture: " + GetPlatformDisplayName(peFile));
if ((corHeader.Flags & CorFlags.ILOnly) == 0)
output.WriteLine("// This assembly contains unmanaged code.");
string runtimeName = GetRuntimeDisplayName(module);
if (runtimeName != null)
output.WriteLine("// Runtime: " + runtimeName);
if ((corHeader.Flags & CorFlags.StrongNameSigned) != 0)
output.WriteLine("// This assembly is signed with a strong name key.");
if (peFile.Reader.ReadDebugDirectory().Any(d => d.Type == DebugDirectoryEntryType.Reproducible))
output.WriteLine("// This assembly was compiled using the /deterministic option.");
if (module.Metadata.MetadataKind != MetadataKind.Ecma335)
output.WriteLine("// This assembly was loaded with Windows Runtime projections applied.");
}
var resolver = module.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences);
var decompiler = new CSharpDecompiler(module, resolver, options.DecompilerSettings) {
else
{
string runtimeName = GetRuntimeDisplayName(module);
if (runtimeName != null)
output.WriteLine("// Runtime: " + runtimeName);
}
if (metadata.IsAssembly)
{
var asm = metadata.GetAssemblyDefinition();
if (asm.HashAlgorithm != System.Reflection.AssemblyHashAlgorithm.None)
output.WriteLine("// Hash algorithm: " + asm.HashAlgorithm.ToString().ToUpper());
if (!asm.PublicKey.IsNil)
{
output.Write("// Public key: ");
var reader = metadata.GetBlobReader(asm.PublicKey);
while (reader.RemainingBytes > 0)
output.Write(reader.ReadByte().ToString("x2"));
output.WriteLine();
}
}
var debugInfo = assembly.GetDebugInfoOrNull();
if (debugInfo != null)
output.WriteLine("// Debug info: " + debugInfo.Description);
output.WriteLine();
var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) {
CancellationToken = options.CancellationToken,
DebugInfoProvider = module.GetDebugInfoOrNull(),
};
SyntaxTree syntaxTree = decompiler.Decompile(handles);
WriteCode(output, options.DecompilerSettings, syntaxTree, decompiler.TypeSystem);
if (options.EscapeInvalidIdentifiers)
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
SyntaxTree st = options.FullDecompilation
? decompiler.DecompileWholeModuleAsSingleFile()
: decompiler.DecompileModuleAndAssemblyAttributes();
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
return null;
}
static List<EntityHandle> CollectFieldsAndCtors(ITypeDefinition type, bool isStatic)
{
var members = new List<EntityHandle>();
foreach (var field in type.Fields)
if (!field.MetadataToken.IsNil && field.IsStatic == isStatic)
members.Add(field.MetadataToken);
foreach (var e in type.Events)
if (!e.MetadataToken.IsNil && e.IsStatic == isStatic)
members.Add(e.MetadataToken);
foreach (var p in type.Properties)
if (!p.MetadataToken.IsNil && p.IsStatic == isStatic && !p.IsIndexer)
members.Add(p.MetadataToken);
foreach (var ctor in type.Methods)
if (!ctor.MetadataToken.IsNil && ctor.IsConstructor && ctor.IsStatic == isStatic)
members.Add(ctor.MetadataToken);
return members;
}
sealed class SelectCtorTransform(IMethod ctor) : IAstTransform
{
readonly HashSet<ISymbol?> removedSymbols = new();
public void Run(AstNode rootNode, TransformContext context)
{
ConstructorDeclaration? ctorDecl = null;
foreach (var node in rootNode.Children)
{
switch (node)
{
case ConstructorDeclaration cd:
if (cd.GetSymbol() == ctor)
ctorDecl = cd;
else
{
cd.Remove();
removedSymbols.Add(cd.GetSymbol());
}
break;
case FieldDeclaration fd:
if (fd.Variables.All(v => v.Initializer.IsNull))
{
fd.Remove();
removedSymbols.Add(fd.GetSymbol());
}
break;
case EventDeclaration ed:
if (ed.Variables.All(v => v.Initializer.IsNull))
{
ed.Remove();
removedSymbols.Add(ed.GetSymbol());
}
break;
case PropertyDeclaration pd:
if (pd.Initializer.IsNull)
{
pd.Remove();
removedSymbols.Add(pd.GetSymbol());
}
break;
case CustomEventDeclaration:
case IndexerDeclaration:
node.Remove();
removedSymbols.Add(node.GetSymbol());
break;
}
}
if (ctorDecl?.Initializer.ConstructorInitializerType == ConstructorInitializerType.This)
{
foreach (var node in rootNode.Children)
{
if (node is not ConstructorDeclaration)
{
node.Remove();
removedSymbols.Add(node.GetSymbol());
}
}
}
foreach (var node in rootNode.Children)
{
if (node is Comment && removedSymbols.Contains(node.GetSymbol()))
node.Remove();
}
}
}
sealed class SelectFieldTransform(IField field) : IAstTransform
{
public void Run(AstNode rootNode, TransformContext context)
{
foreach (var node in rootNode.Children)
{
switch (node)
{
case EntityDeclaration:
if (node.GetSymbol() != field)
node.Remove();
break;
case Comment c:
if (c.GetSymbol() != field)
node.Remove();
break;
}
}
}
}
static void WriteCode(ITextOutput output, DecompilerSettings settings, SyntaxTree syntaxTree, IDecompilerTypeSystem typeSystem)
@ -159,12 +394,84 @@ namespace ILSpy.Languages @@ -159,12 +394,84 @@ namespace ILSpy.Languages
syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true });
output.IndentationString = settings.CSharpFormattingOptions.IndentationString;
TokenWriter tokenWriter = new TextTokenWriter(output, settings, typeSystem);
// Wrap with semantic highlighting when the output supports it. The writer emits
// BeginSpan/EndSpan around tokens, which AvaloniaEditTextOutput records into a
// RichTextModel that the view's RichTextColorizer paints.
if (output is TextView.ISmartTextOutput smartOutput)
tokenWriter = new CSharpHighlightingTokenWriter(tokenWriter, smartOutput);
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(tokenWriter, settings.CSharpFormattingOptions));
}
void AddWarningMessage(MetadataFile module, ITextOutput output, string line1, string? line2 = null,
string? buttonText = null, global::Avalonia.Media.IImage? buttonImage = null,
System.EventHandler<global::Avalonia.Interactivity.RoutedEventArgs>? buttonClickHandler = null)
{
if (output is TextView.ISmartTextOutput fancyOutput)
{
string text = line1;
if (!string.IsNullOrEmpty(line2))
text += System.Environment.NewLine + line2;
fancyOutput.AddUIElement(() => new global::Avalonia.Controls.StackPanel {
Margin = new global::Avalonia.Thickness(5),
Orientation = global::Avalonia.Layout.Orientation.Horizontal,
Children = {
new global::Avalonia.Controls.Image {
Width = 32,
Height = 32,
Source = Images.Images.Warning,
},
new global::Avalonia.Controls.TextBlock {
Margin = new global::Avalonia.Thickness(5, 0, 0, 0),
Text = text,
},
},
});
fancyOutput.WriteLine();
if (buttonText != null && buttonClickHandler != null)
{
fancyOutput.AddButton(buttonImage, buttonText, buttonClickHandler);
fancyOutput.WriteLine();
}
}
else
{
WriteCommentLine(output, line1);
if (!string.IsNullOrEmpty(line2))
WriteCommentLine(output, line2);
}
}
void AddReferenceAssemblyWarningMessage(MetadataFile module, ITextOutput output)
{
var metadata = module.Metadata;
if (!metadata.GetCustomAttributes(Handle.AssemblyDefinition).HasKnownAttribute(metadata, KnownAttribute.ReferenceAssembly))
return;
AddWarningMessage(module, output, Resources.WarningAsmMarkedRef);
}
void AddReferenceWarningMessage(MetadataFile module, ITextOutput output)
{
// Resolving AssemblyTreeModel via composition would create a circular registration
// (LanguageService → Language → AssemblyTreeModel → LanguageService). Match the WPF
// output by looking the assembly up directly off the module — same predicate (the
// metadata file equality), no service dependency.
if (!HasReferenceErrors(module))
return;
AddWarningMessage(module, output,
Resources.WarningSomeAssemblyReference,
Resources.PropertyManuallyMissingReferencesListLoadedAssemblies);
}
static bool HasReferenceErrors(MetadataFile module)
{
try
{
var atm = AppEnv.AppComposition.Current.GetExport<AssemblyTree.AssemblyTreeModel>();
var loadedAssembly = atm.AssemblyList?.GetAssemblies()
.FirstOrDefault(la => la.GetMetadataFileOrNull() == module);
return loadedAssembly?.LoadedAssemblyReferencesInfo.HasErrors == true;
}
catch
{
return false;
}
}
}
}

151
ILSpy/Languages/ILLanguage.cs

@ -16,19 +16,162 @@ @@ -16,19 +16,162 @@
// 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.Composition;
using System.Linq;
using System.Reflection.Metadata;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
namespace ILSpy.Languages
{
/// <summary>
/// IL output. Uses the default ILAmbience-based formatting from <see cref="Language"/>.
/// </summary>
[Export(typeof(Language))]
[Shared]
public sealed class ILLanguage : Language
public class ILLanguage : Language
{
protected bool detectControlStructure = true;
public override string Name => "IL";
public override string FileExtension => ".il";
// WPF wires DisplaySettings here (ShowMetadataTokens / ShowRawRVAOffsetAndBytes /
// DecodeCustomAttributeBlobs / ShowMetadataTokensInBase10). Avalonia hasn't ported
// DisplaySettings yet, so we mirror the WPF defaults (all false).
protected virtual ReflectionDisassembler CreateDisassembler(ITextOutput output, DecompilationOptions options)
{
output.IndentationString = options.DecompilerSettings.CSharpFormattingOptions.IndentationString;
return new ReflectionDisassembler(output, options.CancellationToken) {
DetectControlStructure = detectControlStructure,
ShowSequencePoints = options.DecompilerSettings.ShowDebugInfo,
ShowMetadataTokens = false,
ShowMetadataTokensInBase10 = false,
ShowRawRVAOffsetAndBytes = false,
ExpandMemberDefinitions = options.DecompilerSettings.ExpandMemberDefinitions,
DecodeCustomAttributeBlobs = false,
};
}
public override void DecompileMethod(IMethod method, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = method.ParentModule!.MetadataFile!;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleMethod(module, (MethodDefinitionHandle)method.MetadataToken);
}
public override void DecompileField(IField field, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = field.ParentModule!.MetadataFile!;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleField(module, (FieldDefinitionHandle)field.MetadataToken);
}
public override void DecompileProperty(IProperty property, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = property.ParentModule!.MetadataFile!;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleProperty(module, (PropertyDefinitionHandle)property.MetadataToken);
var pd = module.Metadata.GetPropertyDefinition((PropertyDefinitionHandle)property.MetadataToken);
var accessors = pd.GetAccessors();
if (!accessors.Getter.IsNil)
{
output.WriteLine();
dis.DisassembleMethod(module, accessors.Getter);
}
if (!accessors.Setter.IsNil)
{
output.WriteLine();
dis.DisassembleMethod(module, accessors.Setter);
}
}
public override void DecompileEvent(IEvent ev, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = ev.ParentModule!.MetadataFile!;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleEvent(module, (EventDefinitionHandle)ev.MetadataToken);
var ed = module.Metadata.GetEventDefinition((EventDefinitionHandle)ev.MetadataToken);
var accessors = ed.GetAccessors();
if (!accessors.Adder.IsNil)
{
output.WriteLine();
dis.DisassembleMethod(module, accessors.Adder);
}
if (!accessors.Remover.IsNil)
{
output.WriteLine();
dis.DisassembleMethod(module, accessors.Remover);
}
if (!accessors.Raiser.IsNil)
{
output.WriteLine();
dis.DisassembleMethod(module, accessors.Raiser);
}
}
public override void DecompileType(ITypeDefinition type, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = type.ParentModule!.MetadataFile!;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleType(module, (TypeDefinitionHandle)type.MetadataToken);
}
public override void DecompileNamespace(string nameSpace, IEnumerable<ITypeDefinition> types, ITextOutput output, DecompilationOptions options)
{
var dis = CreateDisassembler(output, options);
MetadataFile module = types.FirstOrDefault()?.ParentModule!.MetadataFile!;
if (module == null)
return;
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
dis.DisassembleNamespace(nameSpace, module, types.Select(t => (TypeDefinitionHandle)t.MetadataToken));
}
public override ProjectId? DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
output.WriteLine("// " + assembly.FileName);
output.WriteLine();
var module = assembly.GetMetadataFileOrNull();
if (module == null)
return null;
if (options.FullDecompilation && options.SaveAsProjectDirectory != null)
throw new NotSupportedException($"Language '{Name}' does not support exporting assemblies as projects!");
var metadata = module.Metadata;
var dis = CreateDisassembler(output, options);
dis.AssemblyResolver = module.GetAssemblyResolver(loadOnDemand: options.FullDecompilation);
dis.DebugInfo = module.GetDebugInfoOrNull();
if (options.FullDecompilation)
dis.WriteAssemblyReferences(metadata);
if (metadata.IsAssembly)
dis.WriteAssemblyHeader(module);
output.WriteLine();
dis.WriteModuleHeader(module);
if (options.FullDecompilation)
{
output.WriteLine();
output.WriteLine();
dis.WriteModuleContents(module);
}
return null;
}
}
}

117
ILSpy/Languages/Language.cs

@ -19,13 +19,17 @@ @@ -19,13 +19,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.PortableExecutable;
using AvaloniaEdit.Highlighting;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
namespace ILSpy.Languages
{
@ -40,9 +44,6 @@ namespace ILSpy.Languages @@ -40,9 +44,6 @@ namespace ILSpy.Languages
public abstract string FileExtension { get; }
/// <summary>
/// Pretty-prints a type for tree nodes / search results.
/// </summary>
public virtual string TypeToString(IType type, ConversionFlags conversionFlags = ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames)
{
ArgumentNullException.ThrowIfNull(type);
@ -50,9 +51,6 @@ namespace ILSpy.Languages @@ -50,9 +51,6 @@ namespace ILSpy.Languages
return ambience.ConvertType(type);
}
/// <summary>
/// Pretty-prints an entity (method/field/property/event signature) for tree nodes.
/// </summary>
public virtual string EntityToString(IEntity entity, ConversionFlags conversionFlags)
{
ArgumentNullException.ThrowIfNull(entity);
@ -67,36 +65,19 @@ namespace ILSpy.Languages @@ -67,36 +65,19 @@ namespace ILSpy.Languages
return ambience.ConvertSymbol(entity);
}
/// <summary>
/// Writes <paramref name="comment"/> as a single-line comment in this language's syntax.
/// </summary>
public virtual void WriteCommentLine(ITextOutput output, string comment)
{
output.WriteLine("// " + comment);
}
/// <summary>
/// Builds the hover-tooltip text for a metadata entity. Mirrors
/// <c>ICSharpCode.ILSpy.Languages.Language.GetTooltip</c>: a fully-qualified signature
/// without a body, suitable for a single line of help text.
/// </summary>
public virtual string GetTooltip(IEntity entity)
{
ArgumentNullException.ThrowIfNull(entity);
return EntityToString(entity, ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames | ConversionFlags.ShowDeclaringType);
}
/// <summary>
/// Rich-text variant of <see cref="GetTooltip(IEntity)"/> — returns the same signature
/// with semantic highlighting attached. Languages that don't have a syntax-coloured
/// representation fall back to plain text.
/// </summary>
public virtual RichText GetRichTextTooltip(IEntity entity) => new(GetTooltip(entity));
// Default Decompile* implementations write a stub comment so we always produce *something*
// for symbols whose language doesn't have a meaningful decompilation. Real languages
// (CSharpLanguage, eventually ILLanguage) override these.
public virtual void DecompileType(ITypeDefinition type, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, TypeToString(type));
@ -131,6 +112,96 @@ namespace ILSpy.Languages @@ -131,6 +112,96 @@ namespace ILSpy.Languages
WriteCommentLine(output, nameSpace);
}
public virtual ProjectId? DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, assembly.FileName);
var asm = assembly.GetMetadataFileOrNull();
if (asm == null)
return null;
if (options.FullDecompilation && options.SaveAsProjectDirectory != null)
{
throw new NotSupportedException($"Language '{Name}' does not support exporting assemblies as projects!");
}
var metadata = asm.Metadata;
if (metadata.IsAssembly)
{
var name = metadata.GetAssemblyDefinition();
if ((name.Flags & System.Reflection.AssemblyFlags.WindowsRuntime) != 0)
{
WriteCommentLine(output, metadata.GetString(name.Name) + " [WinRT]");
}
else if (metadata.TryGetFullAssemblyName(out string? assemblyName))
{
WriteCommentLine(output, assemblyName);
}
else
{
WriteCommentLine(output, "ERR: Could not read assembly name");
}
}
else
{
WriteCommentLine(output, metadata.GetString(metadata.GetModuleDefinition().Name));
}
return null;
}
public override string ToString() => Name;
static readonly IReadOnlyDictionary<Machine, string> osMachineLookup = new Dictionary<Machine, string>
{
{ (Machine)0x4644, "MacOS" },
{ (Machine)0x7b79, "Linux" },
{ (Machine)0xadc4, "FreeBSD" },
{ (Machine)0x1993, "NetBSD" },
{ (Machine)0x1992, "Sun" },
};
public static string GetPlatformDisplayName(PEFile module)
{
var headers = module.Reader.PEHeaders;
var architecture = headers.CoffHeader.Machine;
var characteristics = headers.CoffHeader.Characteristics;
var corflags = headers.CorHeader!.Flags;
var modifier = string.Empty;
if (!Enum.IsDefined(architecture))
{
foreach (var (osEnum, osText) in osMachineLookup)
{
var candidate = architecture ^ osEnum;
if (Enum.IsDefined(candidate))
{
modifier = osText + " ";
architecture = candidate;
break;
}
}
}
switch (architecture)
{
case Machine.I386:
if ((corflags & CorFlags.Prefers32Bit) != 0)
return modifier + "AnyCPU (32-bit preferred)";
if ((corflags & CorFlags.Requires32Bit) != 0)
return modifier + "x86";
if ((corflags & CorFlags.ILOnly) == 0 && (characteristics & Characteristics.Bit32Machine) != 0)
return modifier + "x86";
return modifier + "AnyCPU (64-bit preferred)";
case Machine.Amd64:
return modifier + "x64";
case Machine.IA64:
return modifier + "Itanium";
default:
return architecture.ToString();
}
}
public static string GetRuntimeDisplayName(MetadataFile module)
{
return module.Metadata.MetadataVersion;
}
}
}

Loading…
Cancel
Save