obfuz/Plugins/dnlib/DotNet/Emit/MethodBodyReaderBase.cs

588 lines
19 KiB
C#

// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using dnlib.IO;
namespace dnlib.DotNet.Emit {
/// <summary>
/// Method body reader base class
/// </summary>
public abstract class MethodBodyReaderBase {
/// <summary>The method reader</summary>
protected DataReader reader;
/// <summary>All parameters</summary>
protected IList<Parameter> parameters;
/// <summary>All locals</summary>
protected IList<Local> locals = new List<Local>();
/// <summary>All instructions</summary>
protected IList<Instruction> instructions;
/// <summary>All exception handlers</summary>
protected IList<ExceptionHandler> exceptionHandlers = new List<ExceptionHandler>();
uint currentOffset;
/// <summary>First byte after the end of the code</summary>
protected uint codeEndOffs;
/// <summary>Start offset of method</summary>
protected uint codeStartOffs;
readonly ModuleContext context;
/// <summary>
/// Gets all parameters
/// </summary>
public IList<Parameter> Parameters => parameters;
/// <summary>
/// Gets all locals
/// </summary>
public IList<Local> Locals => locals;
/// <summary>
/// Gets all instructions
/// </summary>
public IList<Instruction> Instructions => instructions;
/// <summary>
/// Gets all exception handlers
/// </summary>
public IList<ExceptionHandler> ExceptionHandlers => exceptionHandlers;
/// <summary>
/// Constructor
/// </summary>
protected MethodBodyReaderBase() {
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="context">The module context</param>
protected MethodBodyReaderBase(ModuleContext context) {
this.context = context;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="reader">The reader</param>
protected MethodBodyReaderBase(DataReader reader)
: this(reader, null) {
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="reader">The reader</param>
/// <param name="parameters">Method parameters or <c>null</c> if they're not known yet</param>
protected MethodBodyReaderBase(DataReader reader, IList<Parameter> parameters)
: this(reader, parameters, null) {
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="reader">The reader</param>
/// <param name="parameters">Method parameters or <c>null</c> if they're not known yet</param>
/// <param name="context">The module context</param>
protected MethodBodyReaderBase(DataReader reader, IList<Parameter> parameters, ModuleContext context) {
this.reader = reader;
this.parameters = parameters;
this.context = context;
}
/// <summary>
/// Sets new locals
/// </summary>
/// <param name="newLocals">A list of types of all locals or <c>null</c> if none</param>
protected void SetLocals(IList<TypeSig> newLocals) {
var locals = this.locals;
locals.Clear();
if (newLocals is null)
return;
int count = newLocals.Count;
for (int i = 0; i < count; i++)
locals.Add(new Local(newLocals[i]));
}
/// <summary>
/// Sets new locals
/// </summary>
/// <param name="newLocals">A list of types of all locals or <c>null</c> if none</param>
protected void SetLocals(IList<Local> newLocals) {
var locals = this.locals;
locals.Clear();
if (newLocals is null)
return;
int count = newLocals.Count;
for (int i = 0; i < count; i++)
locals.Add(new Local(newLocals[i].Type));
}
/// <summary>
/// Reads all instructions
/// </summary>
/// <param name="numInstrs">Number of instructions to read</param>
protected void ReadInstructions(int numInstrs) {
codeStartOffs = reader.Position;
codeEndOffs = reader.Length; // We don't know the end pos so use the last one
this.instructions = new List<Instruction>(numInstrs);
currentOffset = 0;
var instructions = this.instructions;
for (int i = 0; i < numInstrs && reader.Position < codeEndOffs; i++)
instructions.Add(ReadOneInstruction());
FixBranches();
}
/// <summary>
/// Reads all instructions
/// </summary>
/// <param name="codeSize">Size of code</param>
protected void ReadInstructionsNumBytes(uint codeSize) {
codeStartOffs = reader.Position;
codeEndOffs = reader.Position + codeSize;
if (codeEndOffs < codeStartOffs || codeEndOffs > reader.Length)
throw new InvalidMethodException("Invalid code size");
this.instructions = new List<Instruction>(); //TODO: Estimate number of instructions based on codeSize
currentOffset = 0;
var instructions = this.instructions;
while (reader.Position < codeEndOffs)
instructions.Add(ReadOneInstruction());
reader.Position = codeEndOffs;
FixBranches();
}
/// <summary>
/// Fixes all branch instructions so their operands are set to an <see cref="Instruction"/>
/// instead of an offset.
/// </summary>
void FixBranches() {
var instructions = this.instructions;
int count = instructions.Count;
for (int i = 0; i < count; i++) {
var instr = instructions[i];
switch (instr.OpCode.OperandType) {
case OperandType.InlineBrTarget:
case OperandType.ShortInlineBrTarget:
instr.Operand = GetInstruction((uint)instr.Operand);
break;
case OperandType.InlineSwitch:
var uintTargets = (IList<uint>)instr.Operand;
var targets = new Instruction[uintTargets.Count];
for (int j = 0; j < uintTargets.Count; j++)
targets[j] = GetInstruction(uintTargets[j]);
instr.Operand = targets;
break;
}
}
}
/// <summary>
/// Finds an instruction
/// </summary>
/// <param name="offset">Offset of instruction</param>
/// <returns>The instruction or <c>null</c> if there's no instruction at <paramref name="offset"/>.</returns>
protected Instruction GetInstruction(uint offset) {
// The instructions are sorted and all Offset fields are correct. Do a binary search.
var instructions = this.instructions;
int lo = 0, hi = instructions.Count - 1;
while (lo <= hi && hi != -1) {
int i = (lo + hi) / 2;
var instr = instructions[i];
if (instr.Offset == offset)
return instr;
if (offset < instr.Offset)
hi = i - 1;
else
lo = i + 1;
}
return null;
}
/// <summary>
/// Finds an instruction and throws if it's not present
/// </summary>
/// <param name="offset">Offset of instruction</param>
/// <returns>The instruction</returns>
/// <exception cref="InvalidOperationException">There's no instruction at
/// <paramref name="offset"/></exception>
protected Instruction GetInstructionThrow(uint offset) {
var instr = GetInstruction(offset);
if (instr is not null)
return instr;
throw new InvalidOperationException($"There's no instruction @ {offset:X4}");
}
/// <summary>
/// Reads the next instruction
/// </summary>
Instruction ReadOneInstruction() {
var instr = new Instruction();
instr.Offset = currentOffset;
instr.OpCode = ReadOpCode();
instr.Operand = ReadOperand(instr);
if (instr.OpCode.Code == Code.Switch) {
var targets = (IList<uint>)instr.Operand;
currentOffset += (uint)(instr.OpCode.Size + 4 + 4 * targets.Count);
}
else
currentOffset += (uint)instr.GetSize();
if (currentOffset < instr.Offset)
reader.Position = codeEndOffs;
return instr;
}
/// <summary>
/// Reads the next OpCode from the current position
/// </summary>
OpCode ReadOpCode() {
var op = reader.ReadByte();
if (op == 0xFE)
return OpCodes.TwoByteOpCodes[reader.ReadByte()];
if (op >= 0xF0 && op <= 0xFB && context is not null && reader.BytesLeft >= 1) {
if (context.GetExperimentalOpCode(op, reader.ReadByte()) is OpCode opCode)
return opCode;
else
reader.Position--;
}
return OpCodes.OneByteOpCodes[op];
}
/// <summary>
/// Reads the instruction operand (if any)
/// </summary>
/// <param name="instr">The instruction</param>
object ReadOperand(Instruction instr) =>
instr.OpCode.OperandType switch {
OperandType.InlineBrTarget => ReadInlineBrTarget(instr),
OperandType.InlineField => ReadInlineField(instr),
OperandType.InlineI => ReadInlineI(instr),
OperandType.InlineI8 => ReadInlineI8(instr),
OperandType.InlineMethod => ReadInlineMethod(instr),
OperandType.InlineNone => ReadInlineNone(instr),
OperandType.InlinePhi => ReadInlinePhi(instr),
OperandType.InlineR => ReadInlineR(instr),
OperandType.InlineSig => ReadInlineSig(instr),
OperandType.InlineString => ReadInlineString(instr),
OperandType.InlineSwitch => ReadInlineSwitch(instr),
OperandType.InlineTok => ReadInlineTok(instr),
OperandType.InlineType => ReadInlineType(instr),
OperandType.InlineVar => ReadInlineVar(instr),
OperandType.ShortInlineBrTarget => ReadShortInlineBrTarget(instr),
OperandType.ShortInlineI => ReadShortInlineI(instr),
OperandType.ShortInlineR => ReadShortInlineR(instr),
OperandType.ShortInlineVar => ReadShortInlineVar(instr),
_ => throw new InvalidOperationException("Invalid OpCode.OperandType"),
};
/// <summary>
/// Reads a <see cref="OperandType.InlineBrTarget"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual uint ReadInlineBrTarget(Instruction instr) => instr.Offset + (uint)instr.GetSize() + reader.ReadUInt32();
/// <summary>
/// Reads a <see cref="OperandType.InlineField"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract IField ReadInlineField(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineI"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual int ReadInlineI(Instruction instr) => reader.ReadInt32();
/// <summary>
/// Reads a <see cref="OperandType.InlineI8"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual long ReadInlineI8(Instruction instr) => reader.ReadInt64();
/// <summary>
/// Reads a <see cref="OperandType.InlineMethod"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract IMethod ReadInlineMethod(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineNone"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual object ReadInlineNone(Instruction instr) => null;
/// <summary>
/// Reads a <see cref="OperandType.InlinePhi"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual object ReadInlinePhi(Instruction instr) => null;
/// <summary>
/// Reads a <see cref="OperandType.InlineR"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual double ReadInlineR(Instruction instr) => reader.ReadDouble();
/// <summary>
/// Reads a <see cref="OperandType.InlineSig"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract MethodSig ReadInlineSig(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineString"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract string ReadInlineString(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineSwitch"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual IList<uint> ReadInlineSwitch(Instruction instr) {
var num = reader.ReadUInt32();
long offsetAfterInstr = (long)instr.Offset + (long)instr.OpCode.Size + 4L + (long)num * 4;
if (offsetAfterInstr > uint.MaxValue || codeStartOffs + offsetAfterInstr > codeEndOffs) {
reader.Position = codeEndOffs;
return Array2.Empty<uint>();
}
var targets = new uint[num];
uint offset = (uint)offsetAfterInstr;
for (int i = 0; i < targets.Length; i++)
targets[i] = offset + reader.ReadUInt32();
return targets;
}
/// <summary>
/// Reads a <see cref="OperandType.InlineTok"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract ITokenOperand ReadInlineTok(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineType"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected abstract ITypeDefOrRef ReadInlineType(Instruction instr);
/// <summary>
/// Reads a <see cref="OperandType.InlineVar"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual IVariable ReadInlineVar(Instruction instr) {
if (IsArgOperandInstruction(instr))
return ReadInlineVarArg(instr);
return ReadInlineVarLocal(instr);
}
/// <summary>
/// Reads a <see cref="OperandType.InlineVar"/> (a parameter) operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual Parameter ReadInlineVarArg(Instruction instr) => GetParameter(reader.ReadUInt16());
/// <summary>
/// Reads a <see cref="OperandType.InlineVar"/> (a local) operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual Local ReadInlineVarLocal(Instruction instr) => GetLocal(reader.ReadUInt16());
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineBrTarget"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual uint ReadShortInlineBrTarget(Instruction instr) => instr.Offset + (uint)instr.GetSize() + (uint)reader.ReadSByte();
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineI"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual object ReadShortInlineI(Instruction instr) {
if (instr.OpCode.Code == Code.Ldc_I4_S)
return reader.ReadSByte();
return reader.ReadByte();
}
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineR"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual float ReadShortInlineR(Instruction instr) => reader.ReadSingle();
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineVar"/> operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual IVariable ReadShortInlineVar(Instruction instr) {
if (IsArgOperandInstruction(instr))
return ReadShortInlineVarArg(instr);
return ReadShortInlineVarLocal(instr);
}
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineVar"/> (a parameter) operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual Parameter ReadShortInlineVarArg(Instruction instr) => GetParameter(reader.ReadByte());
/// <summary>
/// Reads a <see cref="OperandType.ShortInlineVar"/> (a local) operand
/// </summary>
/// <param name="instr">The current instruction</param>
/// <returns>The operand</returns>
protected virtual Local ReadShortInlineVarLocal(Instruction instr) => GetLocal(reader.ReadByte());
/// <summary>
/// Returns <c>true</c> if it's one of the ldarg/starg instructions that have an operand
/// </summary>
/// <param name="instr">The instruction to check</param>
protected static bool IsArgOperandInstruction(Instruction instr) {
switch (instr.OpCode.Code) {
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarga:
case Code.Ldarga_S:
case Code.Starg:
case Code.Starg_S:
return true;
default:
return false;
}
}
/// <summary>
/// Returns a parameter
/// </summary>
/// <param name="index">A parameter index</param>
/// <returns>A <see cref="Parameter"/> or <c>null</c> if <paramref name="index"/> is invalid</returns>
protected Parameter GetParameter(int index) {
var parameters = this.parameters;
if ((uint)index < (uint)parameters.Count)
return parameters[index];
return null;
}
/// <summary>
/// Returns a local
/// </summary>
/// <param name="index">A local index</param>
/// <returns>A <see cref="Local"/> or <c>null</c> if <paramref name="index"/> is invalid</returns>
protected Local GetLocal(int index) {
var locals = this.locals;
if ((uint)index < (uint)locals.Count)
return locals[index];
return null;
}
/// <summary>
/// Add an exception handler if it appears valid
/// </summary>
/// <param name="eh">The exception handler</param>
/// <returns><c>true</c> if it was added, <c>false</c> otherwise</returns>
protected bool Add(ExceptionHandler eh) {
uint tryStart = GetOffset(eh.TryStart);
uint tryEnd = GetOffset(eh.TryEnd);
if (tryEnd <= tryStart)
return false;
uint handlerStart = GetOffset(eh.HandlerStart);
uint handlerEnd = GetOffset(eh.HandlerEnd);
if (handlerEnd <= handlerStart)
return false;
if (eh.IsFilter) {
if (eh.FilterStart is null)
return false;
if (eh.FilterStart.Offset >= handlerStart)
return false;
}
if (handlerStart <= tryStart && tryStart < handlerEnd)
return false;
if (handlerStart < tryEnd && tryEnd <= handlerEnd)
return false;
if (tryStart <= handlerStart && handlerStart < tryEnd)
return false;
if (tryStart < handlerEnd && handlerEnd <= tryEnd)
return false;
// It's probably valid, so let's add it.
exceptionHandlers.Add(eh);
return true;
}
/// <summary>
/// Gets the offset of an instruction
/// </summary>
/// <param name="instr">The instruction or <c>null</c> if the offset is the first offset
/// at the end of the method.</param>
/// <returns>The instruction offset</returns>
uint GetOffset(Instruction instr) {
if (instr is not null)
return instr.Offset;
var instructions = this.instructions;
if (instructions.Count == 0)
return 0;
return instructions[instructions.Count - 1].Offset;
}
/// <summary>
/// Restores a <see cref="MethodDef"/>'s body with the parsed method instructions
/// and exception handlers
/// </summary>
/// <param name="method">The method that gets updated with the instructions, locals, and
/// exception handlers.</param>
public virtual void RestoreMethod(MethodDef method) {
var body = method.Body;
body.Variables.Clear();
var locals = this.locals;
if (locals is not null) {
int count = locals.Count;
for (int i = 0; i < count; i++)
body.Variables.Add(locals[i]);
}
body.Instructions.Clear();
var instructions = this.instructions;
if (instructions is not null) {
int count = instructions.Count;
for (int i = 0; i < count; i++)
body.Instructions.Add(instructions[i]);
}
body.ExceptionHandlers.Clear();
var exceptionHandlers = this.exceptionHandlers;
if (exceptionHandlers is not null) {
int count = exceptionHandlers.Count;
for (int i = 0; i < count; i++)
body.ExceptionHandlers.Add(exceptionHandlers[i]);
}
}
}
}