From ce3d63ecc19683204a1ee375e3fa8e81d7bc1b94 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 10 May 2017 10:29:07 -0700 Subject: [PATCH 1/7] Enable NoPIA with refout and refonly --- docs/features/refout.md | 32 ++- .../Portable/CSharpResources.Designer.cs | 9 - .../CSharp/Portable/CSharpResources.resx | 3 - .../CommandLine/CSharpCommandLineParser.cs | 5 - .../CSharp/Portable/Errors/ErrorCode.cs | 2 +- .../Test/CommandLine/CommandLineTests.cs | 12 - .../Test/Emit/Emit/CompilationEmitTests.cs | 223 ++++++++++++++++-- .../CodeAnalysisResources.Designer.cs | 9 - .../Core/Portable/CodeAnalysisResources.resx | 3 - .../Core/Portable/Compilation/Compilation.cs | 7 - .../VisualBasicCommandLineParser.vb | 4 - .../VisualBasic/Portable/Errors/Errors.vb | 1 - .../Portable/VBResources.Designer.vb | 9 - .../VisualBasic/Portable/VBResources.resx | 3 - .../Test/CommandLine/CommandLineTests.vb | 8 - .../Test/Emit/Emit/CompilationEmitTests.vb | 18 -- .../CommonTestBase.CompilationVerifier.cs | 2 +- 17 files changed, 215 insertions(+), 135 deletions(-) diff --git a/docs/features/refout.md b/docs/features/refout.md index 81253cb00f310..29bbc5f82fa82 100644 --- a/docs/features/refout.md +++ b/docs/features/refout.md @@ -1,6 +1,7 @@ # Reference assemblies Reference assemblies are metadata-only assemblies with the minimum amount of metadata to preserve the compile-time behavior of consumers (diagnostics may be affected, though). +The compiler may choose to remove more metadata in later versions, if it is determined to be safe (ie. respects the principle above). ## Scenarios There are 4 scenarios: @@ -13,17 +14,14 @@ There are 4 scenarios: ## Definition of ref assemblies Metadata-only assembly have their method bodies replaced with a single `throw null` body, but include all members except anonymous types. The reason for using `throw null` bodies (as opposed to no bodies) is so that PEVerify could run and pass (thus validating the completeness of the metadata). -Ref assemblies further remove metadata (private members) from metadata-only assemblies. +Ref assemblies will include an assembly-level `ReferenceAssembly` attribute. This attribute may be specified in should (then we won't need to synthesize it). Because of this attribute, runtimes will refuse to load ref assemblies for execution (but they can still be loaded Reflection-only mode). +Ref assemblies further remove metadata (private members) from metadata-only assemblies: -A reference assembly will only have references for what it needs in the API surface. The real assembly may have additional references related to specific implementations. For instance, the reference assembly for `class C { private void M() { dynamic .... } }` will not have any references for `dynamic` types. - -Private function-members (methods, properties and events) will be removed. If there are no `InternalsVisibleTo` attributes, do the same for internal function-members - -All types (including private or nested types) must be kept in reference assemblies. - -All fields of a struct will be kept (in C# 7.1 timeframe), but this can later be refined. There are three cases to consider (ref case, struct case, generic case), where we could possibly substitute the fields with adequate placeholders that would minimize the rate of change of the ref assembly. - -Reference assemblies will include an assembly-level `ReferenceAssembly` attribute. If such an attribute is found in source, we won't need to synthesize it. Because of this attribute, runtimes will refuse to load reference assemblies. +- A ref assembly will only have references for what it needs in the API surface. The real assembly may have additional references related to specific implementations. For instance, the ref assembly for `class C { private void M() { dynamic d = 1; ... } }` will not reference any types required for `dynamic`. +- Private function-members (methods, properties and events) will be removed. If there are no `InternalsVisibleTo` attributes, do the same for internal function-members +- But all types (including private or nested types) must be kept in ref assemblies. All attributes must be kept (even internal ones). +- All virtual methods will be kept. Explicit interface implementations will be kept. +- All fields of a struct will be kept. (This is a candidate for post-C#-7.1 refinement) ## API changes @@ -32,15 +30,15 @@ Two mutually exclusive command-line parameters will be added to `csc.exe` and `v - `/refout` - `/refonly` -The `/refout` parameter specifies a file path where the ref assembly should be output. This translates to `metadataPeStream` in the `Emit` API (see details below). The filename for the ref assembly should generally match that of the primary assembly, but it can be in a different folder. +The `/refout` parameter specifies a file path where the ref assembly should be output. This translates to `metadataPeStream` in the `Emit` API (see details below). The filename for the ref assembly should generally match that of the primary assembly (we will warn when they don't match). The recommended convention (used by MSBuild) is to place the ref assembly in a "ref/" sub-folder relative to the primary assembly. -The `/refonly` parameter is a flag that indicates that a ref assembly should be output instead of an implementation assembly. +The `/refonly` parameter is a flag that indicates that a ref assembly should be output instead of an implementation assembly, as the primary output. The `/refonly` parameter is not allowed together with the `/refout` parameter, as it doesn't make sense to have both the primary and secondary outputs be ref assemblies. Also, the `/refonly` parameter silently disables outputting PDBs, as ref assemblies cannot be executed. The `/refonly` parameter translates to `EmitMetadataOnly` being `true`, and `IncludePrivateMembers` being `false` in the `Emit` API (see details below). -Neither `/refonly` nor `/refout` are permitted with `/target:module`, `/addmodule`, or `/link:...` options. +Neither `/refonly` nor `/refout` are permitted with net modules (`/target:module`, `/addmodule` options). The compilation from the command-line will either produce both assemblies (implementation and ref) or neither. There is no "partial success" scenario. -When the compiler produces documentation, it is un-affected by either the `/refonly` or `/refout` parameters. +When the compiler produces documentation, it is un-affected by either the `/refonly` or `/refout` parameters. This may change in the future. ### CscTask/CoreCompile The `CoreCompile` target will support a new output, called `IntermediateRefAssembly`, which parallels the existing `IntermediateAssembly`. @@ -49,6 +47,8 @@ Both of those basically map to the `/refout` command-line parameter. An additional task, called `CopyRefAssembly`, will be provided along with the existing `Csc` task. It takes a `SourcePath` and a `DestinationPath` and generally copies the file from the source over to the destination. But if it can determine that the contents of those two files match (by comparing their MVIDs, see details below), then the destination file is left untouched. +As a side-note, `CopyRefAssembly` uses the same assembly resolution/redirection trick as `Csc` and `Vbc`, to avoid type loading problems with `System.IO.FileSystem`. + ### CodeAnalysis APIs It is already possible to produce metadata-only assemblies by using `EmitOptions.EmitMetadataOnly`, which is used in IDE scenarios with cross-language dependencies. The compiler will be updated to honour the `EmitOptions.IncludePrivateMembers` flag as well. When combined with `EmitMetadataOnly` or a `metadataPeStream` in `Emit`, a ref assembly will be produced. @@ -68,11 +68,7 @@ As mentioned above, there may be further refinements after C# 7.1: - Produce ref assemblies even when there are errors outside method bodies (emitting error types when `EmitOptions.TolerateErrors` is set) - When the compiler produces documentation, the contents produced could be filtered down to match the APIs that go into the primary output. In other words, the documentation could be filtered down when using the `/refonly` parameter. - ## Open questions -- should explicit method implementations be included in ref assemblies? -- Non-public attributes on public APIs (emit attribute based on accessibility rule) -- ref assemblies and NoPia ## Related issues - Produce ref assemblies from command-line and msbuild (https://github.com/dotnet/roslyn/issues/2184) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 8ecf42d4f1745..e0330009414e0 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -6730,15 +6730,6 @@ internal static string ERR_NoDynamicPhantomOnBaseIndexer { } } - /// - /// Looks up a localized string similar to Cannot embed types when using /refout or /refonly.. - /// - internal static string ERR_NoEmbeddedTypeWhenRefOutOrRefOnly { - get { - return ResourceManager.GetString("ERR_NoEmbeddedTypeWhenRefOutOrRefOnly", resourceCulture); - } - } - /// /// Looks up a localized string similar to Program does not contain a static 'Main' method suitable for an entry point. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index abca5ec5f2823..e286576fa98bd 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -2303,9 +2303,6 @@ If such a class is used as a base class and if the deriving class defines a dest Cannot compile net modules when using /refout or /refonly. - - Cannot embed types when using /refout or /refonly. - Overloadable operator expected diff --git a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs index cbde4e6815d12..c28578c8eb9b5 100644 --- a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs +++ b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs @@ -1178,11 +1178,6 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoRefOutWhenRefOnly); } - if ((refOnly || outputRefFilePath != null) && metadataReferences.Any(r => r.Properties.EmbedInteropTypes)) - { - AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly); - } - if (outputKind == OutputKind.NetModule && (refOnly || outputRefFilePath != null)) { AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index fc6def429db0c..b3c1a489e22fc 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1481,7 +1481,7 @@ internal enum ErrorCode ERR_NoRefOutWhenRefOnly = 8308, ERR_NoNetModuleOutputWhenRefOutOrRefOnly = 8309, - ERR_NoEmbeddedTypeWhenRefOutOrRefOnly = 8310, + // Available = 8310, ERR_BadDynamicMethodArgDefaultLiteral = 8311, ERR_DefaultLiteralNotValid = 8312, WRN_DefaultInSwitch = 8313, diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 616e19367c623..6ca4fdf65f3a2 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -2886,18 +2886,6 @@ public void ParseOut() // error CS8301: Do not use refout when using refonly. Diagnostic(ErrorCode.ERR_NoRefOutWhenRefOnly).WithLocation(1, 1)); - parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/link:b", "a.cs" }, baseDirectory); - parsedArgs.Errors.Verify( - // error CS8357: Cannot embed types when using /refout or /refonly. - Diagnostic(ErrorCode.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly).WithLocation(1, 1) - ); - - parsedArgs = DefaultParse(new[] { "/refonly", "/link:b", "a.cs" }, baseDirectory); - parsedArgs.Errors.Verify( - // error CS8357: Cannot embed types when using /refout or /refonly. - Diagnostic(ErrorCode.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly).WithLocation(1, 1) - ); - parsedArgs = DefaultParse(new[] { "/refonly:incorrect", "a.cs" }, baseDirectory); parsedArgs.Errors.Verify( // error CS2007: Unrecognized option: '/refonly:incorrect' diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index dfe2050db72b5..3530d406a6a80 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; @@ -614,6 +615,204 @@ public class C CompareAssemblies(sourceTemplate, left, right, expectedMatch, includePrivateMembers: false); } + [ConditionalFact(typeof(ClrOnly), typeof(DesktopOnly))] + public void RefAssembly_NoPia() + { + string piaSource = @" +using System; +using System.Runtime.InteropServices; + +[assembly: Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58257"")] +[assembly: ImportedFromTypeLib(""Pia1.dll"")] + +public struct S { public int field; } + +[ComImport()] +[Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58280"")] +public interface ITest1 +{ + S M(); +}"; + + var pia = CreateStandardCompilation(piaSource, options: TestOptions.ReleaseDll, assemblyName: "pia"); + pia.VerifyEmitDiagnostics(); + + string source = @" +public class D : ITest1 +{ + public S M() + { + throw null; + } +} +"; + verifyRefOnly(pia.EmitToImageReference(embedInteropTypes: true)); + verifyRefOut(pia.EmitToImageReference(embedInteropTypes: true)); + + verifyRefOnly(pia.ToMetadataReference(embedInteropTypes: true)); + verifyRefOut(pia.ToMetadataReference(embedInteropTypes: true)); + + void verifyRefOnly(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, + options: new EmitOptions(includePrivateMembers: false).WithEmitMetadataOnly(true)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var refImage = output.ToImmutable(); + verifyNoPia(refImage); + } + } + + void verifyRefOut(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.DebugDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var image = output.ToImmutable(); + var refImage = metadataOutput.ToImmutable(); + + verifyNoPia(image); + verifyNoPia(refImage); + } + } + + void verifyNoPia(ImmutableArray image) + { + var reference = CompilationVerifier.LoadTestEmittedExecutableForSymbolValidation(image, OutputKind.DynamicallyLinkedLibrary); + var comp = CreateStandardCompilation("", references: new[] { reference }); + var referencedAssembly = comp.GetReferencedAssemblySymbol(reference); + var module = (PEModuleSymbol)referencedAssembly.Modules[0]; + + var itest1 = module.GlobalNamespace.GetMember("ITest1"); + Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute").ToString()); + + var method = (PEMethodSymbol)itest1.GetMember("M"); + Assert.Equal("S ITest1.M()", method.ToTestDisplayString()); + + var s = (NamedTypeSymbol)method.ReturnType; + Assert.Equal("S", s.ToTestDisplayString()); + + var field = s.GetMember("field"); + Assert.Equal("System.Int32 S.field", field.ToTestDisplayString()); + } + } + + [ConditionalFact(typeof(ClrOnly), typeof(DesktopOnly))] + public void RefAssembly_NoPia_ReferenceFromMethodBody() + { + string piaSource = @" +using System; +using System.Runtime.InteropServices; + +[assembly: Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58257"")] +[assembly: ImportedFromTypeLib(""Pia1.dll"")] + +public struct S { public int field; } + +[ComImport()] +[Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58280"")] +public interface ITest1 +{ + S M(); +}"; + + var pia = CreateStandardCompilation(piaSource, options: TestOptions.ReleaseDll, assemblyName: "pia"); + pia.VerifyEmitDiagnostics(); + + string source = @" +public class D +{ + public void M2() + { + ITest1 x = null; + S s = x.M(); + } +} +"; + verifyRefOnly(pia.EmitToImageReference(embedInteropTypes: true)); + verifyRefOut(pia.EmitToImageReference(embedInteropTypes: true)); + + verifyRefOnly(pia.ToMetadataReference(embedInteropTypes: true)); + verifyRefOut(pia.ToMetadataReference(embedInteropTypes: true)); + + void verifyRefOnly(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, + options: new EmitOptions(includePrivateMembers: false).WithEmitMetadataOnly(true)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var refImage = output.ToImmutable(); + verifyNoPia(refImage, expectMissing: true); + } + } + + void verifyRefOut(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var image = output.ToImmutable(); + var refImage = metadataOutput.ToImmutable(); + + verifyNoPia(image, expectMissing: false); + verifyNoPia(refImage, expectMissing: false); + } + } + + // The ref assembly produced by refout has more types than that produced by refonly, + // because refout will bind the method bodies (and therefore populate more referenced types). + // This will be refined in the future. Follow-up issue: https://github.com/dotnet/roslyn/issues/19403 + void verifyNoPia(ImmutableArray image, bool expectMissing) + { + var reference = CompilationVerifier.LoadTestEmittedExecutableForSymbolValidation(image, OutputKind.DynamicallyLinkedLibrary); + var comp = CreateStandardCompilation("", references: new[] { reference }); + var referencedAssembly = comp.GetReferencedAssemblySymbol(reference); + var module = (PEModuleSymbol)referencedAssembly.Modules[0]; + + var itest1 = module.GlobalNamespace.GetMember("ITest1"); + if (expectMissing) + { + Assert.Null(itest1); + return; + } + + Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute").ToString()); + + var method = (PEMethodSymbol)itest1.GetMember("M"); + Assert.Equal("S ITest1.M()", method.ToTestDisplayString()); + + var s = (NamedTypeSymbol)method.ReturnType; + Assert.Equal("S", s.ToTestDisplayString()); + + var field = s.GetMember("field"); + Assert.Equal("System.Int32 S.field", field.ToTestDisplayString()); + } + } + [Theory] [InlineData("internal void M() { }", "", Match.Different)] public void RefAssembly_InvariantToSomeChangesWithInternalsVisibleTo(string left, string right, Match expectedMatch) @@ -1149,30 +1348,6 @@ public void EmitMetadataOnly_DisallowEmbeddingPdb() } } - [Fact] - public void RefAssembly_DisallowEmbeddingTypes() - { - CSharpCompilation comp = CreateCompilation("", options: TestOptions.DebugDll, - references: new MetadataReference[] - { - TestReferences.SymbolsTests.NoPia.GeneralPia.WithEmbedInteropTypes(true) - }); - - using (var output = new MemoryStream()) - { - Assert.Throws(() => comp.Emit(output, - options: EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false))); - } - - using (var output = new MemoryStream()) - using (var metadataOutput = new MemoryStream()) - { - Assert.Throws(() => comp.Emit(output, - metadataPEStream: metadataOutput, - options: EmitOptions.Default.WithIncludePrivateMembers(false))); - } - } - [Fact] public void EmitMetadata() { diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs index 61e4fd59a5707..d8aed8a10efe1 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs @@ -477,15 +477,6 @@ internal static string EmbeddingPdbUnexpectedWhenEmittingMetadata { } } - /// - /// Looks up a localized string similar to Embedded interop type references should not be given when emitting a ref assembly.. - /// - internal static string EmbedInteropTypesUnexpectedWhenEmittingRefAssembly { - get { - return ResourceManager.GetString("EmbedInteropTypesUnexpectedWhenEmittingRefAssembly", resourceCulture); - } - } - /// /// Looks up a localized string similar to A key in the pathMap is empty.. /// diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index cbf5f16a7c308..792684abef194 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -333,9 +333,6 @@ PDB stream should not be given when emitting metadata only. - - Embedded interop type references should not be given when emitting a ref assembly. - Metadata PE stream should not be given when emitting metadata only. diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index be14eaa324e02..ed58f52d8d86d 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -2087,13 +2087,6 @@ public EmitResult Emit( throw new ArgumentException(CodeAnalysisResources.IncludingPrivateMembersUnexpectedWhenEmittingToMetadataPeStream, nameof(metadataPEStream)); } - if ((metadataPEStream != null || options?.EmitMetadataOnly == true) && - options?.IncludePrivateMembers == false && - References.Any(r => r.Properties.EmbedInteropTypes)) - { - throw new ArgumentException(CodeAnalysisResources.EmbedInteropTypesUnexpectedWhenEmittingRefAssembly, nameof(metadataPEStream)); - } - if (options?.DebugInformationFormat == DebugInformationFormat.Embedded && options?.EmitMetadataOnly == true) { diff --git a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb index 029a9e8b343a9..5f58fd2094c52 100644 --- a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb +++ b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb @@ -1203,10 +1203,6 @@ lVbRuntimePlus: AddDiagnostic(diagnostics, ERRID.ERR_NoRefOutWhenRefOnly) End If - If (refOnly OrElse outputRefFileName IsNot Nothing) AndAlso metadataReferences.Any(Function(r) r.Properties.EmbedInteropTypes) Then - AddDiagnostic(diagnostics, ERRID.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly) - End If - If outputKind = OutputKind.NetModule AndAlso (refOnly OrElse outputRefFileName IsNot Nothing) Then AddDiagnostic(diagnostics, ERRID.ERR_NoNetModuleOutputWhenRefOutOrRefOnly) End If diff --git a/src/Compilers/VisualBasic/Portable/Errors/Errors.vb b/src/Compilers/VisualBasic/Portable/Errors/Errors.vb index e1354d47b40bd..5303649b024cf 100644 --- a/src/Compilers/VisualBasic/Portable/Errors/Errors.vb +++ b/src/Compilers/VisualBasic/Portable/Errors/Errors.vb @@ -1733,7 +1733,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ERR_NoRefOutWhenRefOnly = 37300 ERR_NoNetModuleOutputWhenRefOutOrRefOnly = 37301 - ERR_NoEmbeddedTypeWhenRefOutOrRefOnly = 37302 '// WARNINGS BEGIN HERE WRN_UseOfObsoleteSymbol2 = 40000 diff --git a/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb b/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb index 19140e975e4dd..50815e2e104e2 100644 --- a/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb +++ b/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb @@ -8070,15 +8070,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - ''' - ''' Looks up a localized string similar to Cannot embed types when using /refout or /refonly.. - ''' - Friend ReadOnly Property ERR_NoEmbeddedTypeWhenRefOutOrRefOnly() As String - Get - Return ResourceManager.GetString("ERR_NoEmbeddedTypeWhenRefOutOrRefOnly", resourceCulture) - End Get - End Property - ''' ''' Looks up a localized string similar to Array bounds cannot appear in type specifiers.. ''' diff --git a/src/Compilers/VisualBasic/Portable/VBResources.resx b/src/Compilers/VisualBasic/Portable/VBResources.resx index aa0c8d2866e81..d7239d6c732ba 100644 --- a/src/Compilers/VisualBasic/Portable/VBResources.resx +++ b/src/Compilers/VisualBasic/Portable/VBResources.resx @@ -5471,9 +5471,6 @@ Do not use refout when using refonly. - - Cannot embed types when using /refout or /refonly. - Cannot compile net modules when using /refout or /refonly. diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 0e3f43a3cdf81..bdc7f4bb09dca 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -2923,14 +2923,6 @@ print Goodbye, World" parsedArgs.Errors.Verify( Diagnostic(ERRID.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)) - parsedArgs = DefaultParse({"/refout:ref.dll", "/link:b", "a.vb"}, baseDirectory) - parsedArgs.Errors.Verify( - Diagnostic(ERRID.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly).WithLocation(1, 1)) - - parsedArgs = DefaultParse({"/refonly", "/link:b", "a.vb"}, baseDirectory) - parsedArgs.Errors.Verify( - Diagnostic(ERRID.ERR_NoEmbeddedTypeWhenRefOutOrRefOnly).WithLocation(1, 1)) - parsedArgs = DefaultParse({"/refonly", "/target:module", "a.vb"}, baseDirectory) parsedArgs.Errors.Verify( Diagnostic(ERRID.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)) diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb index a2939a18115fa..f79bc3827226a 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb @@ -886,24 +886,6 @@ End Class" End Using End Sub - - Public Sub RefAssembly_DisallowEmbeddingTypes() - Dim comp = CreateCompilation("", references:={MscorlibRef.WithEmbedInteropTypes(True)}, - options:=TestOptions.DebugDll) - - Using output As New MemoryStream() - Assert.Throws(Of ArgumentException)(Function() comp.Emit(output, - options:=EmitOptions.Default.WithEmitMetadataOnly(True).WithIncludePrivateMembers(False))) - End Using - - Using output As New MemoryStream() - Using metadataOutput = New MemoryStream() - Assert.Throws(Of ArgumentException)(Function() comp.Emit(output, metadataPEStream:=metadataOutput, - options:=EmitOptions.Default.WithIncludePrivateMembers(False))) - End Using - End Using - End Sub - Public Sub EmitMetadataOnly_DisallowMetadataPeStream() Dim comp = CreateCompilation("", references:={MscorlibRef}, diff --git a/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs b/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs index 67a41c836262f..774c3c76da1c1 100644 --- a/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs +++ b/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs @@ -311,7 +311,7 @@ private IModuleSymbol GetModuleSymbolForEmittedImage(ImmutableArray peImag return assemblies.Last(); } - private static MetadataReference LoadTestEmittedExecutableForSymbolValidation( + internal static MetadataReference LoadTestEmittedExecutableForSymbolValidation( ImmutableArray image, OutputKind outputKind, string display = null) From c1c5d4092b9a6567ca088553f4d6c7e324aa600c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 10 May 2017 13:06:43 -0700 Subject: [PATCH 2/7] Add test for struct with backing field of property --- .../Test/Emit/Emit/CompilationEmitTests.cs | 32 +++++++++++++++++-- .../Test/Emit/Emit/CompilationEmitTests.vb | 31 +++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 3530d406a6a80..5d250e11c7c4f 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1151,7 +1151,7 @@ void VerifyIgnoresDiagnostics(EmitOptions emitOptions, bool success) } [Fact] - public void VerifyRefAssembly() + public void RefAssembly_VerifyTypesAndMembers() { string source = @" public class PublicClass @@ -1213,7 +1213,7 @@ public class PublicClass MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitMetadataOnly)); - // verify metadata (types, members, attributes) of the metadata-only assembly + // verify metadata (types, members, attributes) of the ref assembly var emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); CompileAndVerify(comp, emitOptions: emitRefOnly, verify: true); @@ -1239,6 +1239,34 @@ public class PublicClass MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitRefOnly)); } + [Fact] + public void RefAssembly_VerifyTypesAndMembersOnStruct() + { + string source = @" +internal struct InternalStruct +{ + internal int P { get; set; } +} +"; + CSharpCompilation comp = CreateCompilation(source, references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + // verify metadata (types, members, attributes) of the ref assembly + var emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + CompileAndVerify(comp, emitOptions: emitRefOnly, verify: true); + + var refImage = comp.EmitToImageReference(emitRefOnly); + var compWithRef = CreateCompilation("", references: new[] { MscorlibRef, refImage }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + AssertEx.Equal( + new[] { "", "InternalStruct" }, + compWithRef.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); + + AssertEx.Equal( + new[] { "System.Int32 InternalStruct.

k__BackingField", "InternalStruct..ctor()" }, + compWithRef.GetMember("InternalStruct").GetMembers().Select(m => m.ToTestDisplayString())); + } + [Fact] public void EmitMetadataOnly_DisallowPdbs() { diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb index f79bc3827226a..255aeb4c0f08b 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb @@ -774,7 +774,7 @@ End Class" End Sub - Public Sub VerifyRefAssembly() + Public Sub RefAssembly_VerifyTypesAndMembers() Dim source = " Public MustInherit Class PublicClass Public Sub PublicMethod() @@ -873,6 +873,35 @@ End Class" MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitRefOnly)) End Sub + + Public Sub RefAssembly_VerifyTypesAndMembersOnStruct() + Dim source = " +Friend Structure InternalStruct + Friend Property P As Integer +End Structure" + Dim comp As Compilation = CreateCompilation(source, references:={MscorlibRef}, + options:=TestOptions.DebugDll.WithDeterministic(True)) + + Dim verifier = CompileAndVerify(comp, emitOptions:=EmitOptions.Default.WithEmitMetadataOnly(True), verify:=True) + + ' verify metadata (types, members, attributes) of the ref assembly + Dim emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(True).WithIncludePrivateMembers(False) + CompileAndVerify(comp, emitOptions:=emitRefOnly, verify:=True) + + Dim refImage = comp.EmitToImageReference(emitRefOnly) + Dim compWithRef = CreateCompilation("", references:={MscorlibRef, refImage}, + options:=TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)) + Dim refAssembly As AssemblySymbol = compWithRef.SourceModule.GetReferencedAssemblySymbols().Last() + AssertEx.SetEqual( + {"", "InternalStruct"}, + refAssembly.GlobalNamespace.GetMembers().Select(Function(m) m.ToDisplayString())) + + AssertEx.SetEqual( + {"Sub InternalStruct..ctor()", "InternalStruct._P As System.Int32"}, + compWithRef.GetMember(Of NamedTypeSymbol)("InternalStruct").GetMembers(). + Select(Function(m) m.ToTestDisplayString())) + End Sub + Public Sub EmitMetadataOnly_DisallowPdbs() Dim comp = CreateCompilation("", references:={MscorlibRef}, From 61ec44f57563528784e3653f5acac9c2dbc57c31 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 10 May 2017 13:19:06 -0700 Subject: [PATCH 3/7] Add test for event in VB --- .../Test/Emit/Emit/CompilationEmitTests.cs | 16 ++++++++-- .../Test/Emit/Emit/CompilationEmitTests.vb | 32 +++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 5d250e11c7c4f..35372880f652c 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1160,6 +1160,8 @@ public class PublicClass private void PrivateMethod() { System.Console.Write(""Hello""); } protected void ProtectedMethod() { System.Console.Write(""Hello""); } internal void InternalMethod() { System.Console.Write(""Hello""); } + public event System.Action PublicEvent; + internal event System.Action InternalEvent; } "; CSharpCompilation comp = CreateCompilation(source, references: new[] { MscorlibRef }, @@ -1178,7 +1180,10 @@ public class PublicClass AssertEx.Equal( new[] { "void PublicClass.PublicMethod()", "void PublicClass.PrivateMethod()", "void PublicClass.ProtectedMethod()", "void PublicClass.InternalMethod()", - "PublicClass..ctor()" }, + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "void PublicClass.InternalEvent.add", "void PublicClass.InternalEvent.remove", + "PublicClass..ctor()", + "event System.Action PublicClass.PublicEvent", "event System.Action PublicClass.InternalEvent" }, compWithReal.GetMember("PublicClass").GetMembers() .Select(m => m.ToTestDisplayString())); @@ -1202,7 +1207,10 @@ public class PublicClass AssertEx.Equal( new[] { "void PublicClass.PublicMethod()", "void PublicClass.PrivateMethod()", "void PublicClass.ProtectedMethod()", "void PublicClass.InternalMethod()", - "PublicClass..ctor()" }, + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "void PublicClass.InternalEvent.add", "void PublicClass.InternalEvent.remove", + "PublicClass..ctor()", + "event System.Action PublicClass.PublicEvent", "event System.Action PublicClass.InternalEvent" }, compWithMetadata.GetMember("PublicClass").GetMembers().Select(m => m.ToTestDisplayString())); AssertEx.Equal( @@ -1225,7 +1233,9 @@ public class PublicClass compWithRef.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); AssertEx.Equal( - new[] { "void PublicClass.PublicMethod()", "void PublicClass.ProtectedMethod()", "PublicClass..ctor()" }, + new[] { "void PublicClass.PublicMethod()", "void PublicClass.ProtectedMethod()", + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "PublicClass..ctor()", "event System.Action PublicClass.PublicEvent"}, compWithRef.GetMember("PublicClass").GetMembers().Select(m => m.ToTestDisplayString())); AssertEx.Equal( diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb index 255aeb4c0f08b..493a56abe6ea4 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/CompilationEmitTests.vb @@ -790,6 +790,8 @@ Public MustInherit Class PublicClass System.Console.Write(""Hello"") End Sub Public MustOverride Sub AbstractMethod() + Public Event PublicEvent As System.Action + Friend Event InternalEvent As System.Action End Class" Dim comp As Compilation = CreateCompilation(source, references:={MscorlibRef}, options:=TestOptions.DebugDll.WithDeterministic(True)) @@ -805,10 +807,14 @@ End Class" {"", "PublicClass"}, realAssembly.GlobalNamespace.GetMembers().Select(Function(m) m.ToDisplayString())) - AssertEx.SetEqual( - {"Sub PublicClass.PublicMethod()", "Sub PublicClass.PrivateMethod()", - "Sub PublicClass.InternalMethod()", "Sub PublicClass.ProtectedMethod()", - "Sub PublicClass.AbstractMethod()", "Sub PublicClass..ctor()"}, + AssertEx.Equal( + {"PublicClass.PublicEventEvent As System.Action", "PublicClass.InternalEventEvent As System.Action", + "Sub PublicClass..ctor()", "Sub PublicClass.PublicMethod()", + "Sub PublicClass.PrivateMethod()", "Sub PublicClass.ProtectedMethod()", + "Sub PublicClass.InternalMethod()", "Sub PublicClass.AbstractMethod()", + "Sub PublicClass.add_PublicEvent(obj As System.Action)", "Sub PublicClass.remove_PublicEvent(obj As System.Action)", + "Sub PublicClass.add_InternalEvent(obj As System.Action)", "Sub PublicClass.remove_InternalEvent(obj As System.Action)", + "Event PublicClass.PublicEvent As System.Action", "Event PublicClass.InternalEvent As System.Action"}, compWithReal.GetMember(Of NamedTypeSymbol)("PublicClass").GetMembers(). Select(Function(m) m.ToTestDisplayString())) @@ -830,10 +836,14 @@ End Class" {"", "PublicClass"}, metadataAssembly.GlobalNamespace.GetMembers().Select(Function(m) m.ToDisplayString())) - AssertEx.SetEqual( - {"Sub PublicClass.PublicMethod()", "Sub PublicClass.PrivateMethod()", - "Sub PublicClass.InternalMethod()", "Sub PublicClass.ProtectedMethod()", - "Sub PublicClass.AbstractMethod()", "Sub PublicClass..ctor()"}, + AssertEx.Equal( + {"PublicClass.PublicEventEvent As System.Action", "PublicClass.InternalEventEvent As System.Action", + "Sub PublicClass..ctor()", "Sub PublicClass.PublicMethod()", + "Sub PublicClass.PrivateMethod()", "Sub PublicClass.ProtectedMethod()", + "Sub PublicClass.InternalMethod()", "Sub PublicClass.AbstractMethod()", + "Sub PublicClass.add_PublicEvent(obj As System.Action)", "Sub PublicClass.remove_PublicEvent(obj As System.Action)", + "Sub PublicClass.add_InternalEvent(obj As System.Action)", "Sub PublicClass.remove_InternalEvent(obj As System.Action)", + "Event PublicClass.PublicEvent As System.Action", "Event PublicClass.InternalEvent As System.Action"}, compWithMetadata.GetMember(Of NamedTypeSymbol)("PublicClass").GetMembers(). Select(Function(m) m.ToTestDisplayString())) @@ -845,7 +855,7 @@ End Class" MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitMetadataOnly)) - ' verify metadata (types, members, attributes) of the metadata-only assembly + ' verify metadata (types, members, attributes) of the ref assembly Dim emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(True).WithIncludePrivateMembers(False) CompileAndVerify(comp, emitOptions:=emitRefOnly, verify:=True) @@ -859,7 +869,9 @@ End Class" AssertEx.SetEqual( {"Sub PublicClass..ctor()", "Sub PublicClass.PublicMethod()", - "Sub PublicClass.ProtectedMethod()", "Sub PublicClass.AbstractMethod()"}, + "Sub PublicClass.ProtectedMethod()", "Sub PublicClass.AbstractMethod()", + "Sub PublicClass.add_PublicEvent(obj As System.Action)", "Sub PublicClass.remove_PublicEvent(obj As System.Action)", + "Event PublicClass.PublicEvent As System.Action"}, compWithRef.GetMember(Of NamedTypeSymbol)("PublicClass").GetMembers(). Select(Function(m) m.ToTestDisplayString())) From 1ec5e180780e3ed645977620362978656849ff26 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 10 May 2017 15:23:00 -0700 Subject: [PATCH 4/7] Verify that version number matches between assembly and ref assembly --- .../CSharp/Test/Emit/Emit/CompilationEmitTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 35372880f652c..16ad1f531c5e6 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1065,17 +1065,20 @@ private static void VerifyRefAssemblyClient(string lib_cs, string source, Action [Theory] [InlineData("")] [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1"")]")] - [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1.0.*"")]")] + [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1.0.0.*"")]")] public void RefAssembly_EmitAsDeterministic(string source) { var comp = CreateStandardCompilation(source, options: TestOptions.DebugDll.WithDeterministic(false)); var (image1, refImage1) = emitRefOut(); + verifyIdentitiesMatch(image1, refImage1); // The resolution of the PE header time date stamp is seconds, and we want to make sure that has an opportunity to change // between calls to Emit. Thread.Sleep(TimeSpan.FromSeconds(1)); var (image2, refImage2) = emitRefOut(); + verifyIdentitiesMatch(image2, refImage2); + var refImage3 = emitRefOnly(); AssertEx.Equal(refImage1, refImage2); @@ -1085,6 +1088,13 @@ public void RefAssembly_EmitAsDeterministic(string source) var refImage4 = emitRefOnly(); AssertEx.Equal(refImage1, refImage4); + void verifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage) + { + var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + var id2 = ModuleMetadata.CreateFromImage(secondImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + Assert.Equal(id1, id2); + } + (ImmutableArray image, ImmutableArray refImage) emitRefOut() { using (var output = new MemoryStream()) From 97ad221c4309344d59e4d618d5ce8569652425d9 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 10 May 2017 16:16:01 -0700 Subject: [PATCH 5/7] Add tests for signing and assembly version --- .../Test/Emit/Emit/CompilationEmitTests.cs | 123 ++++++++++++------ 1 file changed, 80 insertions(+), 43 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 16ad1f531c5e6..335bc398f50ce 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1063,64 +1063,101 @@ private static void VerifyRefAssemblyClient(string lib_cs, string source, Action } [Theory] - [InlineData("")] - [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1"")]")] - [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1.0.0.*"")]")] - public void RefAssembly_EmitAsDeterministic(string source) - { - var comp = CreateStandardCompilation(source, options: TestOptions.DebugDll.WithDeterministic(false)); - - var (image1, refImage1) = emitRefOut(); - verifyIdentitiesMatch(image1, refImage1); - - // The resolution of the PE header time date stamp is seconds, and we want to make sure that has an opportunity to change + [InlineData("", false)] + [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1"")]", false)] + [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1.0.0.*"")]", true)] + public void RefAssembly_EmitAsDeterministic(string source, bool hasWildcard) + { + var name = GetUniqueName(); + var options = TestOptions.DebugDll.WithDeterministic(false); + var comp1 = CreateStandardCompilation(source, options: options, assemblyName: name); + + var (out1, refOut1) = EmitRefOut(comp1); + var refOnly1 = EmitRefOnly(comp1); + VerifyIdentitiesMatch(out1, refOut1); + VerifyIdentitiesMatch(out1, refOnly1); + AssertEx.Equal(refOut1, refOut1); + + // The resolution of the PE header time date stamp is seconds (divided by two), and we want to make sure that has an opportunity to change // between calls to Emit. - Thread.Sleep(TimeSpan.FromSeconds(1)); - var (image2, refImage2) = emitRefOut(); - verifyIdentitiesMatch(image2, refImage2); + Thread.Sleep(TimeSpan.FromSeconds(3)); - var refImage3 = emitRefOnly(); + // Re-using the same compilation results in the same time stamp + var (out15, refOut15) = EmitRefOut(comp1); + VerifyIdentitiesMatch(out1, out15); + VerifyIdentitiesMatch(refOut1, refOut15); + AssertEx.Equal(refOut1, refOut15); - AssertEx.Equal(refImage1, refImage2); - AssertEx.Equal(refImage1, refImage3); + // Using a new compilation results in new time stamp + var comp2 = CreateStandardCompilation(source, options: options, assemblyName: name); + var (out2, refOut2) = EmitRefOut(comp2); + var refOnly2 = EmitRefOnly(comp2); + VerifyIdentitiesMatch(out2, refOut2); + VerifyIdentitiesMatch(out2, refOnly2); - var comp2 = CreateStandardCompilation(source, options: TestOptions.DebugDll.WithDeterministic(false)); - var refImage4 = emitRefOnly(); - AssertEx.Equal(refImage1, refImage4); + VerifyIdentitiesMatch(out1, out2, expectMatch: !hasWildcard); + VerifyIdentitiesMatch(refOut1, refOut2, expectMatch: !hasWildcard); - void verifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage) + if (hasWildcard) + { + AssertEx.NotEqual(refOut1, refOut2); + AssertEx.NotEqual(refOut1, refOnly2); + } + else { - var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); - var id2 = ModuleMetadata.CreateFromImage(secondImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); - Assert.Equal(id1, id2); + // If no wildcards, the binaries are emitted deterministically + AssertEx.Equal(refOut1, refOut2); + AssertEx.Equal(refOut1, refOnly2); } + } - (ImmutableArray image, ImmutableArray refImage) emitRefOut() + private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, bool expectMatch = true) + { + var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + var id2 = ModuleMetadata.CreateFromImage(secondImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + Assert.Equal(expectMatch, id1 == id2); + } + + private (ImmutableArray image, ImmutableArray refImage) EmitRefOut(CSharpCompilation comp) + { + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) { - using (var output = new MemoryStream()) - using (var metadataOutput = new MemoryStream()) - { - var options = EmitOptions.Default.WithIncludePrivateMembers(false); - comp.VerifyEmitDiagnostics(); - var result = comp.Emit(output, metadataPEStream: metadataOutput, - options: options); - return (output.ToImmutable(), metadataOutput.ToImmutable()); - } + var options = EmitOptions.Default.WithIncludePrivateMembers(false); + comp.VerifyEmitDiagnostics(); + var result = comp.Emit(output, metadataPEStream: metadataOutput, + options: options); + return (output.ToImmutable(), metadataOutput.ToImmutable()); } + } - ImmutableArray emitRefOnly() + ImmutableArray EmitRefOnly(CSharpCompilation comp) + { + using (var output = new MemoryStream()) { - using (var output = new MemoryStream()) - { - var options = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); - comp.VerifyEmitDiagnostics(); - var result = comp.Emit(output, - options: options); - return output.ToImmutable(); - } + var options = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + comp.VerifyEmitDiagnostics(); + var result = comp.Emit(output, + options: options); + return output.ToImmutable(); } } + [Fact] + public void RefAssembly_PublicSigning() + { + var snk = Temp.CreateFile().WriteAllBytes(TestResources.General.snKey); + + var comp = CreateStandardCompilation("public class C{}", + options: TestOptions.ReleaseDll.WithCryptoKeyFile(snk.Path).WithPublicSign(true)); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifyIdentitiesMatch(image, refImage); + VerifyIdentitiesMatch(image, refOnlyImage); + } + [Theory] [InlineData("public int M() { error(); }", true)] [InlineData("public int M() { error() }", false)] // This may get relaxed. See follow-up issue https://github.com/dotnet/roslyn/issues/17612 From de989f4309cae229fcbdbe8b7e592a86425c99a4 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 11 May 2017 09:53:17 -0700 Subject: [PATCH 6/7] Address PR feedback from Aleksey --- docs/features/refout.md | 2 + .../Test/CommandLine/CommandLineTests.cs | 6 ++ .../Test/Emit/Emit/CompilationEmitTests.cs | 77 +++++++++++++++++-- .../Test/CommandLine/CommandLineTests.vb | 6 ++ 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/docs/features/refout.md b/docs/features/refout.md index 29bbc5f82fa82..ad13f0458cbb1 100644 --- a/docs/features/refout.md +++ b/docs/features/refout.md @@ -39,6 +39,7 @@ Neither `/refonly` nor `/refout` are permitted with net modules (`/target:module The compilation from the command-line will either produce both assemblies (implementation and ref) or neither. There is no "partial success" scenario. When the compiler produces documentation, it is un-affected by either the `/refonly` or `/refout` parameters. This may change in the future. +The main purpose of the `/refout` option is to speed up incremental build scenarios. The current implementation for this flag can produce a ref assembly with more metadata than `/refonly` (for instance, anonymous types). This is a candidate for post-C#-7.1 refinement. ### CscTask/CoreCompile The `CoreCompile` target will support a new output, called `IntermediateRefAssembly`, which parallels the existing `IntermediateAssembly`. @@ -64,6 +65,7 @@ Going back to the 4 driving scenarios: ## Future As mentioned above, there may be further refinements after C# 7.1: +- Further reduce the metadata in ref assemblies produced by `/refout`, to match those produced by `/refonly`. - Controlling internals (producing public ref assemblies) - Produce ref assemblies even when there are errors outside method bodies (emitting error types when `EmitOptions.TolerateErrors` is set) - When the compiler produces documentation, the contents produced could be filtered down to match the APIs that go into the primary output. In other words, the documentation could be filtered down when using the `/refonly` parameter. diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 6ca4fdf65f3a2..3c72b5364eace 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -2886,6 +2886,12 @@ public void ParseOut() // error CS8301: Do not use refout when using refonly. Diagnostic(ErrorCode.ERR_NoRefOutWhenRefOnly).WithLocation(1, 1)); + parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/link:b", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify(); + + parsedArgs = DefaultParse(new[] { "/refonly", "/link:b", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify(); + parsedArgs = DefaultParse(new[] { "/refonly:incorrect", "a.cs" }, baseDirectory); parsedArgs.Errors.Verify( // error CS2007: Unrecognized option: '/refonly:incorrect' diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 335bc398f50ce..d2fac6877a2df 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -646,11 +646,13 @@ public S M() } } "; - verifyRefOnly(pia.EmitToImageReference(embedInteropTypes: true)); - verifyRefOut(pia.EmitToImageReference(embedInteropTypes: true)); + var piaImageReference = pia.EmitToImageReference(embedInteropTypes: true); + verifyRefOnly(piaImageReference); + verifyRefOut(piaImageReference); - verifyRefOnly(pia.ToMetadataReference(embedInteropTypes: true)); - verifyRefOut(pia.ToMetadataReference(embedInteropTypes: true)); + var piaMetadataReference = pia.ToMetadataReference(embedInteropTypes: true); + verifyRefOnly(piaMetadataReference); + verifyRefOut(piaMetadataReference); void verifyRefOnly(MetadataReference reference) { @@ -696,13 +698,14 @@ void verifyNoPia(ImmutableArray image) var module = (PEModuleSymbol)referencedAssembly.Modules[0]; var itest1 = module.GlobalNamespace.GetMember("ITest1"); - Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute").ToString()); + Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")); var method = (PEMethodSymbol)itest1.GetMember("M"); Assert.Equal("S ITest1.M()", method.ToTestDisplayString()); var s = (NamedTypeSymbol)method.ReturnType; Assert.Equal("S", s.ToTestDisplayString()); + Assert.NotNull(s.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")); var field = s.GetMember("field"); Assert.Equal("System.Int32 S.field", field.ToTestDisplayString()); @@ -797,6 +800,7 @@ void verifyNoPia(ImmutableArray image, bool expectMissing) if (expectMissing) { Assert.Null(itest1); + Assert.Null(module.GlobalNamespace.GetMember("S")); return; } @@ -1111,11 +1115,26 @@ public void RefAssembly_EmitAsDeterministic(string source, bool hasWildcard) } } - private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, bool expectMatch = true) + private void VerifySigned(ImmutableArray image, bool expectSigned = true) + { + using (var reader = new PEReader(image)) + { + var flags = reader.PEHeaders.CorHeader.Flags; + Assert.Equal(expectSigned, flags.HasFlag(CorFlags.StrongNameSigned)); + } + } + + private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, + bool expectMatch = true, bool expectPublicKey = false) { var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); var id2 = ModuleMetadata.CreateFromImage(secondImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); Assert.Equal(expectMatch, id1 == id2); + if (expectPublicKey) + { + Assert.True(id1.HasPublicKey); + Assert.True(id2.HasPublicKey); + } } private (ImmutableArray image, ImmutableArray refImage) EmitRefOut(CSharpCompilation comp) @@ -1154,8 +1173,50 @@ public void RefAssembly_PublicSigning() comp.VerifyDiagnostics(); var (image, refImage) = EmitRefOut(comp); var refOnlyImage = EmitRefOnly(comp); - VerifyIdentitiesMatch(image, refImage); - VerifyIdentitiesMatch(image, refOnlyImage); + VerifySigned(image); + VerifySigned(refImage); + VerifySigned(refOnlyImage); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); + } + + [Fact] + public void RefAssembly_StrongNameProvider() + { + var signedDllOptions = TestOptions.ReleaseDll. + WithCryptoKeyFile(SigningTestHelpers.KeyPairFile). + WithStrongNameProvider(new SigningTestHelpers.VirtualizedStrongNameProvider(ImmutableArray.Empty)); + + var comp = CreateStandardCompilation("public class C{}", options: signedDllOptions); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifySigned(image); + VerifySigned(refImage); + VerifySigned(refOnlyImage); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); + } + + [Fact] + public void RefAssembly_StrongNameProviderAndDelaySign() + { + var signedDllOptions = TestOptions.ReleaseDll + .WithCryptoKeyFile(SigningTestHelpers.KeyPairFile) + .WithDelaySign(true) + .WithStrongNameProvider(new SigningTestHelpers.VirtualizedStrongNameProvider(ImmutableArray.Empty)); + + var comp = CreateStandardCompilation("public class C{}", options: signedDllOptions); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifySigned(image, expectSigned: false); + VerifySigned(refImage, expectSigned: false); + VerifySigned(refOnlyImage, expectSigned: false); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); } [Theory] diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index bdc7f4bb09dca..caf04f3fa5cc1 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -2923,6 +2923,12 @@ print Goodbye, World" parsedArgs.Errors.Verify( Diagnostic(ERRID.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)) + parsedArgs = DefaultParse({"/refout:ref.dll", "/link:b", "a.vb"}, baseDirectory) + parsedArgs.Errors.Verify() + + parsedArgs = DefaultParse({"/refonly", "/link:b", "a.vb"}, baseDirectory) + parsedArgs.Errors.Verify() + parsedArgs = DefaultParse({"/refonly", "/target:module", "a.vb"}, baseDirectory) parsedArgs.Errors.Verify( Diagnostic(ERRID.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)) From 97e1535d8156d92648f9caa47ec7cb816ffb3eca Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 11 May 2017 13:06:19 -0700 Subject: [PATCH 7/7] Address more PR feedback --- docs/features/refout.md | 2 +- .../Test/Emit/Emit/CompilationEmitTests.cs | 45 ++++++------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/docs/features/refout.md b/docs/features/refout.md index ad13f0458cbb1..9231dd37ee3f9 100644 --- a/docs/features/refout.md +++ b/docs/features/refout.md @@ -14,7 +14,7 @@ There are 4 scenarios: ## Definition of ref assemblies Metadata-only assembly have their method bodies replaced with a single `throw null` body, but include all members except anonymous types. The reason for using `throw null` bodies (as opposed to no bodies) is so that PEVerify could run and pass (thus validating the completeness of the metadata). -Ref assemblies will include an assembly-level `ReferenceAssembly` attribute. This attribute may be specified in should (then we won't need to synthesize it). Because of this attribute, runtimes will refuse to load ref assemblies for execution (but they can still be loaded Reflection-only mode). +Ref assemblies will include an assembly-level `ReferenceAssembly` attribute. This attribute may be specified in source (then we won't need to synthesize it). Because of this attribute, runtimes will refuse to load ref assemblies for execution (but they can still be loaded Reflection-only mode). Ref assemblies further remove metadata (private members) from metadata-only assemblies: - A ref assembly will only have references for what it needs in the API surface. The real assembly may have additional references related to specific implementations. For instance, the ref assembly for `class C { private void M() { dynamic d = 1; ... } }` will not reference any types required for `dynamic`. diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index d2fac6877a2df..db307fc169220 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -658,36 +658,17 @@ void verifyRefOnly(MetadataReference reference) { var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, references: new MetadataReference[] { reference }); - using (var output = new MemoryStream()) - { - EmitResult emitResult = comp.Emit(output, - options: new EmitOptions(includePrivateMembers: false).WithEmitMetadataOnly(true)); - Assert.True(emitResult.Success); - emitResult.Diagnostics.Verify(); - - var refImage = output.ToImmutable(); - verifyNoPia(refImage); - } + var refOnlyImage = EmitRefOnly(comp); + verifyNoPia(refOnlyImage); } void verifyRefOut(MetadataReference reference) { var comp = CreateStandardCompilation(source, options: TestOptions.DebugDll, references: new MetadataReference[] { reference }); - using (var output = new MemoryStream()) - using (var metadataOutput = new MemoryStream()) - { - EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, - options: new EmitOptions(includePrivateMembers: false)); - Assert.True(emitResult.Success); - emitResult.Diagnostics.Verify(); - - var image = output.ToImmutable(); - var refImage = metadataOutput.ToImmutable(); - - verifyNoPia(image); - verifyNoPia(refImage); - } + var (image, refImage) = EmitRefOut(comp); + verifyNoPia(image); + verifyNoPia(refImage); } void verifyNoPia(ImmutableArray image) @@ -744,11 +725,13 @@ public void M2() } } "; - verifyRefOnly(pia.EmitToImageReference(embedInteropTypes: true)); - verifyRefOut(pia.EmitToImageReference(embedInteropTypes: true)); + var piaImageReference = pia.EmitToImageReference(embedInteropTypes: true); + verifyRefOnly(piaImageReference); + verifyRefOut(piaImageReference); - verifyRefOnly(pia.ToMetadataReference(embedInteropTypes: true)); - verifyRefOut(pia.ToMetadataReference(embedInteropTypes: true)); + var piaMetadataReference = pia.ToMetadataReference(embedInteropTypes: true); + verifyRefOnly(piaMetadataReference); + verifyRefOut(piaMetadataReference); void verifyRefOnly(MetadataReference reference) { @@ -1124,7 +1107,7 @@ private void VerifySigned(ImmutableArray image, bool expectSigned = true) } } - private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, + private static void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, bool expectMatch = true, bool expectPublicKey = false) { var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); @@ -1137,7 +1120,7 @@ private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArr } } - private (ImmutableArray image, ImmutableArray refImage) EmitRefOut(CSharpCompilation comp) + private static (ImmutableArray image, ImmutableArray refImage) EmitRefOut(CSharpCompilation comp) { using (var output = new MemoryStream()) using (var metadataOutput = new MemoryStream()) @@ -1150,7 +1133,7 @@ private void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArr } } - ImmutableArray EmitRefOnly(CSharpCompilation comp) + private static ImmutableArray EmitRefOnly(CSharpCompilation comp) { using (var output = new MemoryStream()) {