Exemplo n.º 1
0
        /// <summary>
        /// Emits the compilation into given <see cref="ModuleBuilder"/> using Reflection.Emit APIs.
        /// </summary>
        /// <param name="compilation">Compilation.</param>
        /// <param name="moduleBuilder">
        /// The module builder to add the types into. Can be reused for multiple compilation units.
        /// </param>
        /// <param name="assemblyLoader">
        /// Loads an assembly given an <see cref="AssemblyIdentity"/>.
        /// This callback is used for loading assemblies referenced by the compilation.
        /// <see cref="System.Reflection.Assembly.Load(AssemblyName)"/> is used if not specified.
        /// </param>
        /// <param name="assemblySymbolMapper">
        /// Applied when converting assembly symbols to assembly references.
        /// <see cref="IAssemblySymbol"/> is mapped to its <see cref="IAssemblySymbol.Identity"/> by default.
        /// </param>
        /// <param name="cancellationToken">Can be used to cancel the emit process.</param>
        /// <param name="recoverOnError">If false the method returns an unsuccessful result instead of falling back to CCI writer.</param>
        /// <param name="compiledAssemblyImage">Assembly image, returned only if we fallback to CCI writer.</param>
        /// <param name="entryPoint">An entry point or null if not applicable or on failure.</param>
        /// <param name="diagnostics">Diagnostics.</param>
        /// <returns>True on success, false if a compilation error occurred or the compilation doesn't contain any code or declarations.</returns>
        /// <remarks>
        /// Reflection.Emit doesn't support all metadata constructs. If an unsupported construct is
        /// encountered a metadata writer that procudes uncollectible code is used instead. This is
        /// indicated by
        /// <see cref="ReflectionEmitResult.IsUncollectible"/> flag on the result.
        ///
        /// Reusing <see cref="System.Reflection.Emit.ModuleBuilder"/> may be beneficial in certain
        /// scenarios. For example, when emitting a sequence of code snippets one at a time (like in
        /// REPL). All the snippets can be compiled into a single module as long as the types being
        /// emitted have unique names. Reusing a single module/assembly reduces memory overhead. On
        /// the other hand, collectible assemblies are units of collection. Defining too many
        /// unrelated types in a single assemly might prevent the unused types to be collected.
        ///
        /// No need to provide a name override when using Reflection.Emit, since the assembly already
        /// exists.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Referenced assembly can't be resolved.</exception>
        internal static bool Emit(
            this Compilation compilation,
            ModuleBuilder moduleBuilder,
            AssemblyLoader assemblyLoader,
            Func <IAssemblySymbol, AssemblyIdentity> assemblySymbolMapper,
            bool recoverOnError,
            DiagnosticBag diagnostics,
            CancellationToken cancellationToken,
            out MethodInfo entryPoint,
            out byte[] compiledAssemblyImage)
        {
            compiledAssemblyImage = default(byte[]);

            var moduleBeingBuilt = compilation.CreateModuleBuilder(
                emitOptions: EmitOptions.Default,
                manifestResources: null,
                assemblySymbolMapper: assemblySymbolMapper,
                testData: null,
                diagnostics: diagnostics,
                cancellationToken: cancellationToken);

            if (moduleBeingBuilt == null)
            {
                entryPoint = null;
                return(false);
            }

            if (!compilation.Compile(
                    moduleBeingBuilt,
                    win32Resources: null,
                    xmlDocStream: null,
                    generateDebugInfo: false,
                    diagnostics: diagnostics,
                    filterOpt: null,
                    cancellationToken: cancellationToken))
            {
                entryPoint = null;
                return(false);
            }

            Cci.IMethodReference cciEntryPoint = moduleBeingBuilt.EntryPoint;

            cancellationToken.ThrowIfCancellationRequested();

            DiagnosticBag metadataDiagnostics = DiagnosticBag.GetInstance();

            var context = new EmitContext((Cci.IModule)moduleBeingBuilt, null, metadataDiagnostics);

            // try emit via Reflection.Emit
            try
            {
                var referencedAssemblies = from referencedAssembly in compilation.GetBoundReferenceManager().GetReferencedAssemblies()
                                           let peReference = referencedAssembly.Key as PortableExecutableReference
                                                             select KeyValuePair.Create(
                    moduleBeingBuilt.Translate(referencedAssembly.Value, metadataDiagnostics),
                    (peReference != null)?peReference.FilePath : null);

                entryPoint = ReflectionEmitter.Emit(
                    context,
                    referencedAssemblies,
                    moduleBuilder,
                    assemblyLoader ?? AssemblyLoader.Default,
                    cciEntryPoint,
                    cancellationToken);

                // translate metadata errors.
                return(compilation.FilterAndAppendAndFreeDiagnostics(diagnostics, ref metadataDiagnostics));
            }
            catch (TypeLoadException)
            {
                // attempted to emit reference to a type that can't be loaded (has invalid metadata)
            }
            catch (NotSupportedException)
            {
                // nop
            }

            // TODO (tomat):
            //
            // Another possible approach would be to just return an error, that we can't emit via
            // Ref.Emit and let the user choose another method of emitting. For that we would want
            // to preserve the state of the Emit.Assembly object with all the compiled methods so
            // that the subsequent emit doesn't need to compile method bodies again.

            // TODO (tomat):
            //
            // If Ref.Emit fails to emit the code the type builders already created will stay
            // defined on the module builder. Ideally we would clean them up but Ref.Emit doesn't
            // provide any API to do so. In fact it also keeps baked TypeBuilders alive as well.

            if (!recoverOnError)
            {
                metadataDiagnostics.Free();
                entryPoint = null;
                return(false);
            }

            using (var stream = new System.IO.MemoryStream())
            {
                Cci.PeWriter.WritePeToStream(
                    context,
                    compilation.MessageProvider,
                    stream,
                    pdbWriterOpt: null,
                    allowMissingMethodBodies: false,
                    deterministic: false,
                    cancellationToken: cancellationToken);

                compiledAssemblyImage = stream.ToArray();
            }

            var compiledAssembly = Assembly.Load(compiledAssemblyImage);

            entryPoint = (cciEntryPoint != null) ? ReflectionEmitter.ResolveEntryPoint(compiledAssembly, cciEntryPoint, context) : null;

            // translate metadata errors.
            return(compilation.FilterAndAppendAndFreeDiagnostics(diagnostics, ref metadataDiagnostics));
        }
