protected void ScanMethod(MethodBase aMethod, bool aIsPlug, string sourceItem) { var xParams = aMethod.GetParameters(); var xParamTypes = new Type[xParams.Length]; // Dont use foreach, enum generaly keeps order but // isn't guaranteed. //string xMethodFullName = LabelName.GenerateFullName(aMethod); for (int i = 0; i < xParams.Length; i++) { xParamTypes[i] = xParams[i].ParameterType; Queue(xParamTypes[i], aMethod, "Parameter"); } var xIsDynamicMethod = aMethod.DeclaringType == null; // Queue Types directly related to method if (!aIsPlug) { // Don't queue declaring types of plugs if (!xIsDynamicMethod) { // dont queue declaring types of dynamic methods either, those dont have a declaring type Queue(aMethod.DeclaringType, aMethod, "Declaring Type"); } } if (aMethod is SysReflection.MethodInfo) { Queue(((SysReflection.MethodInfo)aMethod).ReturnType, aMethod, "Return Type"); } if (aMethod.GetFullName().IndexOf("CreateComparer", StringComparison.OrdinalIgnoreCase) != -1) { ; } // Scan virtuals #region Virtuals scan if (!xIsDynamicMethod && aMethod.IsVirtual) { // 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; var xVirtType = aMethod.DeclaringType; MethodBase xNewVirtMethod; while (true) { xVirtType = xVirtType.BaseType; if (xVirtType == null) { // We've reached object, can't go farther xNewVirtMethod = null; } else { xNewVirtMethod = xVirtType.GetMethod(aMethod.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, xParamTypes, null); if (xNewVirtMethod != null) { if (!xNewVirtMethod.IsVirtual) { // This can happen if a virtual "replaces" a non virtual // above it that is not virtual. xNewVirtMethod = null; } } } // We dont bother to add these to Queue, because we have to do a // full downlevel scan if its a new base virtual anyways. if (xNewVirtMethod == null) { // If its already in the list, we mark it null // so we dont do a full downlevel scan. if (mVirtuals.Contains(xVirtMethod)) { xVirtMethod = null; } break; } xVirtMethod = xNewVirtMethod; } // New virtual base found, we need to downscan it // If it was already in mVirtuals, then ScanType will take // care of new additions. if (xVirtMethod != null) { Queue(xVirtMethod, aMethod, "Virtual Base"); mVirtuals.Add(xVirtMethod); // List changes as we go, cant be foreach for (int i = 0; i < mItemsList.Count; i++) { if (mItemsList[i] is Type) { var xType = (Type)mItemsList[i]; if ((xType.IsSubclassOf(xVirtMethod.DeclaringType)) || ((xVirtMethod.DeclaringType.IsInterface) && (xVirtMethod.DeclaringType.IsAssignableFrom(xType)))) { var xNewMethod = xType.GetMethod(aMethod.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, xParamTypes, null); if (xNewMethod != null) { // We need to check IsVirtual, a non virtual could // "replace" a virtual above it? if (xNewMethod.IsVirtual) { Queue(xNewMethod, aMethod, "Virtual Downscan"); } } } } } } } #endregion MethodBase xPlug = null; // Plugs may use plugs, but plugs won't be plugged over themself var inl = aMethod.GetCustomAttribute <InlineAttribute>(); if (!aIsPlug && !xIsDynamicMethod) { // Check to see if method is plugged, if it is we don't scan body xPlug = mPlugManager.ResolvePlug(aMethod, xParamTypes); if (xPlug != null) { //ScanMethod(xPlug, true, "Plug method"); if (inl == null) { Queue(xPlug, aMethod, "Plug method"); } } } if (xPlug == null) { bool xNeedsPlug = false; if ((aMethod.Attributes & MethodAttributes.PinvokeImpl) != 0) { // pinvoke methods dont have an embedded implementation xNeedsPlug = true; } else { var xImplFlags = aMethod.GetMethodImplementationFlags(); // todo: prob even more if ((xImplFlags & MethodImplAttributes.Native) != 0 || (xImplFlags & MethodImplAttributes.InternalCall) != 0) { // native implementations cannot be compiled xNeedsPlug = true; } } if (xNeedsPlug) { throw new Exception("Native code encountered, plug required. Please see https://github.com/CosmosOS/Cosmos/wiki/Plugs). " + LabelName.GenerateFullName(aMethod) + "." + Environment.NewLine + " Called from :" + Environment.NewLine + sourceItem); } //TODO: As we scan each method, we could update or put in a new list // that has the resolved plug so we don't have to reresolve it again // later for compilation. // Scan the method body for more type and method refs //TODO: Dont queue new items if they are plugged // or do we need to queue them with a resolved ref in a new list? if (inl != null) { return; // cancel inline } List <ILOpCode> xOpCodes; xOpCodes = mReader.ProcessMethod(aMethod); if (xOpCodes != null) { ProcessInstructions(xOpCodes); foreach (var xOpCode in xOpCodes) { if (xOpCode is ILOpCodes.OpMethod) { Queue(((ILOpCodes.OpMethod)xOpCode).Value, aMethod, "Call", sourceItem); } else if (xOpCode is ILOpCodes.OpType) { Queue(((ILOpCodes.OpType)xOpCode).Value, aMethod, "OpCode Value"); } else if (xOpCode is ILOpCodes.OpField) { var xOpField = (ILOpCodes.OpField)xOpCode; //TODO: Need to do this? Will we get a ILOpCodes.OpType as well? Queue(xOpField.Value.DeclaringType, aMethod, "OpCode Value"); if (xOpField.Value.IsStatic) { //TODO: Why do we add static fields, but not instance? // AW: instance fields are "added" always, as part of a type, but for static fields, we need to emit a datamember Queue(xOpField.Value, aMethod, "OpCode Value"); } } else if (xOpCode is ILOpCodes.OpToken) { var xTokenOp = (ILOpCodes.OpToken)xOpCode; if (xTokenOp.ValueIsType) { Queue(xTokenOp.ValueType, aMethod, "OpCode Value"); } if (xTokenOp.ValueIsField) { Queue(xTokenOp.ValueField.DeclaringType, aMethod, "OpCode Value"); if (xTokenOp.ValueField.IsStatic) { //TODO: Why do we add static fields, but not instance? // AW: instance fields are "added" always, as part of a type, but for static fields, we need to emit a datamember Queue(xTokenOp.ValueField, aMethod, "OpCode Value"); } } } } } } }
protected void ScanMethod(MethodBase aMethod, bool aIsPlug, string sourceItem) { CompilerHelpers.Debug($"ILScanner: ScanMethod"); CompilerHelpers.Debug($"Method = '{aMethod}'"); CompilerHelpers.Debug($"IsPlug = '{aIsPlug}'"); CompilerHelpers.Debug($"Source = '{sourceItem}'"); var xParams = aMethod.GetParameters(); var xParamTypes = new Type[xParams.Length]; // Dont use foreach, enum generaly keeps order but // isn't guaranteed. //string xMethodFullName = LabelName.GetFullName(aMethod); for (int i = 0; i < xParams.Length; i++) { xParamTypes[i] = xParams[i].ParameterType; Queue(xParamTypes[i], aMethod, "Parameter"); } var xIsDynamicMethod = aMethod.DeclaringType == null; // Queue Types directly related to method if (!aIsPlug) { // Don't queue declaring types of plugs if (!xIsDynamicMethod) { // dont queue declaring types of dynamic methods either, those dont have a declaring type Queue(aMethod.DeclaringType, aMethod, "Declaring Type"); } } if (aMethod is MethodInfo) { Queue(((MethodInfo)aMethod).ReturnType, aMethod, "Return Type"); } // Scan virtuals #region Virtuals scan if (!xIsDynamicMethod && aMethod.IsVirtual) { // 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; var xVirtType = aMethod.DeclaringType; MethodBase xNewVirtMethod; while (true) { xVirtType = xVirtType.BaseType; if (xVirtType == null) { // We've reached object, can't go farther xNewVirtMethod = null; } else { xNewVirtMethod = xVirtType.GetMethod(aMethod.Name, xParamTypes); if (xNewVirtMethod != null) { if (!xNewVirtMethod.IsVirtual) { // This can happen if a virtual "replaces" a non virtual // above it that is not virtual. xNewVirtMethod = null; } } } // We dont bother to add these to Queue, because we have to do a // full downlevel scan if its a new base virtual anyways. if (xNewVirtMethod == null) { // If its already in the list, we mark it null // so we dont do a full downlevel scan. if (mVirtuals.Contains(xVirtMethod)) { xVirtMethod = null; } break; } xVirtMethod = xNewVirtMethod; } // New virtual base found, we need to downscan it // If it was already in mVirtuals, then ScanType will take // care of new additions. if (xVirtMethod != null) { Queue(xVirtMethod, aMethod, "Virtual Base"); mVirtuals.Add(xVirtMethod); // List changes as we go, cant be foreach for (int i = 0; i < mItemsList.Count; i++) { if (mItemsList[i] is Type xType && xType != xVirtMethod.DeclaringType && !xType.IsInterface) { if (xType.IsSubclassOf(xVirtMethod.DeclaringType)) { var enumerable = xType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(method => method.Name == aMethod.Name && method.GetParameters().Select(param => param.ParameterType).SequenceEqual(xParamTypes)); // We need to check IsVirtual, a non virtual could // "replace" a virtual above it? var xNewMethod = enumerable.FirstOrDefault(m => m.IsVirtual); while (xNewMethod != null && (xNewMethod.Attributes & MethodAttributes.NewSlot) != 0) { xType = xType.BaseType; xNewMethod = enumerable.Where(m => m.DeclaringType == xType).SingleOrDefault(); } if (xNewMethod != null) { Queue(xNewMethod, aMethod, "Virtual Downscan"); } } else if (xVirtMethod.DeclaringType.IsInterface && xType.GetInterfaces().Contains(xVirtMethod.DeclaringType) && (xType.BaseType != typeof(Array) || !xVirtMethod.DeclaringType.IsGenericType)) { var xInterfaceMap = xType.GetInterfaceMap(xVirtMethod.DeclaringType); var xMethodIndex = Array.IndexOf(xInterfaceMap.InterfaceMethods, xVirtMethod); if (xMethodIndex != -1) { var xMethod = xInterfaceMap.TargetMethods[xMethodIndex]; if (xMethod.DeclaringType == xType) { Queue(xInterfaceMap.TargetMethods[xMethodIndex], aMethod, "Virtual Downscan"); } } } } } } } #endregion Virtuals scan MethodBase xPlug = null; // Plugs may use plugs, but plugs won't be plugged over themself var inl = aMethod.GetCustomAttribute <InlineAttribute>(); if (!aIsPlug && !xIsDynamicMethod) { // Check to see if method is plugged, if it is we don't scan body xPlug = mPlugManager.ResolvePlug(aMethod, xParamTypes); if (xPlug != null) { //ScanMethod(xPlug, true, "Plug method"); if (inl == null) { Queue(xPlug, aMethod, "Plug method"); } } } if (xPlug == null) { bool xNeedsPlug = false; if ((aMethod.Attributes & MethodAttributes.PinvokeImpl) != 0) { // pinvoke methods dont have an embedded implementation xNeedsPlug = true; } else { var xImplFlags = aMethod.GetMethodImplementationFlags(); // todo: prob even more if (xImplFlags.HasFlag(MethodImplAttributes.Native) || xImplFlags.HasFlag(MethodImplAttributes.InternalCall)) { // native implementations cannot be compiled xNeedsPlug = true; } } if (xNeedsPlug) { throw new Exception(Environment.NewLine + "Native code encountered, plug required." + Environment.NewLine + " DO NOT REPORT THIS AS A BUG." + Environment.NewLine + " Please see http://www.gocosmos.org/docs/plugs/missing/" + Environment.NewLine + " Need plug for: " + LabelName.GetFullName(aMethod) + "(Plug Signature: " + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(aMethod)) + " ). " + Environment.NewLine + " Static: " + aMethod.IsStatic + Environment.NewLine + " Assembly: " + aMethod.DeclaringType.Assembly.FullName + Environment.NewLine + " Called from:" + Environment.NewLine + sourceItem + Environment.NewLine); } //TODO: As we scan each method, we could update or put in a new list // that has the resolved plug so we don't have to reresolve it again // later for compilation. // Scan the method body for more type and method refs //TODO: Dont queue new items if they are plugged // or do we need to queue them with a resolved ref in a new list? if (inl != null) { return; // cancel inline } List <ILOpCode> xOpCodes; xOpCodes = mReader.ProcessMethod(aMethod); if (xOpCodes != null) { ProcessInstructions(xOpCodes); foreach (var xOpCode in xOpCodes) { if (xOpCode is ILOpCodes.OpMethod) { Queue(((ILOpCodes.OpMethod)xOpCode).Value, aMethod, "Call", sourceItem); } else if (xOpCode is ILOpCodes.OpType) { Queue(((ILOpCodes.OpType)xOpCode).Value, aMethod, "OpCode Value"); } else if (xOpCode is ILOpCodes.OpField xOpField) { //TODO: Need to do this? Will we get a ILOpCodes.OpType as well? Queue(xOpField.Value.DeclaringType, aMethod, "OpCode Value"); if (xOpField.Value.IsStatic) { //TODO: Why do we add static fields, but not instance? // AW: instance fields are "added" always, as part of a type, but for static fields, we need to emit a datamember Queue(xOpField.Value, aMethod, "OpCode Value"); } } else if (xOpCode is ILOpCodes.OpToken xOpToken) { if (xOpToken.ValueIsType) { Queue(xOpToken.ValueType, aMethod, "OpCode Value"); } if (xOpToken.ValueIsField) { Queue(xOpToken.ValueField.DeclaringType, aMethod, "OpCode Value"); if (xOpToken.ValueField.IsStatic) { //TODO: Why do we add static fields, but not instance? // AW: instance fields are "added" always, as part of a type, but for static fields, we need to emit a datamember Queue(xOpToken.ValueField, aMethod, "OpCode Value"); } } } } } } }