using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
namespace Semmle.Extraction.CIL.Entities
{
///
/// A CIL instruction.
///
internal class Instruction : UnlabelledEntity
{
///
/// The additional data following the opcode, if any.
///
public enum Payload
{
None, TypeTok, Field, Target8, Class,
Method, Arg8, Local8, Target32, Int8,
Int16, Int32, Int64, Float32, Float64,
CallSiteDesc, Switch, String, Constructor, ValueType,
Type, Arg16, Ignore8, Token, Local16, MethodRef
}
///
/// For each Payload, how many additional bytes in the bytestream need to be read.
///
private static readonly int[] payloadSizes = {
0, 4, 4, 1, 4,
4, 1, 1, 4, 1,
2, 4, 8, 4, 8,
4, -1, 4, 4, 4,
4, 2, 1, 4, 2, 4 };
// Maps opcodes to payloads for each instruction.
private static readonly Dictionary opPayload = new Dictionary()
{
{ ILOpCode.Nop, Payload.None },
{ ILOpCode.Break, Payload.None },
{ ILOpCode.Ldarg_0, Payload.None },
{ ILOpCode.Ldarg_1, Payload.None },
{ ILOpCode.Ldarg_2, Payload.None },
{ ILOpCode.Ldarg_3, Payload.None },
{ ILOpCode.Ldloc_0, Payload.None },
{ ILOpCode.Ldloc_1, Payload.None },
{ ILOpCode.Ldloc_2, Payload.None },
{ ILOpCode.Ldloc_3, Payload.None },
{ ILOpCode.Stloc_0, Payload.None },
{ ILOpCode.Stloc_1, Payload.None },
{ ILOpCode.Stloc_2, Payload.None },
{ ILOpCode.Stloc_3, Payload.None },
{ ILOpCode.Ldarg_s, Payload.Arg8 },
{ ILOpCode.Ldarga_s, Payload.Arg8 },
{ ILOpCode.Starg_s, Payload.Arg8 },
{ ILOpCode.Ldloc_s, Payload.Local8 },
{ ILOpCode.Ldloca_s, Payload.Local8 },
{ ILOpCode.Stloc_s, Payload.Local8 },
{ ILOpCode.Ldnull, Payload.None },
{ ILOpCode.Ldc_i4_m1, Payload.None },
{ ILOpCode.Ldc_i4_0, Payload.None },
{ ILOpCode.Ldc_i4_1, Payload.None },
{ ILOpCode.Ldc_i4_2, Payload.None },
{ ILOpCode.Ldc_i4_3, Payload.None },
{ ILOpCode.Ldc_i4_4, Payload.None },
{ ILOpCode.Ldc_i4_5, Payload.None },
{ ILOpCode.Ldc_i4_6, Payload.None },
{ ILOpCode.Ldc_i4_7, Payload.None },
{ ILOpCode.Ldc_i4_8, Payload.None },
{ ILOpCode.Ldc_i4_s, Payload.Int8 },
{ ILOpCode.Ldc_i4, Payload.Int32 },
{ ILOpCode.Ldc_i8, Payload.Int64 },
{ ILOpCode.Ldc_r4, Payload.Float32 },
{ ILOpCode.Ldc_r8, Payload.Float64 },
{ ILOpCode.Dup, Payload.None },
{ ILOpCode.Pop, Payload.None },
{ ILOpCode.Jmp, Payload.Method },
{ ILOpCode.Call, Payload.Method },
{ ILOpCode.Calli, Payload.CallSiteDesc },
{ ILOpCode.Ret, Payload.None },
{ ILOpCode.Br_s, Payload.Target8 },
{ ILOpCode.Brfalse_s, Payload.Target8 },
{ ILOpCode.Brtrue_s, Payload.Target8 },
{ ILOpCode.Beq_s, Payload.Target8 },
{ ILOpCode.Bge_s, Payload.Target8 },
{ ILOpCode.Bgt_s, Payload.Target8 },
{ ILOpCode.Ble_s, Payload.Target8 },
{ ILOpCode.Blt_s, Payload.Target8 },
{ ILOpCode.Bne_un_s, Payload.Target8 },
{ ILOpCode.Bge_un_s, Payload.Target8 },
{ ILOpCode.Bgt_un_s, Payload.Target8 },
{ ILOpCode.Ble_un_s, Payload.Target8 },
{ ILOpCode.Blt_un_s, Payload.Target8 },
{ ILOpCode.Br, Payload.Target32 },
{ ILOpCode.Brfalse, Payload.Target32 },
{ ILOpCode.Brtrue, Payload.Target32 },
{ ILOpCode.Beq, Payload.Target32 },
{ ILOpCode.Bge, Payload.Target32 },
{ ILOpCode.Bgt, Payload.Target32 },
{ ILOpCode.Ble, Payload.Target32 },
{ ILOpCode.Blt, Payload.Target32 },
{ ILOpCode.Bne_un, Payload.Target32 },
{ ILOpCode.Bge_un, Payload.Target32 },
{ ILOpCode.Bgt_un, Payload.Target32 },
{ ILOpCode.Ble_un, Payload.Target32 },
{ ILOpCode.Blt_un, Payload.Target32 },
{ ILOpCode.Switch, Payload.Switch },
{ ILOpCode.Ldind_i1, Payload.None },
{ ILOpCode.Ldind_u1, Payload.None },
{ ILOpCode.Ldind_i2, Payload.None },
{ ILOpCode.Ldind_u2, Payload.None },
{ ILOpCode.Ldind_i4, Payload.None },
{ ILOpCode.Ldind_u4, Payload.None },
{ ILOpCode.Ldind_i8, Payload.None },
{ ILOpCode.Ldind_i, Payload.None },
{ ILOpCode.Ldind_r4, Payload.None },
{ ILOpCode.Ldind_r8, Payload.None },
{ ILOpCode.Ldind_ref, Payload.None },
{ ILOpCode.Stind_ref, Payload.None },
{ ILOpCode.Stind_i1, Payload.None },
{ ILOpCode.Stind_i2, Payload.None },
{ ILOpCode.Stind_i4, Payload.None },
{ ILOpCode.Stind_i8, Payload.None },
{ ILOpCode.Stind_r4, Payload.None },
{ ILOpCode.Stind_r8, Payload.None },
{ ILOpCode.Add, Payload.None },
{ ILOpCode.Sub, Payload.None },
{ ILOpCode.Mul, Payload.None },
{ ILOpCode.Div, Payload.None },
{ ILOpCode.Div_un, Payload.None },
{ ILOpCode.Rem, Payload.None },
{ ILOpCode.Rem_un, Payload.None },
{ ILOpCode.And, Payload.None },
{ ILOpCode.Or, Payload.None },
{ ILOpCode.Xor, Payload.None },
{ ILOpCode.Shl, Payload.None },
{ ILOpCode.Shr, Payload.None },
{ ILOpCode.Shr_un, Payload.None },
{ ILOpCode.Neg, Payload.None },
{ ILOpCode.Not, Payload.None },
{ ILOpCode.Conv_i1, Payload.None },
{ ILOpCode.Conv_i2, Payload.None },
{ ILOpCode.Conv_i4, Payload.None },
{ ILOpCode.Conv_i8, Payload.None },
{ ILOpCode.Conv_r4, Payload.None },
{ ILOpCode.Conv_r8, Payload.None },
{ ILOpCode.Conv_u4, Payload.None },
{ ILOpCode.Conv_u8, Payload.None },
{ ILOpCode.Callvirt, Payload.MethodRef },
{ ILOpCode.Cpobj, Payload.TypeTok },
{ ILOpCode.Ldobj, Payload.TypeTok },
{ ILOpCode.Ldstr, Payload.String },
{ ILOpCode.Newobj, Payload.Constructor },
{ ILOpCode.Castclass, Payload.Class },
{ ILOpCode.Isinst, Payload.Class },
{ ILOpCode.Conv_r_un, Payload.None },
{ ILOpCode.Unbox, Payload.ValueType },
{ ILOpCode.Throw, Payload.None },
{ ILOpCode.Ldfld, Payload.Field },
{ ILOpCode.Ldflda, Payload.Field },
{ ILOpCode.Stfld, Payload.Field },
{ ILOpCode.Ldsfld, Payload.Field },
{ ILOpCode.Ldsflda, Payload.Field },
{ ILOpCode.Stsfld, Payload.Field },
{ ILOpCode.Stobj, Payload.Field },
{ ILOpCode.Conv_ovf_i1_un, Payload.None },
{ ILOpCode.Conv_ovf_i2_un, Payload.None },
{ ILOpCode.Conv_ovf_i4_un, Payload.None },
{ ILOpCode.Conv_ovf_i8_un, Payload.None },
{ ILOpCode.Conv_ovf_u1_un, Payload.None },
{ ILOpCode.Conv_ovf_u2_un, Payload.None },
{ ILOpCode.Conv_ovf_u4_un, Payload.None },
{ ILOpCode.Conv_ovf_u8_un, Payload.None },
{ ILOpCode.Conv_ovf_i_un, Payload.None },
{ ILOpCode.Conv_ovf_u_un, Payload.None },
{ ILOpCode.Box, Payload.TypeTok },
{ ILOpCode.Newarr, Payload.TypeTok },
{ ILOpCode.Ldlen, Payload.None },
{ ILOpCode.Ldelema, Payload.Class },
{ ILOpCode.Ldelem_i1, Payload.None },
{ ILOpCode.Ldelem_u1, Payload.None },
{ ILOpCode.Ldelem_i2, Payload.None },
{ ILOpCode.Ldelem_u2, Payload.None },
{ ILOpCode.Ldelem_i4, Payload.None },
{ ILOpCode.Ldelem_u4, Payload.None },
{ ILOpCode.Ldelem_i8, Payload.None },
{ ILOpCode.Ldelem_i, Payload.None },
{ ILOpCode.Ldelem_r4, Payload.None },
{ ILOpCode.Ldelem_r8, Payload.None },
{ ILOpCode.Ldelem_ref, Payload.None },
{ ILOpCode.Stelem_i, Payload.None },
{ ILOpCode.Stelem_i1, Payload.None },
{ ILOpCode.Stelem_i2, Payload.None },
{ ILOpCode.Stelem_i4, Payload.None },
{ ILOpCode.Stelem_i8, Payload.None },
{ ILOpCode.Stelem_r4, Payload.None },
{ ILOpCode.Stelem_r8, Payload.None },
{ ILOpCode.Stelem_ref, Payload.None },
{ ILOpCode.Ldelem, Payload.TypeTok },
{ ILOpCode.Stelem, Payload.TypeTok },
{ ILOpCode.Unbox_any, Payload.TypeTok },
{ ILOpCode.Conv_ovf_i1, Payload.None },
{ ILOpCode.Conv_ovf_u1, Payload.None },
{ ILOpCode.Conv_ovf_i2, Payload.None },
{ ILOpCode.Conv_ovf_u2, Payload.None },
{ ILOpCode.Conv_ovf_i4, Payload.None },
{ ILOpCode.Conv_ovf_u4, Payload.None },
{ ILOpCode.Conv_ovf_i8, Payload.None },
{ ILOpCode.Conv_ovf_u8, Payload.None },
{ ILOpCode.Refanyval, Payload.Type },
{ ILOpCode.Ckfinite, Payload.None },
{ ILOpCode.Mkrefany, Payload.Class },
{ ILOpCode.Ldtoken, Payload.Token },
{ ILOpCode.Conv_u2, Payload.None },
{ ILOpCode.Conv_u1, Payload.None },
{ ILOpCode.Conv_i, Payload.None },
{ ILOpCode.Conv_ovf_i, Payload.None },
{ ILOpCode.Conv_ovf_u, Payload.None },
{ ILOpCode.Add_ovf, Payload.None },
{ ILOpCode.Add_ovf_un, Payload.None },
{ ILOpCode.Mul_ovf, Payload.None },
{ ILOpCode.Mul_ovf_un, Payload.None },
{ ILOpCode.Sub_ovf, Payload.None },
{ ILOpCode.Sub_ovf_un, Payload.None },
{ ILOpCode.Endfinally, Payload.None },
{ ILOpCode.Leave, Payload.Target32 },
{ ILOpCode.Leave_s, Payload.Target8 },
{ ILOpCode.Stind_i, Payload.None },
{ ILOpCode.Conv_u, Payload.None },
{ ILOpCode.Arglist, Payload.None },
{ ILOpCode.Ceq, Payload.None },
{ ILOpCode.Cgt, Payload.None },
{ ILOpCode.Cgt_un, Payload.None },
{ ILOpCode.Clt, Payload.None },
{ ILOpCode.Clt_un, Payload.None },
{ ILOpCode.Ldftn, Payload.Method },
{ ILOpCode.Ldvirtftn, Payload.Method },
{ ILOpCode.Ldarg, Payload.Arg16 },
{ ILOpCode.Ldarga, Payload.Arg16 },
{ ILOpCode.Starg, Payload.Arg16 },
{ ILOpCode.Ldloc, Payload.Local16 },
{ ILOpCode.Ldloca, Payload.Local16 },
{ ILOpCode.Stloc, Payload.Local16 },
{ ILOpCode.Localloc, Payload.None },
{ ILOpCode.Endfilter, Payload.None },
{ ILOpCode.Unaligned, Payload.Ignore8 },
{ ILOpCode.Volatile, Payload.None },
{ ILOpCode.Tail, Payload.None },
{ ILOpCode.Initobj, Payload.TypeTok },
{ ILOpCode.Constrained, Payload.Type },
{ ILOpCode.Cpblk, Payload.None },
{ ILOpCode.Initblk, Payload.None },
{ ILOpCode.Rethrow, Payload.None },
{ ILOpCode.Sizeof, Payload.TypeTok },
{ ILOpCode.Refanytype, Payload.None },
{ ILOpCode.Readonly, Payload.None }
};
public DefinitionMethod Method { get; }
public ILOpCode OpCode { get; }
public int Offset { get; }
public int Index { get; }
private readonly int payloadValue;
private readonly uint unsignedPayloadValue;
public Payload PayloadType
{
get
{
if (!opPayload.TryGetValue(OpCode, out var result))
throw new InternalError("Unknown op code " + OpCode);
return result;
}
}
public override string ToString() => Index + ": " + OpCode;
///
/// The number of bytes of this instruction,
/// including the payload (if any).
///
public int Width
{
get
{
if (OpCode == ILOpCode.Switch)
return 5 + 4 * payloadValue;
return ((int)OpCode > 255 ? 2 : 1) + PayloadSize;
}
}
private readonly byte[] data;
private int PayloadSize => payloadSizes[(int)PayloadType];
///
/// Reads the instruction from a byte stream.
///
/// The byte stream.
/// The offset of the instruction.
/// The index of this instruction in the callable.
public Instruction(Context cx, DefinitionMethod method, byte[] data, int offset, int index) : base(cx)
{
Method = method;
Offset = offset;
Index = index;
this.data = data;
int opcode = data[offset];
++offset;
/*
* An opcode is either 1 or 2 bytes, followed by an optional payload depending on the instruction.
* Instructions where the first byte is 0xfe are 2-byte instructions.
*/
if (opcode == 0xfe)
opcode = opcode << 8 | data[offset++];
OpCode = (ILOpCode)opcode;
switch (PayloadSize)
{
case 0:
payloadValue = 0;
break;
case 1:
payloadValue = (sbyte)data[offset];
unsignedPayloadValue = data[offset];
break;
case 2:
payloadValue = BitConverter.ToInt16(data, offset);
unsignedPayloadValue = BitConverter.ToUInt16(data, offset);
break;
case -1: // Switch
case 4:
payloadValue = BitConverter.ToInt32(data, offset);
break;
case 8: // Not handled here.
break;
default:
throw new InternalError("Unhandled CIL instruction Payload");
}
}
public override IEnumerable Contents
{
get
{
var offset = Offset;
if (Method.Implementation is null)
{
yield break;
}
yield return Tuples.cil_instruction(this, (int)OpCode, Index, Method.Implementation);
switch (PayloadType)
{
case Payload.String:
yield return Tuples.cil_value(this, Context.MdReader.GetUserString(MetadataTokens.UserStringHandle(payloadValue)));
break;
case Payload.Float32:
yield return Tuples.cil_value(this, BitConverter.ToSingle(data, offset).ToString());
break;
case Payload.Float64:
yield return Tuples.cil_value(this, BitConverter.ToDouble(data, offset).ToString());
break;
case Payload.Int8:
yield return Tuples.cil_value(this, data[offset].ToString());
break;
case Payload.Int16:
yield return Tuples.cil_value(this, BitConverter.ToInt16(data, offset).ToString());
break;
case Payload.Int32:
yield return Tuples.cil_value(this, BitConverter.ToInt32(data, offset).ToString());
break;
case Payload.Int64:
yield return Tuples.cil_value(this, BitConverter.ToInt64(data, offset).ToString());
break;
case Payload.Constructor:
case Payload.Method:
case Payload.MethodRef:
case Payload.Class:
case Payload.TypeTok:
case Payload.Token:
case Payload.Type:
case Payload.Field:
case Payload.ValueType:
{
// A generic EntityHandle.
var handle = MetadataTokens.EntityHandle(payloadValue);
var target = Context.CreateGeneric(Method, handle);
yield return target;
if (target is not null)
{
yield return Tuples.cil_access(this, target);
}
else
{
throw new InternalError($"Unable to create payload type {PayloadType} for opcode {OpCode}");
}
break;
}
case Payload.Arg8:
case Payload.Arg16:
if (Method.Parameters is not null)
{
yield return Tuples.cil_access(this, Method.Parameters[(int)unsignedPayloadValue]);
}
break;
case Payload.Local8:
case Payload.Local16:
if (Method.LocalVariables is not null)
{
yield return Tuples.cil_access(this, Method.LocalVariables[(int)unsignedPayloadValue]);
}
break;
case Payload.None:
case Payload.Target8:
case Payload.Target32:
case Payload.Switch:
case Payload.Ignore8:
// These are not handled here.
// Some of these are handled by JumpContents().
break;
case Payload.CallSiteDesc:
{
var handle = MetadataTokens.EntityHandle(payloadValue);
IExtractedEntity? target = null;
try
{
target = Context.CreateGeneric(Method, handle);
}
catch (Exception exc)
{
Context.Extractor.Logger.Log(Util.Logging.Severity.Warning, $"Couldn't interpret payload of payload type {PayloadType} as a function pointer type. {exc.Message} {exc.StackTrace}");
}
if (target is not null)
{
yield return target;
yield return Tuples.cil_access(this, target);
}
else
{
throw new InternalError($"Unable to create payload type {PayloadType} for opcode {OpCode}");
}
break;
}
default:
throw new InternalError($"Unhandled payload type {PayloadType}");
}
}
}
// Called to populate the jumps in each instruction.
public IEnumerable JumpContents(Dictionary jump_table)
{
int target;
Instruction? inst;
switch (PayloadType)
{
case Payload.Target8:
target = Offset + payloadValue + 2;
break;
case Payload.Target32:
target = Offset + payloadValue + 5;
break;
case Payload.Switch:
var end = Offset + Width;
var offset = Offset + 5;
for (var b = 0; b < payloadValue; ++b, offset += 4)
{
target = BitConverter.ToInt32(data, offset) + end;
if (jump_table.TryGetValue(target, out inst))
{
yield return Tuples.cil_switch(this, b, inst);
}
else
{
throw new InternalError("Invalid jump target");
}
}
yield break;
default:
// Not a jump
yield break;
}
if (jump_table.TryGetValue(target, out inst))
{
yield return Tuples.cil_jump(this, inst);
}
else
{
// Sometimes instructions can jump outside the current method.
// TODO: Find a solution to this.
// For now, just log the error
Context.ExtractionError("A CIL instruction jumps outside the current method", null, Extraction.Entities.GeneratedLocation.Create(Context), "", Util.Logging.Severity.Warning);
}
}
}
}