diff --git a/src/AsmResolver.DotNet/CustomAttribute.cs b/src/AsmResolver.DotNet/CustomAttribute.cs index d3d8023f0..ddfc5d11e 100644 --- a/src/AsmResolver.DotNet/CustomAttribute.cs +++ b/src/AsmResolver.DotNet/CustomAttribute.cs @@ -25,6 +25,17 @@ protected CustomAttribute(MetadataToken token) _signature = new LazyVariable(GetSignature); } + /// + /// Creates a new custom attribute. + /// + /// The constructor of the attribute to call. + public CustomAttribute(ICustomAttributeType? constructor) + : this(new MetadataToken(TableIndex.CustomAttribute, 0)) + { + Constructor = constructor; + Signature = new CustomAttributeSignature(); + } + /// /// Creates a new custom attribute. /// diff --git a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs index 2e903be4c..193f8526a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs @@ -65,10 +65,7 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke $"Invalid signature blob index in custom attribute {MetadataToken}."); } - return CustomAttributeSignature.FromReader( - new BlobReadContext(_context), - Constructor, - ref reader); + return CustomAttributeSignature.FromReader(new BlobReadContext(_context), Constructor, reader); } } } diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 15d4834fb..3cb564c0c 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; @@ -12,55 +13,26 @@ namespace AsmResolver.DotNet.Signatures /// public class CustomAttributeSignature : ExtendableBlobSignature { - private readonly List _fixedArguments; - private readonly List _namedArguments; - private const ushort CustomAttributeSignaturePrologue = 0x0001; - /// - /// Reads a single custom attribute signature from the input stream. + /// The header value of every custom attribute signature. /// - /// The blob reader context. - /// The constructor that was called. - /// The input stream. - /// The signature. - /// Occurs when the input stream does not point to a valid signature. - public static CustomAttributeSignature? FromReader(in BlobReadContext context, ICustomAttributeType ctor, ref BinaryStreamReader reader) - { - ushort prologue = reader.ReadUInt16(); - if (prologue != CustomAttributeSignaturePrologue) - { - context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature."); - return null; - } - - var result = new CustomAttributeSignature(); - - // Read fixed arguments. - var parameterTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); - result._fixedArguments.Capacity = parameterTypes.Count; - for (int i = 0; i < parameterTypes.Count; i++) - { - var argument = CustomAttributeArgument.FromReader(context, parameterTypes[i], ref reader); - result._fixedArguments.Add(argument); - } - - // Read named arguments. - ushort namedArgumentCount = reader.ReadUInt16(); - result._namedArguments.Capacity = namedArgumentCount; - for (int i = 0; i < namedArgumentCount; i++) - { - var argument = CustomAttributeNamedArgument.FromReader(context, ref reader); - result._namedArguments.Add(argument); - } + protected const ushort CustomAttributeSignaturePrologue = 0x0001; - return result; - } + private List? _fixedArguments; + private List? _namedArguments; /// /// Creates a new empty custom attribute signature. /// public CustomAttributeSignature() - : this(Enumerable.Empty(), Enumerable.Empty()) + { + } + + /// + /// Creates a new custom attribute signature with the provided fixed arguments. + /// + public CustomAttributeSignature(params CustomAttributeArgument[] fixedArguments) + : this(fixedArguments, Enumerable.Empty()) { } @@ -84,12 +56,86 @@ public CustomAttributeSignature(IEnumerable fixedArgume /// /// Gets a collection of fixed arguments that are passed onto the constructor of the attribute. /// - public IList FixedArguments => _fixedArguments; + public IList FixedArguments + { + get + { + EnsureIsInitialized(); + return _fixedArguments; + } + } /// /// Gets a collection of values that are assigned to fields and/or members of the attribute class. /// - public IList NamedArguments => _namedArguments; + public IList NamedArguments + { + get + { + EnsureIsInitialized(); + return _namedArguments; + } + } + + /// + /// Reads a single custom attribute signature from the input stream. + /// + /// The blob reader context. + /// The constructor that was called. + /// The input stream. + /// The signature. + /// Occurs when the input stream does not point to a valid signature. + public static CustomAttributeSignature FromReader( + in BlobReadContext context, + ICustomAttributeType ctor, + in BinaryStreamReader reader) + { + var argumentTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); + return new SerializedCustomAttributeSignature(context, argumentTypes, reader); + } + + /// + /// Gets a value indicating whether the and collections + /// are initialized or not. + /// + [MemberNotNullWhen(true, nameof(_fixedArguments))] + [MemberNotNullWhen(true, nameof(_namedArguments))] + protected bool IsInitialized => _fixedArguments is not null && _namedArguments is not null; + + /// + /// Ensures that the and are initialized. + /// + [MemberNotNull(nameof(_fixedArguments))] + [MemberNotNull(nameof(_namedArguments))] + protected void EnsureIsInitialized() + { + if (IsInitialized) + return; + + var fixedArguments = new List(); + var namedArguments = new List(); + + Initialize(fixedArguments, namedArguments); + + lock (this) + { + if (IsInitialized) + return; + _fixedArguments = fixedArguments; + _namedArguments = namedArguments; + } + } + + /// + /// Initializes the argument collections of the signature. + /// + /// The collection that will receive the fixed arguments. + /// The collection that will receive the named arguments. + protected virtual void Initialize( + IList fixedArguments, + IList namedArguments) + { + } /// public override string ToString() @@ -110,4 +156,5 @@ protected override void WriteContents(BlobSerializationContext context) NamedArguments[i].Write(context); } } + } diff --git a/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs new file mode 100644 index 000000000..e1a6bc9de --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Collections.Generic; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; + +namespace AsmResolver.DotNet.Signatures +{ + /// + /// Provides a lazy initialized implementation of the class. + /// + public class SerializedCustomAttributeSignature : CustomAttributeSignature + { + private readonly BlobReadContext _context; + private readonly TypeSignature[] _fixedArgTypes; + private readonly BinaryStreamReader _reader; + + /// + /// Initializes a new lazy custom attribute signature from an input blob stream reader. + /// + /// The blob reading context the signature is situated in. + /// The types of all fixed arguments. + /// The input blob reader. + public SerializedCustomAttributeSignature( + in BlobReadContext context, + IEnumerable fixedArgTypes, + in BinaryStreamReader reader) + { + _context = context; + _fixedArgTypes = fixedArgTypes.ToArray(); + _reader = reader; + } + + /// + protected override void Initialize( + IList fixedArguments, + IList namedArguments) + { + var reader = _reader.Fork(); + + // Verify magic header. + ushort prologue = reader.ReadUInt16(); + if (prologue != CustomAttributeSignaturePrologue) + _context.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)); + + // Read named arguments. + ushort namedArgumentCount = reader.ReadUInt16(); + for (int i = 0; i < namedArgumentCount; i++) + namedArguments.Add(CustomAttributeNamedArgument.FromReader(_context, ref reader)); + } + + /// + protected override void WriteContents(BlobSerializationContext context) + { + // If the arguments are not initialized yet, it means nobody accessed the fixed or named arguments of the + // signature. In such a case, we can safely assume nothing has changed to the signature. + // + // Since custom attribute signatures reference types by their fully qualified name, they do not contain + // any metadata tokens or type indices. Thus, regardless of whether the assembly changed or not, we can + // always just use the raw blob signature without risking breaking any references into the assembly. + // This can save a lot of processing time, given the fact that many custom attribute arguments are Enum + // typed arguments, which would require an expensive type resolution to determine the size of an element. + + if (IsInitialized) + base.WriteContents(context); + else + _reader.Fork().WriteToOutput(context.Writer); + } + } +} diff --git a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs index 206b2cb80..cd4924fe3 100644 --- a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs +++ b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.Linq; using AsmResolver.DotNet; +using AsmResolver.IO; using BenchmarkDotNet.Attributes; using static AsmResolver.Benchmarks.Properties.Resources; @@ -12,15 +14,22 @@ public class ModuleReadWriteBenchmark private static readonly byte[] HelloWorldApp = HelloWorld; private static readonly byte[] CrackMeApp = Test; private static readonly byte[] ManyMethods = Utilities.DecompressDeflate(HelloWorld_ManyMethods); - private static readonly byte[] CoreLib; + private static readonly IInputFile SystemPrivateCoreLib; + private static readonly IInputFile SystemRuntime; + private static readonly IInputFile SystemPrivateXml; private readonly MemoryStream _outputStream = new(); static ModuleReadWriteBenchmark() { - var resolver = new DotNetCoreAssemblyResolver(new Version(3, 1, 0)); - string path = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v4_0_0_0)!.ManifestModule!.FilePath; - CoreLib = File.ReadAllBytes(path); + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + + var fs = new ByteArrayFileService(); + SystemPrivateCoreLib = fs.OpenFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + SystemRuntime = fs.OpenFile(Path.Combine(runtimePath, "System.Runtime.dll")); + SystemPrivateXml = fs.OpenFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); } [Benchmark] @@ -63,9 +72,23 @@ public void ManyMethods_ReadWrite() } [Benchmark] - public void CoreLib_ReadWrite() + public void SystemPrivateCoreLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemPrivateCoreLib); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemRuntimeLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemRuntime); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemPrivateXml_ReadWrite() { - var module = ModuleDefinition.FromBytes(CoreLib); + var module = ModuleDefinition.FromFile(SystemPrivateXml); module.Write(_outputStream); } } diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index 3c7da128b..1e65acde3 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -1,15 +1,12 @@ using System; using System.IO; using System.Linq; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.CustomAttributes; using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.IO; using AsmResolver.PE; -using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -18,7 +15,7 @@ namespace AsmResolver.DotNet.Tests { public class CustomAttributeTest { - private readonly SignatureComparer _comparer = new SignatureComparer(); + private readonly SignatureComparer _comparer = new(); [Fact] public void ReadConstructor() @@ -27,7 +24,7 @@ public void ReadConstructor() var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -41,7 +38,7 @@ public void PersistentConstructor() var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -51,7 +48,7 @@ public void ReadParent() string filePath = typeof(CustomAttributesTestClass).Assembly.Location; var image = PEImage.FromFile(filePath); - var tablesStream = image.DotNetDirectory.Metadata.GetStream(); + var tablesStream = image.DotNetDirectory!.Metadata!.GetStream(); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute); var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute); @@ -73,13 +70,19 @@ public void ReadParent() Assert.Equal(parentToken, attribute.Parent.MetadataToken); } - private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false) + private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false, bool access = 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); var attribute = method.CustomAttributes - .First(c => c.Constructor.DeclaringType.Name == nameof(TestCaseAttribute)); + .First(c => c.Constructor!.DeclaringType!.Name == nameof(TestCaseAttribute)); + + if (access) + { + _ = attribute.Signature!.FixedArguments; + _ = attribute.Signature.NamedArguments; + } if (rebuild) attribute = RebuildAndLookup(attribute); @@ -89,24 +92,28 @@ private static CustomAttribute GetCustomAttributeTestCase(string methodName, boo private static CustomAttribute RebuildAndLookup(CustomAttribute attribute) { var stream = new MemoryStream(); - var method = (MethodDefinition) attribute.Parent; - method.Module.Write(stream); + var method = (MethodDefinition) attribute.Parent!; + method.Module!.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); return newModule - .TopLevelTypes.First(t => t.FullName == method.DeclaringType.FullName) + .TopLevelTypes.First(t => t.FullName == method.DeclaringType!.FullName) .Methods.First(f => f.Name == method.Name) .CustomAttributes[0]; } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32Argument), rebuild); + var attribute = GetCustomAttributeTestCase( + nameof(CustomAttributesTestClass.FixedInt32Argument), + rebuild, + access); - Assert.Single(attribute.Signature.FixedArguments); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -114,12 +121,13 @@ public void FixedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -127,12 +135,13 @@ public void FixedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -140,41 +149,44 @@ public void FixedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedNullTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedNullTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument), rebuild); - var fixedArg = Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument),rebuild, access); + var fixedArg = Assert.Single(attribute.Signature!.FixedArguments); Assert.Null(fixedArg.Element); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; Assert.Equal( - attribute.Constructor.Module.CorLibTypeFactory.String, + attribute.Constructor!.Module!.CorLibTypeFactory.String, argument.Element as TypeSignature, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedComplexTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedComplexTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; - var factory = attribute.Constructor.Module.CorLibTypeFactory; + var factory = attribute.Constructor!.Module!.CorLibTypeFactory; var listRef = new TypeReference(factory.CorLibScope, "System.Collections.Generic", "KeyValuePair`2"); var instance = new GenericInstanceTypeSignature(listRef, false, @@ -185,12 +197,13 @@ public void FixedComplexTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -199,12 +212,13 @@ public void NamedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -213,12 +227,13 @@ public void NamedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -227,16 +242,17 @@ public void NamedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var expected = new TypeReference( - attribute.Constructor.Module.CorLibTypeFactory.CorLibScope, + attribute.Constructor!.Module!.CorLibTypeFactory.CorLibScope, "System", "Int32"); var argument = attribute.Signature.NamedArguments[0]; @@ -250,22 +266,23 @@ public void IsCompilerGeneratedMember() var module = ModuleDefinition.FromFile(typeof(SingleProperty).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(SingleProperty)); var property = type.Properties.First(); - var setMethod = property.SetMethod; + var setMethod = property.SetMethod!; Assert.True(setMethod.IsCompilerGenerated()); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void GenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void GenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object); @@ -274,16 +291,17 @@ public void GenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void ArrayGenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void ArrayGenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new SzArrayTypeSignature( new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object) @@ -294,64 +312,69 @@ public void ArrayGenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void IntPassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void IntPassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); Assert.Equal(123, ((BoxedArgument) argument.Element).Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void TypePassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void TypePassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; Assert.IsAssignableFrom(argument.Element); Assert.Equal(module.CorLibTypeFactory.Int32, (ITypeDescriptor) ((BoxedArgument) argument.Element).Value, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32NullArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32NullArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.True(argument.IsNullArray); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.False(argument.IsNullArray); Assert.Empty(argument.Elements); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Array(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Array(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.Equal(new[] { @@ -360,12 +383,13 @@ public void FixedInt32Array(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayNullAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayNullAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; @@ -373,25 +397,27 @@ public void FixedInt32ArrayNullAsObject(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; - Assert.Equal(new object[0], boxedArgument.Value); + Assert.Equal(Array.Empty(), boxedArgument.Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; @@ -400,5 +426,43 @@ public void FixedInt32ArrayAsObject(bool rebuild) 1, 2, 3, 4 }, boxedArgument.Value); } + + [Fact] + public void CreateNewWithFixedArgumentsViaConstructor() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String)), + new CustomAttributeSignature(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message"))); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } + + [Fact] + public void CreateNewWithFixedArgumentsViaProperty() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String))); + attribute.Signature!.FixedArguments.Add(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message")); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } } } diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 62b350668..44f3c016f 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -359,5 +359,41 @@ public void NewModuleShouldContainSingleReferenceToCorLib() var reference = Assert.Single(module.AssemblyReferences); Assert.Equal(KnownCorLibs.NetStandard_v2_0_0_0, reference, Comparer); } + + [Fact] + public void RewriteSystemPrivateCoreLib() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemRuntime() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Runtime.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemPrivateXml() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } } }