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

Support Generic Custom Attributes #401

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ public static CustomAttributeSignature FromReader(
ICustomAttributeType ctor,
in BinaryStreamReader reader)
{
var genericContext = GenericContext.FromMethod(ctor);
var argumentTypes = ctor.Signature?.ParameterTypes ?? Array.Empty<TypeSignature>();
return new SerializedCustomAttributeSignature(context, argumentTypes, reader);
return new SerializedCustomAttributeSignature(context, argumentTypes, genericContext, reader);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ public static SecurityAttribute FromReader(in BlobReaderContext context, ref Bin
}

for (int i = 0; i < namedArgumentCount; i++)
{
var argument = CustomAttributeNamedArgument.FromReader(context, ref reader);
result.NamedArguments.Add(argument);
}
result.NamedArguments.Add(CustomAttributeNamedArgument.FromReader(context, ref reader));

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@ namespace AsmResolver.DotNet.Signatures
/// </summary>
public class SerializedCustomAttributeSignature : CustomAttributeSignature
{
private readonly BlobReaderContext _context;
private readonly BlobReaderContext _readerContext;
private readonly GenericContext _genericContext;
private readonly TypeSignature[] _fixedArgTypes;
private readonly BinaryStreamReader _reader;

/// <summary>
/// Initializes a new lazy custom attribute signature from an input blob stream reader.
/// </summary>
/// <param name="context">The blob reading context the signature is situated in.</param>
/// <param name="readerContext">The blob reading context the signature is situated in.</param>
/// <param name="fixedArgTypes">The types of all fixed arguments.</param>
/// <param name="genericContext">The generic context the arguments live in.</param>
/// <param name="reader">The input blob reader.</param>
public SerializedCustomAttributeSignature(
in BlobReaderContext context,
public SerializedCustomAttributeSignature(in BlobReaderContext readerContext,
IEnumerable<TypeSignature> fixedArgTypes,
in GenericContext genericContext,
in BinaryStreamReader reader)
{
_context = context;
_readerContext = readerContext;
_genericContext = genericContext;
_fixedArgTypes = fixedArgTypes.ToArray();
_reader = reader;
}
Expand All @@ -40,16 +43,19 @@ protected override void Initialize(
// Verify magic header.
ushort prologue = reader.ReadUInt16();
if (prologue != CustomAttributeSignaturePrologue)
_context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature.");
_readerContext.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature.");

// Read fixed arguments.
for (int i = 0; i < _fixedArgTypes.Length; i++)
fixedArguments.Add(CustomAttributeArgument.FromReader(_context, _fixedArgTypes[i], ref reader));
{
var instantiatedType = _fixedArgTypes[i].InstantiateGenericTypes(_genericContext);
fixedArguments.Add(CustomAttributeArgument.FromReader(_readerContext, instantiatedType, ref reader));
}

// Read named arguments.
ushort namedArgumentCount = reader.ReadUInt16();
for (int i = 0; i < namedArgumentCount; i++)
namedArguments.Add(CustomAttributeNamedArgument.FromReader(_context, ref reader));
namedArguments.Add(CustomAttributeNamedArgument.FromReader(_readerContext, ref reader));
}

/// <inheritdoc />
Expand Down
214 changes: 198 additions & 16 deletions test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,22 @@ public void ReadParent()
Assert.Equal(parentToken, attribute.Parent.MetadataToken);
}

private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false, bool access = false)
private static CustomAttribute GetCustomAttributeTestCase(
string methodName,
bool rebuild = false,
bool access = false,
bool generic = false)
{
var module = ModuleDefinition.FromFile(typeof(CustomAttributesTestClass).Assembly.Location);
var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass));
var method = type.Methods.First(m => m.Name == methodName);

string attributeName = nameof(TestCaseAttribute);
if (generic)
attributeName += "`1";

var attribute = method.CustomAttributes
.First(c => c.Constructor!.DeclaringType!.Name == nameof(TestCaseAttribute));
.First(c => c.Constructor!.DeclaringType!.Name.Value.StartsWith(attributeName));

if (access)
{
Expand Down Expand Up @@ -286,8 +295,8 @@ public void GenericTypeArgument(bool rebuild, bool access)
var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken);
var expected = new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object);

Assert.IsAssignableFrom<TypeSignature>(argument.Element);
Assert.Equal(expected, (TypeSignature) argument.Element, _comparer);
var element = Assert.IsAssignableFrom<TypeSignature>(argument.Element);
Assert.Equal(expected, element, _comparer);
}

[Theory]
Expand All @@ -307,8 +316,8 @@ public void ArrayGenericTypeArgument(bool rebuild, bool access)
new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object)
);

Assert.IsAssignableFrom<TypeSignature>(argument.Element);
Assert.Equal(expected, (TypeSignature) argument.Element, _comparer);
var element = Assert.IsAssignableFrom<TypeSignature>(argument.Element);
Assert.Equal(expected, element, _comparer);
}

