Skip to content
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

Add public APIs for persisted AssemblyBulder #97177

Merged
merged 10 commits into from
Jan 21, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,7 @@ protected override void SetCustomAttributeCore(ConstructorInfo con, ReadOnlySpan
binaryAttribute);
}
}

protected override void SaveCore(Stream stream) => throw new NotSupportedException();
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -32,8 +33,59 @@ public ModuleBuilder DefineDynamicModule(string name)
return GetDynamicModuleCore(name);
}

/// <summary>
/// Defines an <see cref="AssemblyBuilder"/> that can be saved to a file or stream.
/// </summary>
/// <param name="name">The name of the assembly.</param>
/// <param name="coreAssembly">The assembly that denotes the "system assembly" that houses the well-known types such as <see cref="object"/></param>
/// <param name="assemblyAttributes">A collection that contains the attributes of the assembly.</param>
/// <returns>An <see cref="AssemblyBuilder"/> that can be persisted.</returns>
/// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="name.Name"/> or <paramref name="coreAssembly"/> is null.</exception>
/// <remarks>Currently the persisted assembly doesn't support running, need to save it and load back to run.</remarks>
public static AssemblyBuilder DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null)
{
ArgumentNullException.ThrowIfNull(name);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
ArgumentNullException.ThrowIfNull(coreAssembly);

Type? assemblyType = Type.GetType("System.Reflection.Emit.AssemblyBuilderImpl, System.Reflection.Emit", throwOnError: true);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved

if (assemblyType == null)
{
throw new TypeLoadException(SR.Format(SR.TypeLoad_TypeNotFound, "System.Reflection.Emit.AssemblyBuilderImpl"));
}

ConstructorInfo con = assemblyType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, [typeof(AssemblyName), typeof(Assembly), typeof(IEnumerable<CustomAttributeBuilder>)])!;
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
return (AssemblyBuilder)con.Invoke([name, coreAssembly, assemblyAttributes]);
}

protected abstract ModuleBuilder? GetDynamicModuleCore(string name);

/// <summary>
/// Serializes the assembly to stream.
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="stream">The stream to which the assembly serialized.</param>
/// <exception cref="ArgumentNullException"> <paramref name="stream"/> is null </exception>
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
public void Save(Stream stream) => SaveCore(stream);

/// <summary>
/// Saves the assembly to disk.
/// </summary>
/// <param name="assemblyFileName">The file name of the assembly.</param>
/// <exception cref="ArgumentNullException"> <paramref name="assemblyFileName"/> is null </exception>
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
public void Save(string assemblyFileName)
{
ArgumentNullException.ThrowIfNull(assemblyFileName);

using var peStream = new FileStream(assemblyFileName, FileMode.Create, FileAccess.Write);
SaveCore(peStream);
}

/// <summary>
/// When implemented in derived type serializer the assembly to stream.
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="stream">The stream to which the assembly serialized.</param>
protected abstract void SaveCore(Stream stream);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it not possible for someone externally to derive from AssemblyBuilder in .NET 8 and earlier? Adding a new abstract method will break anyone who did.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We made the AssemblyBuilder abstract in .NET 8, before it was sealed. So only .NET 8 can be considered, and I think it's unlikely somebody would have derived from AssemblyBuilder and build their own implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was possible to derive from AssemblyBuilder in .NET 8, then this is a binary breaking change. I think we need to make it virtual with the base implementation throwing.
cc: @terrajobst

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


