// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using dnlib.DotNet.Emit;
namespace dnlib.DotNet.Writer {
///
/// Returns tokens of token types, strings and signatures
///
public interface ITokenProvider : IWriterError {
///
/// Gets the token of
///
/// A token type or a string or a signature
/// The token
MDToken GetToken(object o);
///
/// Gets a StandAloneSig token
///
/// All locals
/// The original token or 0 if none
/// A StandAloneSig token or 0 if is
/// empty.
MDToken GetToken(IList locals, uint origToken);
}
///
/// Writes CIL method bodies
///
public sealed class MethodBodyWriter : MethodBodyWriterBase {
readonly ITokenProvider helper;
CilBody cilBody;
bool keepMaxStack;
uint codeSize;
uint maxStack;
byte[] code;
byte[] extraSections;
uint localVarSigTok;
///
/// Gets the code as a byte array. This is valid only after calling .
/// The size of this array is not necessarily a multiple of 4, even if there are exception
/// handlers present. See also
///
public byte[] Code => code;
///
/// Gets the extra sections (exception handlers) as a byte array or null if there
/// are no exception handlers. This is valid only after calling
///
public byte[] ExtraSections => extraSections;
///
/// Gets the token of the locals
///
public uint LocalVarSigTok => localVarSigTok;
///
/// Constructor
///
/// Helps this instance
/// The method
public MethodBodyWriter(ITokenProvider helper, MethodDef method)
: this(helper, method, false) {
}
///
/// Constructor
///
/// Helps this instance
/// The method
/// Keep the original max stack value that has been initialized
/// in
public MethodBodyWriter(ITokenProvider helper, MethodDef method, bool keepMaxStack)
: base(method.Body.Instructions, method.Body.ExceptionHandlers) {
this.helper = helper;
cilBody = method.Body;
this.keepMaxStack = keepMaxStack;
}
///
/// Constructor
///
/// Helps this instance
/// The CIL method body
public MethodBodyWriter(ITokenProvider helper, CilBody cilBody)
: this(helper, cilBody, false) {
}
///
/// Constructor
///
/// Helps this instance
/// The CIL method body
/// Keep the original max stack value that has been initialized
/// in
public MethodBodyWriter(ITokenProvider helper, CilBody cilBody, bool keepMaxStack)
: base(cilBody.Instructions, cilBody.ExceptionHandlers) {
this.helper = helper;
this.cilBody = cilBody;
this.keepMaxStack = keepMaxStack;
}
internal MethodBodyWriter(ITokenProvider helper) {
this.helper = helper;
}
internal void Reset(CilBody cilBody, bool keepMaxStack) {
Reset(cilBody.Instructions, cilBody.ExceptionHandlers);
this.cilBody = cilBody;
this.keepMaxStack = keepMaxStack;
codeSize = 0;
maxStack = 0;
code = null;
extraSections = null;
localVarSigTok = 0;
}
///
/// Writes the method body
///
public void Write() {
codeSize = InitializeInstructionOffsets();
maxStack = keepMaxStack ? cilBody.MaxStack : GetMaxStack();
if (NeedFatHeader())
WriteFatHeader();
else
WriteTinyHeader();
if (exceptionHandlers.Count > 0)
WriteExceptionHandlers();
}
///
/// Gets the code and (possible) exception handlers in one array. The exception handlers
/// are 4-byte aligned.
///
/// The code and any exception handlers
public byte[] GetFullMethodBody() {
int padding = Utils.AlignUp(code.Length, 4) - code.Length;
var bytes = new byte[code.Length + (extraSections is null ? 0 : padding + extraSections.Length)];
Array.Copy(code, 0, bytes, 0, code.Length);
if (extraSections is not null)
Array.Copy(extraSections, 0, bytes, code.Length + padding, extraSections.Length);
return bytes;
}
bool NeedFatHeader() {
//TODO: If locals has cust attrs or custom debug infos, we also need a fat header
return codeSize > 0x3F ||
exceptionHandlers.Count > 0 ||
cilBody.HasVariables ||
maxStack > 8;
}
void WriteFatHeader() {
if (maxStack > ushort.MaxValue) {
Error("MaxStack is too big");
maxStack = ushort.MaxValue;
}
ushort flags = 0x3003;
if (exceptionHandlers.Count > 0)
flags |= 8;
if (cilBody.InitLocals)
flags |= 0x10;
code = new byte[12 + codeSize];
var writer = new ArrayWriter(code);
writer.WriteUInt16(flags);
writer.WriteUInt16((ushort)maxStack);
writer.WriteUInt32(codeSize);
writer.WriteUInt32(localVarSigTok = helper.GetToken(GetLocals(), cilBody.LocalVarSigTok).Raw);
if (WriteInstructions(ref writer) != codeSize)
Error("Didn't write all code bytes");
}
IList GetLocals() {
var localsSig = new TypeSig[cilBody.Variables.Count];
for (int i = 0; i < cilBody.Variables.Count; i++)
localsSig[i] = cilBody.Variables[i].Type;
return localsSig;
}
void WriteTinyHeader() {
localVarSigTok = 0;
code = new byte[1 + codeSize];
var writer = new ArrayWriter(code);
writer.WriteByte((byte)((codeSize << 2) | 2));
if (WriteInstructions(ref writer) != codeSize)
Error("Didn't write all code bytes");
}
void WriteExceptionHandlers() {
if (NeedFatExceptionClauses())
extraSections = WriteFatExceptionClauses();
else
extraSections = WriteSmallExceptionClauses();
}
bool NeedFatExceptionClauses() {
// Size must fit in a byte, and since one small exception record is 12 bytes
// and header is 4 bytes: x*12+4 <= 255 ==> x <= 20
var exceptionHandlers = this.exceptionHandlers;
if (exceptionHandlers.Count > 20)
return true;
for (int i = 0; i < exceptionHandlers.Count; i++) {
var eh = exceptionHandlers[i];
if (!FitsInSmallExceptionClause(eh.TryStart, eh.TryEnd))
return true;
if (!FitsInSmallExceptionClause(eh.HandlerStart, eh.HandlerEnd))
return true;
}
return false;
}
bool FitsInSmallExceptionClause(Instruction start, Instruction end) {
uint offs1 = GetOffset2(start);
uint offs2 = GetOffset2(end);
if (offs2 < offs1)
return false;
return offs1 <= ushort.MaxValue && offs2 - offs1 <= byte.MaxValue;
}
uint GetOffset2(Instruction instr) {
if (instr is null)
return codeSize;
return GetOffset(instr);
}
byte[] WriteFatExceptionClauses() {
const int maxExceptionHandlers = (0x00FFFFFF - 4) / 24;
var exceptionHandlers = this.exceptionHandlers;
int numExceptionHandlers = exceptionHandlers.Count;
if (numExceptionHandlers > maxExceptionHandlers) {
Error("Too many exception handlers");
numExceptionHandlers = maxExceptionHandlers;
}
var data = new byte[numExceptionHandlers * 24 + 4];
var writer = new ArrayWriter(data);
writer.WriteUInt32((((uint)numExceptionHandlers * 24 + 4) << 8) | 0x41);
for (int i = 0; i < numExceptionHandlers; i++) {
var eh = exceptionHandlers[i];
uint offs1, offs2;
writer.WriteUInt32((uint)eh.HandlerType);
offs1 = GetOffset2(eh.TryStart);
offs2 = GetOffset2(eh.TryEnd);
if (offs2 <= offs1)
Error("Exception handler: TryEnd <= TryStart");
writer.WriteUInt32(offs1);
writer.WriteUInt32(offs2 - offs1);
offs1 = GetOffset2(eh.HandlerStart);
offs2 = GetOffset2(eh.HandlerEnd);
if (offs2 <= offs1)
Error("Exception handler: HandlerEnd <= HandlerStart");
writer.WriteUInt32(offs1);
writer.WriteUInt32(offs2 - offs1);
if (eh.IsCatch)
writer.WriteUInt32(helper.GetToken(eh.CatchType).Raw);
else if (eh.IsFilter)
writer.WriteUInt32(GetOffset2(eh.FilterStart));
else
writer.WriteInt32(0);
}
if (writer.Position != data.Length)
throw new InvalidOperationException();
return data;
}
byte[] WriteSmallExceptionClauses() {
const int maxExceptionHandlers = (0xFF - 4) / 12;
var exceptionHandlers = this.exceptionHandlers;
int numExceptionHandlers = exceptionHandlers.Count;
if (numExceptionHandlers > maxExceptionHandlers) {
Error("Too many exception handlers");
numExceptionHandlers = maxExceptionHandlers;
}
var data = new byte[numExceptionHandlers * 12 + 4];
var writer = new ArrayWriter(data);
writer.WriteUInt32((((uint)numExceptionHandlers * 12 + 4) << 8) | 1);
for (int i = 0; i < numExceptionHandlers; i++) {
var eh = exceptionHandlers[i];
uint offs1, offs2;
writer.WriteUInt16((ushort)eh.HandlerType);
offs1 = GetOffset2(eh.TryStart);
offs2 = GetOffset2(eh.TryEnd);
if (offs2 <= offs1)
Error("Exception handler: TryEnd <= TryStart");
writer.WriteUInt16((ushort)offs1);
writer.WriteByte((byte)(offs2 - offs1));
offs1 = GetOffset2(eh.HandlerStart);
offs2 = GetOffset2(eh.HandlerEnd);
if (offs2 <= offs1)
Error("Exception handler: HandlerEnd <= HandlerStart");
writer.WriteUInt16((ushort)offs1);
writer.WriteByte((byte)(offs2 - offs1));
if (eh.IsCatch)
writer.WriteUInt32(helper.GetToken(eh.CatchType).Raw);
else if (eh.IsFilter)
writer.WriteUInt32(GetOffset2(eh.FilterStart));
else
writer.WriteInt32(0);
}
if (writer.Position != data.Length)
throw new InvalidOperationException();
return data;
}
///
protected override void ErrorImpl(string message) => helper.Error(message);
///
protected override void WriteInlineField(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
///
protected override void WriteInlineMethod(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
///
protected override void WriteInlineSig(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
///
protected override void WriteInlineString(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
///
protected override void WriteInlineTok(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
///
protected override void WriteInlineType(ref ArrayWriter writer, Instruction instr) => writer.WriteUInt32(helper.GetToken(instr.Operand).Raw);
}
}