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