using System; using System.Collections.Generic; using System.Linq; using System.Text; using Mono.Cecil; using UtilityClasses; using Mono.Cecil.Cil; using EcmaCil.IL; using Mono.Cecil.Rocks; namespace MonoCecilToEcmaCil1 { public partial class Reader : IDisposable { private Dictionary mMethods = new Dictionary(); private Dictionary mTypes = new Dictionary(); private Dictionary mArrayTypes = new Dictionary(); private Dictionary mPointerTypes = new Dictionary(); private Dictionary mVirtuals = new Dictionary(); #region queueing system private Queue mQueue = new Queue(); private EcmaCil.MethodMeta EnqueueMethod(MethodReference aMethod, object aSource, string aSourceType) { if (mLogEnabled) { LogMapPoint(aSource, aSourceType, aMethod); } List xTypeArgs = null; var xGenSpec = aMethod as GenericInstanceMethod; TypeDefinition xTypeDef; MethodDefinition xMethodDef; TypeReference xRefType; if (xGenSpec != null) { xMethodDef = ResolveMethod(xGenSpec.ElementMethod); xRefType = xGenSpec.DeclaringType; xTypeArgs = new List(); foreach (TypeReference xArg in xGenSpec.GenericArguments) { xTypeArgs.Add(xArg); } } else { xMethodDef = ResolveMethod(aMethod); xRefType = aMethod.DeclaringType; } #region resolve type xTypeDef = xRefType as TypeDefinition; if (xTypeDef == null) { var xGenType = xRefType as GenericInstanceType; if (xGenType != null) { xTypeDef = ResolveType(xGenType.DeclaringType); if (xTypeArgs == null) { xTypeArgs = new List(); } for (int i = 0; i < xGenType.GenericArguments.Count; i++) { xTypeArgs.Insert(i, xGenType.GenericArguments[i]); } } else { xTypeDef = ResolveType(xRefType); } } #endregion var xQueuedMethod = new QueuedMethod(xTypeDef, xMethodDef, (xTypeArgs == null ? null : xTypeArgs.ToArray())); EcmaCil.MethodMeta xMethodMeta; if(mMethods.TryGetValue(xQueuedMethod, out xMethodMeta)){ return xMethodMeta; } var xDeclaringType = EnqueueType(xRefType, aMethod, "Declaring type"); xMethodMeta = new EcmaCil.MethodMeta(); xMethodMeta.DeclaringType = xDeclaringType; xDeclaringType.Methods.Add(xMethodMeta); mMethods.Add(xQueuedMethod, xMethodMeta); mQueue.Enqueue(xQueuedMethod); return xMethodMeta; } private EcmaCil.TypeMeta EnqueueType(TypeReference aTypeRef, object aSource, string aSourceType) { if (mLogEnabled) { LogMapPoint(aSource, aSourceType, aTypeRef); } List xTypeArgs = null; TypeDefinition xTypeDef; var xArrayType = aTypeRef as ArrayType; if (xArrayType != null) { var xQueuedArrayType = new QueuedArrayType(xArrayType); EcmaCil.ArrayTypeMeta xArrayMeta; if (mArrayTypes.TryGetValue(xQueuedArrayType, out xArrayMeta)) { return xArrayMeta; } var xElemMeta = EnqueueType(xQueuedArrayType.ArrayType.ElementType, aTypeRef, "Array element type"); xArrayMeta = new EcmaCil.ArrayTypeMeta { ElementType = xElemMeta }; mArrayTypes.Add(xQueuedArrayType, xArrayMeta); mQueue.Enqueue(xQueuedArrayType); return xArrayMeta; } var xPointerType = aTypeRef as PointerType; if (xPointerType != null) { var xQueuedPointerType = new QueuedPointerType(xPointerType); EcmaCil.PointerTypeMeta xPointerMeta; if (mPointerTypes.TryGetValue(xQueuedPointerType, out xPointerMeta)) { return xPointerMeta; } var xElemMeta = EnqueueType(xQueuedPointerType.PointerType.ElementType, aTypeRef, "Array element type"); xPointerMeta = new EcmaCil.PointerTypeMeta { ElementType = xElemMeta }; mPointerTypes.Add(xQueuedPointerType, xPointerMeta); mQueue.Enqueue(xQueuedPointerType); return xPointerMeta; } var xGenSpec = aTypeRef as GenericInstanceType; if (xGenSpec != null) { xTypeDef = ResolveType(xGenSpec.ElementType); xTypeArgs = new List(); foreach (TypeReference xArg in xGenSpec.GenericArguments) { xTypeArgs.Add(xArg); } } else { xTypeDef = ResolveType(aTypeRef); } var xQueuedType = new QueuedType(xTypeDef, (xTypeArgs == null ? null : xTypeArgs.ToArray())); EcmaCil.TypeMeta xTypeMeta; if (mTypes.TryGetValue(xQueuedType, out xTypeMeta)) { return xTypeMeta; } xTypeMeta = new EcmaCil.TypeMeta(); if (xTypeDef.BaseType != null) { xTypeMeta.BaseType = EnqueueType(xTypeDef.BaseType, aTypeRef, "Base type"); } mTypes.Add(xQueuedType, xTypeMeta); mQueue.Enqueue(xQueuedType); return xTypeMeta; } #endregion public void Dispose() { GC.SuppressFinalize(this); } public void Clear() { mMethods.Clear(); mTypes.Clear(); mArrayTypes.Clear(); mPointerTypes.Clear(); } public IEnumerable Execute(string assembly) { var xAssemblyDef = AssemblyDefinition.ReadAssembly(assembly); if (xAssemblyDef.EntryPoint == null) { throw new ArgumentException("Main assembly should have entry point!"); } if (xAssemblyDef.EntryPoint.GenericParameters.Count > 0 || xAssemblyDef.EntryPoint.DeclaringType.GenericParameters.Count > 0) { throw new ArgumentException("Main assembly's entry point has generic parameters. This is not supported!"); } EnqueueMethod(xAssemblyDef.EntryPoint, null, "entry point"); // handle queue while (mQueue.Count > 0) { var xItem = mQueue.Dequeue(); if (xItem is QueuedMethod) { var xMethod = (QueuedMethod)xItem; ScanMethod(xMethod, mMethods[xMethod]); continue; } if (xItem is QueuedArrayType) { var xType = (QueuedArrayType)xItem; ScanArrayType(xType, mArrayTypes[xType]); continue; } if (xItem is QueuedPointerType) { var xType = (QueuedPointerType)xItem; ScanPointerType(xType, mPointerTypes[xType]); continue; } if (xItem is QueuedType) { var xType = (QueuedType)xItem; ScanType(xType, mTypes[xType]); continue; } throw new Exception("Queue item not supported: '" + xItem.GetType().FullName + "'!"); } return mTypes.Values.Cast() .Union(mArrayTypes.Values.Cast()) .Union(mPointerTypes.Values.Cast()); } private void ScanPointerType(QueuedPointerType xType, EcmaCil.PointerTypeMeta pointerTypeMeta) { #if DEBUG pointerTypeMeta.Data[EcmaCil.DataIds.DebugMetaId] = "&" + pointerTypeMeta.ElementType.ToString(); #endif } private void ScanArrayType(QueuedArrayType aType, EcmaCil.ArrayTypeMeta arrayTypeMeta) { arrayTypeMeta.Dimensions = aType.ArrayType.Dimensions.Count; // todo: fix? foreach (ArrayDimension xDimension in aType.ArrayType.Dimensions) { if (xDimension.LowerBound != 0 || xDimension.UpperBound != 0) { throw new Exception("Arrays with limited dimensions not supported"); } } #if DEBUG var xSB = new StringBuilder(); xSB.Append(arrayTypeMeta.ElementType.ToString()); xSB.Append("["); xSB.Append(new String(',', arrayTypeMeta.Dimensions - 1)); xSB.Append("]"); arrayTypeMeta.Data[EcmaCil.DataIds.DebugMetaId] = xSB.ToString(); #endif } private void ScanType(QueuedType aType, EcmaCil.TypeMeta aTypeMeta) { #if DEBUG var xSB = new StringBuilder(); xSB.Append(aType.Type.ToString()); if (aType.Args.Length > 0) { xSB.Append("<"); for (int i = 0; i < aType.Args.Length; i++) { xSB.Append(aType.Args[i].ToString()); if (i < (aType.Args.Length - 1)) { xSB.Append(", "); } } xSB.Append(">"); } aTypeMeta.Data[EcmaCil.DataIds.DebugMetaId] = xSB.ToString(); #endif } private void ScanMethod(QueuedMethod aMethod, EcmaCil.MethodMeta aMethodMeta) { // todo: add support for plugs #if DEBUG aMethodMeta.Data[EcmaCil.DataIds.DebugMetaId] = aMethod.Method.ToString(); #endif var xParamOffset = 0; if (!aMethod.Method.IsStatic) { xParamOffset = 1; } aMethodMeta.Parameters = new EcmaCil.MethodParameterMeta[aMethod.Method.Parameters.Count + xParamOffset]; if (!aMethod.Method.IsStatic) { aMethodMeta.Parameters[0] = new EcmaCil.MethodParameterMeta { IsByRef = aMethod.Method.DeclaringType.IsValueType, PropertyType = EnqueueType(aMethod.Method.DeclaringType, aMethod, "Declaring type") }; #if DEBUG aMethodMeta.Parameters[0].Data[EcmaCil.DataIds.DebugMetaId] = "$this"; #endif } for(int i = 0; i < aMethod.Method.Parameters.Count;i++){ var xParam = aMethod.Method.Parameters[i]; var xParamType = xParam.ParameterType; if (xParamType is GenericParameter) { // todo: resolve generics. throw new NotImplementedException(); } var xParamMeta = aMethodMeta.Parameters[i + xParamOffset] = new EcmaCil.MethodParameterMeta(); //if (xParamType is ReferenceType) //{ // xParamType = ((ReferenceType)xParamType).ElementType; // xParamMeta.IsByRef = true; //} var xType = EnqueueType(xParamType, aMethod, "parameter"); #if DEBUG var xSB = new StringBuilder(); xSB.Append(xParam.Name); xSB.Append(": "); if (xParamMeta.IsByRef) { xSB.Append("ref "); } xSB.Append(xParamType.ToString()); xParamMeta.Data[EcmaCil.DataIds.DebugMetaId] = xSB.ToString(); #endif xParamMeta.PropertyType = xType; } if (aMethod.Method.ReturnType.FullName != "System.Void") { aMethodMeta.ReturnType = EnqueueType(aMethod.Method.ReturnType, aMethod.Method, "Return Type"); } aMethodMeta.IsStatic = aMethod.Method.IsStatic; ScanMethodBody(aMethod, aMethodMeta); #region Virtuals scan if (aMethod.Method.IsVirtual) { if (aMethod.Method.HasGenericParameters) { throw new Exception("GEnerics not yet fully supported"); } // For virtuals we need to climb up the type tree // and find the top base method. We then add that top // node to the mVirtuals list. We don't need to add the // types becuase adding DeclaringType will already cause // all ancestor types to be added. var xVirtMethod = aMethod.Method; TypeReference xVirtType = aMethod.Method.DeclaringType; xVirtMethod = xVirtMethod.GetOriginalBaseMethod(); #region old code // MethodReference xNewVirtMethod = null; // while (true) // { // var xNewVirtType = xVirtType.Resolve(); // if (xNewVirtType.HasGenericParameters) // { // throw new Exception("Generics not fully supported yet!"); // } // if (xNewVirtType == null) // { // xVirtType = null; // } // else // { //#warning // todo: verify if next code works ok with generics // var xTempNewVirtMethod = xNewVirtType. .m.Methods..GetMethod(aMethod.Method.Name, aMethod.Method.Parameters); // if (xTempNewVirtMethod !=null) // { // if (xTempNewVirtMethod.IsVirtual) // { // xNewVirtMethod = xTempNewVirtMethod; // } // } // else // { // xNewVirtMethod = null; // } // } // if (xNewVirtMethod == null) // { // if (mVirtuals.ContainsKey(aMethod)) // { // xVirtMethod = null; // } // break; // } // xVirtMethod = xNewVirtMethod.Resolve(); // xVirtType = xNewVirtType.BaseType; // if (xVirtType == null) // { // break; // } // } #endregion old code if (xVirtMethod!=null) { EnqueueMethod(xVirtMethod, aMethod, "Virtual Base"); foreach (var xType in mTypes) { if (xType.Key.Type.IsSubclassOf(xVirtMethod.DeclaringType)) { //xType.Key.Type.res //var xNewMethod = xType.Key.Type.Methods.GetMethod(aMethod.Method.Name, aMethod.Method.Parameters); //if (xNewMethod != null) //{ // // We need to check IsVirtual, a non virtual could // // "replace" a virtual above it? // // MtW: correct // if (xNewMethod.IsVirtual) // { // EnqueueMethod(xNewMethod, aMethod, "Virtual Downscan"); // } //} throw new NotImplementedException(); } } } } #endregion } protected virtual void ScanMethodBody(QueuedMethod aMethod, EcmaCil.MethodMeta aMethodMeta) { if (aMethod.Method.HasBody) { var xBody = aMethod.Method.Body; var xBodyMeta = aMethodMeta.Body = new EcmaCil.MethodBodyMeta(); xBodyMeta.InitLocals = xBody.InitLocals; #region handle exception handling clauses if (xBody.HasExceptionHandlers) { throw new Exception("ExceptionHandlers are not supported yet"); } #endregion #region handle locals xBodyMeta.LocalVariables = new EcmaCil.LocalVariableMeta[xBody.Variables.Count]; for (int i = 0; i < xBody.Variables.Count; i++) { var xVar = xBody.Variables[i]; var xVarMeta = xBodyMeta.LocalVariables[i] = new EcmaCil.LocalVariableMeta(); xVarMeta.LocalType = EnqueueType(xVar.VariableType, aMethod, "Local variable"); #if DEBUG xVarMeta.Data[EcmaCil.DataIds.DebugMetaId] = xVar.Name + ":" + xVar.VariableType.ToString(); #endif } #endregion xBodyMeta.Instructions = new EcmaCil.IL.BaseInstruction[xBody.Instructions.Count]; var xILOffsetToInstructionOffset = new Dictionary(); var xInstructionOffsetToILOffset = new Dictionary(); var xSecondStageInits = new List>(); for (int i = 0; i < xBody.Instructions.Count; i++) { xILOffsetToInstructionOffset.Add(xBody.Instructions[i].Offset, i); xInstructionOffsetToILOffset.Add(i, xBody.Instructions[i].Offset); } for (int i = 0; i < xBody.Instructions.Count; i++) { var xInstr = xBody.Instructions[i]; xBodyMeta.Instructions[i] = CreateInstructionMeta(aMethod, aMethodMeta, xInstr, xILOffsetToInstructionOffset, xInstructionOffsetToILOffset, i, xSecondStageInits, i + 1); } if (xSecondStageInits.Count > 0) { foreach (var xInit in xSecondStageInits) { xInit(aMethodMeta); } } } } private BaseInstruction CreateInstructionMeta(QueuedMethod aMethod, EcmaCil.MethodMeta aMethodMeta, Instruction curInstruction, IDictionary aILToInstructionOffset, IDictionary aInstructionToILOffset, int aCurrentIndex, IList> aSecondStageInits, int aNextIndex) { switch (curInstruction.OpCode.Code) { case Code.Nop: return new InstructionNone(InstructionKindEnum.Nop, aCurrentIndex); case Code.Pop: return new InstructionNone(InstructionKindEnum.Pop, aCurrentIndex); case Code.Ldc_I4_S: return new InstructionInt32(GetInstructionKind(curInstruction.OpCode.Code), aCurrentIndex, (int)(sbyte)curInstruction.Operand); case Code.Stloc_0: return new InstructionInt32(InstructionKindEnum.Stloc, aCurrentIndex, 0); case Code.Stloc_1: return new InstructionInt32(InstructionKindEnum.Stloc, aCurrentIndex, 1); case Code.Ldloc_0: return new InstructionLocal(InstructionKindEnum.Ldloc, aCurrentIndex, aMethodMeta.Body.LocalVariables[0]); case Code.Ldloc_1: return new InstructionLocal(InstructionKindEnum.Ldloc, aCurrentIndex, aMethodMeta.Body.LocalVariables[1]); case Code.Ldloc_2: return new InstructionLocal(InstructionKindEnum.Ldloc, aCurrentIndex, aMethodMeta.Body.LocalVariables[2]); case Code.Call: return new InstructionMethod(InstructionKindEnum.Call, aCurrentIndex, EnqueueMethod(curInstruction.Operand as MethodReference, aMethod.Method, "Operand value")); case Code.Callvirt: return new InstructionMethod(InstructionKindEnum.Callvirt, aCurrentIndex, EnqueueMethod(curInstruction.Operand as MethodReference, aMethod.Method, "Operand value")); case Code.Stloc_2: return new InstructionLocal(InstructionKindEnum.Stloc, aCurrentIndex, aMethodMeta.Body.LocalVariables[2]); case Code.Stloc_3: return new InstructionLocal(InstructionKindEnum.Stloc, aCurrentIndex, aMethodMeta.Body.LocalVariables[3]); case Code.Ret: return new InstructionNone(InstructionKindEnum.Ret, aCurrentIndex); case Code.Ldarg_0: return new InstructionArgument(InstructionKindEnum.Ldarg, aCurrentIndex, aMethodMeta.Parameters[0]); case Code.Ldarg_1: return new InstructionArgument(InstructionKindEnum.Ldarg, aCurrentIndex, aMethodMeta.Parameters[1]); case Code.Add: return new InstructionNone(InstructionKindEnum.Add, aCurrentIndex); case Code.Ldstr: return new InstructionString(InstructionKindEnum.Ldstr, aCurrentIndex, (string)curInstruction.Operand); case Code.Newobj: return new InstructionMethod(InstructionKindEnum.Newobj, aCurrentIndex, EnqueueMethod(curInstruction.Operand as MethodReference, aMethod.Method, "Operand value")); case Code.Br_S: case Code.Brfalse_S: var xInstr = new InstructionBranch(GetInstructionKind(curInstruction.OpCode.Code), aCurrentIndex); var xTargetInstr = (Instruction)curInstruction.Operand; var xTargetOffset = xTargetInstr.Offset; aSecondStageInits.Add(delegate(EcmaCil.MethodMeta aTheMethod) { xInstr.Target = aTheMethod.Body.Instructions[aILToInstructionOffset[xTargetOffset]]; }); return xInstr; // case //case OperandType.InlineNone: // { // xInstrMeta = new InstructionNone(GetInstructionKind(xInstr.OpCode.Code), i + xOffset); // break; // } //case OperandType.ShortInlineI: // { // xInstrMeta = new InstructionInt32(GetInstructionKind(xInstr.OpCode.Code), i + xOffset, (int)(sbyte)xInstr.Operand); // break; // } default: throw new Exception("Op '" + curInstruction.OpCode.Code + "' not implemented!"); } } } }