public void SetCustomAttribute(ConstructorInfo con, byte[] binaryAttribute)
{
ArgumentNullException.ThrowIfNull(con);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ protected AssemblyBuilder() { }
public static System.Reflection.Emit.AssemblyBuilder DefineDynamicAssembly(System.Reflection.AssemblyName name, System.Reflection.Emit.AssemblyBuilderAccess access) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")]
public static System.Reflection.Emit.AssemblyBuilder DefineDynamicAssembly(System.Reflection.AssemblyName name, System.Reflection.Emit.AssemblyBuilderAccess access, System.Collections.Generic.IEnumerable<System.Reflection.Emit.CustomAttributeBuilder>? assemblyAttributes) { throw null; }
public static System.Reflection.Emit.AssemblyBuilder DefinePersistedAssembly(System.Reflection.AssemblyName name, System.Reflection.Assembly coreAssembly, System.Collections.Generic.IEnumerable<System.Reflection.Emit.CustomAttributeBuilder>? assemblyAttributes = null) { throw null; }
public System.Reflection.Emit.ModuleBuilder DefineDynamicModule(string name) { throw null; }
protected abstract System.Reflection.Emit.ModuleBuilder DefineDynamicModuleCore(string name);
public override bool Equals(object? obj) { throw null; }
Expand Down Expand Up @@ -54,6 +55,9 @@ protected AssemblyBuilder() { }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Types might be removed by trimming. If the type name is a string literal, consider using Type.GetType instead.")]
public override System.Type? GetType(string name, bool throwOnError, bool ignoreCase) { throw null; }
public override bool IsDefined(System.Type attributeType, bool inherit) { throw null; }
public void Save(string assemblyFileName) { throw null; }
public void Save(System.IO.Stream stream) { throw null; }
protected abstract void SaveCore(System.IO.Stream stream);
public void SetCustomAttribute(System.Reflection.ConstructorInfo con, byte[] binaryAttribute) { }
public void SetCustomAttribute(System.Reflection.Emit.CustomAttributeBuilder customBuilder) { }
protected abstract void SetCustomAttributeCore(System.Reflection.ConstructorInfo con, System.ReadOnlySpan<byte> binaryAttribute);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<linker>
<assembly fullname="System.Reflection.Emit">
<type fullname="System.Reflection.Emit.AssemblyBuilderImpl">
<!-- Internal API used by tests only. -->
<method name="DefinePersistedAssembly" />
<method name="Save" />
<!-- Internal API called through Reflection by another assembly. -->
<method name=".ctor" />
</type>
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ internal sealed class AssemblyBuilderImpl : AssemblyBuilder

internal List<CustomAttributeWrapper>? _customAttributes;

internal AssemblyBuilderImpl(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes)
internal AssemblyBuilderImpl(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null)
{
ArgumentNullException.ThrowIfNull(name);

name = (AssemblyName)name.Clone();

ArgumentException.ThrowIfNullOrEmpty(name.Name, "AssemblyName.Name");
Expand All @@ -40,10 +38,6 @@ internal AssemblyBuilderImpl(AssemblyName name, Assembly coreAssembly, IEnumerab
}
}

internal static AssemblyBuilderImpl DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly,
IEnumerable<CustomAttributeBuilder>? assemblyAttributes)
=> new AssemblyBuilderImpl(name, coreAssembly, assemblyAttributes);

