Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

578 lines
17 KiB
C#

//
// CodeWriter.cs
//
// Author:
// Jb Evain (jbevain@gmail.com)
//
// (C) 2005 - 2007 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Mono.Cecil.Cil {
using System;
using System.Collections;
using Mono.Cecil;
using Mono.Cecil.Binary;
using Mono.Cecil.Metadata;
using Mono.Cecil.Signatures;
sealed class CodeWriter : BaseCodeVisitor {
ReflectionWriter m_reflectWriter;
MemoryBinaryWriter m_binaryWriter;
MemoryBinaryWriter m_codeWriter;
IDictionary m_localSigCache;
IDictionary m_standaloneSigCache;
IDictionary m_stackSizes;
bool stripped;
public bool Stripped {
get { return stripped; }
set { stripped = value; }
}
public CodeWriter (ReflectionWriter reflectWriter, MemoryBinaryWriter writer)
{
m_reflectWriter = reflectWriter;
m_binaryWriter = writer;
m_codeWriter = new MemoryBinaryWriter ();
m_localSigCache = new Hashtable ();
m_standaloneSigCache = new Hashtable ();
m_stackSizes = new Hashtable ();
}
public RVA WriteMethodBody (MethodDefinition meth)
{
if (meth.Body == null)
return RVA.Zero;
RVA ret = m_reflectWriter.MetadataWriter.GetDataCursor ();
meth.Body.Accept (this);
return ret;
}
public override void VisitMethodBody (MethodBody body)
{
m_codeWriter.Empty ();
}
void WriteToken (MetadataToken token)
{
if (token.RID == 0)
m_codeWriter.Write (0);
else
m_codeWriter.Write (token.ToUInt ());
}
static int GetParameterIndex (MethodBody body, ParameterDefinition p)
{
int idx = body.Method.Parameters.IndexOf (p);
if (idx == -1 && p == body.Method.This)
return 0;
if (body.Method.HasThis)
idx++;
return idx;
}
public override void VisitInstructionCollection (InstructionCollection instructions)
{
MethodBody body = instructions.Container;
long start = m_codeWriter.BaseStream.Position;
ComputeMaxStack (instructions);
foreach (Instruction instr in instructions) {
instr.Offset = (int) (m_codeWriter.BaseStream.Position - start);
if (instr.OpCode.Size == 1)
m_codeWriter.Write (instr.OpCode.Op2);
else {
m_codeWriter.Write (instr.OpCode.Op1);
m_codeWriter.Write (instr.OpCode.Op2);
}
if (instr.OpCode.OperandType != OperandType.InlineNone &&
instr.Operand == null)
throw new ReflectionException ("OpCode {0} have null operand", instr.OpCode.Name);
switch (instr.OpCode.OperandType) {
case OperandType.InlineNone :
break;
case OperandType.InlineSwitch :
Instruction [] targets = (Instruction []) instr.Operand;
for (int i = 0; i < targets.Length + 1; i++)
m_codeWriter.Write ((uint) 0);
break;
case OperandType.ShortInlineBrTarget :
m_codeWriter.Write ((byte) 0);
break;
case OperandType.InlineBrTarget :
m_codeWriter.Write (0);
break;
case OperandType.ShortInlineI :
if (instr.OpCode == OpCodes.Ldc_I4_S)
m_codeWriter.Write ((sbyte) instr.Operand);
else
m_codeWriter.Write ((byte) instr.Operand);
break;
case OperandType.ShortInlineVar :
m_codeWriter.Write ((byte) body.Variables.IndexOf (
(VariableDefinition) instr.Operand));
break;
case OperandType.ShortInlineParam :
m_codeWriter.Write ((byte) GetParameterIndex (body, (ParameterDefinition) instr.Operand));
break;
case OperandType.InlineSig :
WriteToken (GetCallSiteToken ((CallSite) instr.Operand));
break;
case OperandType.InlineI :
m_codeWriter.Write ((int) instr.Operand);
break;
case OperandType.InlineVar :
m_codeWriter.Write ((short) body.Variables.IndexOf (
(VariableDefinition) instr.Operand));
break;
case OperandType.InlineParam :
m_codeWriter.Write ((short) GetParameterIndex (
body, (ParameterDefinition) instr.Operand));
break;
case OperandType.InlineI8 :
m_codeWriter.Write ((long) instr.Operand);
break;
case OperandType.ShortInlineR :
m_codeWriter.Write ((float) instr.Operand);
break;
case OperandType.InlineR :
m_codeWriter.Write ((double) instr.Operand);
break;
case OperandType.InlineString :
WriteToken (new MetadataToken (TokenType.String,
m_reflectWriter.MetadataWriter.AddUserString (instr.Operand as string)));
break;
case OperandType.InlineField :
case OperandType.InlineMethod :
case OperandType.InlineType :
case OperandType.InlineTok :
if (instr.Operand is TypeReference)
WriteToken (GetTypeToken ((TypeReference) instr.Operand));
else if (instr.Operand is GenericInstanceMethod)
WriteToken (m_reflectWriter.GetMethodSpecToken (instr.Operand as GenericInstanceMethod));
else if (instr.Operand is MemberReference)
WriteToken (m_reflectWriter.GetMemberRefToken ((MemberReference) instr.Operand));
else if (instr.Operand is IMetadataTokenProvider)
WriteToken (((IMetadataTokenProvider) instr.Operand).MetadataToken);
else
throw new ReflectionException (
string.Format ("Wrong operand for {0} OpCode: {1}",
instr.OpCode.OperandType,
instr.Operand.GetType ().FullName));
break;
}
}
// patch branches
long pos = m_codeWriter.BaseStream.Position;
foreach (Instruction instr in instructions) {
switch (instr.OpCode.OperandType) {
case OperandType.InlineSwitch :
m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
Instruction [] targets = (Instruction []) instr.Operand;
m_codeWriter.Write ((uint) targets.Length);
foreach (Instruction tgt in targets)
m_codeWriter.Write ((tgt.Offset - (instr.Offset +
instr.OpCode.Size + (4 * (targets.Length + 1)))));
break;
case OperandType.ShortInlineBrTarget :
m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
m_codeWriter.Write ((byte) (((Instruction) instr.Operand).Offset -
(instr.Offset + instr.OpCode.Size + 1)));
break;
case OperandType.InlineBrTarget :
m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
m_codeWriter.Write(((Instruction) instr.Operand).Offset -
(instr.Offset + instr.OpCode.Size + 4));
break;
}
}
m_codeWriter.BaseStream.Position = pos;
}
MetadataToken GetTypeToken (TypeReference type)
{
return m_reflectWriter.GetTypeDefOrRefToken (type);
}
MetadataToken GetCallSiteToken (CallSite cs)
{
uint sig;
int sentinel = cs.GetSentinel ();
if (sentinel > 0)
sig = m_reflectWriter.SignatureWriter.AddMethodDefSig (
m_reflectWriter.GetMethodDefSig (cs));
else
sig = m_reflectWriter.SignatureWriter.AddMethodRefSig (
m_reflectWriter.GetMethodRefSig (cs));
if (m_standaloneSigCache.Contains (sig))
return (MetadataToken) m_standaloneSigCache [sig];
StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (sig);
sasTable.Rows.Add(sasRow);
MetadataToken token = new MetadataToken (TokenType.Signature, (uint) sasTable.Rows.Count);
m_standaloneSigCache [sig] = token;
return token;
}
static int GetLength (Instruction start, Instruction end, InstructionCollection instructions)
{
Instruction last = instructions [instructions.Count - 1];
return (end == instructions.Outside ? last.Offset + last.GetSize () : end.Offset) - start.Offset;
}
static bool IsRangeFat (Instruction start, Instruction end, InstructionCollection instructions)
{
return GetLength (start, end, instructions) >= 256 ||
start.Offset >= 65536;
}
static bool IsFat (ExceptionHandlerCollection seh)
{
for (int i = 0; i < seh.Count; i++) {
ExceptionHandler eh = seh [i];
if (IsRangeFat (eh.TryStart, eh.TryEnd, seh.Container.Instructions))
return true;
if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
return true;
switch (eh.Type) {
case ExceptionHandlerType.Filter :
if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
return true;
break;
}
}
return false;
}
void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
{
m_codeWriter.QuadAlign ();
if (seh.Count < 0x15 && !IsFat (seh)) {
m_codeWriter.Write ((byte) MethodDataSection.EHTable);
m_codeWriter.Write ((byte) (seh.Count * 12 + 4));
m_codeWriter.Write (new byte [2]);
foreach (ExceptionHandler eh in seh) {
m_codeWriter.Write ((ushort) eh.Type);
m_codeWriter.Write ((ushort) eh.TryStart.Offset);
m_codeWriter.Write ((byte) (eh.TryEnd.Offset - eh.TryStart.Offset));
m_codeWriter.Write ((ushort) eh.HandlerStart.Offset);
m_codeWriter.Write ((byte) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
WriteHandlerSpecific (eh);
}
} else {
m_codeWriter.Write ((byte) (MethodDataSection.FatFormat | MethodDataSection.EHTable));
WriteFatBlockSize (seh);
foreach (ExceptionHandler eh in seh) {
m_codeWriter.Write ((uint) eh.Type);
m_codeWriter.Write ((uint) eh.TryStart.Offset);
m_codeWriter.Write ((uint) (eh.TryEnd.Offset - eh.TryStart.Offset));
m_codeWriter.Write ((uint) eh.HandlerStart.Offset);
m_codeWriter.Write ((uint) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
WriteHandlerSpecific (eh);
}
}
}
void WriteFatBlockSize (ExceptionHandlerCollection seh)
{
int size = seh.Count * 24 + 4;
m_codeWriter.Write ((byte) (size & 0xff));
m_codeWriter.Write ((byte) ((size >> 8) & 0xff));
m_codeWriter.Write ((byte) ((size >> 16) & 0xff));
}
void WriteHandlerSpecific (ExceptionHandler eh)
{
switch (eh.Type) {
case ExceptionHandlerType.Catch :
WriteToken (GetTypeToken (eh.CatchType));
break;
case ExceptionHandlerType.Filter :
m_codeWriter.Write ((uint) eh.FilterStart.Offset);
break;
default :
m_codeWriter.Write (0);
break;
}
}
public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
{
MethodBody body = variables.Container as MethodBody;
if (body == null || stripped)
return;
uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
GetLocalVarSig (variables));
if (m_localSigCache.Contains (sig)) {
body.LocalVarToken = (int) m_localSigCache [sig];
return;
}
StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
sig);
sasTable.Rows.Add (sasRow);
body.LocalVarToken = sasTable.Rows.Count;
m_localSigCache [sig] = body.LocalVarToken;
}
public override void TerminateMethodBody (MethodBody body)
{
long pos = m_binaryWriter.BaseStream.Position;
if (body.HasVariables || body.HasExceptionHandlers
|| m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
MethodHeader header = MethodHeader.FatFormat;
if (body.InitLocals)
header |= MethodHeader.InitLocals;
if (body.HasExceptionHandlers)
header |= MethodHeader.MoreSects;
m_binaryWriter.Write ((byte) header);
m_binaryWriter.Write ((byte) 0x30); // (header size / 4) << 4
m_binaryWriter.Write ((short) body.MaxStack);
m_binaryWriter.Write ((int) m_codeWriter.BaseStream.Length);
// the token should be zero if there are no variables
int token = body.HasVariables ? ((int) TokenType.Signature | body.LocalVarToken) : 0;
m_binaryWriter.Write (token);
if (body.HasExceptionHandlers)
WriteExceptionHandlerCollection (body.ExceptionHandlers);
} else
m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
m_codeWriter.BaseStream.Length << 2));
m_binaryWriter.Write (m_codeWriter);
m_binaryWriter.QuadAlign ();
m_reflectWriter.MetadataWriter.AddData (
(int) (m_binaryWriter.BaseStream.Position - pos));
}
public LocalVarSig.LocalVariable GetLocalVariableSig (VariableDefinition var)
{
LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
TypeReference type = var.VariableType;
lv.CustomMods = m_reflectWriter.GetCustomMods (type);
if (type is PinnedType) {
lv.Constraint |= Constraint.Pinned;
type = (type as PinnedType).ElementType;
}
if (type is ReferenceType) {
lv.ByRef = true;
type = (type as ReferenceType).ElementType;
}
lv.Type = m_reflectWriter.GetSigType (type);
return lv;
}
public LocalVarSig GetLocalVarSig (VariableDefinitionCollection vars)
{
LocalVarSig lvs = new LocalVarSig ();
lvs.CallingConvention |= 0x7;
lvs.Count = vars.Count;
lvs.LocalVariables = new LocalVarSig.LocalVariable [lvs.Count];
for (int i = 0; i < lvs.Count; i++) {
lvs.LocalVariables [i] = GetLocalVariableSig (vars [i]);
}
return lvs;
}
void ComputeMaxStack (InstructionCollection instructions)
{
int current = 0;
int max = 0;
m_stackSizes.Clear ();
foreach (ExceptionHandler eh in instructions.Container.ExceptionHandlers) {
switch (eh.Type) {
case ExceptionHandlerType.Catch :
case ExceptionHandlerType.Filter :
m_stackSizes [eh.HandlerStart] = 1;
max = 1;
break;
}
}
foreach (Instruction instr in instructions) {
object savedSize = m_stackSizes [instr];
if (savedSize != null)
current = (int) savedSize;
current -= GetPopDelta (instructions.Container.Method, instr, current);
if (current < 0)
current = 0;
current += GetPushDelta (instr);
if (current > max)
max = current;
// for forward branches, copy the stack size for the instruction that is being branched to
switch (instr.OpCode.OperandType) {
case OperandType.InlineBrTarget:
case OperandType.ShortInlineBrTarget:
m_stackSizes [instr.Operand] = current;
break;
case OperandType.InlineSwitch:
foreach (Instruction target in (Instruction []) instr.Operand)
m_stackSizes [target] = current;
break;
}
switch (instr.OpCode.FlowControl) {
case FlowControl.Branch:
case FlowControl.Throw:
case FlowControl.Return:
// next statement is not reachable from this statement, so reset the stack depth to 0
current = 0;
break;
}
}
instructions.Container.MaxStack = max + 1; // you never know
}
static int GetPushDelta (Instruction instruction)
{
OpCode code = instruction.OpCode;
switch (code.StackBehaviourPush) {
case StackBehaviour.Push0:
return 0;
case StackBehaviour.Push1:
case StackBehaviour.Pushi:
case StackBehaviour.Pushi8:
case StackBehaviour.Pushr4:
case StackBehaviour.Pushr8:
case StackBehaviour.Pushref:
return 1;
case StackBehaviour.Push1_push1:
return 2;
case StackBehaviour.Varpush:
if (code.FlowControl != FlowControl.Call)
break;
IMethodSignature method = (IMethodSignature) instruction.Operand;
return IsVoid (method.ReturnType.ReturnType) ? 0 : 1;
}
throw new NotSupportedException ();
}
static int GetPopDelta (MethodDefinition current, Instruction instruction, int height)
{
OpCode code = instruction.OpCode;
switch (code.StackBehaviourPop) {
case StackBehaviour.Pop0:
return 0;
case StackBehaviour.Popi:
case StackBehaviour.Popref:
case StackBehaviour.Pop1:
return 1;
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:
return 2;
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:
return 3;
case StackBehaviour.PopAll:
return height;
case StackBehaviour.Varpop:
if (code == OpCodes.Ret)
return IsVoid (current.ReturnType.ReturnType) ? 0 : 1;
if (code.FlowControl != FlowControl.Call)
break;
IMethodSignature method = (IMethodSignature) instruction.Operand;
int count = method.HasParameters ? method.Parameters.Count : 0;
if (method.HasThis && code != OpCodes.Newobj)
++count;
return count;
}
throw new NotSupportedException ();
}
static bool IsVoid (TypeReference type)
{
return type.FullName == Constants.Void;
}
}
}