Exemplo n.º 2
0
        static internal CommonTestBase.CompilationVerifier ReflectionEmit(
            CommonTestBase test,
            Compilation compilation,
            IEnumerable <ModuleData> dependencies,
            TestEmitters emitOptions,
            IEnumerable <ResourceDescription> manifestResources,
            SignatureDescription[] expectedSignatures,
            string expectedOutput,
            Action <PEAssembly, TestEmitters> assemblyValidator,
            Action <IModuleSymbol, TestEmitters> symbolValidator,
            bool collectEmittedAssembly,
            bool verify)
        {
            CommonTestBase.CompilationVerifier verifier = null;

            ReflectionEmitter emit = (refEmitSupported, fallback) =>
            {
                bool requestPEImage = verify || assemblyValidator != null || symbolValidator != null;
                ImmutableArray <byte> peImage;

                // TODO(tomat): we should ref.emit in the AppDomain where we load all the dependencies, otherwise ref.emit fails to emit compilation-compilation references.
                bool emitSuccess = ReflectionEmitInternal(compilation,
                                                          expectedOutput,
                                                          requestPEImage,
                                                          out peImage,
                                                          refEmitSupported,
                                                          fallback,
                                                          collectEmittedAssembly,
                                                          peVerify: verify,
                                                          tempRoot: verifier.Temp);

                Assert.Equal(!peImage.IsDefault, requestPEImage && emitSuccess);

                if (!peImage.IsDefault)
                {
                    // TODO(tomat):
                    // We assume that we CompileAndVerify(verify: true) is called if we are going to use VerifyIL.
                    // Ideally expected IL would be a parameter of CompileAndVerify so we know that we need to get the pe image.
                    verifier.EmittedAssemblyData = peImage;
                }

                return(emitSuccess);
            };

            if (emitOptions != TestEmitters.RefEmitBug)
            {
                verifier = new CommonTestBase.CompilationVerifier(test, compilation, dependencies);

                if (emitOptions == TestEmitters.RefEmitUnsupported)
                {
                    // Test that Ref.Emit fails.
                    // The code contains features not supported by Ref.Emit, don't fall back to CCI to test the failure.
                    Assert.False(
                        emit(refEmitSupported: false, fallback: false));

                    // test that Emit falls back to CCI:
                    Assert.True(
                        emit(refEmitSupported: false, fallback: true));
                }
                else
                {
                    // The code should only contain features supported by Ref.Emit.
                    Assert.True(
                        emit(refEmitSupported: true, fallback: false));
                }

                // We're dual-purposing EmitOptions here.  In this context, it
                // tells the validator the version of Emit that is calling it.
                CommonTestBase.RunValidators(verifier, TestEmitters.RefEmit, assemblyValidator, symbolValidator);
            }

            return((emitOptions == TestEmitters.RefEmit) ? verifier : null);
        }