private void WritePEImage(Stream peStream, BlobBuilder ilBuilder)
{
var peHeaderBuilder = new PEHeaderBuilder(
Expand All @@ -63,7 +57,7 @@ private void WritePEImage(Stream peStream, BlobBuilder ilBuilder)
peBlob.WriteContentTo(peStream);
}

internal void Save(Stream stream)
protected override void SaveCore(Stream stream)
{
ArgumentNullException.ThrowIfNull(stream);

Expand Down Expand Up @@ -101,14 +95,6 @@ internal void Save(Stream stream)
private static AssemblyFlags AddContentType(AssemblyFlags flags, AssemblyContentType contentType)
=> (AssemblyFlags)((int)contentType << 9) | flags;

internal void Save(string assemblyFileName)
{
ArgumentNullException.ThrowIfNull(assemblyFileName);

using var peStream = new FileStream(assemblyFileName, FileMode.Create, FileAccess.Write);
Save(peStream);
}

protected override ModuleBuilder DefineDynamicModuleCore(string name)
{
if (_module != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void AssemblyWithDifferentTypes()
aName.CultureInfo = new CultureInfo("en");
aName.Flags = AssemblyNameFlags.Retargetable;

AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(aName, null, typeof(string), out MethodInfo saveMethod);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilder(aName);

ab.SetCustomAttribute(new CustomAttributeBuilder(typeof(AssemblyDelaySignAttribute).GetConstructor([typeof(bool)]), [true]));

Expand Down Expand Up @@ -213,10 +213,12 @@ public void AssemblyWithDifferentTypes()
eventb.SetRemoveOnMethod(mbRemove);
tbEvents.CreateType();

saveMethod.Invoke(ab, [file.Path]);
ab.Save(file.Path);

Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
CheckAssembly(assemblyFromDisk);
using (MetadataLoadContext mlc = new MetadataLoadContext(new CoreMetadataAssemblyResolver()))
{
CheckAssembly(mlc.LoadFromAssemblyPath(file.Path));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void DefineConstructorsTest()
{
using (TempFile file = TempFile.Create())
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
ConstructorBuilder constructor = type.DefineDefaultConstructor(MethodAttributes.Public);
ConstructorBuilder constructor2 = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, [typeof(int)]);
constructor2.DefineParameter(1, ParameterAttributes.None, "parameter1");
Expand All @@ -29,7 +29,7 @@ public void DefineConstructorsTest()
il.Emit(OpCodes.Stfld, fieldBuilderA);
il.Emit(OpCodes.Ret);
type.CreateType();
saveMethod.Invoke(ab, [file.Path]);
ab.Save(file.Path);

using (MetadataLoadContext mlc = new MetadataLoadContext(new CoreMetadataAssemblyResolver()))
{
Expand All @@ -52,7 +52,7 @@ public void DefineConstructorsTest()
[Fact]
public void DefineDefaultConstructor_WithTypeBuilderParent()
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
type.CreateType();
TypeBuilder child = ab.GetDynamicModule("MyModule").DefineType("ChildType", TypeAttributes.Public | TypeAttributes.Class);
child.SetParent(type);
Expand All @@ -70,7 +70,7 @@ public void DefineDefaultConstructor_TypesWithGenericParents()
{
using (TempFile file = TempFile.Create())
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
type.DefineGenericParameters("T");
ConstructorBuilder constructor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
FieldBuilder field = type.DefineField("TestField", typeof(bool), FieldAttributes.Public | FieldAttributes.Static);
Expand All @@ -94,31 +94,34 @@ public void DefineDefaultConstructor_TypesWithGenericParents()
type2.SetParent(genericList);
type2.DefineDefaultConstructor(MethodAttributes.Public);
type2.CreateTypeInfo();
saveMethod.Invoke(ab, [file.Path]);

Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path);
ConstructorInfo[] ctors = assemblyFromDisk.GetType("MyType").GetConstructors();
Assert.Equal(1, ctors.Length);
Assert.Empty(ctors[0].GetParameters());
Type derivedFromFile = assemblyFromDisk.GetType("Derived");
Assert.NotNull(derivedFromFile.GetConstructor(Type.EmptyTypes));
Assert.Equal(genericParent.FullName, derivedFromFile.BaseType.FullName);
Assert.NotNull(assemblyFromDisk.GetType("Type2").GetConstructor(Type.EmptyTypes));
ab.Save(file.Path);

using (MetadataLoadContext mlc = new MetadataLoadContext(new CoreMetadataAssemblyResolver()))
{
Assembly assemblyFromDisk = mlc.LoadFromAssemblyPath(file.Path);
ConstructorInfo[] ctors = assemblyFromDisk.GetType("MyType").GetConstructors();
Assert.Equal(1, ctors.Length);
Assert.Empty(ctors[0].GetParameters());
Type derivedFromFile = assemblyFromDisk.GetType("Derived");
Assert.NotNull(derivedFromFile.GetConstructor(Type.EmptyTypes));
Assert.Equal(genericParent.FullName, derivedFromFile.BaseType.FullName);
Assert.NotNull(assemblyFromDisk.GetType("Type2").GetConstructor(Type.EmptyTypes));
}
}
}

[Fact]
public void DefineDefaultConstructor_Interface_ThrowsInvalidOperationException()
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("MyAssembly"), null, typeof(string), out var _);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilder(new AssemblyName("MyAssembly"));
TypeBuilder type = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);
Assert.Throws<InvalidOperationException>(() => type.DefineDefaultConstructor(MethodAttributes.Public));
}

[Fact]
public void DefineDefaultConstructor_ThrowsNotSupportedException_IfParentNotCreated()
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
TypeBuilder child = ab.GetDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public);
child.SetParent(type);
Assert.Throws<NotSupportedException>(() => child.DefineDefaultConstructor(MethodAttributes.Public));
Expand All @@ -127,14 +130,14 @@ public void DefineDefaultConstructor_ThrowsNotSupportedException_IfParentNotCrea
[Fact]
public void DefineDefaultConstructor_StaticVirtual_ThrowsArgumentException()
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
AssertExtensions.Throws<ArgumentException>(null, () => type.DefineDefaultConstructor(MethodAttributes.Virtual | MethodAttributes.Static));
}

