// dnlib: See LICENSE.txt for more info
using System.Collections.Generic;
using System.IO;
using dnlib.IO;
namespace dnlib.DotNet.Emit {
///
/// Reads strings from #US heap
///
public interface IStringResolver {
///
/// Reads a string from the #US heap
///
/// String token
/// A string
string ReadUserString(uint token);
}
///
/// Resolves instruction operands
///
public interface IInstructionOperandResolver : ITokenResolver, IStringResolver {
}
public static partial class Extensions {
///
/// Resolves a token
///
/// An object
/// The metadata token
/// A or null if is invalid
public static IMDTokenProvider ResolveToken(this IInstructionOperandResolver self, uint token) =>
self.ResolveToken(token, new GenericParamContext());
}
///
/// Reads a .NET method body (header, locals, instructions, exception handlers)
///
public sealed class MethodBodyReader : MethodBodyReaderBase {
readonly IInstructionOperandResolver opResolver;
bool hasReadHeader;
byte headerSize;
ushort flags;
ushort maxStack;
uint codeSize;
uint localVarSigTok;
uint startOfHeader;
uint totalBodySize;
DataReader? exceptionsReader;
readonly GenericParamContext gpContext;
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Use parameters from this method
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader reader, MethodDef method) =>
CreateCilBody(opResolver, reader, null, method.Parameters, new GenericParamContext());
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Use parameters from this method
/// Generic parameter context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader reader, MethodDef method, GenericParamContext gpContext) =>
CreateCilBody(opResolver, reader, null, method.Parameters, gpContext);
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Method parameters
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader reader, IList parameters) =>
CreateCilBody(opResolver, reader, null, parameters, new GenericParamContext());
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Method parameters
/// Generic parameter context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader reader, IList parameters, GenericParamContext gpContext) =>
CreateCilBody(opResolver, reader, null, parameters, gpContext);
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Method parameters
/// Generic parameter context
/// The module context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader reader, IList parameters, GenericParamContext gpContext, ModuleContext context) =>
CreateCilBody(opResolver, reader, null, parameters, gpContext, context);
///
/// Creates a CIL method body or returns an empty one if is not
/// a valid CIL method body.
///
/// The operand resolver
/// All code
/// Exceptions or null if all exception handlers are in
///
/// Method parameters
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, byte[] code, byte[] exceptions, IList parameters) =>
CreateCilBody(opResolver, ByteArrayDataReaderFactory.CreateReader(code), exceptions is null ? (DataReader?)null : ByteArrayDataReaderFactory.CreateReader(exceptions), parameters, new GenericParamContext());
///
/// Creates a CIL method body or returns an empty one if is not
/// a valid CIL method body.
///
/// The operand resolver
/// All code
/// Exceptions or null if all exception handlers are in
///
/// Method parameters
/// Generic parameter context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, byte[] code, byte[] exceptions, IList parameters, GenericParamContext gpContext) =>
CreateCilBody(opResolver, ByteArrayDataReaderFactory.CreateReader(code), exceptions is null ? (DataReader?)null : ByteArrayDataReaderFactory.CreateReader(exceptions), parameters, gpContext);
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters) =>
CreateCilBody(opResolver, codeReader, ehReader, parameters, new GenericParamContext());
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
/// Generic parameter context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters, GenericParamContext gpContext) =>
CreateCilBody(opResolver, codeReader, ehReader, parameters, gpContext, null);
///
/// Creates a CIL method body or returns an empty one if doesn't
/// point to the start of a valid CIL method body.
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
/// Generic parameter context
/// The module context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters, GenericParamContext gpContext, ModuleContext context) {
var mbReader = new MethodBodyReader(opResolver, codeReader, ehReader, parameters, gpContext, context);
if (!mbReader.Read())
return new CilBody();
return mbReader.CreateCilBody();
}
///
/// Creates a CIL method body or returns an empty one if is not
/// a valid CIL method body.
///
/// The operand resolver
/// All code
/// Exceptions or null if all exception handlers are in
///
/// Method parameters
/// Method header flags, eg. 2 if tiny method
/// Max stack
/// Code size
/// Local variable signature token or 0 if none
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, byte[] code, byte[] exceptions, IList parameters, ushort flags, ushort maxStack, uint codeSize, uint localVarSigTok) =>
CreateCilBody(opResolver, code, exceptions, parameters, flags, maxStack, codeSize, localVarSigTok, new GenericParamContext());
///
/// Creates a CIL method body or returns an empty one if is not
/// a valid CIL method body.
///
/// The operand resolver
/// All code
/// Exceptions or null if all exception handlers are in
///
/// Method parameters
/// Method header flags, eg. 2 if tiny method
/// Max stack
/// Code size
/// Local variable signature token or 0 if none
/// Generic parameter context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, byte[] code, byte[] exceptions, IList parameters, ushort flags, ushort maxStack, uint codeSize, uint localVarSigTok, GenericParamContext gpContext) =>
CreateCilBody(opResolver, code, exceptions, parameters, flags, maxStack, codeSize, localVarSigTok, gpContext, null);
///
/// Creates a CIL method body or returns an empty one if is not
/// a valid CIL method body.
///
/// The operand resolver
/// All code
/// Exceptions or null if all exception handlers are in
///
/// Method parameters
/// Method header flags, eg. 2 if tiny method
/// Max stack
/// Code size
/// Local variable signature token or 0 if none
/// Generic parameter context
/// The module context
public static CilBody CreateCilBody(IInstructionOperandResolver opResolver, byte[] code, byte[] exceptions, IList parameters, ushort flags, ushort maxStack, uint codeSize, uint localVarSigTok, GenericParamContext gpContext, ModuleContext context) {
var codeReader = ByteArrayDataReaderFactory.CreateReader(code);
var ehReader = exceptions is null ? (DataReader?)null : ByteArrayDataReaderFactory.CreateReader(exceptions);
var mbReader = new MethodBodyReader(opResolver, codeReader, ehReader, parameters, gpContext, context);
mbReader.SetHeader(flags, maxStack, codeSize, localVarSigTok);
if (!mbReader.Read())
return new CilBody();
return mbReader.CreateCilBody();
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Use parameters from this method
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader reader, MethodDef method)
: this(opResolver, reader, null, method.Parameters, new GenericParamContext()) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Use parameters from this method
/// Generic parameter context
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader reader, MethodDef method, GenericParamContext gpContext)
: this(opResolver, reader, null, method.Parameters, gpContext) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Method parameters
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader reader, IList parameters)
: this(opResolver, reader, null, parameters, new GenericParamContext()) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Method parameters
/// Generic parameter context
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader reader, IList parameters, GenericParamContext gpContext)
: this(opResolver, reader, null, parameters, gpContext) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters)
: this(opResolver, codeReader, ehReader, parameters, new GenericParamContext()) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
/// Generic parameter context
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters, GenericParamContext gpContext)
: this(opResolver, codeReader, ehReader, parameters, gpContext, null) {
}
///
/// Constructor
///
/// The operand resolver
/// A reader positioned at the start of a .NET method body
/// Exception handler reader or null if exceptions aren't
/// present or if contains the exception handlers
/// Method parameters
/// Generic parameter context
/// Module context
public MethodBodyReader(IInstructionOperandResolver opResolver, DataReader codeReader, DataReader? ehReader, IList parameters, GenericParamContext gpContext, ModuleContext context)
: base(codeReader, parameters, context) {
this.opResolver = opResolver;
exceptionsReader = ehReader;
this.gpContext = gpContext;
startOfHeader = uint.MaxValue;
}
///
/// Initializes the method header
///
/// Header flags, eg. 2 if it's a tiny method
/// Max stack
/// Code size
/// Local variable signature token
void SetHeader(ushort flags, ushort maxStack, uint codeSize, uint localVarSigTok) {
hasReadHeader = true;
this.flags = flags;
this.maxStack = maxStack;
this.codeSize = codeSize;
this.localVarSigTok = localVarSigTok;
}
///
/// Reads the method body header, locals, all instructions, and the exception handlers (if any)
///
/// true if it worked, and false if something failed
public bool Read() {
try {
if (!ReadHeader())
return false;
SetLocals(ReadLocals());
ReadInstructions();
ReadExceptionHandlers(out totalBodySize);
return true;
}
catch (InvalidMethodException) {
return false;
}
catch (IOException) {
return false;
}
}
///
/// Reads the method header
///
bool ReadHeader() {
if (hasReadHeader)
return true;
hasReadHeader = true;
startOfHeader = reader.Position;
byte b = reader.ReadByte();
switch (b & 7) {
case 2:
case 6:
// Tiny header. [7:2] = code size, max stack is 8, no locals or exception handlers
flags = 2;
maxStack = 8;
codeSize = (uint)(b >> 2);
localVarSigTok = 0;
headerSize = 1;
break;
case 3:
// Fat header. Can have locals and exception handlers
flags = (ushort)((reader.ReadByte() << 8) | b);
headerSize = (byte)(flags >> 12);
maxStack = reader.ReadUInt16();
codeSize = reader.ReadUInt32();
localVarSigTok = reader.ReadUInt32();
// The CLR allows the code to start inside the method header. But if it does,
// the CLR doesn't read any exceptions.
reader.Position = reader.Position - 12 + headerSize * 4U;
if (headerSize < 3)
flags &= 0xFFF7;
headerSize *= 4;
break;
default:
return false;
}
if ((ulong)reader.Position + codeSize > reader.Length)
return false;
return true;
}
///
/// Reads the locals
///
/// All locals or null if there are none
IList ReadLocals() {
var standAloneSig = opResolver.ResolveToken(localVarSigTok, gpContext) as StandAloneSig;
if (standAloneSig is null)
return null;
var localSig = standAloneSig.LocalSig;
if (localSig is null)
return null;
return localSig.Locals;
}
///
/// Reads all instructions
///
void ReadInstructions() => ReadInstructionsNumBytes(codeSize);
///
protected override IField ReadInlineField(Instruction instr) => opResolver.ResolveToken(reader.ReadUInt32(), gpContext) as IField;
///
protected override IMethod ReadInlineMethod(Instruction instr) => opResolver.ResolveToken(reader.ReadUInt32(), gpContext) as IMethod;
///
protected override MethodSig ReadInlineSig(Instruction instr) {
var standAloneSig = opResolver.ResolveToken(reader.ReadUInt32(), gpContext) as StandAloneSig;
if (standAloneSig is null)
return null;
var sig = standAloneSig.MethodSig;
if (sig is not null)
sig.OriginalToken = standAloneSig.MDToken.Raw;
return sig;
}
///
protected override string ReadInlineString(Instruction instr) => opResolver.ReadUserString(reader.ReadUInt32()) ?? string.Empty;
///
protected override ITokenOperand ReadInlineTok(Instruction instr) => opResolver.ResolveToken(reader.ReadUInt32(), gpContext) as ITokenOperand;
///
protected override ITypeDefOrRef ReadInlineType(Instruction instr) => opResolver.ResolveToken(reader.ReadUInt32(), gpContext) as ITypeDefOrRef;
///
/// Reads all exception handlers
///
void ReadExceptionHandlers(out uint totalBodySize) {
if ((flags & 8) == 0) {
totalBodySize = startOfHeader == uint.MaxValue ? 0 : reader.Position - startOfHeader;
return;
}
bool canSaveTotalBodySize;
DataReader ehReader;
if (exceptionsReader is not null) {
canSaveTotalBodySize = false;
ehReader = exceptionsReader.Value;
}
else {
canSaveTotalBodySize = true;
ehReader = reader;
ehReader.Position = (ehReader.Position + 3) & ~3U;
}
// Only read the first one. Any others aren't used.
byte b = ehReader.ReadByte();
if ((b & 0x3F) != 1) {
totalBodySize = startOfHeader == uint.MaxValue ? 0 : reader.Position - startOfHeader;
return; // Not exception handler clauses
}
if ((b & 0x40) != 0)
ReadFatExceptionHandlers(ref ehReader);
else
ReadSmallExceptionHandlers(ref ehReader);
if (canSaveTotalBodySize)
totalBodySize = startOfHeader == uint.MaxValue ? 0 : ehReader.Position - startOfHeader;
else
totalBodySize = 0;
}
void ReadFatExceptionHandlers(ref DataReader ehReader) {
ehReader.Position--;
int num = (int)((ehReader.ReadUInt32() >> 8) / 24);
for (int i = 0; i < num; i++) {
var eh = new ExceptionHandler((ExceptionHandlerType)ehReader.ReadUInt32());
uint offs = ehReader.ReadUInt32();
eh.TryStart = GetInstruction(offs);
eh.TryEnd = GetInstruction(offs + ehReader.ReadUInt32());
offs = ehReader.ReadUInt32();
eh.HandlerStart = GetInstruction(offs);
eh.HandlerEnd = GetInstruction(offs + ehReader.ReadUInt32());
if (eh.IsCatch)
eh.CatchType = opResolver.ResolveToken(ehReader.ReadUInt32(), gpContext) as ITypeDefOrRef;
else if (eh.IsFilter)
eh.FilterStart = GetInstruction(ehReader.ReadUInt32());
else
ehReader.ReadUInt32();
Add(eh);
}
}
void ReadSmallExceptionHandlers(ref DataReader ehReader) {
int num = (int)((uint)ehReader.ReadByte() / 12);
ehReader.Position += 2;
for (int i = 0; i < num; i++) {
var eh = new ExceptionHandler((ExceptionHandlerType)ehReader.ReadUInt16());
uint offs = ehReader.ReadUInt16();
eh.TryStart = GetInstruction(offs);
eh.TryEnd = GetInstruction(offs + ehReader.ReadByte());
offs = ehReader.ReadUInt16();
eh.HandlerStart = GetInstruction(offs);
eh.HandlerEnd = GetInstruction(offs + ehReader.ReadByte());
if (eh.IsCatch)
eh.CatchType = opResolver.ResolveToken(ehReader.ReadUInt32(), gpContext) as ITypeDefOrRef;
else if (eh.IsFilter)
eh.FilterStart = GetInstruction(ehReader.ReadUInt32());
else
ehReader.ReadUInt32();
Add(eh);
}
}
///
/// Creates a CIL body. Must be called after , and can only be
/// called once.
///
/// A new instance
public CilBody CreateCilBody() {
// Set init locals if it's a tiny method or if the init locals bit is set (fat header)
bool initLocals = flags == 2 || (flags & 0x10) != 0;
var cilBody = new CilBody(initLocals, instructions, exceptionHandlers, locals);
cilBody.HeaderSize = headerSize;
cilBody.MaxStack = maxStack;
cilBody.LocalVarSigTok = localVarSigTok;
cilBody.MetadataBodySize = totalBodySize;
instructions = null;
exceptionHandlers = null;
locals = null;
return cilBody;
}
}
}