// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using dnlib.IO;
namespace dnlib.DotNet.Emit {
///
/// Method body reader base class
///
public abstract class MethodBodyReaderBase {
/// The method reader
protected DataReader reader;
/// All parameters
protected IList parameters;
/// All locals
protected IList locals = new List();
/// All instructions
protected IList instructions;
/// All exception handlers
protected IList exceptionHandlers = new List();
uint currentOffset;
/// First byte after the end of the code
protected uint codeEndOffs;
/// Start offset of method
protected uint codeStartOffs;
readonly ModuleContext context;
///
/// Gets all parameters
///
public IList Parameters => parameters;
///
/// Gets all locals
///
public IList Locals => locals;
///
/// Gets all instructions
///
public IList Instructions => instructions;
///
/// Gets all exception handlers
///
public IList ExceptionHandlers => exceptionHandlers;
///
/// Constructor
///
protected MethodBodyReaderBase() {
}
///
/// Constructor
///
/// The module context
protected MethodBodyReaderBase(ModuleContext context) {
this.context = context;
}
///
/// Constructor
///
/// The reader
protected MethodBodyReaderBase(DataReader reader)
: this(reader, null) {
}
///
/// Constructor
///
/// The reader
/// Method parameters or null if they're not known yet
protected MethodBodyReaderBase(DataReader reader, IList parameters)
: this(reader, parameters, null) {
}
///
/// Constructor
///
/// The reader
/// Method parameters or null if they're not known yet
/// The module context
protected MethodBodyReaderBase(DataReader reader, IList parameters, ModuleContext context) {
this.reader = reader;
this.parameters = parameters;
this.context = context;
}
///
/// Sets new locals
///
/// A list of types of all locals or null if none
protected void SetLocals(IList 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]));
}
///
/// Sets new locals
///
/// A list of types of all locals or null if none
protected void SetLocals(IList 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));
}
///
/// Reads all instructions
///
/// Number of instructions to read
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(numInstrs);
currentOffset = 0;
var instructions = this.instructions;
for (int i = 0; i < numInstrs && reader.Position < codeEndOffs; i++)
instructions.Add(ReadOneInstruction());
FixBranches();
}
///
/// Reads all instructions
///
/// Size of code
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(); //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();
}
///
/// Fixes all branch instructions so their operands are set to an
/// instead of an offset.
///
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)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;
}
}
}
///
/// Finds an instruction
///
/// Offset of instruction
/// The instruction or null if there's no instruction at .
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;
}
///
/// Finds an instruction and throws if it's not present
///
/// Offset of instruction
/// The instruction
/// There's no instruction at
///
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}");
}
///
/// Reads the next instruction
///
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)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;
}
///
/// Reads the next OpCode from the current position
///
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];
}
///
/// Reads the instruction operand (if any)
///
/// The instruction
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"),
};
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual uint ReadInlineBrTarget(Instruction instr) => instr.Offset + (uint)instr.GetSize() + reader.ReadUInt32();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract IField ReadInlineField(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual int ReadInlineI(Instruction instr) => reader.ReadInt32();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual long ReadInlineI8(Instruction instr) => reader.ReadInt64();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract IMethod ReadInlineMethod(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual object ReadInlineNone(Instruction instr) => null;
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual object ReadInlinePhi(Instruction instr) => null;
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual double ReadInlineR(Instruction instr) => reader.ReadDouble();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract MethodSig ReadInlineSig(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract string ReadInlineString(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual IList 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();
}
var targets = new uint[num];
uint offset = (uint)offsetAfterInstr;
for (int i = 0; i < targets.Length; i++)
targets[i] = offset + reader.ReadUInt32();
return targets;
}
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract ITokenOperand ReadInlineTok(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected abstract ITypeDefOrRef ReadInlineType(Instruction instr);
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual IVariable ReadInlineVar(Instruction instr) {
if (IsArgOperandInstruction(instr))
return ReadInlineVarArg(instr);
return ReadInlineVarLocal(instr);
}
///
/// Reads a (a parameter) operand
///
/// The current instruction
/// The operand
protected virtual Parameter ReadInlineVarArg(Instruction instr) => GetParameter(reader.ReadUInt16());
///
/// Reads a (a local) operand
///
/// The current instruction
/// The operand
protected virtual Local ReadInlineVarLocal(Instruction instr) => GetLocal(reader.ReadUInt16());
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual uint ReadShortInlineBrTarget(Instruction instr) => instr.Offset + (uint)instr.GetSize() + (uint)reader.ReadSByte();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual object ReadShortInlineI(Instruction instr) {
if (instr.OpCode.Code == Code.Ldc_I4_S)
return reader.ReadSByte();
return reader.ReadByte();
}
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual float ReadShortInlineR(Instruction instr) => reader.ReadSingle();
///
/// Reads a operand
///
/// The current instruction
/// The operand
protected virtual IVariable ReadShortInlineVar(Instruction instr) {
if (IsArgOperandInstruction(instr))
return ReadShortInlineVarArg(instr);
return ReadShortInlineVarLocal(instr);
}
///
/// Reads a (a parameter) operand
///
/// The current instruction
/// The operand
protected virtual Parameter ReadShortInlineVarArg(Instruction instr) => GetParameter(reader.ReadByte());
///
/// Reads a (a local) operand
///
/// The current instruction
/// The operand
protected virtual Local ReadShortInlineVarLocal(Instruction instr) => GetLocal(reader.ReadByte());
///
/// Returns true if it's one of the ldarg/starg instructions that have an operand
///
/// The instruction to check
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;
}
}
///
/// Returns a parameter
///
/// A parameter index
/// A or null if is invalid
protected Parameter GetParameter(int index) {
var parameters = this.parameters;
if ((uint)index < (uint)parameters.Count)
return parameters[index];
return null;
}
///
/// Returns a local
///
/// A local index
/// A or null if is invalid
protected Local GetLocal(int index) {
var locals = this.locals;
if ((uint)index < (uint)locals.Count)
return locals[index];
return null;
}
///
/// Add an exception handler if it appears valid
///
/// The exception handler
/// true if it was added, false otherwise
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;
}
///
/// Gets the offset of an instruction
///
/// The instruction or null if the offset is the first offset
/// at the end of the method.
/// The instruction offset
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;
}
///
/// Restores a 's body with the parsed method instructions
/// and exception handlers
///
/// The method that gets updated with the instructions, locals, and
/// exception handlers.
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]);
}
}
}
}