diff --git a/src/libraries/System.Reflection.Emit/tests/AssemblyBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/AssemblyBuilderTests.cs index 7a6d835fa63b6a..c2b4cad04c313c 100644 --- a/src/libraries/System.Reflection.Emit/tests/AssemblyBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/AssemblyBuilderTests.cs @@ -411,100 +411,100 @@ private static void VerifyAssemblyBuilder(AssemblyBuilder assembly, AssemblyName Assert.Empty(assembly.GetTypes()); } - private static void SamplePrivateMethod () - { - } + private static void SamplePrivateMethod() + { + } - internal static void SampleInternalMethod () - { - } + internal static void SampleInternalMethod() + { + } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - void Invoke_Private_CrossAssembly_ThrowsMethodAccessException() - { - TypeBuilder tb = Helpers.DynamicType(TypeAttributes.Public); - var mb = tb.DefineMethod ("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + void Invoke_Private_CrossAssembly_ThrowsMethodAccessException() + { + TypeBuilder tb = Helpers.DynamicType(TypeAttributes.Public); + var mb = tb.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); - var ilg = mb.GetILGenerator (); + var ilg = mb.GetILGenerator(); - var callee = typeof (AssemblyTests).GetMethod ("SamplePrivateMethod", BindingFlags.Static | BindingFlags.NonPublic); + var callee = typeof(AssemblyTests).GetMethod("SamplePrivateMethod", BindingFlags.Static | BindingFlags.NonPublic); - ilg.Emit (OpCodes.Call, callee); - ilg.Emit (OpCodes.Ret); + ilg.Emit(OpCodes.Call, callee); + ilg.Emit(OpCodes.Ret); - var ty = tb.CreateType (); + var ty = tb.CreateType(); - var mi = ty.GetMethod ("MyMethod", BindingFlags.Static | BindingFlags.Public); + var mi = ty.GetMethod("MyMethod", BindingFlags.Static | BindingFlags.Public); - var d = (Action) mi.CreateDelegate (typeof(Action)); + var d = (Action)mi.CreateDelegate(typeof(Action)); - Assert.Throws(() => d ()); - } + Assert.Throws(() => d()); + } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - void Invoke_Internal_CrossAssembly_ThrowsMethodAccessException() - { - TypeBuilder tb = Helpers.DynamicType(TypeAttributes.Public); - var mb = tb.DefineMethod ("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + void Invoke_Internal_CrossAssembly_ThrowsMethodAccessException() + { + TypeBuilder tb = Helpers.DynamicType(TypeAttributes.Public); + var mb = tb.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); - var ilg = mb.GetILGenerator (); + var ilg = mb.GetILGenerator(); - var callee = typeof (AssemblyTests).GetMethod ("SampleInternalMethod", BindingFlags.Static | BindingFlags.NonPublic); + var callee = typeof(AssemblyTests).GetMethod("SampleInternalMethod", BindingFlags.Static | BindingFlags.NonPublic); - ilg.Emit (OpCodes.Call, callee); - ilg.Emit (OpCodes.Ret); + ilg.Emit(OpCodes.Call, callee); + ilg.Emit(OpCodes.Ret); - var ty = tb.CreateType (); + var ty = tb.CreateType(); - var mi = ty.GetMethod ("MyMethod", BindingFlags.Static | BindingFlags.Public); + var mi = ty.GetMethod("MyMethod", BindingFlags.Static | BindingFlags.Public); - var d = (Action) mi.CreateDelegate (typeof(Action)); + var d = (Action)mi.CreateDelegate(typeof(Action)); - Assert.Throws(() => d ()); - } + Assert.Throws(() => d()); + } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - void Invoke_Private_SameAssembly_ThrowsMethodAccessException() - { - ModuleBuilder modb = Helpers.DynamicModule(); + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + void Invoke_Private_SameAssembly_ThrowsMethodAccessException() + { + ModuleBuilder modb = Helpers.DynamicModule(); - string calleeName = "PrivateMethod"; + string calleeName = "PrivateMethod"; - TypeBuilder tbCalled = modb.DefineType ("CalledClass", TypeAttributes.Public); - var mbCalled = tbCalled.DefineMethod (calleeName, MethodAttributes.Private | MethodAttributes.Static); - mbCalled.GetILGenerator().Emit (OpCodes.Ret); + TypeBuilder tbCalled = modb.DefineType("CalledClass", TypeAttributes.Public); + var mbCalled = tbCalled.DefineMethod(calleeName, MethodAttributes.Private | MethodAttributes.Static); + mbCalled.GetILGenerator().Emit(OpCodes.Ret); - var tyCalled = tbCalled.CreateType(); - var callee = tyCalled.GetMethod (calleeName, BindingFlags.NonPublic | BindingFlags.Static); + var tyCalled = tbCalled.CreateType(); + var callee = tyCalled.GetMethod(calleeName, BindingFlags.NonPublic | BindingFlags.Static); - TypeBuilder tb = modb.DefineType("CallerClass", TypeAttributes.Public); - var mb = tb.DefineMethod ("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); + TypeBuilder tb = modb.DefineType("CallerClass", TypeAttributes.Public); + var mb = tb.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); - var ilg = mb.GetILGenerator (); + var ilg = mb.GetILGenerator(); - ilg.Emit (OpCodes.Call, callee); - ilg.Emit (OpCodes.Ret); + ilg.Emit(OpCodes.Call, callee); + ilg.Emit(OpCodes.Ret); - var ty = tb.CreateType (); + var ty = tb.CreateType(); - var mi = ty.GetMethod ("MyMethod", BindingFlags.Static | BindingFlags.Public); + var mi = ty.GetMethod("MyMethod", BindingFlags.Static | BindingFlags.Public); - var d = (Action) mi.CreateDelegate (typeof(Action)); + var d = (Action)mi.CreateDelegate(typeof(Action)); - Assert.Throws(() => d ()); - } + Assert.Throws(() => d()); + } - [Fact] - public void DefineDynamicAssembly_AssemblyBuilderLocationIsEmpty_InternalAssemblyBuilderLocationIsEmpty() - { - AssemblyBuilder assembly = Helpers.DynamicAssembly(nameof(DefineDynamicAssembly_AssemblyBuilderLocationIsEmpty_InternalAssemblyBuilderLocationIsEmpty)); - Assembly internalAssemblyBuilder = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.FullName == assembly.FullName); + [Fact] + public void DefineDynamicAssembly_AssemblyBuilderLocationIsEmpty_InternalAssemblyBuilderLocationIsEmpty() + { + AssemblyBuilder assembly = Helpers.DynamicAssembly(nameof(DefineDynamicAssembly_AssemblyBuilderLocationIsEmpty_InternalAssemblyBuilderLocationIsEmpty)); + Assembly internalAssemblyBuilder = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.FullName == assembly.FullName); - Assert.Empty(assembly.Location); - Assert.NotNull(internalAssemblyBuilder); - Assert.Empty(internalAssemblyBuilder.Location); - } + Assert.Empty(assembly.Location); + Assert.NotNull(internalAssemblyBuilder); + Assert.Empty(internalAssemblyBuilder.Location); + } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public static void ThrowsWhenDynamicCodeNotSupported() diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveResourceTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveResourceTests.cs new file mode 100644 index 00000000000000..c9e2012686356b --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveResourceTests.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Globalization; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Resources; +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public class AssemblySaveResourceTests + { + [Theory] + [InlineData(new byte[] { 1 })] + [InlineData(new byte[] { 1, 2 })] // Verify blob alignment padding by adding a byte. + public void ManagedResources(byte[] byteValue) + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssemblyWithResource"), typeof(object).Assembly); + ab.DefineDynamicModule("MyModule"); + MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _); + + using MemoryStream memoryStream = new MemoryStream(); + ResourceWriter myResourceWriter = new ResourceWriter(memoryStream); + myResourceWriter.AddResource("StringResource", "Value"); + myResourceWriter.AddResource("ByteResource", byteValue); + myResourceWriter.Close(); + + byte[] data = memoryStream.ToArray(); + BlobBuilder resourceBlob = new BlobBuilder(); + resourceBlob.WriteInt32(data.Length); + resourceBlob.WriteBytes(data); + int resourceBlobSize = resourceBlob.Count; + + metadata.AddManifestResource( + ManifestResourceAttributes.Public, + metadata.GetOrAddString("MyResource.resources"), + implementation: default, + offset: 0); + + ManagedPEBuilder peBuilder = new ManagedPEBuilder( + header: PEHeaderBuilder.CreateLibraryHeader(), + metadataRootBuilder: new MetadataRootBuilder(metadata), + ilStream: ilStream, + managedResources: resourceBlob); + + BlobBuilder blob = new BlobBuilder(); + peBuilder.Serialize(blob); + + // Ensure the the resource blob wasn't modified by Serialize() due to alignment padding. + // Serialize() pads after the blob to align at ManagedTextSection.ManagedResourcesDataAlignment bytes. + Assert.Equal(resourceBlobSize, resourceBlob.Count); + + // To verify the resources work with runtime APIs, load the assembly into the process instead of + // the normal testing approach of using MetadataLoadContext. + TestAssemblyLoadContext testAssemblyLoadContext = new(); + try + { + Assembly readAssembly = testAssemblyLoadContext.LoadFromStream(new MemoryStream(blob.ToArray())); + + // Use ResourceReader to read the resources. + using Stream readStream = readAssembly.GetManifestResourceStream("MyResource.resources")!; + using ResourceReader reader = new(readStream); + Verify(reader.GetEnumerator()); + + // Use ResourceManager to read the resources. + ResourceManager rm = new ResourceManager("MyResource", readAssembly); + ResourceSet resourceSet = rm.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: false); + Verify(resourceSet.GetEnumerator()); + } + finally + { + testAssemblyLoadContext.Unload(); + } + + void Verify(IDictionaryEnumerator resources) + { + Assert.True(resources.MoveNext()); + DictionaryEntry resource = (DictionaryEntry)resources.Current; + Assert.Equal("ByteResource", resource.Key); + Assert.Equal(byteValue, resource.Value); + + Assert.True(resources.MoveNext()); + resource = (DictionaryEntry)resources.Current; + Assert.Equal("StringResource", resource.Key); + Assert.Equal("Value", resource.Value); + + Assert.False(resources.MoveNext()); + } + } + } +} + diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 56d7b3869a1665..f92aaea6308756 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -71,6 +71,7 @@ + diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataSizes.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataSizes.cs index 526cbcdcee9a84..84180091f1dc23 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataSizes.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataSizes.cs @@ -12,7 +12,7 @@ namespace System.Reflection.Metadata.Ecma335 /// public sealed class MetadataSizes { - private const int StreamAlignment = 4; + internal const int StreamAlignment = 4; // Call the length of the string (including the terminator) m (we require m <= 255); internal const int MaxMetadataVersionByteCount = 0xff - 1; diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedTextSection.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedTextSection.cs index bd30dc7e491024..5a78d368039b90 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedTextSection.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedTextSection.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Reflection.Internal; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; namespace System.Reflection.PortableExecutable { @@ -40,8 +41,8 @@ internal sealed class ManagedTextSection public int MetadataSize { get; } /// - /// The size of managed resource data stream. - /// Aligned to . + /// The size of managed resource data stream (unaligned). + /// When written, will be aligned to . /// public int ResourceDataSize { get; } @@ -147,14 +148,15 @@ public int CalculateOffsetToMappedFieldDataStream() internal int ComputeOffsetToDebugDirectory() { - Debug.Assert(MetadataSize % 4 == 0); - Debug.Assert(ResourceDataSize % 4 == 0); + Debug.Assert(MetadataSize % MetadataSizes.StreamAlignment == 0); - return + int offset = ComputeOffsetToMetadata() + MetadataSize + - ResourceDataSize + + BitArithmetic.Align(ResourceDataSize, ManagedResourcesDataAlignment) + StrongNameSignatureSize; + + return offset; } private int ComputeOffsetToImportTable() @@ -254,7 +256,6 @@ public void Serialize( Debug.Assert(ilBuilder.Count == ILStreamSize); Debug.Assert((mappedFieldDataBuilderOpt?.Count ?? 0) == MappedFieldDataSize); Debug.Assert((resourceBuilderOpt?.Count ?? 0) == ResourceDataSize); - Debug.Assert((resourceBuilderOpt?.Count ?? 0) % 4 == 0); // TODO: avoid recalculation int importTableRva = GetImportTableDirectoryEntry(relativeVirtualAddess).RelativeVirtualAddress; @@ -278,6 +279,7 @@ public void Serialize( if (resourceBuilderOpt != null) { builder.LinkSuffix(resourceBuilderOpt); + builder.WriteBytes(0, BitArithmetic.Align(resourceBuilderOpt.Count, ManagedTextSection.ManagedResourcesDataAlignment) - resourceBuilderOpt.Count); } // strong name signature: @@ -387,7 +389,7 @@ private void WriteCorHeader(BlobBuilder builder, int textSectionRva, int entryPo int metadataRva = textSectionRva + ComputeOffsetToMetadata(); int resourcesRva = metadataRva + MetadataSize; - int signatureRva = resourcesRva + ResourceDataSize; + int signatureRva = resourcesRva + BitArithmetic.Align(ResourceDataSize, ManagedResourcesDataAlignment); int start = builder.Count; @@ -410,7 +412,7 @@ private void WriteCorHeader(BlobBuilder builder, int textSectionRva, int entryPo // ResourcesDirectory: builder.WriteUInt32((uint)(ResourceDataSize == 0 ? 0 : resourcesRva)); // 28 - builder.WriteUInt32((uint)ResourceDataSize); + builder.WriteUInt32((uint)BitArithmetic.Align(ResourceDataSize, ManagedResourcesDataAlignment)); // StrongNameSignatureDirectory: builder.WriteUInt32((uint)(StrongNameSignatureSize == 0 ? 0 : signatureRva)); // 36