-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix alignment padding and add test for saving managed resources #110915
base: main
Are you sure you want to change the base?
Changes from all commits
f21da2f
d199ed7
c011569
f857e01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// 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 Microsoft.DotNet.RemoteExecutor; | ||
using Xunit; | ||
|
||
namespace System.Reflection.Emit.Tests | ||
{ | ||
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] | ||
public class AssemblySaveResourceTests | ||
{ | ||
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
[InlineData(new byte[] { 1 }, "01")] | ||
[InlineData(new byte[] { 1, 2 }, "01-02")] // Verify blob alignment padding by adding a byte. | ||
public void ManagedResources(byte[] byteValue, string byteValueExpected) | ||
{ | ||
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); | ||
|
||
// Create a temporary assembly. | ||
string tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".dll"); | ||
steveharter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
using TempFile file = new TempFile(tempFilePath, blob.ToArray()); | ||
|
||
// To verify the resources work with runtime APIs, load the assembly into the process instead of | ||
// the normal testing approach of using MetadataLoadContext. | ||
using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(static (tempFilePath, byteValue, byteValueExpected) => | ||
{ | ||
Assembly readAssembly = Assembly.LoadFile(tempFilePath); | ||
|
||
// 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()); | ||
|
||
void Verify(IDictionaryEnumerator resources) | ||
{ | ||
Assert.True(resources.MoveNext()); | ||
DictionaryEntry resource = (DictionaryEntry)resources.Current; | ||
Assert.Equal("ByteResource", resource.Key); | ||
Assert.Equal(byteValueExpected, byteValue); | ||
|
||
Assert.True(resources.MoveNext()); | ||
resource = (DictionaryEntry)resources.Current; | ||
Assert.Equal("StringResource", resource.Key); | ||
Assert.Equal("Value", resource.Value); | ||
|
||
Assert.False(resources.MoveNext()); | ||
} | ||
}, tempFilePath, BitConverter.ToString(byteValue), byteValueExpected); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; } | ||
|
||
/// <summary> | ||
/// The size of managed resource data stream. | ||
/// Aligned to <see cref="ManagedResourcesDataAlignment"/>. | ||
/// The size of managed resource data stream (unaligned). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tmat Could you please take a look at the changes in this file? How are the alignment requirements expected to be handled by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping @tmat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember what the expectations were. It's been a while. To me it looks like we should have checked the alignments in |
||
/// When written, will be aligned to <see cref="ManagedResourcesDataAlignment"/>. | ||
/// </summary> | ||
public int ResourceDataSize { get; } | ||
|
||
|
@@ -147,14 +148,16 @@ public int CalculateOffsetToMappedFieldDataStream() | |
|
||
internal int ComputeOffsetToDebugDirectory() | ||
{ | ||
Debug.Assert(MetadataSize % 4 == 0); | ||
Debug.Assert(ResourceDataSize % 4 == 0); | ||
Debug.Assert(MetadataSize % MetadataSizes.StreamAlignment == 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not 100% sure if this is the intent (4 byte alignment across the various sections); I think we could remove this Assert since they should all be aligned anyway |
||
|
||
return | ||
int offset = | ||
ComputeOffsetToMetadata() + | ||
MetadataSize + | ||
ResourceDataSize + | ||
BitArithmetic.Align(ResourceDataSize, ManagedResourcesDataAlignment) + | ||
StrongNameSignatureSize; | ||
|
||
Debug.Assert(offset % MetadataSizes.StreamAlignment == 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return offset; | ||
} | ||
|
||
private int ComputeOffsetToImportTable() | ||
|
@@ -254,7 +257,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 +280,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 +390,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 +413,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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whitespace-only changes to this file