/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using dnlib.DotNet;
using dnlib.PE;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Text;
namespace dnSpy.Contracts.Documents.TreeView {
///
/// Node formatter
///
public readonly struct NodeFormatter {
static bool IsExe(ModuleDef? mod) => mod is not null && (mod.Characteristics & Characteristics.Dll) == 0;
static bool IsExe(IPEImage? peImage) => peImage is not null && (peImage.ImageNTHeaders.FileHeader.Characteristics & Characteristics.Dll) == 0;
static string GetFilename(IDsDocument document) {
string? filename = null;
try {
filename = Path.GetFileName(document.Filename);
}
catch (ArgumentException) {
}
if (string2.IsNullOrEmpty(filename))
filename = document.GetShortName();
return filename;
}
///
/// Writes a namespace
///
/// Output
/// Decompiler
/// Namespace
public void WriteNamespace(ITextColorWriter output, IDecompiler decompiler, string @namespace) => output.WriteNamespace(@namespace);
///
/// Writes a file
///
/// Output
/// Decompiler
/// Document
public void Write(ITextColorWriter output, IDecompiler decompiler, IDsDocument document) {
var filename = GetFilename(document);
var peImage = document.PEImage;
if (peImage is not null)
output.Write(IsExe(peImage) ? BoxedTextColor.AssemblyExe : BoxedTextColor.Assembly, NameUtilities.CleanName(filename));
else
output.Write(BoxedTextColor.Text, NameUtilities.CleanName(filename));
}
///
/// Writes an assembly
///
/// Output
/// Decompiler
/// Assembly
/// true to write tokens
/// true to write version
/// true to write public key token
public void Write(ITextColorWriter output, IDecompiler decompiler, AssemblyDef asm, bool showToken, bool showAssemblyVersion, bool showAssemblyPublicKeyToken) {
output.Write(IsExe(asm.ManifestModule) ? BoxedTextColor.AssemblyExe : BoxedTextColor.Assembly, asm.Name);
bool showAsmVer = showAssemblyVersion;
bool showPublicKeyToken = showAssemblyPublicKeyToken && !PublicKeyBase.IsNullOrEmpty2(asm.PublicKeyToken);
if (showAsmVer || showPublicKeyToken) {
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, "(");
bool needComma = false;
if (showAsmVer) {
if (needComma)
output.WriteCommaSpace();
needComma = true;
output.Write(asm.Version);
}
if (showPublicKeyToken) {
if (needComma)
output.WriteCommaSpace();
needComma = true;
var pkt = asm.PublicKeyToken;
if (PublicKeyBase.IsNullOrEmpty2(pkt))
output.Write(BoxedTextColor.Keyword, "null");
else
output.Write(BoxedTextColor.Number, pkt.ToString());
}
output.Write(BoxedTextColor.Punctuation, ")");
}
WriteToken(output, asm, showToken);
}
///
/// Writes a module
///
/// Output
/// Decompiler
/// Module
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, ModuleDef module, bool showToken) {
output.WriteModule(module.Name);
WriteToken(output, module, showToken);
}
///
/// Writes a token
///
/// Output
/// Token provider
/// true to write tokens
void WriteToken(ITextColorWriter output, IMDTokenProvider tok, bool showToken) {
if (!showToken)
return;
output.WriteSpace();
output.Write(BoxedTextColor.Operator, "@");
output.Write(BoxedTextColor.Number, tok.MDToken.Raw.ToString("X8"));
}
///
/// Writes an assembly reference
///
/// Output
/// Decompiler
/// Assembly reference
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, AssemblyRef asmRef, bool showToken) {
output.Write(BoxedTextColor.Text, NameUtilities.CleanIdentifier(asmRef.Name));
WriteToken(output, asmRef, showToken);
}
///
/// Writes a module reference
///
/// Output
/// Decompiler
/// Module reference
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, ModuleRef modRef, bool showToken) {
output.Write(BoxedTextColor.Text, NameUtilities.CleanIdentifier(modRef.Name));
WriteToken(output, modRef, showToken);
}
///
/// Writes a type
///
/// Output
/// Decompiler
/// Type
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, TypeDef type, bool showToken) {
decompiler.WriteName(output, type);
WriteToken(output, type, showToken);
}
///
/// Writes a type
///
/// Output
/// Decompiler
/// Type
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, ITypeDefOrRef type, bool showToken) {
decompiler.WriteType(output, type, false);
WriteToken(output, type, showToken);
}
///
/// Writes an event
///
/// Output
/// Decompiler
/// Event
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, EventDef @event, bool showToken) {
output.Write(decompiler.MetadataTextColorProvider.GetColor(@event), NameUtilities.CleanIdentifier(@event.Name));
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, ":");
output.WriteSpace();
decompiler.WriteType(output, @event.EventType, false);
WriteToken(output, @event, showToken);
}
///
/// Writes a property
///
/// Output
/// Decompiler
/// Property
/// true to write tokens
/// true if it's an indexer
public void Write(ITextColorWriter output, IDecompiler decompiler, PropertyDef property, bool showToken, bool? isIndexer) {
decompiler.WriteName(output, property, isIndexer);
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, ":");
output.WriteSpace();
decompiler.WriteType(output, property.PropertySig.GetRetType().ToTypeDefOrRef(), false);
WriteToken(output, property, showToken);
}
///
/// Writes a field
///
/// Output
/// Decompiler
/// Field
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, FieldDef field, bool showToken) => WriteField(output, decompiler, field, showToken);
///
/// Writes a method
///
/// Output
/// Decompiler
/// Field
/// true to write tokens
public void WriteField(ITextColorWriter output, IDecompiler decompiler, IField field, bool showToken) {
output.Write(decompiler.MetadataTextColorProvider.GetColor(field), NameUtilities.CleanIdentifier(field.Name));
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, ":");
output.WriteSpace();
if (field.FieldSig?.Type.ToTypeDefOrRef() is ITypeDefOrRef fieldType)
decompiler.WriteType(output, fieldType, false);
else
output.Write(BoxedTextColor.Error, "???");
WriteToken(output, field, showToken);
}
///
/// Writes a method
///
/// Output
/// Decompiler
/// Method
/// true to write tokens
public void Write(ITextColorWriter output, IDecompiler decompiler, MethodDef method, bool showToken) =>
Write(output, decompiler, method, method, method.MethodSig, showToken, false);
void Write(ITextColorWriter output, IDecompiler decompiler, MethodDef? md, IMethod? m, MethodSig? msig, bool showToken, bool showGenericParams) {
if (md is null)
md = m?.ResolveMethodDef();
var method = (md ?? m)!;
Debug2.Assert(method is not null);
var name = method.Name;
if (name == ".ctor")
output.Write(decompiler.MetadataTextColorProvider.GetColor(method.DeclaringType), NameUtilities.CleanIdentifier(method.DeclaringType.Name));
else
output.Write(decompiler.MetadataTextColorProvider.GetColor(method), NameUtilities.CleanIdentifier(name));
if (showGenericParams) {
if (m is MethodSpec ms && ms.GenericInstMethodSig is GenericInstMethodSig gis) {
output.Write(BoxedTextColor.Punctuation, "<");
var genericArgs = gis.GenericArguments;
for (int i = 0; i < genericArgs.Count; i++) {
if (i > 0)
output.WriteCommaSpace();
decompiler.WriteType(output, genericArgs[i].ToTypeDefOrRef(), false, null);
}
output.Write(BoxedTextColor.Punctuation, ">");
}
else if (md is not null && md.GenericParameters.Count > 0) {
output.Write(BoxedTextColor.Punctuation, "<");
var genericArgs = md.GenericParameters;
for (int i = 0; i < genericArgs.Count; i++) {
if (i > 0)
output.WriteCommaSpace();
output.Write(BoxedTextColor.MethodGenericParameter, GetName(md.GenericParameters, i) ?? "???");
}
output.Write(BoxedTextColor.Punctuation, ">");
}
}
output.Write(BoxedTextColor.Punctuation, "(");
if (msig is not null) {
var ps = msig.Params;
var parameters = md?.Parameters;
int paramBaseIndex = md is null || md.IsStatic ? 0 : 1;
for (int i = 0; i < ps.Count; i++) {
var type = ps[i];
if (i > 0)
output.WriteCommaSpace();
var paramDef = parameters is null || paramBaseIndex + i >= parameters.Count ? null : parameters[paramBaseIndex + i].ParamDef;
decompiler.WriteType(output, type.ToTypeDefOrRef(), false, paramDef);
}
}
var callConv = msig is null ? 0 : msig.CallingConvention & CallingConvention.Mask;
if (callConv == CallingConvention.VarArg || callConv == CallingConvention.NativeVarArg) {
if (method.MethodSig.GetParamCount() > 0)
output.WriteCommaSpace();
output.Write(BoxedTextColor.Operator, "...");
}
output.Write(BoxedTextColor.Punctuation, ")");
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, ":");
output.WriteSpace();
decompiler.WriteType(output, (md?.MethodSig ?? msig)?.RetType.ToTypeDefOrRef(), false, md?.Parameters.ReturnParameter.ParamDef);
// m is the original ref, so use its token instead of the method def's token
WriteToken(output, (m ?? md)!, showToken);
}
static string? GetName(IList genericParameters, int number) {
if ((uint)number < (uint)genericParameters.Count && genericParameters[number].Number == number)
return genericParameters[number].Name;
foreach (var gp in genericParameters) {
if (gp.Number == number)
return gp.Name;
}
return null;
}
///
/// Writes a method
///
/// Output
/// Decompiler
/// Method
/// true to write tokens
public void WriteMethod(ITextColorWriter output, IDecompiler decompiler, IMethod method, bool showToken) =>
Write(output, decompiler, method.ResolveMethodDef(), method, method.MethodSig, showToken, true);
}
}