/*
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.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Extension;
using dnSpy.Contracts.Text;
namespace dnSpy.AsmEditor.MethodBody {
[Flags]
enum WriteObjectFlags : uint {
None = 0,
ShortInstruction = 0x00000001,
}
static class BodyUtils {
public static readonly Parameter NullParameter = new Parameter(int.MinValue);
static ISimpleILPrinter? simpleILPrinter;
[ExportAutoLoaded]
sealed class BodyUtilsInit : IAutoLoaded {
[ImportingConstructor]
BodyUtilsInit([ImportMany] IEnumerable simpleILPrinters) => BodyUtils.simpleILPrinter = simpleILPrinters.OrderBy(a => a.Order).FirstOrDefault() ?? new DummyPrinter();
sealed class DummyPrinter : ISimpleILPrinter {
public double Order => 0;
public bool Write(IDecompilerOutput output, IMemberRef? member) {
if (member is null || member is GenericParam)
return false;
Write(output, member);
return true;
}
public void Write(IDecompilerOutput output, TypeSig? type) => Write(output, (object?)type);
public void Write(IDecompilerOutput output, MethodSig? sig) => Write(output, (object?)sig);
void Write(IDecompilerOutput output, object? value) => output.Write($"Missing ISimpleILPrinter: {value}", BoxedTextColor.Text);
}
}
public static bool IsNull(object? op) =>
op is null ||
op == NullParameter ||
op == InstructionVM.Null ||
op == LocalVM.Null;
public static object? TryGetVM(Dictionary ops, object? objModel) {
if (objModel is null)
return null;
if (!ops.TryGetValue(objModel, out var objVm))
return objModel;
return objVm;
}
public static object? TryGetModel(Dictionary ops, object? objVm) {
if (IsNull(objVm))
return null;
Debug2.Assert(objVm is not null);
if (!ops.TryGetValue(objVm, out var objModel))
return objVm;
return objModel;
}
public static object? ToOperandVM(Dictionary ops, object? operand) {
if (operand is IList targets) {
var newTargets = new InstructionVM?[targets.Count];
for (int i = 0; i < newTargets.Length; i++)
newTargets[i] = (InstructionVM?)TryGetVM(ops, (object)targets[i] ?? InstructionVM.Null);
return newTargets;
}
return TryGetVM(ops, operand);
}
public static object? ToOperandModel(Dictionary ops, object? operand) {
if (operand is IList targets) {
var newTargets = new Instruction?[targets.Count];
for (int i = 0; i < newTargets.Length; i++)
newTargets[i] = TryGetModel(ops, targets[i]) as Instruction;
return newTargets;
}
return TryGetModel(ops, operand);
}
public static void UpdateIndexesOffsets(this IList instrs, int index) {
uint offset = index > 0 ? instrs[index - 1].Offset + (uint)instrs[index - 1].GetSize() : 0;
for (; index < instrs.Count; index++) {
var instr = instrs[index];
instr.Index = index;
instr.Offset = offset;
offset += (uint)instr.GetSize();
}
}
public static uint UpdateInstructionOffsets(this IList instrs, int index = 0) {
uint offset = index > 0 ? instrs[index - 1].Offset + (uint)instrs[index - 1].GetSize() : 0;
for (; index < instrs.Count; index++) {
var instr = instrs[index];
instr.Offset = offset;
offset += (uint)instr.GetSize();
}
return offset;
}
public static void SimplifyMacros(this IList instrs, IList locals, IList parameters) {
foreach (var instr in instrs) {
switch (instr.Code) {
case Code.Beq_S:
instr.Code = Code.Beq;
break;
case Code.Bge_S:
instr.Code = Code.Bge;
break;
case Code.Bge_Un_S:
instr.Code = Code.Bge_Un;
break;
case Code.Bgt_S:
instr.Code = Code.Bgt;
break;
case Code.Bgt_Un_S:
instr.Code = Code.Bgt_Un;
break;
case Code.Ble_S:
instr.Code = Code.Ble;
break;
case Code.Ble_Un_S:
instr.Code = Code.Ble_Un;
break;
case Code.Blt_S:
instr.Code = Code.Blt;
break;
case Code.Blt_Un_S:
instr.Code = Code.Blt_Un;
break;
case Code.Bne_Un_S:
instr.Code = Code.Bne_Un;
break;
case Code.Br_S:
instr.Code = Code.Br;
break;
case Code.Brfalse_S:
instr.Code = Code.Brfalse;
break;
case Code.Brtrue_S:
instr.Code = Code.Brtrue;
break;
case Code.Ldarg_0:
instr.Code = Code.Ldarg;
instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 0) ?? BodyUtils.NullParameter;
break;
case Code.Ldarg_1:
instr.Code = Code.Ldarg;
instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 1) ?? BodyUtils.NullParameter;
break;
case Code.Ldarg_2:
instr.Code = Code.Ldarg;
instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 2) ?? BodyUtils.NullParameter;
break;
case Code.Ldarg_3:
instr.Code = Code.Ldarg;
instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 3) ?? BodyUtils.NullParameter;
break;
case Code.Ldarg_S:
instr.Code = Code.Ldarg;
break;
case Code.Ldarga_S:
instr.Code = Code.Ldarga;
break;
case Code.Ldc_I4_0:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 0;
break;
case Code.Ldc_I4_1:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 1;
break;
case Code.Ldc_I4_2:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 2;
break;
case Code.Ldc_I4_3:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 3;
break;
case Code.Ldc_I4_4:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 4;
break;
case Code.Ldc_I4_5:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 5;
break;
case Code.Ldc_I4_6:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 6;
break;
case Code.Ldc_I4_7:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 7;
break;
case Code.Ldc_I4_8:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = 8;
break;
case Code.Ldc_I4_M1:
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = -1;
break;
case Code.Ldc_I4_S:
if (instr.InstructionOperandVM.SByte.HasError)
break;
instr.Code = Code.Ldc_I4;
instr.InstructionOperandVM.Int32.Value = instr.InstructionOperandVM.SByte.Value;
break;
case Code.Ldloc_0:
instr.Code = Code.Ldloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 0) ?? LocalVM.Null;
break;
case Code.Ldloc_1:
instr.Code = Code.Ldloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 1) ?? LocalVM.Null;
break;
case Code.Ldloc_2:
instr.Code = Code.Ldloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 2) ?? LocalVM.Null;
break;
case Code.Ldloc_3:
instr.Code = Code.Ldloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 3) ?? LocalVM.Null;
break;
case Code.Ldloc_S:
instr.Code = Code.Ldloc;
break;
case Code.Ldloca_S:
instr.Code = Code.Ldloca;
break;
case Code.Leave_S:
instr.Code = Code.Leave;
break;
case Code.Starg_S:
instr.Code = Code.Starg;
break;
case Code.Stloc_0:
instr.Code = Code.Stloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 0) ?? LocalVM.Null;
break;
case Code.Stloc_1:
instr.Code = Code.Stloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 1) ?? LocalVM.Null;
break;
case Code.Stloc_2:
instr.Code = Code.Stloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 2) ?? LocalVM.Null;
break;
case Code.Stloc_3:
instr.Code = Code.Stloc;
instr.InstructionOperandVM.OperandListItem = ReadList(locals, 3) ?? LocalVM.Null;
break;
case Code.Stloc_S:
instr.Code = Code.Stloc;
break;
}
}
}
static T? ReadList(IList? list, int index) where T : class {
if (list is null || index < 0 || index >= list.Count)
return null;
return list[index];
}
public static void OptimizeMacros(this IList instrs) {
foreach (var instr in instrs) {
Parameter? arg;
LocalVM? local;
switch (instr.Code) {
case Code.Ldarg:
case Code.Ldarg_S:
arg = instr.InstructionOperandVM.Value as Parameter;
if (arg is null)
break;
if (arg.Index == 0)
instr.Code = Code.Ldarg_0;
else if (arg.Index == 1)
instr.Code = Code.Ldarg_1;
else if (arg.Index == 2)
instr.Code = Code.Ldarg_2;
else if (arg.Index == 3)
instr.Code = Code.Ldarg_3;
else if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
instr.Code = Code.Ldarg_S;
break;
case Code.Ldarga:
arg = instr.InstructionOperandVM.Value as Parameter;
if (arg is null)
break;
if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
instr.Code = Code.Ldarga_S;
break;
case Code.Ldc_I4:
case Code.Ldc_I4_S:
int i4;
if (instr.Code == Code.Ldc_I4) {
if (instr.InstructionOperandVM.Int32.HasError)
break;
i4 = instr.InstructionOperandVM.Int32.Value;
}
else {
if (instr.InstructionOperandVM.SByte.HasError)
break;
i4 = instr.InstructionOperandVM.SByte.Value;
}
switch (i4) {
case 0: instr.Code = Code.Ldc_I4_0; break;
case 1: instr.Code = Code.Ldc_I4_1; break;
case 2: instr.Code = Code.Ldc_I4_2; break;
case 3: instr.Code = Code.Ldc_I4_3; break;
case 4: instr.Code = Code.Ldc_I4_4; break;
case 5: instr.Code = Code.Ldc_I4_5; break;
case 6: instr.Code = Code.Ldc_I4_6; break;
case 7: instr.Code = Code.Ldc_I4_7; break;
case 8: instr.Code = Code.Ldc_I4_8; break;
case-1: instr.Code = Code.Ldc_I4_M1; break;
default:
if (sbyte.MinValue <= i4 && i4 <= sbyte.MaxValue) {
instr.Code = Code.Ldc_I4_S;
instr.InstructionOperandVM.SByte.Value = (sbyte)i4;
}
break;
}
break;
case Code.Ldloc:
case Code.Ldloc_S:
local = instr.InstructionOperandVM.Value as LocalVM;
if (local is null)
break;
if (local.Index == 0)
instr.Code = Code.Ldloc_0;
else if (local.Index == 1)
instr.Code = Code.Ldloc_1;
else if (local.Index == 2)
instr.Code = Code.Ldloc_2;
else if (local.Index == 3)
instr.Code = Code.Ldloc_3;
else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
instr.Code = Code.Ldloc_S;
break;
case Code.Ldloca:
local = instr.InstructionOperandVM.Value as LocalVM;
if (local is null)
break;
if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
instr.Code = Code.Ldloca_S;
break;
case Code.Starg:
arg = instr.InstructionOperandVM.Value as Parameter;
if (arg is null)
break;
if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
instr.Code = Code.Starg_S;
break;
case Code.Stloc:
case Code.Stloc_S:
local = instr.InstructionOperandVM.Value as LocalVM;
if (local is null)
break;
if (local.Index == 0)
instr.Code = Code.Stloc_0;
else if (local.Index == 1)
instr.Code = Code.Stloc_1;
else if (local.Index == 2)
instr.Code = Code.Stloc_2;
else if (local.Index == 3)
instr.Code = Code.Stloc_3;
else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
instr.Code = Code.Stloc_S;
break;
}
}
instrs.OptimizeBranches();
}
public static void SimplifyBranches(this IList instrs) {
foreach (var instr in instrs) {
switch (instr.Code) {
case Code.Beq_S: instr.Code = Code.Beq; break;
case Code.Bge_S: instr.Code = Code.Bge; break;
case Code.Bgt_S: instr.Code = Code.Bgt; break;
case Code.Ble_S: instr.Code = Code.Ble; break;
case Code.Blt_S: instr.Code = Code.Blt; break;
case Code.Bne_Un_S: instr.Code = Code.Bne_Un; break;
case Code.Bge_Un_S: instr.Code = Code.Bge_Un; break;
case Code.Bgt_Un_S: instr.Code = Code.Bgt_Un; break;
case Code.Ble_Un_S: instr.Code = Code.Ble_Un; break;
case Code.Blt_Un_S: instr.Code = Code.Blt_Un; break;
case Code.Br_S: instr.Code = Code.Br; break;
case Code.Brfalse_S:instr.Code = Code.Brfalse; break;
case Code.Brtrue_S: instr.Code = Code.Brtrue; break;
case Code.Leave_S: instr.Code = Code.Leave; break;
}
}
}
public static void OptimizeBranches(this IList instrs) {
while (true) {
instrs.UpdateInstructionOffsets();
bool modified = false;
foreach (var instr in instrs) {
OpCode shortOpCode;
switch (instr.Code) {
case Code.Beq: shortOpCode = OpCodes.Beq_S; break;
case Code.Bge: shortOpCode = OpCodes.Bge_S; break;
case Code.Bge_Un: shortOpCode = OpCodes.Bge_Un_S; break;
case Code.Bgt: shortOpCode = OpCodes.Bgt_S; break;
case Code.Bgt_Un: shortOpCode = OpCodes.Bgt_Un_S; break;
case Code.Ble: shortOpCode = OpCodes.Ble_S; break;
case Code.Ble_Un: shortOpCode = OpCodes.Ble_Un_S; break;
case Code.Blt: shortOpCode = OpCodes.Blt_S; break;
case Code.Blt_Un: shortOpCode = OpCodes.Blt_Un_S; break;
case Code.Bne_Un: shortOpCode = OpCodes.Bne_Un_S; break;
case Code.Br: shortOpCode = OpCodes.Br_S; break;
case Code.Brfalse: shortOpCode = OpCodes.Brfalse_S; break;
case Code.Brtrue: shortOpCode = OpCodes.Brtrue_S; break;
case Code.Leave: shortOpCode = OpCodes.Leave_S; break;
default: continue;
}
var targetInstr = instr.InstructionOperandVM.Value as InstructionVM;
if (targetInstr is null)
continue;
int afterShortInstr;
if (targetInstr.Offset >= instr.Offset) {
// Target is >= this instruction so use the offset after
// current instruction
afterShortInstr = (int)instr.Offset + instr.GetSize();
}
else {
// Target is < this instruction so use the offset after
// the short instruction
const int operandSize = 1;
afterShortInstr = (int)instr.Offset + shortOpCode.Size + operandSize;
}
int displ = (int)targetInstr.Offset - afterShortInstr;
if (sbyte.MinValue <= displ && displ <= sbyte.MaxValue) {
instr.Code = shortOpCode.Code;
modified = true;
}
}
if (!modified)
break;
}
}
public static void WriteObject(ITextColorWriter output, object? obj, WriteObjectFlags flags = WriteObjectFlags.None) {
Debug2.Assert(simpleILPrinter is not null);
if (IsNull(obj)) {
output.Write(BoxedTextColor.Keyword, "null");
return;
}
Debug2.Assert(obj is not null);
if (obj is IMemberRef mr) {
if (simpleILPrinter.Write(TextColorWriterToDecompilerOutput.Create(output), mr))
return;
}
if (obj is LocalVM local) {
output.Write(BoxedTextColor.Local, IdentifierEscaper.Escape(GetLocalName(local.Name, local.Index)));
output.WriteSpace();
output.WriteLocalParameterIndex(local.Index);
return;
}
if (obj is Parameter parameter) {
if (parameter.IsHiddenThisParameter)
output.Write(BoxedTextColor.Keyword, "this");
else {
output.Write(BoxedTextColor.Parameter, IdentifierEscaper.Escape(GetParameterName(parameter.Name, parameter.Index)));
output.WriteSpace();
output.WriteLocalParameterIndex(parameter.Index);
}
return;
}
if (obj is InstructionVM instr) {
if ((flags & WriteObjectFlags.ShortInstruction) != 0)
output.WriteShort(instr);
else
output.WriteLong(instr);
return;
}
if (obj is IList instrs) {
output.Write(instrs);
return;
}
if (obj is MethodSig methodSig) {
simpleILPrinter.Write(TextColorWriterToDecompilerOutput.Create(output), methodSig);
return;
}
if (obj is TypeSig ts) {
simpleILPrinter.Write(TextColorWriterToDecompilerOutput.Create(output), ts);
return;
}
if (obj is Code) {
output.Write(BoxedTextColor.OpCode, ((Code)obj).ToOpCode().Name);
return;
}
// This code gets called by the combobox and it sometimes passes in the empty string.
// It's never shown in the UI.
Debug.Assert(string.Empty.Equals(obj), "Shouldn't be here");
output.Write(BoxedTextColor.Text, obj.ToString());
}
static string GetLocalName(string? name, int index) {
if (!string2.IsNullOrEmpty(name))
return name;
return $"V_{index}";
}
static string GetParameterName(string? name, int index) {
if (!string2.IsNullOrEmpty(name))
return name;
return $"A_{index}";
}
static void WriteLocalParameterIndex(this ITextColorWriter output, int index) {
output.Write(BoxedTextColor.Punctuation, "(");
output.Write(BoxedTextColor.Number, index.ToString());
output.Write(BoxedTextColor.Punctuation, ")");
}
static void WriteLong(this ITextColorWriter output, InstructionVM instr) {
output.WriteShort(instr);
output.WriteSpace();
output.Write(BoxedTextColor.OpCode, instr.Code.ToOpCode().Name);
output.WriteSpace();
Write(output, instr.InstructionOperandVM);
}
static void Write(this ITextColorWriter output, InstructionOperandVM opvm) {
switch (opvm.InstructionOperandType) {
case MethodBody.InstructionOperandType.None:
break;
case MethodBody.InstructionOperandType.SByte: output.Write(BoxedTextColor.Number, opvm.SByte.StringValue); break;
case MethodBody.InstructionOperandType.Byte: output.Write(BoxedTextColor.Number, opvm.Byte.StringValue); break;
case MethodBody.InstructionOperandType.Int32: output.Write(BoxedTextColor.Number, opvm.Int32.StringValue); break;;
case MethodBody.InstructionOperandType.Int64: output.Write(BoxedTextColor.Number, opvm.Int64.StringValue); break;;
case MethodBody.InstructionOperandType.Single: output.Write(BoxedTextColor.Number, opvm.Single.StringValue); break;;
case MethodBody.InstructionOperandType.Double: output.Write(BoxedTextColor.Number, opvm.Double.StringValue); break;;
case MethodBody.InstructionOperandType.String: output.Write(BoxedTextColor.String, opvm.String.StringValue); break;;
case MethodBody.InstructionOperandType.Field:
case MethodBody.InstructionOperandType.Method:
case MethodBody.InstructionOperandType.Token:
case MethodBody.InstructionOperandType.Type:
case MethodBody.InstructionOperandType.MethodSig:
case MethodBody.InstructionOperandType.SwitchTargets:
case MethodBody.InstructionOperandType.BranchTarget:
case MethodBody.InstructionOperandType.Local:
case MethodBody.InstructionOperandType.Parameter:
WriteObject(output, opvm.Value, WriteObjectFlags.ShortInstruction);
break;
default: throw new InvalidOperationException();
}
}
static void WriteShort(this ITextColorWriter output, InstructionVM instr) {
output.Write(BoxedTextColor.Number, instr.Index.ToString());
output.WriteSpace();
output.Write(BoxedTextColor.Punctuation, "(");
output.Write(BoxedTextColor.Label, instr.Offset.ToString("X4"));
output.Write(BoxedTextColor.Punctuation, ")");
}
static void Write(this ITextColorWriter output, IList instrs) {
output.Write(BoxedTextColor.Punctuation, "[");
for (int i = 0; i < instrs.Count; i++) {
if (i > 0) {
output.Write(BoxedTextColor.Punctuation, ",");
output.WriteSpace();
}
output.WriteShort(instrs[i]);
}
output.Write(BoxedTextColor.Punctuation, "]");
}
}
}