Click here to Skip to main content
15,881,173 members
Articles / Programming Languages / C#

Building .NET Coverage Tool

Rate me:
Please Sign up or sign in to vote.
4.94/5 (34 votes)
25 Aug 2009MIT8 min read 84.6K   2.3K   109  
This article is a walkthrough for building a .NET coverage tool
//
// 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;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions