/// <summary> /// Attempts to extract contracts from the specified assembly file. Used to verify that /// Foxtrot can still extract contracts from a rewritten assembly. /// </summary> private static void ExtractContracts(string assemblyFile, Options options) { // use assembly resolver from Foxtrot.Extractor var resolver = new AssemblyResolver( resolvedPaths: new string[0], libpaths: options.LibPaths, usePDB: false, preserveShortBranches: true, trace: false, postLoad: (r, assemblyNode) => { ContractNodes contractNodes = null; Extractor.ExtractContracts( assemblyNode, null, null, null, null, out contractNodes, e => Assert.True(false, e.ToString()), false); }); var assembly = resolver.ProbeForAssembly( assemblyName: Path.GetFileNameWithoutExtension(assemblyFile), referencingModuleDirectory: Path.GetDirectoryName(assemblyFile), exts: new string[] { Path.GetExtension(assemblyFile) }); // the assembly must be resolved and have no metadata import errors Assert.NotNull(assembly); Assert.True(assembly.MetadataImportErrors == null || assembly.MetadataImportErrors.Count == 0, "Parsing back the rewritten assembly produced metadata import errors"); }
private bool TryLoadContractNodes(ref AssemblyNode assembly) { ContractNodes nodes = null; foreach (Module module in assembly.Modules) { IAssemblyResolver assemblyResolver = module.Definition.AssemblyResolver; foreach (AssemblyNameReference reference in module.Definition.AssemblyReferences) { AssemblyDefinition def = assemblyResolver.Resolve(reference); nodes = ContractNodes.GetContractNodes(new AssemblyNode(def), (s) => { }); if (nodes != null) { break; } } } if (nodes == null) { return(false); } var extractor = new ContractExtractor(nodes, assembly, true); assembly = (AssemblyNode)extractor.Visit(assembly); return(true); }
private static int InternalMain(string[] args) { options = new FoxtrotOptions(); options.Parse(args); if (!options.nologo) { var version = typeof (FoxtrotOptions).Assembly.GetName().Version; Console.WriteLine("Microsoft (R) .NET Contract Rewriter Version {0}", version); Console.WriteLine("Copyright (C) Microsoft Corporation. All rights reserved."); Console.WriteLine(""); } if (options.HasErrors) { options.PrintErrorsAndExit(Console.Out); return -1; } if (options.HelpRequested) { options.PrintOptions("", Console.Out); return 0; } if (options.breakIntoDebugger) { Debugger.Launch(); } #if DEBUG if (options.nobox) { Debug.Listeners.Clear(); // listen for failed assertions Debug.Listeners.Add(new ExitTraceListener()); } #else Debug.Listeners.Clear(); #endif if (options.repro) { WriteReproFile(args); } var resolver = new AssemblyResolver( options.resolvedPaths, options.libpaths, options.debug, options.shortBranches, options.verbose > 2, PostLoadExtractionHook); GlobalAssemblyCache.probeGAC = options.useGAC; // Connect to LeaderBoard SendLeaderBoardRewriterFeature(options); int errorReturnValue = -1; IDictionary assemblyCache = new Hashtable(); // Trigger static initializer of SystemTypes var savedGACFlag = GlobalAssemblyCache.probeGAC; GlobalAssemblyCache.probeGAC = false; TypeNode dummy = SystemTypes.Object; TargetPlatform.Clear(); TargetPlatform.AssemblyReferenceFor = null; GlobalAssemblyCache.probeGAC = savedGACFlag; try { // Validate the command-line arguments. if (options.output != "same") { if (!Path.IsPathRooted(options.output)) { string s = Directory.GetCurrentDirectory(); options.output = Path.Combine(s, options.output); } } if (options.assembly == null && options.GeneralArguments.Count == 1) { options.assembly = options.GeneralArguments[0]; } if (!File.Exists(options.assembly)) { throw new FileNotFoundException(String.Format("The given assembly '{0}' does not exist.", options.assembly)); } InitializePlatform(resolver, assemblyCache); if (options.passthrough) { options.rewrite = false; } if (!(options.rewrite || options.passthrough)) { Console.WriteLine("Error: Need to specify at least one of: /rewrite, /pass"); options.PrintOptions("", Console.Out); return errorReturnValue; } if (options.extractSourceText && !options.debug) { Console.WriteLine("Error: Cannot specify /sourceText without also specifying /debug"); options.PrintOptions("", Console.Out); return errorReturnValue; } if (!(0 <= options.level && options.level <= 4)) { Console.WriteLine("Error: incorrect /level: {0}. /level must be between 0 and 4 (inclusive)", options.level); return errorReturnValue; } if (options.automaticallyLookForOOBs && options.contracts != null && 0 < options.contracts.Count) { Console.WriteLine("Error: Out of band contracts are being automatically applied, all files specified using the contracts option are ignored."); return errorReturnValue; } // Sanity check: just make sure that all files specified for out-of-band contracts actually exist bool atLeastOneOobNotFound = false; if (options.contracts != null) { foreach (string oob in options.contracts) { bool found = false; if (File.Exists(oob)) found = true; if (!found) { if (options.libpaths != null) { foreach (string dir in options.libpaths) { if (File.Exists(Path.Combine(dir, oob))) { found = true; break; } } } if (!found) { Console.WriteLine("Error: Contract file '" + oob + "' could not be found"); atLeastOneOobNotFound = true; } } } } if (atLeastOneOobNotFound) { return errorReturnValue; } // Load the assembly to be rewritten originalAssemblyName = Path.GetFileNameWithoutExtension(options.assembly); AssemblyNode assemblyNode = AssemblyNode.GetAssembly(options.assembly, TargetPlatform.StaticAssemblyCache, true, options.debug, true, options.shortBranches , delegate(AssemblyNode a) { //Console.WriteLine("Loaded '" + a.Name + "' from '" + a.Location.ToString() + "'"); PossiblyLoadOOB(resolver, a, originalAssemblyName); }); if (assemblyNode == null) throw new FileLoadException("The given assembly could not be loaded.", options.assembly); // Check to see if any metadata errors were reported if (assemblyNode.MetadataImportWarnings != null && assemblyNode.MetadataImportWarnings.Count > 0) { string msg = "\tThere were warnings reported in " + assemblyNode.Name + "'s metadata.\n"; foreach (Exception e in assemblyNode.MetadataImportWarnings) { msg += "\t" + e.Message; } Console.WriteLine(msg); } if (assemblyNode.MetadataImportErrors != null && assemblyNode.MetadataImportErrors.Count > 0) { string msg = "\tThere were errors reported in " + assemblyNode.Name + "'s metadata.\n"; foreach (Exception e in assemblyNode.MetadataImportErrors) { msg += "\t" + e.Message; } Console.WriteLine(msg); throw new InvalidOperationException("Foxtrot: " + msg); } else { //Console.WriteLine("\tThere were no errors reported in {0}'s metadata.", assemblyNode.Name); } // Load the rewriter assembly if any AssemblyNode rewriterMethodAssembly = null; if (options.rewriterMethods != null && 0 < options.rewriterMethods.Length) { string[] pieces = options.rewriterMethods.Split(','); if (!(pieces.Length == 2 || pieces.Length == 3)) { Console.WriteLine("Error: Need to provide two or three comma separated arguments to /rewriterMethods"); options.PrintOptions("", Console.Out); return errorReturnValue; } string assemName = pieces[0]; rewriterMethodAssembly = resolver.ProbeForAssembly(assemName, null, resolver.AllExt); if (rewriterMethodAssembly == null) { Console.WriteLine("Error: Could not open assembly '" + assemName + "'"); return errorReturnValue; } string nameSpaceName = null; string bareClassName = null; if (pieces.Length == 2) { // interpret A.B.C as namespace A.B and class C // no nested classes allowed. string namespaceAndClassName = pieces[1]; int lastDot = namespaceAndClassName.LastIndexOf('.'); nameSpaceName = lastDot == -1 ? "" : namespaceAndClassName.Substring(0, lastDot); bareClassName = namespaceAndClassName.Substring(lastDot + 1); userSpecifiedContractType = rewriterMethodAssembly.GetType(Identifier.For(nameSpaceName), Identifier.For(bareClassName)); } else { // pieces.Length == 3 // namespace can be A.B and class can be C.D nameSpaceName = pieces[1]; bareClassName = pieces[2]; userSpecifiedContractType = GetPossiblyNestedType(rewriterMethodAssembly, nameSpaceName, bareClassName); } if (userSpecifiedContractType == null) { Console.WriteLine("Error: Could not find type '" + bareClassName + "' in the namespace '" + nameSpaceName + "' in the assembly '" + assemName + "'"); return errorReturnValue; } } // Load the ASTs for all of the contract methods AssemblyNode contractAssembly = null; if (!options.passthrough) { // The contract assembly should be determined in the following order: // 0. if the option contractLibrary was specified, use that. // 1. the assembly being rewritten // 2. the system assembly // 3. the microsoft.contracts library if (options.contractLibrary != null) { contractAssembly = resolver.ProbeForAssembly(options.contractLibrary, Path.GetDirectoryName(options.assembly), resolver.EmptyAndDllExt); if (contractAssembly != null) DefaultContractLibrary = ContractNodes.GetContractNodes(contractAssembly, options.EmitError); if (contractAssembly == null || DefaultContractLibrary == null) { Console.WriteLine("Error: could not load Contracts API from assembly '{0}'", options.contractLibrary); return -1; } } else { if (DefaultContractLibrary == null) { // See if contracts are in the assembly we're rewriting DefaultContractLibrary = ContractNodes.GetContractNodes(assemblyNode, options.EmitError); } if (DefaultContractLibrary == null) { // See if contracts are in Mscorlib DefaultContractLibrary = ContractNodes.GetContractNodes(SystemTypes.SystemAssembly, options.EmitError); } // try to load Microsoft.Contracts.dll var microsoftContractsLibrary = resolver.ProbeForAssembly("Microsoft.Contracts", Path.GetDirectoryName(options.assembly), resolver.DllExt); if (microsoftContractsLibrary != null) { BackupContractLibrary = ContractNodes.GetContractNodes(microsoftContractsLibrary, options.EmitError); } if (DefaultContractLibrary == null && BackupContractLibrary != null) { DefaultContractLibrary = BackupContractLibrary; } } } if (DefaultContractLibrary == null) { if (options.output == "same") { Console.WriteLine( "Warning: Runtime Contract Checking requested, but contract class could not be found. Did you forget to reference the contracts assembly?"); return 0; // Returning success so that it doesn't break any build processes. } else { // Then an output file was specified (assume it is not the same as the input assembly, but that could be checked here). // In that case, consider it an error to not have found a contract class. // No prinicpled reason, but this is a common pitfall that several users have run into and the rewriter just // didn't do anything without giving any reason. Console.WriteLine( "Error: Runtime Contract Checking requested, but contract class could not be found. Did you forget to reference the contracts assembly?"); return errorReturnValue; } } if (0 < options.verbose) { Console.WriteLine("Trace: Using '" + DefaultContractLibrary.ContractClass.DeclaringModule.Location + "' for the definition of the contract class"); } // Make sure we extract contracts from the system assembly and the system.dll assemblies. // As they are already loaded, they will never trigger the post assembly load event. // But even if we are rewriting one of these assemblies (and so *not* trying to extract // the contracts at this point), still need to hook up our resolver to them. If this // isn't done, then any references chased down from them might not get resolved. bool isPreloadedAssembly = false; CheckIfPreloaded(resolver, assemblyNode, SystemTypes.SystemAssembly, ref isPreloadedAssembly); CheckIfPreloaded(resolver, assemblyNode, SystemTypes.SystemDllAssembly, ref isPreloadedAssembly); //CheckIfPreloaded(resolver, assemblyNode, SystemTypes.SystemRuntimeWindowsRuntimeAssembly, ref isPreloadedAssembly); //CheckIfPreloaded(resolver, assemblyNode, SystemTypes.SystemRuntimeWindowsRuntimeUIXamlAssembly, ref isPreloadedAssembly); if (!isPreloadedAssembly) { assemblyNode.AssemblyReferenceResolution += resolver.ResolveAssemblyReference; } MikesArchitecture(resolver, assemblyNode, DefaultContractLibrary, BackupContractLibrary); return options.GetErrorCount(); } catch (Exception exception) { SendLeaderBoardFailure(); // Redirect the exception message to the console and quit. Console.Error.WriteLine(new System.CodeDom.Compiler.CompilerError(exception.Source, 0, 0, null, exception.Message)); return errorReturnValue; } finally { // Reset statics userSpecifiedContractType = null; DefaultContractLibrary = null; BackupContractLibrary = null; originalAssemblyName = null; options = null; // eagerly close all assemblies due to pdb file locking issues DisposeAssemblies(assemblyCache.Values); // copy needed since Dispose actually removes things from the StaticAssemblyCache object[] assemblies = new object[TargetPlatform.StaticAssemblyCache.Values.Count]; TargetPlatform.StaticAssemblyCache.Values.CopyTo(assemblies, 0); DisposeAssemblies(assemblies); } }
private static void MikesArchitecture(AssemblyResolver resolver, AssemblyNode assemblyNode, ContractNodes contractNodes, ContractNodes backupContracts) { #if false var originalsourceDir = Path.GetDirectoryName(assemblyNode.Location); int oldPeVerifyCode = options.verify ? PEVerify(assemblyNode.Location, originalsourceDir) : -1; #endif // Check to see if the assembly has already been rewritten if (!options.passthrough) { if (ContractNodes.IsAlreadyRewritten(assemblyNode)) { if (!options.allowRewritten) { Console.WriteLine("Assembly '" + assemblyNode.Name + "' has already been rewritten. I, your poor humble servant, cannot rewrite it. Instead I must give up without rewriting it. Help!"); } return; } } // Extract the contracts from the code (includes checking the contracts) string contractFileName = Path.GetFileNameWithoutExtension(assemblyNode.Location) + ".Contracts"; if (options.contracts == null || options.contracts.Count <= 0) contractFileName = null; if (options.contracts != null && !options.contracts.Exists(name => name.Equals(assemblyNode.Name + ".Contracts.dll", StringComparison.OrdinalIgnoreCase))) { contractFileName = null; } AssemblyNode contractAssembly = null; if (contractFileName != null) { contractAssembly = resolver.ProbeForAssembly(contractFileName, assemblyNode.Directory, resolver.DllExt); } if (!options.passthrough) { ContractNodes usedContractNodes; Extractor.ExtractContracts(assemblyNode, contractAssembly, contractNodes, backupContracts, contractNodes, out usedContractNodes, options.EmitError, false); // important to extract source before we perform any more traversals due to contract instantiation. Otherwise, // we might get copies of contracts due to instantiation that have no source text yet. // Extract the text from the sources (optional) if (usedContractNodes != null && options.extractSourceText) { GenerateDocumentationFromPDB gd = new GenerateDocumentationFromPDB(contractNodes); gd.VisitForDoc(assemblyNode); } // After all contracts have been extracted in assembly, do some post-extractor checks // we run these even if no contracts were extracted due to checks having to do with overrides var contractNodesForChecks = usedContractNodes != null ? usedContractNodes : contractNodes; if (contractNodesForChecks != null) { PostExtractorChecker pec = new PostExtractorChecker(contractNodesForChecks, options.EmitError, false, options.fSharp, options.IsLegacyModeAssembly, options.addInterfaceWrappersWhenNeeded, options.level); if (contractAssembly != null) { pec.VisitForPostCheck(contractAssembly); } else { pec.VisitForPostCheck(assemblyNode); } } // don't really need to test, since if they are the same, the assignment doesn't change that // but this is to emphasize that the nodes used in the AST by the extractor are different // than what we thought we were using. if (options.GetErrorCount() > 0) { // we are done. // But first, report any metadata errors so they are not masked by the errors CheckForMetaDataErrors(assemblyNode); return; } } // If we have metadata errors, cop out { #if false for (int i = 0; i < assemblyNode.ModuleReferences.Count; i++) { Module m = assemblyNode.ModuleReferences[i].Module; Console.WriteLine("Location for referenced module '{0}' is '{1}'", m.Name, m.Location); } #endif if (CheckForMetaDataErrors(assemblyNode)) { throw new Exception("Rewrite aborted due to metadata errors. Check output window"); } for (int i = 0; i < assemblyNode.AssemblyReferences.Count; i++) { AssemblyNode aref = assemblyNode.AssemblyReferences[i].Assembly; if (CheckForMetaDataErrors(aref)) { throw new Exception("Rewrite aborted due to metadata errors. Check output window"); } } } // Inject the contracts into the code (optional) if (options.rewrite && !options.passthrough) { // Rewrite the assembly in memory. ContractNodes cnForRuntime = null; // make sure to use the correct contract nodes for runtime code generation. We may have Contractnodes pointing to microsoft.Contracts even though // the code relies on mscorlib to provide the contracts. So make sure the code references the contract nodes first. if (contractNodes != null && contractNodes.ContractClass != null && contractNodes.ContractClass.DeclaringModule != SystemTypes.SystemAssembly) { string assemblyNameContainingContracts = contractNodes.ContractClass.DeclaringModule.Name; for (int i = 0; i < assemblyNode.AssemblyReferences.Count; i++) { if (assemblyNode.AssemblyReferences[i].Name == assemblyNameContainingContracts) { cnForRuntime = contractNodes; break; // runtime actually references the contract library } } } if (cnForRuntime == null) { // try to grab the system assembly contracts cnForRuntime = ContractNodes.GetContractNodes(SystemTypes.SystemAssembly, null); } if (cnForRuntime == null) { // Can happen if the assembly does not use contracts directly, but inherits them from some other place // Use the normal contractNodes in this case (actually we should generate whatever we grab from ContractNodes) cnForRuntime = contractNodes; } RuntimeContractMethods runtimeContracts = new RuntimeContractMethods(userSpecifiedContractType, cnForRuntime, assemblyNode, options.throwOnFailure, options.level, options.publicSurfaceOnly, options.callSiteRequires, options.recursionGuard, options.hideFromDebugger, options.IsLegacyModeAssembly); Rewriter rewriter = new Rewriter(assemblyNode, runtimeContracts, options.EmitError, options.inheritInvariants, options.skipQuantifiers); rewriter.Verbose = 0 < options.verbose; rewriter.Visit(assemblyNode); // Perform this check only when there are no out-of-band contracts in use due to rewriter bug #336 if (contractAssembly == null) { PostRewriteChecker checker = new PostRewriteChecker(options.EmitError); checker.Visit(assemblyNode); } } //Console.WriteLine(">>>Finished Rewriting<<<"); // Set metadata version for target the same as for the source TargetPlatform.TargetRuntimeVersion = assemblyNode.TargetRuntimeVersion; // Write out the assembly (optional) if (options.rewrite || options.passthrough) { bool updateInPlace = options.output == "same"; string pdbFile = Path.ChangeExtension(options.assembly, ".pdb"); bool pdbExists = File.Exists(pdbFile); string backupAssembly = options.assembly + ".original"; string backupPDB = pdbFile + ".original"; if (updateInPlace) { // Write the rewritten assembly in a temporary location. options.output = options.assembly; MoveAssemblyFileAndPDB(options.output, pdbFile, pdbExists, backupAssembly, backupPDB); } // Write the assembly. // Don't pass the debugInfo flag to WriteModule unless the PDB file exists. assemblyNode.WriteModule(options.output, options.debug && pdbExists && options.writePDBFile); string outputDir = updateInPlace ? Path.GetDirectoryName(options.assembly) : Path.GetDirectoryName(options.output); // Re-attach external file resources to the new output assembly. MoveModuleResources(assemblyNode, outputDir); #if false if (oldPeVerifyCode == 0) { var newPeVerifyCode = PEVerify(assemblyNode.Location, originalsourceDir); if (newPeVerifyCode > 0) { if (updateInPlace) { // move original back in place MoveAssemblyFileAndPDB(backupAssembly, backupPDB, pdbExists, options.output, pdbFile); } throw new Exception("Rewrite failed to produce verifiable assembly"); } else if (newPeVerifyCode == 0) { Console.WriteLine("rewriter output verified"); } } #endif if (updateInPlace) { if (!options.keepOriginalFiles) { try { File.Delete(backupAssembly); } catch { // there are situations where the exe is still in use } if (options.debug && pdbExists && options.writePDBFile) { try { File.Delete(backupPDB); } catch { // I know this is stupid, but somehow on some machines we get an AccessError trying to delete the pdb. // so we leave it in place. } } } } } }