diff --git a/Samples/AsmMeta/AsmMeta.cs b/Samples/AsmMeta/AsmMeta.cs new file mode 100644 index 0000000..ace76cf --- /dev/null +++ b/Samples/AsmMeta/AsmMeta.cs @@ -0,0 +1,400 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All Rights Reserved. +// +//----------------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Cci; +using Microsoft.Cci.MutableCodeModel; +using Microsoft.Cci.MutableContracts; + +namespace AsmMeta { + delegate void ErrorLogger(string format, params string[] args); + + class AsmMetaOptions : OptionParsing { + + [OptionDescription("Attribute to exempt from whatever polarity keepAttributes is", ShortForm = "a")] + public List attrs = new List(); + + [OptionDescription("Behave as the original AsmMeta", ShortForm = "b")] + public bool backwardCompatibility = false; + + [OptionDescription("Emit contracts", ShortForm = "c")] + public bool contracts = true; + + [OptionDescription("Specify what elements to keep", ShortForm = "k")] + public KeepOptions whatToEmit = KeepOptions.All; + + [OptionDescription("Only emit security transparent & safe API's", ShortForm = "ot")] + public bool onlySecurityTransparent = false; + + [OptionDescription("Emit attributes", ShortForm = "ka")] + public bool emitAttributes = true; + + [OptionDescription("Output (full) path for the reference assembly.", ShortForm = "out")] + public string output = null; + + [OptionDescription("Rename the assembly itself.", ShortForm = "r")] + public bool rename = true; + + [OptionDescription("Just rename the assembly, don't modify it in any other way.", ShortForm = "ro")] + public bool renameOnly = false; + + [OptionDescription("When emitting contracts, include the source text of the condition. (Ignored if /contracts is not specified.)", ShortForm = "st")] + public bool includeSourceTextInContract = true; + + [OptionDescription("Produce a PDB for output", ShortForm = "pdb")] + public bool writePDB = false; + + [OptionDescription("Break into debugger", ShortForm = "break")] + public bool doBreak = false; + + [OptionDescription("Search path for referenced assemblies")] + public List libPaths = new List(); + + [OptionDescription("Full paths to candidate dlls to load for resolution.")] + public List resolvedPaths = new List(); + + [OptionDescription("Search the GAC for assemblies", ShortForm = "gac")] + public bool searchGAC = false; + } + + class AsmMeta { + + internal ITypeReference/*?*/ compilerGeneratedAttributeType = null; + internal ITypeReference/*?*/ contractClassType = null; + internal ITypeReference/*?*/ systemAttributeType = null; + internal ITypeReference/*?*/ systemBooleanType = null; + internal ITypeReference/*?*/ systemStringType = null; + internal ITypeReference/*?*/ systemObjectType = null; + internal ITypeReference/*?*/ systemVoidType = null; + public readonly AsmMetaOptions options; + + private ErrorLogger errorLogger; + + public AsmMeta(ErrorLogger errorLogger) { + this.errorLogger = errorLogger; + this.options = new AsmMetaOptions(); + } + + static int Main(string[] args) { + // Make this thing as robust as possible even against JIT failures: put all processing in a sub-method + // and call it from here. This method should have as few dependencies as possible. + int result = 0; + var startTime = DateTime.Now; + try { + #region Turn off all Debug.Assert calls in the infrastructure so this try-catch will report any errors + System.Diagnostics.Debug.Listeners.Clear(); + System.Diagnostics.Trace.Listeners.Clear(); + #endregion Turn off all Debug.Assert calls in the infrastructure so this try-catch will report any errors + result = RealMain(args); + } catch (Exception e) { // swallow everything and just return an error code + Console.WriteLine("AsmMeta failed with uncaught exception: {0}", e.Message); + Console.WriteLine("Stack trace: {0}", e.StackTrace); + return 1; + } finally { + var delta = DateTime.Now - startTime; + Console.WriteLine("elapsed time: {0}ms", delta.TotalMilliseconds); + } + return result; // success + } + + static int RealMain(string[] args) { + int errorReturnValue = -1; + + #region Parse the command-line arguments. + AsmMeta asmmeta = new AsmMeta(ConsoleErrorLogger); + asmmeta.options.Parse(args); + if (asmmeta.options.HelpRequested) { + asmmeta.options.PrintOptions(""); + return errorReturnValue; + } + if (asmmeta.options.HasErrors) { + asmmeta.options.PrintErrorsAndExit(Console.Out); + } + #endregion + + return asmmeta.Run(); + } + + static void ConsoleErrorLogger(string format, params string[] args) { + Console.WriteLine(format, args); + } + + internal int Run() { + SecurityKeepOptions securityKeepOptions = options.onlySecurityTransparent ? SecurityKeepOptions.OnlyNonCritical : SecurityKeepOptions.All; + + if (options.doBreak) { + System.Diagnostics.Debugger.Launch(); + } + + string assemblyName = null; + if (options.GeneralArguments.Count == 1) { + assemblyName = options.GeneralArguments[0]; + } + if (assemblyName == null) { + errorLogger("Must specify an input file."); + return 1; + } + + using (var host = new AsmMetaHostEnvironment(options.libPaths.ToArray(), options.searchGAC)) + { + foreach (var p in options.resolvedPaths) + { + host.AddResolvedPath(p); + } + + IAssembly/*?*/ assembly = host.LoadUnitFrom(assemblyName) as IAssembly; + if (assembly == null || assembly is Dummy) + { + errorLogger(assemblyName + " is not a PE file containing a CLR assembly, or an error occurred when loading it."); + return 1; + } + + var rewrittenAttribute = CreateTypeReference(host, assembly, "System.Diagnostics.Contracts.RuntimeContractsAttribute"); + if (AttributeHelper.Contains(assembly.Attributes, rewrittenAttribute)) + { + errorLogger(assemblyName + " is already rewritten, cannot generate a reference assembly from it."); + return 1; + } + + if (options.backwardCompatibility) + { // redundant because RemoveMethodBodies.ctor also does this when the flag is set + options.whatToEmit = KeepOptions.ExtVis; + options.emitAttributes = false; + } + + PdbReader/*?*/ pdbReader = null; + if (options.includeSourceTextInContract) + { // No point getting the PDB file unless we want to use it for source text + string pdbFile = Path.ChangeExtension(assembly.Location, "pdb"); + if (File.Exists(pdbFile)) + { + using (var pdbStream = File.OpenRead(pdbFile)) + { + pdbReader = new PdbReader(pdbStream, host); + } + } + else + { + errorLogger("Could not load the PDB file for the assembly '" + assembly.Name.Value + "' . Source text will not be preserved in the reference assembly. Proceeding anyway."); + } + } + using (pdbReader) + { + + + // We might be working on the assembly that defines the contract class and/or the type System.Void. + // (Or any other platform type!) + // But the host.PlatformType object has not been duplicated, so its properties will continue to be references to the immutable ones. + // This is OK, except if the assembly is being renamed. + this.compilerGeneratedAttributeType = host.PlatformType.SystemRuntimeCompilerServicesCompilerGeneratedAttribute; + this.contractClassType = host.PlatformType.SystemDiagnosticsContractsContract; + this.systemAttributeType = host.PlatformType.SystemAttribute; + this.systemBooleanType = host.PlatformType.SystemBoolean; + this.systemStringType = host.PlatformType.SystemString; + this.systemObjectType = host.PlatformType.SystemObject; + this.systemVoidType = host.PlatformType.SystemVoid; + //new FindPlatformTypes(this).Traverse(assembly); //update the above fields if any of those types are defined in mutable + + Assembly mutable = new MetadataDeepCopier(host).Copy(assembly); + + #region Rename the assembly in a separate pass because things done in later passes depend on interned keys that are computed based (in part) on the assembly identity + + if (options.rename) + { + if (options.output != null) + mutable.Name = host.NameTable.GetNameFor(Path.GetFileNameWithoutExtension(options.output)); + else + mutable.Name = host.NameTable.GetNameFor(assembly.Name.Value + ".Contracts"); + mutable.ModuleName = mutable.Name; + mutable.Kind = ModuleKind.DynamicallyLinkedLibrary; + mutable = (Assembly)RenameAssembly.ReparentAssemblyIdentity(host, assembly.AssemblyIdentity, mutable.AssemblyIdentity, mutable); + } + + #endregion Rename the assembly in a separate pass because things done in later passes depend on interned keys that are computed based (in part) on the assembly identity + if (!options.renameOnly) + { + if (options.contracts) + { + + if (options.rename) + { + // We might be working on the assembly that defines the contract class and/or the type System.Void. + // (Or any other platform type!) + // But the host.PlatformType object has not been duplicated, so its properties will continue to be references to the immutable ones. + // This is OK, except if the assembly is being renamed. + + var systemNamespace = GetFirstMatchingNamespace(host, mutable.UnitNamespaceRoot, "System"); + if (systemNamespace != null) + { + var typeDefinition = GetFirstMatchingTypeDefinition(host, systemNamespace, "Attribute"); + if (typeDefinition != null) this.systemAttributeType = typeDefinition; + typeDefinition = GetFirstMatchingTypeDefinition(host, systemNamespace, "Boolean"); + if (typeDefinition != null) this.systemBooleanType = typeDefinition; + typeDefinition = GetFirstMatchingTypeDefinition(host, systemNamespace, "Object"); + if (typeDefinition != null) this.systemObjectType = typeDefinition; + typeDefinition = GetFirstMatchingTypeDefinition(host, systemNamespace, "String"); + if (typeDefinition != null) this.systemStringType = typeDefinition; + typeDefinition = GetFirstMatchingTypeDefinition(host, systemNamespace, "Void"); + if (typeDefinition != null) this.systemVoidType = typeDefinition; + + var systemRuntimeNamespace = GetFirstMatchingNamespace(host, systemNamespace, "Runtime"); + if (systemRuntimeNamespace != null) + { + var systemRuntimeCompilerServicesNamespace = GetFirstMatchingNamespace(host, systemRuntimeNamespace, "CompilerServices"); + if (systemRuntimeCompilerServicesNamespace != null) + { + typeDefinition = GetFirstMatchingTypeDefinition(host, systemRuntimeCompilerServicesNamespace, "CompilerGeneratedAttribute"); + if (typeDefinition != null) this.compilerGeneratedAttributeType = typeDefinition; + } + } + + var systemDiagnosticsNamespace = GetFirstMatchingNamespace(host, systemNamespace, "Diagnostics"); + if (systemDiagnosticsNamespace != null) + { + var systemDiagnosticsContractsNamespace = GetFirstMatchingNamespace(host, systemDiagnosticsNamespace, "Contracts"); + if (systemDiagnosticsContractsNamespace != null) + { + typeDefinition = GetFirstMatchingTypeDefinition(host, systemDiagnosticsContractsNamespace, "Contract"); + if (typeDefinition != null) this.contractClassType = typeDefinition; + } + } + } + } + + mutable = AsmMetaRewriter.RewriteModule(host, + pdbReader, + mutable, + contractClassType, + compilerGeneratedAttributeType, + systemAttributeType, + systemBooleanType, + systemObjectType, + systemStringType, + systemVoidType + ) as Assembly; + } + #region Delete things that are not to be kept + if (options.backwardCompatibility || options.whatToEmit != KeepOptions.All || !options.emitAttributes || (options.attrs != null && 0 < options.attrs.Count)) + { + DeleteThings thinner = new DeleteThings(host, options.whatToEmit, securityKeepOptions, options.emitAttributes, options.attrs.ToArray(), options.backwardCompatibility); + if (securityKeepOptions == SecurityKeepOptions.OnlyNonCritical && host.platformType.SystemSecuritySecurityCriticalAttribute.ResolvedType is Dummy) + { + errorLogger("You asked to remove security critical methods, but the version of mscorlib doesn't support the SecurityCriticalAttribute."); + return 1; + } + thinner.RewriteChildren(mutable); + #region Fix up dangling references to things that were deleted + FixUpReferences patcher = new FixUpReferences(host, thinner.WhackedMethods, thinner.WhackedTypes); + patcher.RewriteChildren(mutable); + #endregion Fix up dangling references to things that were deleted + } + #endregion Delete things that are not to be kept + #region Output is always a dll, so mark the assembly as that + mutable.EntryPoint = Dummy.MethodReference; + mutable.Kind = ModuleKind.DynamicallyLinkedLibrary; + #endregion Output is always a dll, so mark the assembly as that + } + + assembly = mutable; + + string outputPath; + if (options.output != null) // user specified, they'd better make sure it doesn't conflict with anything + outputPath = options.output; + else if (options.rename) // A.dll ==> A.Contracts.dll (Always! Even if the input is an exe!) + outputPath = assembly.Name.Value + ".dll"; + else // A.dll ==> A.dll.meta + outputPath = assembly.Name.Value + Path.GetExtension(assemblyName) + ".meta"; + // NB: Do *not* pass a pdbWriter to WritePeToStream. No need to create a PDB file and if + // it is provided, then it might find things (like constants in a scope) that don't get + // visited and so don't have any type references modified as they should be. + using (var outputFile = File.Create(outputPath)) + { + if (pdbReader != null && options.writePDB) + { + using (var pdbWriter = new PdbWriter(Path.ChangeExtension(outputPath, "pdb"), pdbReader)) + { + // Need to not pass in a local scope provider until such time as we have one that will use the mutator + // to remap things (like the type of a scope constant) from the original assembly to the mutated one. + PeWriter.WritePeToStream(assembly, host, outputFile, pdbReader, null /*pdbReader*/, pdbWriter); + } + } + else + { + PeWriter.WritePeToStream(assembly, host, outputFile); + } + } + } + } + return 0; // success + } + + private INamespaceDefinition/*?*/ GetFirstMatchingNamespace(AsmMetaHostEnvironment host, INamespaceDefinition nameSpace, string name) { + foreach (var mem in nameSpace.GetMembersNamed(host.NameTable.GetNameFor(name), false)) { + var ns = mem as INamespaceDefinition; + if (ns != null) return ns; + } + return null; + } + private INamespaceTypeDefinition/*?*/ GetFirstMatchingTypeDefinition(AsmMetaHostEnvironment host, INamespaceDefinition nameSpace, string name) { + foreach (var mem in nameSpace.GetMembersNamed(host.NameTable.GetNameFor(name), false)) { + var ntd = mem as INamespaceTypeDefinition; + if (ntd != null) return ntd; + } + return null; + } + /// + /// Creates a type reference anchored in the given assembly reference and whose names are relative to the given host. + /// When the type name has periods in it, a structured reference with nested namespaces is created. + /// + private static INamespaceTypeReference CreateTypeReference(IMetadataHost host, IAssemblyReference assemblyReference, string typeName) { + IUnitNamespaceReference ns = new Microsoft.Cci.Immutable.RootUnitNamespaceReference(assemblyReference); + string[] names = typeName.Split('.'); + for (int i = 0, n = names.Length - 1; i < n; i++) + ns = new Microsoft.Cci.Immutable.NestedUnitNamespaceReference(ns, host.NameTable.GetNameFor(names[i])); + return new Microsoft.Cci.Immutable.NamespaceTypeReference(host, ns, host.NameTable.GetNameFor(names[names.Length - 1]), 0, false, false, true, PrimitiveTypeCode.NotPrimitive); + } + + } + + internal class AsmMetaHostEnvironment : FullyResolvedPathHost { + + internal readonly Microsoft.Cci.Immutable.PlatformType platformType; + + internal AsmMetaHostEnvironment(string[] libPaths, bool searchGAC) + : base() { + foreach (var p in libPaths) { + this.AddLibPath(p); + } + this.SearchInGAC = searchGAC; + this.platformType = new Microsoft.Cci.Immutable.PlatformType(this); + } + + public override IUnit LoadUnitFrom(string location) { + IUnit result = this.peReader.OpenModule(BinaryDocument.GetBinaryDocumentForFile(location, this)); + this.RegisterAsLatest(result); + return result; + } + + protected override IPlatformType GetPlatformType() { + return this.platformType; + } + + /// + /// override this here to not use memory mapped files since we want to use asmmeta in msbuild and it is sticky + /// + public override IBinaryDocumentMemoryBlock/*?*/ OpenBinaryDocument(IBinaryDocument sourceDocument) { + try { + IBinaryDocumentMemoryBlock binDocMemoryBlock = UnmanagedBinaryMemoryBlock.CreateUnmanagedBinaryMemoryBlock(sourceDocument.Location, sourceDocument); + this.disposableObjectAllocatedByThisHost.Add((IDisposable)binDocMemoryBlock); + return binDocMemoryBlock; + } catch (IOException) { + return null; + } + } + } + +} diff --git a/Samples/AsmMeta/AsmMeta.csproj b/Samples/AsmMeta/AsmMeta.csproj new file mode 100644 index 0000000..70bf199 --- /dev/null +++ b/Samples/AsmMeta/AsmMeta.csproj @@ -0,0 +1,240 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {469FD908-B6E8-4BF7-8A47-AF541FA8927E} + Exe + Properties + AsmMeta + AsmMeta + v4.0 + 512 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + 0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 414 + AnyCPU + AllRules.ruleset + False + False + True + False + False + False + False + False + False + False + False + False + False + False + False + False + True + False + False + False + True + False + False + True + + + + + + + False + Full + Build + 0 + + + pdbonly + true + bin\Release\ + + + prompt + 4 + AllRules.ruleset + AnyCPU + False + False + True + False + False + False + True + True + True + True + True + True + True + True + False + True + False + True + False + False + False + False + True + False + True + True + True + False + False + + + + + + + + True + False + False + True + Full + Build + 0 + + + + + + + + + Build\Version.cs + + + + + {0703D916-A881-45E6-A5CD-6BC50E2E30E2} + ContractExtractor + + + {08156C78-403A-4112-AD81-8646AC51CD2F} + ILGenerator + + + {34B9A0CE-DF18-4CBC-8F7A-90C2B74338D5} + PeReader + + + {304A8B0B-851B-4AA6-A17D-5F87F39C5E5C} + PeWriter + + + {319E151C-8F33-49E7-81C9-30F02F9BA90A} + MutableMetadataModel + + + {319E150C-8F33-49E7-81CA-30F02F9BA90A} + MutableCodeModel + + + {4A34A3C5-6176-49D7-A4C5-B2B671247F8F} + MetadataHelper + + + {33CAB640-0D03-43DF-81BD-22CDC6C0A597} + MetadataModel + + + {A6A31B03-7C3D-4DE6-AA73-BE88116BC40A} + PdbReader + + + {6D83F687-ABB5-40B3-915E-CA53DA0EB7F3} + PdbWriter + + + {A9103DDA-3A93-441A-8326-6EB20518C251} + CSharpSourceEmitter + + + {4B0054FD-124A-4037-9965-BDB55E6BF389} + SourceModel + + + {035FEA7F-0D36-4AE4-B694-EC45191B9AF2} + CodeModel + + + + + + + + 3.5 + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + PreserveNewest + + + + + \ No newline at end of file diff --git a/Samples/AsmMeta/AsmMeta.exe.config b/Samples/AsmMeta/AsmMeta.exe.config new file mode 100644 index 0000000..fc7e4df --- /dev/null +++ b/Samples/AsmMeta/AsmMeta.exe.config @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/Samples/AsmMeta/CCRefGenTask.cs b/Samples/AsmMeta/CCRefGenTask.cs new file mode 100644 index 0000000..bbbd9ba --- /dev/null +++ b/Samples/AsmMeta/CCRefGenTask.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +namespace Microsoft.Research { + public class CCRefGen : Task { + #region Properties + + bool sourceText = true; + public bool IncludeSourceTextInContracts { get { return sourceText; } set { sourceText = value; } } + + public bool Verifiable { get; set; } + + public string Output { get; set; } + + public string Input { get; set; } + + public bool WritePDB { get; set; } + + public ITaskItem[] LibPaths { get; set; } + + public ITaskItem[] ResolvedPaths { get; set; } + + public bool Break { get; set; } + + #endregion + + + public override bool Execute() { + +#if DEBUG + if (!System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Launch(); + } +#endif + + try { + #region Setup parameters + + if (Input == null) { this.Log.LogError("Input parameter must be specified"); return false; } + + AsmMeta.AsmMeta asmmeta = new AsmMeta.AsmMeta(this.ErrorLogger); + asmmeta.options.GeneralArguments.Add(Input); + asmmeta.options.output = Output; + asmmeta.options.includeSourceTextInContract = IncludeSourceTextInContracts; + asmmeta.options.writePDB = WritePDB; + if (ResolvedPaths!= null) { + foreach (var lp in ResolvedPaths) + { + asmmeta.options.resolvedPaths.Add(lp.ItemSpec); + } + } + if (LibPaths != null) + { + foreach (var lp in LibPaths) + { + asmmeta.options.libPaths.Add(lp.ItemSpec); + } + } + asmmeta.options.doBreak = Break; + #endregion + + var result = asmmeta.Run(); + + return (result == 0); + } catch (Exception e) { + this.Log.LogError("Exception: {0} caught", e.Message); + return false; + } + } + + void ErrorLogger(string format, params string[] args) { + this.Log.LogError(format, args); + } + + } + +} \ No newline at end of file diff --git a/Samples/AsmMeta/Properties/AssemblyInfo.cs b/Samples/AsmMeta/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..04f92fe --- /dev/null +++ b/Samples/AsmMeta/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AsmMeta")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5ec36bc2-06a7-4ed3-8714-3c3cfed37c6c")] + diff --git a/Samples/AsmMeta/RemoveMethodBodies.cs b/Samples/AsmMeta/RemoveMethodBodies.cs new file mode 100644 index 0000000..ce3ed88 --- /dev/null +++ b/Samples/AsmMeta/RemoveMethodBodies.cs @@ -0,0 +1,407 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All Rights Reserved. +// +//----------------------------------------------------------------------------- +using System; +using System.IO; +using Microsoft.Cci; +using Microsoft.Cci.MetadataReader; +using Microsoft.Cci.MutableCodeModel; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Serialization; // needed for defining exception .ctors +using System.Text; +using System.Diagnostics.Contracts; + +namespace AsmMeta { + // For deciding whether to keep a member, there are several interesting axes: + // Visibility: Keep everything, or only externally visible items, or external + friends (w/ FriendAccessAllowedAttribute) + // Security: Keep all methods, or only methods not marked with the SecurityCriticalAttribute. May add an HPA filter. + // Obsolete methods: Keep items marked with ObsoleteAttribute, or only if the ObsoleteAttribute's IsError flag is set, or none. + // Inclusion or exclusion list: Did the user tell us via a file to explicitly include or exclude this member? + // + // Secondary overriding consideration: For a value type appearing in the reference assembly, all of its fields + // must also appear in the reference assembly so that managed C++ can get precise size information for the value + // type. This may mean including private members that would otherwise be excluded. + enum KeepOptions { All, ExtVis, NonPrivate }; + enum SecurityKeepOptions { All, OnlyNonCritical, /* ExcludeMethodsWithStrictHPAs */ }; + + internal class DeleteThings : MetadataRewriter { + private KeepOptions WhatToKeep = KeepOptions.All; + private SecurityKeepOptions SecurityWhatToKeep = SecurityKeepOptions.All; + private bool KeepAttributes = true; + private Dictionary ExemptAttributes = new Dictionary(); + private IMethodReference/*?*/ entryPoint = null; + private bool entryPointKept; + /// + /// Behave just like the original AsmMeta + /// + private bool backwardCompat; + + /// + /// Tables for keeping track of everything that has been whacked. Used for a second pass to fix up references + /// to things that have been deleted. + /// + internal Dictionary WhackedTypes = new Dictionary(); + internal Dictionary WhackedMethods = new Dictionary(); + + private AsmMetaHostEnvironment asmMetaHostEnvironment; + + /// + /// Use this constructor when you don't want some things emitted into the output. + /// For instance, if is EmitOptions.ExtVis + /// then all methods, types, etc., that are not visible outside of the assembly are not emitted into the output. + /// + /// + /// Indicates what to emit and what to leave out. + /// For instance, if it is EmitOptions.ExtVis + /// then all methods, types, etc., that are not visible outside of the assembly + /// are not emitted into the output. + /// + /// + /// Specify whether to keep custom attributes on types, methods, assembly, etc. + /// + /// + /// A list of attribute names that are exempt from the polarity of . + /// For instance, if is true, then if an attribute is in the list, that + /// means to not emit it. Conversely, if is false, then if it is in the + /// list, it means to emit it. + /// + /// + /// When true, then this behaves just like the original AsmMeta. That means, among other things, that the argument values + /// of and are ignored and the values KeepOptions.ExtVis and + /// false, respectively, are used instead. + /// + public DeleteThings(AsmMetaHostEnvironment host, KeepOptions e, SecurityKeepOptions transparency, bool keepAttributes, string[] exemptAttributes, bool backwardCompatibility) + : base(host) { + this.asmMetaHostEnvironment = host; + if (backwardCompatibility) { + this.WhatToKeep = KeepOptions.ExtVis; + this.KeepAttributes = false; + this.backwardCompat = true; + this.SecurityWhatToKeep = SecurityKeepOptions.All; + } else { + this.WhatToKeep = e; + this.KeepAttributes = keepAttributes; + this.SecurityWhatToKeep = transparency; + } + for (int i = 0, n = exemptAttributes == null ? 0 : exemptAttributes.Length; i < n; i++) { + this.ExemptAttributes[exemptAttributes[i]] = true; + } + } + + static private bool IsFamilyOrIsFamilyORAssembly(ITypeDefinitionMember typeDefinitionMember) { + return typeDefinitionMember.Visibility == TypeMemberVisibility.Family || typeDefinitionMember.Visibility == TypeMemberVisibility.FamilyOrAssembly; + } + static private bool IsPublic(ITypeDefinition typeDefinition) { + INamespaceTypeDefinition namespaceTypeDefinition = typeDefinition as INamespaceTypeDefinition; + if (namespaceTypeDefinition != null) return namespaceTypeDefinition.IsPublic; + INestedTypeDefinition nestedTypeDefinition = typeDefinition as INestedTypeDefinition; + if (nestedTypeDefinition != null) return nestedTypeDefinition.Visibility == TypeMemberVisibility.Public; + return false; + } + + #region ShouldWhack + private bool ShouldWhack(ICustomAttribute a) { + string name = TypeHelper.GetTypeName(a.Type); + return this.KeepAttributes == this.ExemptAttributes.ContainsKey(name); + } + private bool ShouldWhack(ITypeDefinition typeDefinition) { + if (SecurityWhatToKeep == SecurityKeepOptions.OnlyNonCritical) { + if (IsSecurityCritical(typeDefinition)) + return true; + } + switch (this.WhatToKeep) { + case KeepOptions.All: + return false; + case KeepOptions.ExtVis: + if (typeDefinition is INamespaceTypeDefinition || typeDefinition is INestedTypeDefinition) + return !TypeHelper.IsVisibleOutsideAssembly(typeDefinition); + return false; // REVIEW: what is the right thing to do here? + case KeepOptions.NonPrivate: + INamespaceTypeDefinition namespaceTypeDefinition = typeDefinition as INamespaceTypeDefinition; + if (namespaceTypeDefinition != null) return !namespaceTypeDefinition.IsPublic; + INestedTypeDefinition nestedTypeDefinition = typeDefinition as INestedTypeDefinition; + if (nestedTypeDefinition != null) return nestedTypeDefinition.Visibility == TypeMemberVisibility.Private; + return false; + default: + return false; + } + } + private bool ShouldWhack(ITypeDefinitionMember mem) { + if (SecurityWhatToKeep == SecurityKeepOptions.OnlyNonCritical) { + if (IsSecurityCritical(mem)) + return true; + } + return ( + ((this.WhatToKeep == KeepOptions.ExtVis) && !MemberHelper.IsVisibleOutsideAssembly(mem)) + || + (this.WhatToKeep == KeepOptions.NonPrivate && mem.Visibility == TypeMemberVisibility.Private) + ); + } + // Does NOT include methods marked with SecuritySafeCriticalAttribute. + private bool IsSecurityCritical(ITypeDefinition type) { + return AttributeHelper.Contains(type.Attributes, this.asmMetaHostEnvironment.platformType.SystemSecuritySecurityCriticalAttribute); + } + // Does NOT include methods marked with SecuritySafeCriticalAttribute. + private bool IsSecurityCritical(ITypeDefinitionMember member) { + return AttributeHelper.Contains(member.Attributes, this.asmMetaHostEnvironment.platformType.SystemSecuritySecurityCriticalAttribute); + } + #endregion ShouldWhack + + public override void RewriteChildren(Assembly assembly) { + base.RewriteChildren(assembly); + if (this.backwardCompat) { + //modifiedAssembly.AssemblyReferences = new List(); + assembly.SecurityAttributes = new List(); + } + return; + } + + public override void RewriteChildren(Module module) { + if (module.EntryPoint is Dummy) + this.entryPoint = module.EntryPoint; + else + this.entryPoint = null; + + base.RewriteChildren(module); + + if (this.entryPoint != null && !this.entryPointKept) module.EntryPoint = Dummy.MethodReference; + if (this.backwardCompat) module.TrackDebugData = false; // not preserved by the original AsmMeta + return; + } + + public override List Rewrite(List namespaceMembers) { + List newList = new List(); + foreach (var namespaceMember in namespaceMembers) { + INamespaceTypeDefinition namespaceTypeDefinition = namespaceMember as INamespaceTypeDefinition; + if (namespaceTypeDefinition != null) { + if (this.ShouldWhack(namespaceTypeDefinition)) { + if (!this.WhackedTypes.ContainsKey(namespaceTypeDefinition.InternedKey)) + this.WhackedTypes.Add(namespaceTypeDefinition.InternedKey, true); + continue; + } + var mutableNamespaceTypeDefinition = (NamespaceTypeDefinition)this.Rewrite(namespaceTypeDefinition); + if (this.backwardCompat) + mutableNamespaceTypeDefinition.IsBeforeFieldInit = false; + newList.Add(mutableNamespaceTypeDefinition); + } else { + newList.Add(base.Rewrite(namespaceMember)); + } + } + return newList; + } + + public override List Rewrite(List nestedTypes) { + List newList = new List(); + for (int i = 0, n = nestedTypes.Count; i < n; i++) { + var nestedTypeDefinition = (NestedTypeDefinition)nestedTypes[i]; + if (nestedTypeDefinition != null && this.ShouldWhack((ITypeDefinition)nestedTypeDefinition)) { + if (!this.WhackedTypes.ContainsKey(nestedTypeDefinition.InternedKey)) + this.WhackedTypes.Add(nestedTypeDefinition.InternedKey, true); + continue; + } + this.Rewrite(nestedTypeDefinition); + if (this.backwardCompat) + nestedTypeDefinition.IsBeforeFieldInit = false; + newList.Add(nestedTypeDefinition); + } + return newList; + } + + public override List Rewrite(List customAttributes) { + List newList = new List(); + + foreach (CustomAttribute customAttribute in customAttributes) { + bool keep = true; + if (ShouldWhack(customAttribute)) { + keep = false; // priority goes to KeepAttribute setting + } else { + if (this.WhatToKeep != KeepOptions.All) { + if (ShouldWhack(customAttribute.Type.ResolvedType)) { + keep = false; + } else if (customAttribute.Arguments != null) { + // need to make sure that if there are any arguments that are types, those + // types are public. Otherwise whack the attribute + foreach (IMetadataExpression argument in customAttribute.Arguments) { + ITypeDefinition typeDefinition = argument.Type as ITypeDefinition; + if (typeDefinition != null) { + IMetadataConstant ct = argument as IMetadataConstant; + if (ct != null) { + ITypeDefinition constantValue = ct as ITypeDefinition; + if (ShouldWhack(constantValue)) { + keep = false; + break; // only need to find one to decide to whack attribute + } + } + } + } + } + } + } + if (keep) { + newList.Add(customAttribute); + } + } + return newList; + } + + public override List Rewrite(List events) { + List newList = new List(); + foreach (EventDefinition eventDefinition in events) { + if (!ShouldWhack(eventDefinition)) { + newList.Add(this.Rewrite(eventDefinition)); + } + } + return newList; + } + + public override List Rewrite(List fields) { + List newList = new List(); + foreach (FieldDefinition fieldDefinition in fields) { + if (!ShouldWhack(fieldDefinition)) { + newList.Add(this.Rewrite(fieldDefinition)); + } + } + return newList; + } + + public override List Rewrite(List methods) { + List newList = new List(); + foreach (MethodDefinition methodDefinition in methods) { + bool keep; + #region Decide whether to keep method or not + if (this.WhatToKeep != KeepOptions.All || this.SecurityWhatToKeep != SecurityKeepOptions.All) { + if (false && this.entryPoint != null && this.entryPoint == methodDefinition) { // I just checked the original AsmMeta's behavior and it deletes private Main methods! + keep = true; // need entry point even if it is not visible + //} else if (method.DeclaringMember != null && method.DeclaringMember.IsVisibleOutsideAssembly) { + // keep = true; // believe it or not, one accessor might not be visible, but the other one might be! + } else if (IsFamilyOrIsFamilyORAssembly(methodDefinition) + && IsPublic(methodDefinition.ContainingTypeDefinition) + && methodDefinition.ContainingTypeDefinition.IsSealed + ) { + keep = true; // compatibility with AsmMeta's rules... + } else { + keep = !ShouldWhack(methodDefinition); + } + } else { + keep = true; + } + #endregion + if (keep) { // still need to delete its body + if (this.entryPoint == methodDefinition) this.entryPointKept = true; + newList.Add(this.Rewrite(methodDefinition)); + } else { + if (!this.WhackedMethods.ContainsKey(methodDefinition.InternedKey)) + this.WhackedMethods.Add(methodDefinition.InternedKey, true); + } + } + return newList; + } + + public override List Rewrite(List properties) { + List newList = new List(); + foreach (PropertyDefinition propertyDefinition in properties) { + if (!ShouldWhack(propertyDefinition)) { + newList.Add(this.Rewrite(propertyDefinition)); + } + } + return newList; + } + } + + internal class FixUpReferences : MetadataRewriter { + + private Dictionary whackedMethods; + private Dictionary whackedTypes; + + public FixUpReferences(IMetadataHost host, Dictionary WhackedMethods, Dictionary WhackedTypes) + : base(host) { + this.whackedMethods = WhackedMethods; + this.whackedTypes = WhackedTypes; + } + + //TODO: what about events? + + public override void RewriteChildren(PropertyDefinition propertyDefinition) { + base.RewriteChildren(propertyDefinition); + if (propertyDefinition.Accessors != null && 0 < propertyDefinition.Accessors.Count) { + var accessors = new List(propertyDefinition.Accessors.Count); + foreach (var methodReference in propertyDefinition.Accessors) + if (!this.whackedMethods.ContainsKey(methodReference.InternedKey)) + accessors.Add(methodReference); + propertyDefinition.Accessors = accessors; + } + //TODO: what about the getter and setter? + return; + } + + public override void RewriteChildren(NamespaceTypeDefinition namespaceTypeDefinition) { + this.PruneInterfacesAndExplicitImplementationOverrides(namespaceTypeDefinition); + base.RewriteChildren(namespaceTypeDefinition); + } + + public override void RewriteChildren(NestedTypeDefinition nestedTypeDefinition) { + this.PruneInterfacesAndExplicitImplementationOverrides(nestedTypeDefinition); + base.RewriteChildren(nestedTypeDefinition); + } + + private void PruneInterfacesAndExplicitImplementationOverrides(NamedTypeDefinition typeDefinition) { + #region Prune the list of interfaces this type implements (if necessary) + if (typeDefinition.Interfaces != null && 0 < typeDefinition.Interfaces.Count) { + var newInterfaceList = new List(); + foreach (var iface in typeDefinition.Interfaces) { + if (!this.whackedTypes.ContainsKey(iface.InternedKey)) + newInterfaceList.Add(iface); + } + typeDefinition.Interfaces = newInterfaceList; + } + #endregion Prune the list of interfaces this type implements (if necessary) + #region Prune the list of explicit implementation overrides (as necessary) + if (typeDefinition.ExplicitImplementationOverrides != null && 0 < typeDefinition.ExplicitImplementationOverrides.Count) { + var newExplicitImplementationOverrides = new List(); + foreach (IMethodImplementation methodImpl in typeDefinition.ExplicitImplementationOverrides) { + if (!this.whackedMethods.ContainsKey(methodImpl.ImplementingMethod.InternedKey)) { + newExplicitImplementationOverrides.Add(methodImpl); + } + } + typeDefinition.ExplicitImplementationOverrides = newExplicitImplementationOverrides; + } + #endregion Prune the list of explicit implementation overrides (as necessary) + } + } + + internal class RenameAssembly : MetadataRewriter { + + private RenameAssembly(IMetadataHost host) + : base(host) { + } + + private AssemblyIdentity originalAssemblyIdentity = null; + private IAssemblyReference replacementAssemblyReference = null; + + public static IUnit ReparentAssemblyIdentity(IMetadataHost host, AssemblyIdentity targetAssemblyIdentity, AssemblyIdentity sourceAssemblyIdentity, IUnit unit) { + Contract.Requires(targetAssemblyIdentity != null); + Contract.Requires(sourceAssemblyIdentity != null); + var rar = new RenameAssembly(host); + rar.originalAssemblyIdentity = targetAssemblyIdentity; + rar.replacementAssemblyReference = new Microsoft.Cci.Immutable.AssemblyReference(host, sourceAssemblyIdentity); + return rar.Rewrite(unit); + } + + /// + /// The object model does not guarantee that the assembly references are shared, so + /// since the assembly (which itself can be a reference) is being updated, this method is + /// needed in order to guarantee that all references see the update. + /// + public override IAssemblyReference Rewrite(IAssemblyReference assemblyReference) { + return (assemblyReference.AssemblyIdentity.Equals(originalAssemblyIdentity)) + ? + replacementAssemblyReference + : + base.Rewrite(assemblyReference); + } + } + +} diff --git a/Samples/AsmMeta/Rewriter.cs b/Samples/AsmMeta/Rewriter.cs new file mode 100644 index 0000000..4ada54f --- /dev/null +++ b/Samples/AsmMeta/Rewriter.cs @@ -0,0 +1,685 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All Rights Reserved. +// +//----------------------------------------------------------------------------- +using System; +using System.IO; +using Microsoft.Cci; +using Microsoft.Cci.MetadataReader; +using Microsoft.Cci.MutableCodeModel; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Serialization; // needed for defining exception .ctors +using System.Text; +using System.Diagnostics.Contracts; + +namespace AsmMeta { + internal class AsmMetaRewriter : MetadataRewriter { + + private PdbReader pdbReader; + private readonly IName MoveNextName; + + /// + /// Maps contract method name to three arg version + /// Maps generic Requires method as RequiresE. + /// + private Dictionary threeArgumentVersionofContractMethod = new Dictionary(); + private NamespaceTypeDefinition/*?*/ classHoldingThreeArgVersions = null; + + /// + /// Used to recognize calls to methods in the Contract class + /// + private readonly ITypeReference compilerGeneratedAttributeType = null; + private readonly ITypeReference contractClassType = null; + private readonly ITypeReference systemAttributeType = null; + private readonly ITypeReference systemBooleanType = null; + private readonly ITypeReference systemStringType = null; + private readonly ITypeReference systemObjectType = null; + private readonly ITypeReference systemVoidType = null; + private readonly ITypeReference systemArgumentExceptionType = null; + CustomAttribute ContractReferenceAssemblyAttributeInstance; + /// + /// The list of all types in the assembly being mutated. Any types introduced to the assembly must be added to this list + /// as well as to the members list of their containing namespaces or containing types. + /// + List allTypes; // TODO: See if this can be deleted. + private IMethodDefinition currentMethod; + + + private AsmMetaRewriter( + IMetadataHost host, + PdbReader pdbReader, + ITypeReference contractClassType, + ITypeReference compilerGeneratedAttributeType, + ITypeReference systemAttributeType, + ITypeReference systemBooleanType, + ITypeReference systemObjectType, + ITypeReference systemStringType, + ITypeReference systemVoidType + ) + : base(host) { + this.pdbReader = pdbReader; + this.MoveNextName = host.NameTable.GetNameFor("MoveNext"); + this.contractClassType = contractClassType; + this.compilerGeneratedAttributeType = compilerGeneratedAttributeType; + this.systemAttributeType = systemAttributeType; + this.systemBooleanType = systemBooleanType; + this.systemObjectType = systemObjectType; + this.systemStringType = systemStringType; + this.systemVoidType = systemVoidType; + } + + public static IAssembly RewriteModule(IMetadataHost host, PdbReader pdbReader, IAssembly assembly, ITypeReference contractClassType, + ITypeReference compilerGeneratedAttributeType, + ITypeReference systemAttributeType, + ITypeReference systemBooleanType, + ITypeReference systemObjectType, + ITypeReference systemStringType, + ITypeReference systemVoidType) { + var me = new AsmMetaRewriter(host, pdbReader, contractClassType, compilerGeneratedAttributeType, systemAttributeType, systemBooleanType, systemObjectType, systemStringType, systemVoidType); + return me.Rewrite(assembly); + } + + public override void RewriteChildren(Assembly assembly) { + this.allTypes = assembly.AllTypes; + base.RewriteChildren(assembly); + #region Add assembly-level attribute marking this assembly as a reference assembly + if (assembly.AssemblyAttributes == null) assembly.AssemblyAttributes = new List(1); + assembly.AssemblyAttributes.Add(this.ContractReferenceAssemblyAttributeInstance); + #endregion Add assembly-level attribute marking this assembly as a reference assembly + #region Remove assembly-level attribute marking this as a declarative assembly + var contractDeclarativeAssemblyAttribute = UnitHelper.FindType(this.host.NameTable, assembly, "System.Diagnostics.Contracts.ContractDeclarativeAssemblyAttribute") + as INamespaceTypeDefinition; + if (contractDeclarativeAssemblyAttribute != null) { + assembly.AssemblyAttributes.RemoveAll(ca => TypeHelper.TypesAreEquivalent(ca.Type, contractDeclarativeAssemblyAttribute)); + } + + #endregion + return; + } + + public override void RewriteChildren(RootUnitNamespace rootUnitNamespace) { + this.classHoldingThreeArgVersions = this.GetContractClass(rootUnitNamespace); + + base.RewriteChildren(rootUnitNamespace); + + #region Possibly add class for any contract methods that were defined. + if (0 < this.threeArgumentVersionofContractMethod.Count) { + // Only add class to assembly if any 3 arg versions were actually created + rootUnitNamespace.Members.Add(classHoldingThreeArgVersions); + this.allTypes.Add(classHoldingThreeArgVersions); + } + #endregion Possibly add class for any contract methods that were defined. + + #region Create a reference to [ContractReferenceAssembly] to mark the assembly with + INamespaceTypeDefinition contractReferenceAssemblyAttribute = null; + #region First see if we can find it in the same assembly as the one we are rewriting + var unit = rootUnitNamespace.Unit; + contractReferenceAssemblyAttribute = UnitHelper.FindType(this.host.NameTable, unit, "System.Diagnostics.Contracts.ContractReferenceAssemblyAttribute") + as INamespaceTypeDefinition; + #endregion First see if we can find it in the same assembly as the one we are rewriting + #region If it doesn't exist there, then define it in the same place that the three-argument versions are defined + if (contractReferenceAssemblyAttribute is Dummy) { + contractReferenceAssemblyAttribute = CreateContractReferenceAssemblyAttribute(rootUnitNamespace); + } + #endregion If it doesn't exist there, then define it in the same place that the three-argument versions are defined + #region Create a reference to the ctor + var ctorRef = new Microsoft.Cci.MutableCodeModel.MethodReference() { + CallingConvention = CallingConvention.HasThis, + ContainingType = contractReferenceAssemblyAttribute, + InternFactory = this.host.InternFactory, + Name = host.NameTable.Ctor, + Type = systemVoidType, + }; + var rm = ctorRef.ResolvedMethod; + this.ContractReferenceAssemblyAttributeInstance = new CustomAttribute() { + Constructor = ctorRef, + }; + #endregion Create a reference to the ctor + #endregion Create a reference to [ContractReferenceAssembly] to mark the assembly with + + return; + } + + public override void RewriteChildren(MethodBody methodBody) { + + this.currentMethod = methodBody.MethodDefinition; + + var compilerGenerated = TypeHelper.IsCompilerGenerated(currentMethod.ContainingTypeDefinition); + // Method contracts from iterators (and async methods) end up in the MoveNext method in the compiler generated + // class that implements the state machine. So they need to be treated specially. + var isMoveNextMethodInCompilerGeneratedMethod = compilerGenerated && currentMethod.Name == this.MoveNextName; + + if ((compilerGenerated && !isMoveNextMethodInCompilerGeneratedMethod) || this.IsIteratorOrAsyncMethod(currentMethod)) { + return; + } + + uint lastOffset; + if (!TryGetOffsetOfLastContractCall(methodBody.Operations, out lastOffset)) + return; + // And here is the special handling: currently the contract extractors expect to find the contracts + // in the MoveNext method. So don't move them to the iterator/async method, just convert all of the + // contracts in the MoveNext method into the three-arg version and keep the whole method body. No + // point trying to truncate it since the contracts are not in a prefix of the instructions, but are + // buried within the state machine. + if (isMoveNextMethodInCompilerGeneratedMethod) { + var lastIndex = methodBody.Operations.Count - 1; + lastOffset = methodBody.Operations[lastIndex].Offset; + } + + try { + + var ilRewriter = new MyILRewriter(this.host, this, lastOffset, isMoveNextMethodInCompilerGeneratedMethod); + var rewrittenMethodBody = ilRewriter.Rewrite(methodBody); + + var locals = rewrittenMethodBody.LocalVariables; + + methodBody.LocalVariables = locals as List ?? new List(locals); + + var ops = rewrittenMethodBody.Operations; + methodBody.Operations = ops as List ?? new List(ops); + + methodBody.OperationExceptionInformation = new List(); + + methodBody.MaxStack = rewrittenMethodBody.MaxStack; + methodBody.MaxStack++; + + //// REVIEW: Is this okay to do here? Or does it need to be done in the IL Rewriter? + //#region All done: add return statement + //if (!isMoveNextMethodInCompilerGeneratedMethod) { + // if (currentMethod.Type.TypeCode != PrimitiveTypeCode.Void) { + // LocalDefinition ld = new LocalDefinition() { + // Type = currentMethod.Type, + // }; + // if (methodBody.LocalVariables == null) methodBody.LocalVariables = new List(); + // methodBody.LocalVariables.Add(ld); + // methodBody.Operations.Add(new Operation(){ OperationCode= OperationCode.Ldloc, Value = ld, }); + // methodBody.MaxStack++; + // } + // methodBody.Operations.Add(new Operation() { OperationCode = OperationCode.Ret, }); + //} + //#endregion All done: add return statement + + return; + + } catch (ExtractorException) { + Console.WriteLine("Warning: Unable to extract contract from the method '{0}'.", + MemberHelper.GetMemberSignature(currentMethod, NameFormattingOptions.SmartTypeName)); + methodBody.OperationExceptionInformation = new List(); + methodBody.Operations = new List(); + methodBody.MaxStack = 0; + return; + } + } + + /// + /// Methods that are iterators or async methods must have their bodies preserved + /// in the reference assembly because (for now) their contracts are left in the + /// MoveNext method found in the nested type defined in the containing type of this + /// method. The contract extraction that is done by downstream tools depends on + /// *this* method containing the preamble that creates an instance of that nested + /// type, otherwise they assume the method does *not* have any contracts (which + /// is incorrect). + /// + private bool IsIteratorOrAsyncMethod(IMethodDefinition currentMethod) { + // walk the Operations looking for the first newobj instruction + IMethodReference ctor = null; + foreach (var op in currentMethod.Body.Operations) { + if (op.OperationCode == OperationCode.Newobj) { + ctor = op.Value as IMethodReference; + break; + } + } + if (ctor == null) return false; + var nestedType = ctor.ContainingType as INestedTypeReference; + if (nestedType == null) return false; + if (nestedType.ContainingType.InternedKey != currentMethod.ContainingType.InternedKey) return false; + if (!TypeHelper.IsCompilerGenerated(ctor.ResolvedMethod.ContainingTypeDefinition)) return false; + var m = TypeHelper.GetMethod(nestedType.ResolvedType, this.host.NameTable.GetNameFor("MoveNext")); + return !(m is Dummy); + } + + private bool TryGetOffsetOfLastContractCall(List/*?*/ instrs, out uint offset) { + offset = uint.MaxValue; + if (instrs == null) return false; // not found + for (int i = instrs.Count - 1; 0 <= i; i--) { + IOperation op = instrs[i]; + if (op.OperationCode != OperationCode.Call) continue; + IMethodReference method = op.Value as IMethodReference; + if (method == null) continue; + if (Microsoft.Cci.MutableContracts.ContractHelper.IsValidatorOrAbbreviator(method)) { + offset = op.Offset; + return true; + } + var methodName = method.Name.Value; + if (TypeHelper.TypesAreEquivalent(method.ContainingType, this.contractClassType) + && IsNameOfPublicContractMethod(methodName) + ) { + offset = op.Offset; + return true; + } + } + return false; // not found + } + + private bool IsNameOfPublicContractMethod(string methodName) { + switch (methodName) { + case "Requires": + case "RequiresAlways": + case "Ensures": + case "EnsuresOnThrow": + case "Invariant": + case "EndContractBlock": + return true; + default: + return false; + } + } + + private class MyILRewriter : ILRewriter { + private AsmMetaRewriter parent; + private uint lastOffset; + private bool moveNextMethod; + + public MyILRewriter(IMetadataHost host, AsmMetaRewriter parent, uint lastOffset, bool moveNextMethod) + : base(host, parent.pdbReader, parent.pdbReader) { + this.parent = parent; + this.lastOffset = lastOffset; + this.moveNextMethod = moveNextMethod; + } + + protected override void EmitOperation(IOperation operation) { + if (operation.Offset <= this.lastOffset) { + if (this.parent.IsCallToContractMethod(operation)) { + EmitContractCall(operation); + } else { + base.EmitOperation(operation); + } + if (!this.moveNextMethod && operation.Offset == this.lastOffset) { + #region All done: add return statement + if (this.parent.currentMethod.Type.TypeCode != PrimitiveTypeCode.Void) { + var ld = new LocalDefinition() { + Type = this.parent.currentMethod.Type, + }; + base.EmitOperation(new Operation() { OperationCode = OperationCode.Ldloc, Value = ld, }); + base.TrackLocal(ld); + } + base.EmitOperation(new Operation() { OperationCode = OperationCode.Ret, }); + #endregion All done: add return statement + } + } + + } + + private void EmitContractCall(IOperation op) { + IMethodReference originalMethod = op.Value as IMethodReference; + IMethodReference methodToUse = this.parent.GetCorrespondingThreeArgContractMethod(originalMethod); + string sourceText = null; + if (this.parent.pdbReader != null) { + int startColumn; + string sourceLanguage; + this.parent.GetSourceTextFromOperation(op, out sourceText, out startColumn, out sourceLanguage); + if (sourceText != null) { + int firstSourceTextIndex = sourceText.IndexOf('('); + int lastSourceTextIndex = this.parent.GetLastSourceTextIndex(sourceText, originalMethod, firstSourceTextIndex + 1); + firstSourceTextIndex = firstSourceTextIndex == -1 ? 0 : firstSourceTextIndex + 1; // the +1 is to skip the opening paren + if (lastSourceTextIndex <= firstSourceTextIndex) { + //Console.WriteLine(sourceText); + lastSourceTextIndex = sourceText.Length; // if something went wrong, at least get the whole source text. + } + sourceText = sourceText.Substring(firstSourceTextIndex, lastSourceTextIndex - firstSourceTextIndex); + var indentSize = firstSourceTextIndex + (startColumn - 1); // -1 because columns start at one, not zero + sourceText = AdjustIndentationOfMultilineSourceText(sourceText, indentSize); + } + } + if (originalMethod.ParameterCount == 1) { + // add null user message + base.EmitOperation(new Operation() { OperationCode = OperationCode.Ldnull, }); + } + if (sourceText == null) { + base.EmitOperation(new Operation() { OperationCode = OperationCode.Ldnull }); + } else { + base.EmitOperation(new Operation() { OperationCode = OperationCode.Ldstr, Value = sourceText }); + } + base.EmitOperation(new Operation() { OperationCode = OperationCode.Call, Value = methodToUse }); + return; + } + + private static string AdjustIndentationOfMultilineSourceText(string sourceText, int trimLength) { + if (!sourceText.Contains("\n")) return sourceText; + var lines = sourceText.Split('\n'); + if (lines.Length == 1) return sourceText; + for (int i = 1; i < lines.Length; i++) { + var currentLine = lines[i]; + if (trimLength < currentLine.Length) { + var prefix = currentLine.Substring(0, trimLength); + if (All(prefix, ' ')) { + lines[i] = currentLine.Substring(trimLength); + } + } + } + var numberOfLinesToJoin = String.IsNullOrEmpty(lines[lines.Length - 1].TrimStart(whiteSpace)) ? lines.Length - 1 : lines.Length; + return String.Join("\n", lines, 0, numberOfLinesToJoin); + } + static char[] whiteSpace = { ' ', '\t' }; + private static bool All(string s, char c) { + foreach (var x in s) + if (x != c) return false; + return true; + } + + + + + } + + private NamespaceTypeDefinition GetContractClass(RootUnitNamespace unitNamespace) { + var contractClass = UnitHelper.FindType(this.host.NameTable, (IModule)unitNamespace.Unit, "System.Diagnostics.Contracts.Contract") + as NamespaceTypeDefinition; + + if (contractClass != null) return contractClass; + return CreateContractClass(unitNamespace); + } + private NamespaceTypeDefinition CreateContractClass(UnitNamespace unitNamespace) { + + var contractTypeName = this.host.NameTable.GetNameFor("Contract"); + var contractNamespaceName = this.host.NameTable.GetNameFor("System.Diagnostics.Contracts"); + + Microsoft.Cci.MethodReference compilerGeneratedCtor = + new Microsoft.Cci.MethodReference( + this.host, + this.compilerGeneratedAttributeType, + CallingConvention.HasThis, + this.systemVoidType, + this.host.NameTable.Ctor, + 0); + CustomAttribute compilerGeneratedAttribute = new CustomAttribute(); + compilerGeneratedAttribute.Constructor = compilerGeneratedCtor; + + var contractsNs = new NestedUnitNamespace() { + ContainingUnitNamespace = unitNamespace, + Name = contractNamespaceName, + }; + NamespaceTypeDefinition result = new NamespaceTypeDefinition() { + // NB: The string name must be kept in sync with the code that recognizes contract + // methods!! + Name = contractTypeName, + Attributes = new List{ compilerGeneratedAttribute }, + BaseClasses = new List{ this.systemObjectType }, + ContainingUnitNamespace = contractsNs, + InternFactory = this.host.InternFactory, + IsBeforeFieldInit = true, + IsClass = true, + IsSealed = true, + Layout = LayoutKind.Auto, + StringFormat = StringFormatKind.Ansi, + }; + return result; + } + private NamespaceTypeDefinition CreateContractReferenceAssemblyAttribute(IRootUnitNamespace rootNs) { + + var internFactory = this.host.InternFactory; + var nameTable = this.host.NameTable; + + var contractReferenceAssemblyAttributeName = nameTable.GetNameFor("ContractReferenceAssemblyAttribute"); + var contractNamespaceName = nameTable.GetNameFor("System.Diagnostics.Contracts"); + + #region Define type + CustomAttribute compilerGeneratedAttribute = new CustomAttribute() { + Constructor = new Microsoft.Cci.MethodReference( + this.host, + this.compilerGeneratedAttributeType, + CallingConvention.HasThis, + this.systemVoidType, + this.host.NameTable.Ctor, + 0) + }; + + var contractsNs = new NestedUnitNamespace() { + ContainingUnitNamespace = rootNs, + Name = contractNamespaceName, + }; + NamespaceTypeDefinition result = new NamespaceTypeDefinition() { + Name = contractReferenceAssemblyAttributeName, + Attributes = new List{ compilerGeneratedAttribute }, + BaseClasses = new List{ this.systemAttributeType }, + ContainingUnitNamespace = contractsNs, //unitNamespace, + InternFactory = internFactory, + IsBeforeFieldInit = true, + IsClass = true, + IsSealed = true, + Methods = new List(), + Layout = LayoutKind.Auto, + StringFormat = StringFormatKind.Ansi, + }; + contractsNs.Members.Add(result); + this.allTypes.Add(result); + #endregion Define type + #region Define the ctor + List statements = new List(); + SourceMethodBody body = new SourceMethodBody(this.host) { + LocalsAreZeroed = true, + Block = new BlockStatement() { Statements = statements }, + }; + MethodDefinition ctor = new MethodDefinition() { + Body = body, + CallingConvention = CallingConvention.HasThis, + ContainingTypeDefinition = result, + InternFactory = internFactory, + IsRuntimeSpecial = true, + IsStatic = false, + IsSpecialName = true, + Name = nameTable.Ctor, + Type = this.systemVoidType, + Visibility = TypeMemberVisibility.Public, + }; + body.MethodDefinition = ctor; + var thisRef = new ThisReference() { Type = result, }; + // base(); + foreach (var baseClass in result.BaseClasses) { + var baseCtor = new Microsoft.Cci.MutableCodeModel.MethodReference() { + CallingConvention = CallingConvention.HasThis, + ContainingType = baseClass, + GenericParameterCount = 0, + InternFactory = this.host.InternFactory, + Name = nameTable.Ctor, + Type = this.systemVoidType, + }; + statements.Add( + new ExpressionStatement() { + Expression = new MethodCall() { + MethodToCall = baseCtor, + IsStaticCall = false, // REVIEW: Is this needed in addition to setting the ThisArgument? + ThisArgument = new ThisReference() { Type = result, }, + Type = this.systemVoidType, // REVIEW: Is this the right way to do this? + Arguments = new List(), + } + } + ); + break; + } + + // return; + statements.Add(new ReturnStatement()); + result.Methods.Add(ctor); + #endregion Define the ctor + return result; + } + + private bool IsCallToContractMethod(IOperation op) { + if (op.OperationCode != OperationCode.Call) return false; + IMethodReference method = op.Value as IMethodReference; + if (method == null) return false; + ITypeReference contractClass = method.ContainingType; + if (!TypeHelper.TypesAreEquivalent(contractClass, this.contractClassType)) return false; + switch (method.Name.Value) { + case "Requires": + case "RequiresAlways": // TODO: Remove once RequiresAlways is gone from the library + case "Ensures": + case "EnsuresOnThrow": + case "Invariant": + case "Assert": + case "Assume": + return true; + default: + return false; + } + } + + private IMethodReference GetCorrespondingThreeArgContractMethod(IMethodReference originalMethod) { + ushort genericParameters = 0; + string contractMethodName = originalMethod.Name.Value; + string keyName = contractMethodName; + IGenericMethodInstanceReference methodInstance = originalMethod as IGenericMethodInstanceReference; + if (methodInstance != null) { + originalMethod = methodInstance.GenericMethod; + genericParameters = originalMethod.GenericParameterCount; + keyName = originalMethod.Name.Value + genericParameters; + } + + #region Backward compatibility with v4 Beta 1 which went out with RequiresAlways in it (REMOVE WHEN THAT IS DELETED) + bool backwardCompat = false; + if (contractMethodName.Equals("RequiresAlways")) { + contractMethodName = "Requires1"; // The one is for the generic parameter + genericParameters = 1; + backwardCompat = true; + } + #endregion Backward compatibility with v4 Beta 1 which went out with RequiresAlways in it (REMOVE WHEN THAT IS DELETED) + + IMethodReference methodToUse; + this.threeArgumentVersionofContractMethod.TryGetValue(keyName, out methodToUse); + if (methodToUse == null) { + #region Create a method + methodToUse = CreateThreeArgVersionOfMethod(contractMethodName, keyName, genericParameters, backwardCompat); + #endregion Create a method + } + if (genericParameters != 0) { + // instantiate method to use + methodToUse = new Microsoft.Cci.Immutable.GenericMethodInstanceReference(methodToUse, + backwardCompat ? IteratorHelper.GetSingletonEnumerable(this.systemArgumentExceptionType) : methodInstance.GenericArguments, + this.host.InternFactory); + var key = methodToUse.InternedKey; + } + return methodToUse; + } + + private IMethodReference CreateThreeArgVersionOfMethod(string originalMethodName, string keyName, ushort genericParameters, bool backwardCompat) { + MethodBody body = new MethodBody() { + Operations = new List{ new Operation() { OperationCode = OperationCode.Ret, } }, + }; + + MethodDefinition threeArgVersion = new MethodDefinition() { + Body = body, + CallingConvention = CallingConvention.Default, // Isn't it the default for the calling convention to be the default? + ContainingTypeDefinition = this.classHoldingThreeArgVersions, + GenericParameters = new List(genericParameters), + Name = backwardCompat ? this.host.NameTable.GetNameFor("Requires") : this.host.NameTable.GetNameFor(originalMethodName), + Type = this.systemVoidType, + Visibility = TypeMemberVisibility.Public, + IsStatic = true, + InternFactory = this.host.InternFactory, // NB: without this, the method has an interned key of zero, which gets confused with other dummy interned keys!! + }; + if (genericParameters != 0) { + threeArgVersion.CallingConvention = CallingConvention.Generic; + var typeArg = new GenericMethodParameter() { + Name = this.host.NameTable.GetNameFor("TException"), + DefiningMethod = threeArgVersion, + InternFactory = this.host.InternFactory, + }; + // TODO: add SystemException base type? + threeArgVersion.GenericParameters.Add(typeArg); + } + List paramList = new List(); + paramList.Add( + new ParameterDefinition() { + ContainingSignature = threeArgVersion, + Name = this.host.NameTable.GetNameFor("condition"), + Type = this.systemBooleanType, + Index = 0, + }); + paramList.Add( + new ParameterDefinition() { + ContainingSignature = threeArgVersion, + Name = this.host.NameTable.GetNameFor("userSuppliedString"), + Type = this.systemStringType, + Index = 1, + }); + paramList.Add( + new ParameterDefinition() { + ContainingSignature = threeArgVersion, + Name = this.host.NameTable.GetNameFor("sourceText"), + Type = this.systemStringType, + Index = 2, + }); + threeArgVersion.Parameters = paramList; + body.MethodDefinition = threeArgVersion; + this.threeArgumentVersionofContractMethod.Add(keyName, threeArgVersion); + if (this.classHoldingThreeArgVersions.Methods == null) this.classHoldingThreeArgVersions.Methods = new List(1); + this.classHoldingThreeArgVersions.Methods.Add(threeArgVersion); + + return threeArgVersion; + } + + private int GetLastSourceTextIndex(string sourceText, IMethodReference originalMethod, int startSourceTextIndex) { + if (originalMethod.ParameterCount == 1) { + return sourceText.LastIndexOf(')'); // supposedly the character after the first (and only) argument + } else { + return IndexOfWhileSkippingBalancedThings(sourceText, startSourceTextIndex, ','); // supposedly the character after the first argument + } + } + + private int IndexOfWhileSkippingBalancedThings(string source, int startIndex, char targetChar) { + int i = startIndex; + while (i < source.Length) { + if (source[i] == targetChar) break; + else if (source[i] == '(') i = IndexOfWhileSkippingBalancedThings(source, i + 1, ')') + 1; + else if (source[i] == '"') i = IndexOfWhileSkippingBalancedThings(source, i + 1, '"') + 1; + else i++; + } + return i; + } + + private void GetSourceTextFromOperation(IOperation op, out string sourceText, out int startColumn, out string sourceLanguage) { + sourceText = null; + startColumn = 0; + sourceLanguage = "unknown"; + foreach (IPrimarySourceLocation psloc in this.pdbReader.GetClosestPrimarySourceLocationsFor(op.Location)) { + if (!String.IsNullOrEmpty(psloc.Source)) { + sourceText = psloc.Source; + startColumn = psloc.StartColumn; + if (psloc.SourceDocument != null) { + sourceLanguage = psloc.SourceDocument.SourceLanguage; + } + //Console.WriteLine("[{0}]: {1}", i, psloc.Source); + break; + } + } + } + + + + /// + /// Exceptions thrown during extraction. Should not escape this class. + /// + private class ExtractorException : Exception { + /// + /// Exception specific to an error occurring in the contract extractor + /// + public ExtractorException() { } + /// + /// Exception specific to an error occurring in the contract extractor + /// + public ExtractorException(string s) : base(s) { } + /// + /// Exception specific to an error occurring in the contract extractor + /// + public ExtractorException(string s, Exception inner) : base(s, inner) { } + /// + /// Exception specific to an error occurring in the contract extractor + /// + public ExtractorException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + } +} \ No newline at end of file diff --git a/Samples/AsmMeta/TestAgainstOriginalAsmMeta/GenericTest1.GenericTest b/Samples/AsmMeta/TestAgainstOriginalAsmMeta/GenericTest1.GenericTest new file mode 100644 index 0000000..b8bc2ad --- /dev/null +++ b/Samples/AsmMeta/TestAgainstOriginalAsmMeta/GenericTest1.GenericTest @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Samples/HelloContracts/HelloContracts.csproj b/Samples/HelloContracts/HelloContracts.csproj index d8760e3..eebd026 100644 --- a/Samples/HelloContracts/HelloContracts.csproj +++ b/Samples/HelloContracts/HelloContracts.csproj @@ -79,6 +79,10 @@ {0703D916-A881-45E6-A5CD-6BC50E2E30E2} ContractExtractor + + {a9103dda-3a93-441a-8326-6eb20518c251} + CSharpSourceEmitter + {319E150C-8F33-49E7-81CA-30F02F9BA90A} MutableCodeModel @@ -87,10 +91,6 @@ {A555D4CB-F16F-4049-A8CF-180B8A05C755} NewILToCodeModel - - {A9103DDA-3A93-441A-8326-6EB20518C251} - CSharpSourceEmitter -