Skip to content

Commit

Permalink
Merge pull request #401 from Washi1337/issue/generic-attribute-ctors
Browse files Browse the repository at this point in the history
Support Generic Custom Attributes
  • Loading branch information
Washi1337 authored Jan 11, 2023
2 parents 39cec98 + d1d53e7 commit 63ee031
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 29 deletions.
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

0 comments on commit 63ee031

Please sign in to comment.