/* 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); } }