// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using dnlib.DotNet.Pdb; namespace dnlib.DotNet.Emit { /// /// A CIL instruction (opcode + operand) /// public sealed class Instruction { /// /// The opcode /// public OpCode OpCode; /// /// The opcode operand /// public object Operand; /// /// Offset of the instruction in the method body /// public uint Offset; /// /// PDB sequence point or null if none /// public SequencePoint SequencePoint; /// /// Default constructor /// public Instruction() { } /// /// Constructor /// /// Opcode public Instruction(OpCode opCode) => OpCode = opCode; /// /// Constructor /// /// Opcode /// The operand public Instruction(OpCode opCode, object operand) { OpCode = opCode; Operand = operand; } /// /// Creates a new instruction with no operand /// /// The opcode /// A new instance public static Instruction Create(OpCode opCode) { if (opCode.OperandType != OperandType.InlineNone) throw new ArgumentException("Must be a no-operand opcode", nameof(opCode)); return new Instruction(opCode); } /// /// Creates a new instruction with a operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, byte value) { if (opCode.Code != Code.Unaligned) throw new ArgumentException("Opcode does not have a byte operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with a operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, sbyte value) { if (opCode.Code != Code.Ldc_I4_S) throw new ArgumentException("Opcode does not have a sbyte operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with an operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, int value) { if (opCode.OperandType != OperandType.InlineI) throw new ArgumentException("Opcode does not have an int32 operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with a operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, long value) { if (opCode.OperandType != OperandType.InlineI8) throw new ArgumentException("Opcode does not have an int64 operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with a operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, float value) { if (opCode.OperandType != OperandType.ShortInlineR) throw new ArgumentException("Opcode does not have a real4 operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with a operand /// /// The opcode /// The value /// A new instance public static Instruction Create(OpCode opCode, double value) { if (opCode.OperandType != OperandType.InlineR) throw new ArgumentException("Opcode does not have a real8 operand", nameof(opCode)); return new Instruction(opCode, value); } /// /// Creates a new instruction with a string operand /// /// The opcode /// The string /// A new instance public static Instruction Create(OpCode opCode, string s) { if (opCode.OperandType != OperandType.InlineString) throw new ArgumentException("Opcode does not have a string operand", nameof(opCode)); return new Instruction(opCode, s); } /// /// Creates a new instruction with an instruction target operand /// /// The opcode /// Target instruction /// A new instance public static Instruction Create(OpCode opCode, Instruction target) { if (opCode.OperandType != OperandType.ShortInlineBrTarget && opCode.OperandType != OperandType.InlineBrTarget) throw new ArgumentException("Opcode does not have an instruction operand", nameof(opCode)); return new Instruction(opCode, target); } /// /// Creates a new instruction with an instruction target list operand /// /// The opcode /// The targets /// A new instance public static Instruction Create(OpCode opCode, IList targets) { if (opCode.OperandType != OperandType.InlineSwitch) throw new ArgumentException("Opcode does not have a targets array operand", nameof(opCode)); return new Instruction(opCode, targets); } /// /// Creates a new instruction with a type operand /// /// The opcode /// The type /// A new instance public static Instruction Create(OpCode opCode, ITypeDefOrRef type) { if (opCode.OperandType != OperandType.InlineType && opCode.OperandType != OperandType.InlineTok) throw new ArgumentException("Opcode does not have a type operand", nameof(opCode)); return new Instruction(opCode, type); } /// /// Creates a new instruction with a type operand /// /// The opcode /// The type /// A new instance public static Instruction Create(OpCode opCode, CorLibTypeSig type) => Create(opCode, type.TypeDefOrRef); /// /// Creates a new instruction with a method/field operand /// /// The opcode /// The method/field /// A new instance public static Instruction Create(OpCode opCode, MemberRef mr) { if (opCode.OperandType != OperandType.InlineField && opCode.OperandType != OperandType.InlineMethod && opCode.OperandType != OperandType.InlineTok) throw new ArgumentException("Opcode does not have a field operand", nameof(opCode)); return new Instruction(opCode, mr); } /// /// Creates a new instruction with a field operand /// /// The opcode /// The field /// A new instance public static Instruction Create(OpCode opCode, IField field) { if (opCode.OperandType != OperandType.InlineField && opCode.OperandType != OperandType.InlineTok) throw new ArgumentException("Opcode does not have a field operand", nameof(opCode)); return new Instruction(opCode, field); } /// /// Creates a new instruction with a method operand /// /// The opcode /// The method /// A new instance public static Instruction Create(OpCode opCode, IMethod method) { if (opCode.OperandType != OperandType.InlineMethod && opCode.OperandType != OperandType.InlineTok) throw new ArgumentException("Opcode does not have a method operand", nameof(opCode)); return new Instruction(opCode, method); } /// /// Creates a new instruction with a token operand /// /// The opcode /// The token /// A new instance public static Instruction Create(OpCode opCode, ITokenOperand token) { if (opCode.OperandType != OperandType.InlineTok) throw new ArgumentException("Opcode does not have a token operand", nameof(opCode)); return new Instruction(opCode, token); } /// /// Creates a new instruction with a method signature operand /// /// The opcode /// The method signature /// A new instance public static Instruction Create(OpCode opCode, MethodSig methodSig) { if (opCode.OperandType != OperandType.InlineSig) throw new ArgumentException("Opcode does not have a method sig operand", nameof(opCode)); return new Instruction(opCode, methodSig); } /// /// Creates a new instruction with a method parameter operand /// /// The opcode /// The method parameter /// A new instance public static Instruction Create(OpCode opCode, Parameter parameter) { if (opCode.OperandType != OperandType.ShortInlineVar && opCode.OperandType != OperandType.InlineVar) throw new ArgumentException("Opcode does not have a method parameter operand", nameof(opCode)); return new Instruction(opCode, parameter); } /// /// Creates a new instruction with a method local operand /// /// The opcode /// The method local /// A new instance public static Instruction Create(OpCode opCode, Local local) { if (opCode.OperandType != OperandType.ShortInlineVar && opCode.OperandType != OperandType.InlineVar) throw new ArgumentException("Opcode does not have a method local operand", nameof(opCode)); return new Instruction(opCode, local); } /// /// Creates a ldci4 instruction /// /// Operand value /// A new instance public static Instruction CreateLdcI4(int value) { switch (value) { case -1:return OpCodes.Ldc_I4_M1.ToInstruction(); case 0: return OpCodes.Ldc_I4_0.ToInstruction(); case 1: return OpCodes.Ldc_I4_1.ToInstruction(); case 2: return OpCodes.Ldc_I4_2.ToInstruction(); case 3: return OpCodes.Ldc_I4_3.ToInstruction(); case 4: return OpCodes.Ldc_I4_4.ToInstruction(); case 5: return OpCodes.Ldc_I4_5.ToInstruction(); case 6: return OpCodes.Ldc_I4_6.ToInstruction(); case 7: return OpCodes.Ldc_I4_7.ToInstruction(); case 8: return OpCodes.Ldc_I4_8.ToInstruction(); } if (sbyte.MinValue <= value && value <= sbyte.MaxValue) return new Instruction(OpCodes.Ldc_I4_S, (sbyte)value); return new Instruction(OpCodes.Ldc_I4, value); } /// /// Gets the size in bytes of the instruction /// /// public int GetSize() { var opCode = OpCode; switch (opCode.OperandType) { case OperandType.InlineBrTarget: case OperandType.InlineField: case OperandType.InlineI: case OperandType.InlineMethod: case OperandType.InlineSig: case OperandType.InlineString: case OperandType.InlineTok: case OperandType.InlineType: case OperandType.ShortInlineR: return opCode.Size + 4; case OperandType.InlineI8: case OperandType.InlineR: return opCode.Size + 8; case OperandType.InlineNone: case OperandType.InlinePhi: default: return opCode.Size; case OperandType.InlineSwitch: var targets = Operand as IList; return opCode.Size + 4 + (targets is null ? 0 : targets.Count * 4); case OperandType.InlineVar: return opCode.Size + 2; case OperandType.ShortInlineBrTarget: case OperandType.ShortInlineI: case OperandType.ShortInlineVar: return opCode.Size + 1; } } static bool IsSystemVoid(TypeSig type) => type.RemovePinnedAndModifiers().GetElementType() == ElementType.Void; /// /// Updates with the new stack size /// /// Current stack size public void UpdateStack(ref int stack) => UpdateStack(ref stack, false); /// /// Updates with the new stack size /// /// Current stack size /// true if the method has a return value, /// false otherwise public void UpdateStack(ref int stack, bool methodHasReturnValue) { CalculateStackUsage(methodHasReturnValue, out int pushes, out int pops); if (pops == -1) stack = 0; else stack += pushes - pops; } /// /// Calculates stack usage /// /// Updated with number of stack pushes /// Updated with number of stack pops or -1 if the stack should /// be cleared. public void CalculateStackUsage(out int pushes, out int pops) => CalculateStackUsage(false, out pushes, out pops); /// /// Calculates stack usage /// /// true if method has a return value /// Updated with number of stack pushes /// Updated with number of stack pops or -1 if the stack should /// be cleared. public void CalculateStackUsage(bool methodHasReturnValue, out int pushes, out int pops) { var opCode = OpCode; if (opCode.FlowControl == FlowControl.Call) CalculateStackUsageCall(opCode.Code, out pushes, out pops); else CalculateStackUsageNonCall(opCode, methodHasReturnValue, out pushes, out pops); } void CalculateStackUsageCall(Code code, out int pushes, out int pops) { pushes = 0; pops = 0; // It doesn't push or pop anything. The stack should be empty when JMP is executed. if (code == Code.Jmp) return; MethodSig sig; var op = Operand; if (op is IMethod method) sig = method.MethodSig; else sig = op as MethodSig; // calli instruction if (sig is null) return; bool implicitThis = sig.ImplicitThis; if (!IsSystemVoid(sig.RetType) || (code == Code.Newobj && sig.HasThis)) pushes++; pops += sig.Params.Count; var paramsAfterSentinel = sig.ParamsAfterSentinel; if (paramsAfterSentinel is not null) pops += paramsAfterSentinel.Count; if (implicitThis && code != Code.Newobj) pops++; if (code == Code.Calli) pops++; } void CalculateStackUsageNonCall(OpCode opCode, bool hasReturnValue, out int pushes, out int pops) { switch (opCode.StackBehaviourPush) { case StackBehaviour.Push0: pushes = 0; break; case StackBehaviour.Push1: case StackBehaviour.Pushi: case StackBehaviour.Pushi8: case StackBehaviour.Pushr4: case StackBehaviour.Pushr8: case StackBehaviour.Pushref: pushes = 1; break; case StackBehaviour.Push1_push1: pushes = 2; break; case StackBehaviour.Varpush: // only call, calli, callvirt which are handled elsewhere default: pushes = 0; break; } switch (opCode.StackBehaviourPop) { case StackBehaviour.Pop0: pops = 0; break; case StackBehaviour.Pop1: case StackBehaviour.Popi: case StackBehaviour.Popref: pops = 1; break; case StackBehaviour.Pop1_pop1: case StackBehaviour.Popi_pop1: case StackBehaviour.Popi_popi: case StackBehaviour.Popi_popi8: case StackBehaviour.Popi_popr4: case StackBehaviour.Popi_popr8: case StackBehaviour.Popref_pop1: case StackBehaviour.Popref_popi: pops = 2; break; case StackBehaviour.Popi_popi_popi: case StackBehaviour.Popref_popi_popi: case StackBehaviour.Popref_popi_popi8: case StackBehaviour.Popref_popi_popr4: case StackBehaviour.Popref_popi_popr8: case StackBehaviour.Popref_popi_popref: case StackBehaviour.Popref_popi_pop1: pops = 3; break; case StackBehaviour.PopAll: pops = -1; break; case StackBehaviour.Varpop: // call, calli, callvirt, newobj (all handled elsewhere), and ret if (hasReturnValue) pops = 1; else pops = 0; break; default: pops = 0; break; } } /// /// Checks whether it's one of the leave instructions /// public bool IsLeave() => OpCode == OpCodes.Leave || OpCode == OpCodes.Leave_S; /// /// Checks whether it's one of the br instructions /// public bool IsBr() => OpCode == OpCodes.Br || OpCode == OpCodes.Br_S; /// /// Checks whether it's one of the brfalse instructions /// public bool IsBrfalse() => OpCode == OpCodes.Brfalse || OpCode == OpCodes.Brfalse_S; /// /// Checks whether it's one of the brtrue instructions /// public bool IsBrtrue() => OpCode == OpCodes.Brtrue || OpCode == OpCodes.Brtrue_S; /// /// Checks whether it's one of the conditional branch instructions (bcc, brtrue, brfalse) /// public bool IsConditionalBranch() { switch (OpCode.Code) { case Code.Bge: case Code.Bge_S: case Code.Bge_Un: case Code.Bge_Un_S: case Code.Blt: case Code.Blt_S: case Code.Blt_Un: case Code.Blt_Un_S: case Code.Bgt: case Code.Bgt_S: case Code.Bgt_Un: case Code.Bgt_Un_S: case Code.Ble: case Code.Ble_S: case Code.Ble_Un: case Code.Ble_Un_S: case Code.Brfalse: case Code.Brfalse_S: case Code.Brtrue: case Code.Brtrue_S: case Code.Beq: case Code.Beq_S: case Code.Bne_Un: case Code.Bne_Un_S: return true; default: return false; } } /// /// Checks whether this is one of the ldc.i4 instructions /// public bool IsLdcI4() { switch (OpCode.Code) { case Code.Ldc_I4_M1: case Code.Ldc_I4_0: case Code.Ldc_I4_1: case Code.Ldc_I4_2: case Code.Ldc_I4_3: case Code.Ldc_I4_4: case Code.Ldc_I4_5: case Code.Ldc_I4_6: case Code.Ldc_I4_7: case Code.Ldc_I4_8: case Code.Ldc_I4_S: case Code.Ldc_I4: return true; default: return false; } } /// /// Returns a ldc.i4 instruction's operand /// /// The integer value /// isn't one of the /// ldc.i4 opcodes public int GetLdcI4Value() => OpCode.Code switch { Code.Ldc_I4_M1 => -1, Code.Ldc_I4_0 => 0, Code.Ldc_I4_1 => 1, Code.Ldc_I4_2 => 2, Code.Ldc_I4_3 => 3, Code.Ldc_I4_4 => 4, Code.Ldc_I4_5 => 5, Code.Ldc_I4_6 => 6, Code.Ldc_I4_7 => 7, Code.Ldc_I4_8 => 8, Code.Ldc_I4_S => (sbyte)Operand, Code.Ldc_I4 => (int)Operand, _ => throw new InvalidOperationException($"Not a ldc.i4 instruction: {this}"), }; /// /// Checks whether it's one of the ldarg instructions, but does not check /// whether it's one of the ldarga instructions. /// public bool IsLdarg() { switch (OpCode.Code) { case Code.Ldarg: case Code.Ldarg_S: case Code.Ldarg_0: case Code.Ldarg_1: case Code.Ldarg_2: case Code.Ldarg_3: return true; default: return false; } } /// /// Checks whether it's one of the ldloc instructions, but does not check /// whether it's one of the ldloca instructions. /// public bool IsLdloc() { switch (OpCode.Code) { case Code.Ldloc: case Code.Ldloc_0: case Code.Ldloc_1: case Code.Ldloc_2: case Code.Ldloc_3: case Code.Ldloc_S: return true; default: return false; } } /// /// Checks whether it's one of the starg instructions /// public bool IsStarg() { switch (OpCode.Code) { case Code.Starg: case Code.Starg_S: return true; default: return false; } } /// /// Checks whether it's one of the stloc instructions /// public bool IsStloc() { switch (OpCode.Code) { case Code.Stloc: case Code.Stloc_0: case Code.Stloc_1: case Code.Stloc_2: case Code.Stloc_3: case Code.Stloc_S: return true; default: return false; } } /// /// Returns the local if it's a ldloc, stloc or ldloca instruction /// /// The locals /// The local or null if it's not a ldloc, stloc or ldloca /// instruction or if the local doesn't exist. public Local GetLocal(IList locals) { int index; var code = OpCode.Code; switch (code) { case Code.Ldloc: case Code.Ldloc_S: case Code.Stloc: case Code.Stloc_S: case Code.Ldloca: case Code.Ldloca_S: return Operand as Local; case Code.Ldloc_0: case Code.Ldloc_1: case Code.Ldloc_2: case Code.Ldloc_3: index = code - Code.Ldloc_0; break; case Code.Stloc_0: case Code.Stloc_1: case Code.Stloc_2: case Code.Stloc_3: index = code - Code.Stloc_0; break; default: return null; } if ((uint)index < (uint)locals.Count) return locals[index]; return null; } /// /// Gets the index of the instruction's parameter operand or -1 if the parameter /// is missing or if it's not an instruction with a parameter operand. /// public int GetParameterIndex() { switch (OpCode.Code) { case Code.Ldarg_0: return 0; case Code.Ldarg_1: return 1; case Code.Ldarg_2: return 2; case Code.Ldarg_3: return 3; case Code.Starg: case Code.Starg_S: case Code.Ldarga: case Code.Ldarga_S: case Code.Ldarg: case Code.Ldarg_S: var parameter = Operand as Parameter; if (parameter is not null) return parameter.Index; break; } return -1; } /// /// Returns a method parameter /// /// All parameters /// A parameter or null if it doesn't exist public Parameter GetParameter(IList parameters) { int i = GetParameterIndex(); if ((uint)i < (uint)parameters.Count) return parameters[i]; return null; } /// /// Returns an argument type /// /// Method signature /// Declaring type (only needed if it's an instance method) /// The type or null if it doesn't exist public TypeSig GetArgumentType(MethodSig methodSig, ITypeDefOrRef declaringType) { if (methodSig is null) return null; int index = GetParameterIndex(); if (index == 0 && methodSig.ImplicitThis) { if (declaringType is null) return null; TypeSig declSig; bool isValueType; if (declaringType is TypeSpec spec) { declSig = spec.TypeSig; isValueType = declSig.IsValueType; } else { // Consistent with ParameterList.UpdateThisParameterType var td = declaringType.ResolveTypeDef(); if (td is null) return declaringType.ToTypeSig(); isValueType = td.IsValueType; ClassOrValueTypeSig cvSig = isValueType ? new ValueTypeSig(td) : new ClassSig(td); if (td.HasGenericParameters) { int gpCount = td.GenericParameters.Count; var genArgs = new List(gpCount); for (int i = 0; i < gpCount; i++) genArgs.Add(new GenericVar(i, td)); declSig = new GenericInstSig(cvSig, genArgs); } else declSig = cvSig; } return isValueType ? new ByRefSig(declSig) : declSig; } if (methodSig.ImplicitThis) index--; if ((uint)index < (uint)methodSig.Params.Count) return methodSig.Params[index]; return null; } /// /// Clone this instance. The and fields /// are shared by this instance and the created instance. /// public Instruction Clone() => new Instruction { Offset = Offset, OpCode = OpCode, Operand = Operand, SequencePoint = SequencePoint, }; /// public override string ToString() => InstructionPrinter.ToString(this); } static partial class Extensions { /// /// Gets the opcode or if is null /// /// this /// public static OpCode GetOpCode(this Instruction self) => self?.OpCode ?? OpCodes.UNKNOWN1; /// /// Gets the operand or null if is null /// /// this /// public static object GetOperand(this Instruction self) => self?.Operand; /// /// Gets the offset or 0 if is null /// /// this /// public static uint GetOffset(this Instruction self) => self?.Offset ?? 0; /// /// Gets the sequence point or null if is null /// /// this /// public static dnlib.DotNet.Pdb.SequencePoint GetSequencePoint(this Instruction self) => self?.SequencePoint; } }