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