Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public void PersistedAssemblyBuilder_SaveValidations()
Assert.Throws<ArgumentNullException>("assemblyFileName", () => ab.Save(assemblyFileName: null));
Assert.Throws<ArgumentNullException>("stream", () => ab.Save(stream: null));
Assert.Throws<InvalidOperationException>(() => ab.Save(assemblyFileName: "File")); // no module defined

PersistedAssemblyBuilder afterGenerateMetadata = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder generatedTypeBuilder);
generatedTypeBuilder.CreateType();
afterGenerateMetadata.GenerateMetadata(out BlobBuilder _, out BlobBuilder _);
Assert.Throws<InvalidOperationException>(() => afterGenerateMetadata.Save(new MemoryStream()));
}

[Fact]
Expand All @@ -58,6 +63,45 @@ public void PersistedAssemblyBuilder_GenerateMetadataValidation()
Assert.NotNull(ilStream);
Assert.NotNull(mappedFieldData);
Assert.Throws<InvalidOperationException>(() => ab.GenerateMetadata(out var _, out var _)); // cannot re-generate metadata

PersistedAssemblyBuilder afterSave = AssemblySaveTools.PopulateAssemblyBuilderAndTypeBuilder(out TypeBuilder typeBuilder);
typeBuilder.CreateType();
afterSave.Save(new MemoryStream());
Assert.Throws<InvalidOperationException>(() => afterSave.GenerateMetadata(out BlobBuilder _, out BlobBuilder _));
}

[Fact]
public void PersistedAssemblyBuilder_AssemblyIdentityRoundTrip()
{
AssemblyName assemblyName = new AssemblyName("MyIdentityAssembly")
{
Flags = AssemblyNameFlags.PublicKey | AssemblyNameFlags.Retargetable,
Version = new Version(9, 8, 7, 6),
CultureInfo = new CultureInfo("tr-TR")
};

byte[] expectedPublicKey = typeof(string).Assembly.GetName().GetPublicKey();
Assert.NotEmpty(expectedPublicKey);
assemblyName.SetPublicKey(expectedPublicKey);

PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilder(assemblyName);
TypeBuilder typeBuilder = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.CreateType();
using MemoryStream stream = new MemoryStream();
ab.Save(stream);

stream.Position = 0;
using PEReader peReader = new PEReader(stream);
MetadataReader metadataReader = peReader.GetMetadataReader();
AssemblyDefinition assemblyDefinition = metadataReader.GetAssemblyDefinition();
AssemblyName loadedAssemblyName = assemblyDefinition.GetAssemblyNameInfo().ToAssemblyName();

Assert.Equal(assemblyName.Name, loadedAssemblyName.Name);
Assert.Equal(assemblyName.Version, loadedAssemblyName.Version);
Assert.Equal(assemblyName.CultureName, loadedAssemblyName.CultureName);
Assert.Equal(assemblyName.ContentType, loadedAssemblyName.ContentType);
Assert.Equal(assemblyName.Flags, loadedAssemblyName.Flags);
Assert.Equal(expectedPublicKey, loadedAssemblyName.GetPublicKey());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,42 @@ public void EnumBuilderSetCustomAttributesTest()
}
}

[Fact]
public void NamedArgumentsOnlyEnumArrayCustomAttributeRoundTrips()
{
using (TempFile file = TempFile.Create())
{
PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilder(new AssemblyName("NamedArgumentsAttributeAssembly"));
ModuleBuilder module = ab.DefineDynamicModule("Module");
TypeBuilder typeBuilder = module.DefineType("AttributedType", TypeAttributes.Public | TypeAttributes.Class);

ConstructorInfo ctor = typeof(NamedArgumentsOnlyAttribute).GetConstructor(Type.EmptyTypes);
PropertyInfo modesProperty = typeof(NamedArgumentsOnlyAttribute).GetProperty(nameof(NamedArgumentsOnlyAttribute.Modes));
PropertyInfo primaryModeProperty = typeof(NamedArgumentsOnlyAttribute).GetProperty(nameof(NamedArgumentsOnlyAttribute.PrimaryMode));
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(
ctor,
Array.Empty<object>(),
new[] { modesProperty, primaryModeProperty },
new object[] { new[] { AttributeEdgeMode.Second, AttributeEdgeMode.First }, AttributeEdgeMode.Second });

typeBuilder.SetCustomAttribute(attributeBuilder);
typeBuilder.CreateType();
ab.Save(file.Path);

using MetadataLoadContext mlc = new MetadataLoadContext(new CoreMetadataAssemblyResolver());
Type typeFromDisk = mlc.LoadFromAssemblyPath(file.Path).GetType("AttributedType");
CustomAttributeData attributeFromDisk = typeFromDisk.GetCustomAttributesData().Single(c => c.AttributeType.Name == nameof(NamedArgumentsOnlyAttribute));

CustomAttributeNamedArgument modesNamedArgument = attributeFromDisk.NamedArguments.Single(na => na.MemberName == nameof(NamedArgumentsOnlyAttribute.Modes));
IList<CustomAttributeTypedArgument> modes = (IList<CustomAttributeTypedArgument>)modesNamedArgument.TypedValue.Value;
Assert.Equal((int)AttributeEdgeMode.Second, (int)modes[0].Value);
Assert.Equal((int)AttributeEdgeMode.First, (int)modes[1].Value);

CustomAttributeNamedArgument primaryModeNamedArgument = attributeFromDisk.NamedArguments.Single(na => na.MemberName == nameof(NamedArgumentsOnlyAttribute.PrimaryMode));
Assert.Equal((int)AttributeEdgeMode.Second, (int)primaryModeNamedArgument.TypedValue.Value);
}
}