[Theory]
Expand All @@ -322,8 +331,8 @@ public void IntPassedOnAsObject(bool rebuild, bool access)
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject),rebuild, access);
var argument = attribute.Signature!.FixedArguments[0];

Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(123, ((BoxedArgument) argument.Element).Value);
var element = Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(123, element.Value);
}

[Theory]
Expand All @@ -338,8 +347,8 @@ public void TypePassedOnAsObject(bool rebuild, bool access)
var argument = attribute.Signature!.FixedArguments[0];

var module = attribute.Constructor!.Module!;
Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(module.CorLibTypeFactory.Int32, (ITypeDescriptor) ((BoxedArgument) argument.Element).Value, _comparer);
var element = Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(module.CorLibTypeFactory.Int32, (ITypeDescriptor) element.Value, _comparer);
}

[Theory]
Expand Down Expand Up @@ -391,8 +400,7 @@ public void FixedInt32ArrayNullAsObject(bool rebuild, bool access)
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument),rebuild, access);
var argument = attribute.Signature!.FixedArguments[0];

Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
var boxedArgument = (BoxedArgument) argument.Element;
var boxedArgument = Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Null(boxedArgument.Value);
}

Expand All @@ -405,8 +413,7 @@ public void FixedInt32EmptyArrayAsObject(bool rebuild, bool access)
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument),rebuild, access);
var argument = attribute.Signature!.FixedArguments[0];

Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
var boxedArgument = (BoxedArgument) argument.Element;
var boxedArgument =Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(Array.Empty<object>(), boxedArgument.Value);
}

Expand All @@ -419,8 +426,7 @@ public void FixedInt32ArrayAsObject(bool rebuild, bool access)
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument),rebuild, access);
var argument = attribute.Signature!.FixedArguments[0];

Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
var boxedArgument = (BoxedArgument) argument.Element;
var boxedArgument = Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(new[]
{
1, 2, 3, 4
Expand Down Expand Up @@ -464,5 +470,181 @@ public void CreateNewWithFixedArgumentsViaProperty()
var argument = Assert.Single(attribute.Signature.FixedArguments);
Assert.Equal("My Message", argument.Element);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericInt32Argument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericInt32Argument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

int value = Assert.IsAssignableFrom<int>(argument.Element);
Assert.Equal(1, value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericStringArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericStringArgument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

string value = Assert.IsAssignableFrom<Utf8String>(argument.Element);
Assert.Equal("Fixed string generic argument", value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericInt32ArrayArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericInt32ArrayArgument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

Assert.Equal(new int[] {1, 2, 3, 4}, argument.Elements.Cast<int>());
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericInt32ArrayAsObjectArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericInt32ArrayAsObjectArgument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

var boxedArgument = Assert.IsAssignableFrom<BoxedArgument>(argument.Element);
Assert.Equal(new[]
{
1, 2, 3, 4
}, boxedArgument.Value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericTypeArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericTypeArgument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

var expected = attribute.Constructor!.Module!.CorLibTypeFactory.Int32;
var element = Assert.IsAssignableFrom<TypeSignature>(argument.Element);
Assert.Equal(expected, element, _comparer);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void FixedGenericTypeNullArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedGenericTypeNullArgument),
rebuild, access, true);
var argument = attribute.Signature!.FixedArguments[0];

Assert.Null(argument.Element);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericInt32Argument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericInt32Argument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

Assert.Equal("Value", argument.MemberName);
int value = Assert.IsAssignableFrom<int>(argument.Argument.Element);
Assert.Equal(1, value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericStringArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericStringArgument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

Assert.Equal("Value", argument.MemberName);
string value = Assert.IsAssignableFrom<Utf8String>(argument.Argument.Element);
Assert.Equal("Named string generic argument", value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericInt32ArrayArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericInt32ArrayArgument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

Assert.Equal("Value", argument.MemberName);
Assert.Equal(new int[] {1,2,3,4}, argument.Argument.Elements.Cast<int>());
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericInt32ArrayAsObjectArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericInt32ArrayAsObjectArgument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

Assert.Equal("Value", argument.MemberName);
var boxedArgument = Assert.IsAssignableFrom<BoxedArgument>(argument.Argument.Element);
Assert.Equal(new[]
{
1, 2, 3, 4
}, boxedArgument.Value);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericTypeArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericTypeArgument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

var expected = attribute.Constructor!.Module!.CorLibTypeFactory.Int32;
var element = Assert.IsAssignableFrom<TypeSignature>(argument.Argument.Element);
Assert.Equal(expected, element, _comparer);
}

[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NamedGenericTypeNullArgument(bool rebuild, bool access)
{
var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedGenericTypeNullArgument),
rebuild, access, true);
var argument = attribute.Signature!.NamedArguments[0];

Assert.Null(argument.Argument.Element);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>11</LangVersion>
</PropertyGroup>

</Project>
Loading