/// <summary> /// Checks that all abstract methods and properties of the base class have been implemented, /// as they exist currently in the target assembly. /// </summary> /// <returns><c>false</c>, if one of more abstract methods were not implemented in the derived class, /// <c>true</c> otherwise.</returns> /// <param name="type">A class.</param> /// <remarks>Resolves the base class against the reference assemblies supplied on the command line.</remarks> static bool CheckAbstractMethods(TypeDefinition type) { bool failure = false; // ensure all abstract methods in the base class are overridden TypeDefinition @base = null; // 14 Jan 2017 S. Baer // If the type itself is abstract, then it doesn't need to implement all // of the abstract members in it's base class if (null != type.BaseType && !type.IsAbstract) { // resolve the base class so we're checking against the version of the library that we want try { @base = type.BaseType.Resolve(); } catch (AssemblyResolutionException) { logger.Warning("Couldn't resolve base class: {0}", type.BaseType.FullName); } if (null != @base) { // skip if base class isn't defined in one of the reference assemblies var scope = @base.Module.Assembly.Name; // be consistent if (!cache.ContainsKey(scope.Name)) { return(true); } Console.WriteLine(" Overrides ({0})", @base.FullName); foreach (var method in @base.Methods) { if (!method.IsAbstract) { continue; } bool is_overridden = null != Utils.TryMatchMethod(type, method); if (is_overridden) { Pretty.Instruction(ResolutionStatus.Success, scope.Name, method.FullName); } else { failure = true; Pretty.Instruction(ResolutionStatus.Failure, scope.Name, method.FullName); } } } } return(!failure); }
static int Main(string[] args) { if (args.Length < 1) { Usage("Not enough arguments"); return(ERROR_BAD_COMMAND); } // control verbosity if (args[0] == "--quiet" || args[0] == "-q") { quiet = true; logger.Level = Logger.LogLevel.WARNING; args = args.Skip(1).ToArray(); } else if (args[0] == "--debug") { logger.Level = Logger.LogLevel.DEBUG; args = args.Skip(1).ToArray(); } // again, check if we have enough arguments if (args.Length < 1) { Usage("Not enough arguments"); return(ERROR_BAD_COMMAND); } // should we return an error code if pinvokes exist? bool treatPInvokeAsError = false; if (args[0] == "--treat-pinvoke-as-error") { treatPInvokeAsError = true; args = args.Skip(1).ToArray(); } // again, check if we have enough arguments if (args.Length < 1) { Usage("Not enough arguments"); return(ERROR_BAD_COMMAND); } // first arg is the path to the main assembly being processed string fileName = args[0]; if (!File.Exists(fileName)) { // if the file doesn't exist, it might be a directory // TODO: handle directories if (Directory.Exists(fileName)) { logger.Error("{0} appears to be a directory; .NET assemblies only, please.", fileName); return(ERROR_NOT_DOTNET); } logger.Error("Couldn't find {0}. Are you sure it exists?", fileName); return(ERROR_NOT_THERE); } // check that the main file is a dot net assembly // this gives a clearer error message than the "one or more..." error try { System.Reflection.AssemblyName.GetAssemblyName(fileName); } catch (System.BadImageFormatException) { logger.Error("{0} is not a .NET assembly.", fileName); return(ERROR_NOT_DOTNET); } // load module and assembly resolver ModuleDefinition module; CustomAssemblyResolver customResolver; try { // second arg and onwards should be paths to reference assemblies // instantiate custom assembly resolver that loads reference assemblies into cache // note: ONLY these assemblies will be available to the resolver customResolver = new CustomAssemblyResolver(args.Skip(1)); // load the plugin module (with the custom assembly resolver) // TODO: perhaps we should load the plugin assembly then iterate through all modules module = ModuleDefinition.ReadModule(fileName, new ReaderParameters { AssemblyResolver = customResolver }); } catch (BadImageFormatException) { logger.Error("One (or more) of the files specified is not a .NET assembly"); return(ERROR_NOT_DOTNET); } catch (FileNotFoundException e) { logger.Error("Couldn't find {0}. Are you sure it exists?", e.FileName); return(ERROR_NOT_THERE); } if (module.Assembly.Name.Name == "") { logger.Error("Assembly has no name. This is unexpected."); return(ERROR_UNHANDLED_EXCEPTION); } // extract cached reference assemblies from custom assembly resolver // we'll query these later to make sure we only attempt to resolve a reference when the // definition is defined in an assembly in this list cache = customResolver.Cache; // print assembly name logger.Info("{0}\n", module.Assembly.FullName); // print assembly references (buildtime) if (module.AssemblyReferences.Count > 0) { logger.Info("Assembly references:", module.Assembly.Name.Name); foreach (AssemblyNameReference reference in module.AssemblyReferences) { logger.Info(" {0}", reference.FullName); } } logger.Info(""); // print cached assembly names (i.e. runtime references) if (args.Length > 1) { logger.Info("Cached assemblies:"); foreach (var assembly in args.Skip(1)) { logger.Info(" {0}", AssemblyDefinition.ReadAssembly(assembly).FullName); } } else // no reference assemblies. Grab the skipping rope { logger.Warning("Empty resolution cache (no reference assemblies specified)"); } logger.Info(""); // mixed-mode? bool isMixed = (module.Attributes & ModuleAttributes.ILOnly) != ModuleAttributes.ILOnly; logger.Info("Mixed-mode? {0}\n", isMixed); // global failure/pinvoke trackers for setting return code bool failure = false; bool pinvoke = false; List <TypeDefinition> types = GetAllTypesAndNestedTypes(module.Types); // iterate over all the TYPES foreach (TypeDefinition type in types) { Pretty.Class("{0}", type.FullName); // iterate over all the METHODS that have a method body foreach (MethodDefinition method in type.Methods) { Pretty.Method("{0}", method.FullName); if (!method.HasBody) // skip if no body { continue; } // iterate over all the INSTRUCTIONS foreach (var instruction in method.Body.Instructions) { // skip if no operand if (instruction.Operand == null) { continue; } logger.Debug( "Found instruction at {0} with code: {1}", instruction.Offset, instruction.OpCode.Code); string instructionString = instruction.Operand.ToString() // for sake of consistency .Replace("{", "{{").Replace("}", "}}"); // escape curly brackets // get the scope (the name of the assembly in which the operand is defined) IMetadataScope scope = GetOperandScope(instruction.Operand); if (scope != null) { // pinvoke? ModuleReference nativeModule; bool isPInvoke = IsPInvoke(instruction.Operand, out nativeModule); if (isPInvoke && nativeModule != null) { Pretty.Instruction(ResolutionStatus.PInvoke, nativeModule.Name, instructionString); pinvoke = true; continue; } // skip if scope is not in the list of cached reference assemblies if (!cache.ContainsKey(scope.Name)) { Pretty.Instruction(ResolutionStatus.Skipped, scope.Name, instructionString); continue; } logger.Debug("{0} is on the list so let's try to resolve it", scope.Name); logger.Debug(instruction.Operand.ToString()); // try to resolve operand // this is the big question - does the field/method/class exist in one of // the cached reference assemblies bool success = TryResolve(instruction.Operand, type); if (success || CheckMultidimensionalArray(instruction, method, type, scope)) { Pretty.Instruction(ResolutionStatus.Success, scope.Name, instructionString); } else { Pretty.Instruction(ResolutionStatus.Failure, scope.Name, instructionString); failure = true; // set global failure (non-zero exit code) } } } } // check that all abstract methods in the base type (where appropriate) have been implemented // note: base type resolved against the referenced assemblies failure |= CheckAbstractMethods(type) == false; } // exit code if (failure) { return(ERROR_COMPAT); } if (pinvoke && treatPInvokeAsError) { return(ERROR_PINVOKE); } return(0); // a-ok }