private void AssertEnumAttributes(string fullName, object value, CustomAttributeData testAttribute)
{
Assert.Equal(fullName, testAttribute.AttributeType.FullName);
Expand All @@ -522,4 +558,16 @@ public class BoolAttribute : Attribute
private bool _b;
public BoolAttribute(bool myBool) { _b = myBool; }
}

public enum AttributeEdgeMode
{
First,
Second
}

public class NamedArgumentsOnlyAttribute : Attribute
{
public AttributeEdgeMode[] Modes { get; set; } = [];
public AttributeEdgeMode PrimaryMode { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3066,5 +3066,47 @@ public void ReferenceGenericMembersInOtherGeneratedAssembly()
tlc.Unload();
}
}

[Fact]
public void ReferenceNestedGenericTypeWithConstraintAcrossGeneratedAssemblies()
{
using (TempFile file = TempFile.Create())
using (TempFile file2 = TempFile.Create())
{
PersistedAssemblyBuilder producerAssembly = new PersistedAssemblyBuilder(new AssemblyName("ProducerAssembly"), typeof(object).Assembly);
ModuleBuilder producerModule = producerAssembly.DefineDynamicModule("ProducerModule");
TypeBuilder hostType = producerModule.DefineType("HostType", TypeAttributes.Public | TypeAttributes.Class);
GenericTypeParameterBuilder hostGenericParameter = hostType.DefineGenericParameters("T")[0];
hostGenericParameter.SetGenericParameterAttributes(GenericParameterAttributes.ReferenceTypeConstraint);
hostType.DefineDefaultConstructor(MethodAttributes.Public);
hostType.CreateType();
producerAssembly.Save(file.Path);

PersistedAssemblyBuilder consumerAssembly = new PersistedAssemblyBuilder(new AssemblyName("ConsumerAssembly"), typeof(object).Assembly);
ModuleBuilder consumerModule = consumerAssembly.DefineDynamicModule("ConsumerModule");
TypeBuilder consumerType = consumerModule.DefineType("ConsumerType", TypeAttributes.Public | TypeAttributes.Class);

Type constructedHostType = hostType.MakeGenericType(typeof(string));
Type nestedGenericReturnType = typeof(List<>).MakeGenericType(constructedHostType);
MethodBuilder factoryMethod = consumerType.DefineMethod("CreateList", MethodAttributes.Public | MethodAttributes.Static, nestedGenericReturnType, Type.EmptyTypes);
ILGenerator il = factoryMethod.GetILGenerator();
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ret);
consumerType.CreateType();
consumerAssembly.Save(file2.Path);

TestAssemblyLoadContext tlc = new TestAssemblyLoadContext();
tlc.LoadFromAssemblyPath(file.Path);
Type consumerTypeFromDisk = tlc.LoadFromAssemblyPath(file2.Path).GetType("ConsumerType");
MethodInfo createListMethod = consumerTypeFromDisk.GetMethod("CreateList");
Type listElementType = createListMethod.ReturnType.GetGenericArguments()[0];

Assert.True(listElementType.IsGenericType);
Assert.Equal("HostType", listElementType.GetGenericTypeDefinition().Name);
Assert.Equal(typeof(string).FullName, listElementType.GetGenericArguments()[0].FullName);
Assert.Equal("ProducerAssembly", listElementType.Assembly.GetName().Name);
tlc.Unload();
}
}
}
}
Loading