[Fact]
public void DefineDefaultConstructor_ParentNoDefaultConstructor_ThrowsNotSupportedException()
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
FieldBuilder field = type.DefineField("TestField", typeof(int), FieldAttributes.Family);

ConstructorBuilder constructor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(int) });
Expand All @@ -157,7 +160,7 @@ public void DefineDefaultConstructor_ParentNoDefaultConstructor_ThrowsNotSupport
[InlineData(MethodAttributes.PrivateScope)]
public void DefineDefaultConstructor_ParentPrivateDefaultConstructor_ThrowsNotSupportedException(MethodAttributes attributes)
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder baseType, out MethodInfo _);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder baseType);
ConstructorBuilder constructor = baseType.DefineConstructor(attributes, CallingConventions.HasThis, new[] { typeof(int) });
constructor.GetILGenerator().Emit(OpCodes.Ret);

Expand All @@ -169,7 +172,7 @@ public void DefineDefaultConstructor_ParentPrivateDefaultConstructor_ThrowsNotSu
[Fact]
public void GetConstructor_DeclaringTypeOfConstructorGenericTypeDefinition()
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
type.DefineGenericParameters("T");

ConstructorBuilder ctor = type.DefineDefaultConstructor(MethodAttributes.PrivateScope | MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName);
Expand All @@ -180,7 +183,7 @@ public void GetConstructor_DeclaringTypeOfConstructorGenericTypeDefinition()
[Fact]
public void TypeBuilder_GetConstructorWorks()
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);
type.DefineGenericParameters("T");

ConstructorBuilder ctor = type.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName);
Expand All @@ -194,7 +197,7 @@ public void TypeBuilder_GetConstructorWorks()
[Fact]
public void GetConstructor_DeclaringTypeOfConstructorNotGenericTypeDefinitionOfType_ThrowsArgumentException()
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type1, out MethodInfo _);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type1);
type1.DefineGenericParameters("T");

TypeBuilder type2 = ((ModuleBuilder)type1.Module).DefineType("TestType2", TypeAttributes.Class | TypeAttributes.Public);
Expand All @@ -210,7 +213,7 @@ public void GetConstructor_DeclaringTypeOfConstructorNotGenericTypeDefinitionOfT
[Fact]
public void GetConstructor_TypeNotGeneric_ThrowsArgumentException()
{
AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo _);
AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder type);

ConstructorBuilder ctor = type.DefineDefaultConstructor(MethodAttributes.Public);

Expand Down
Loading
Loading