diff --git a/src/AsmResolver.DotNet/Collections/Parameter.cs b/src/AsmResolver.DotNet/Collections/Parameter.cs index feca2527a..ce23969a0 100644 --- a/src/AsmResolver.DotNet/Collections/Parameter.cs +++ b/src/AsmResolver.DotNet/Collections/Parameter.cs @@ -76,6 +76,17 @@ public TypeSignature ParameterType /// public string Name => Definition?.Name ?? GetDummyArgumentName(MethodSignatureIndex); + /// + /// Creates a or returns the existing corresponding to this parameter. + /// If a is created it is automatically added to the method definition. + /// + public ParameterDefinition GetOrCreateDefinition() + { + if (_parentCollection is null) + throw new InvalidOperationException("Cannot create a parameter definition for a parameter that has been removed from its parent collection."); + return _parentCollection.GetOrCreateParameterDefinition(this); + } + [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] private static string GetDummyArgumentName(int index) { diff --git a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs index a2e8b4c5d..1692a8db3 100644 --- a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs +++ b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs @@ -127,11 +127,24 @@ private void UpdateParameterTypes() private TypeSignature? GetThisParameterType() { - if (_owner.DeclaringType is null) + var declaringType = _owner.DeclaringType; + if (declaringType is null) return null; - var result = _owner.DeclaringType.ToTypeSignature(); - if (_owner.DeclaringType.IsValueType) + TypeSignature result; + if (declaringType.GenericParameters.Count > 0) + { + var genArgs = new TypeSignature[declaringType.GenericParameters.Count]; + for (int i = 0; i < genArgs.Length; i++) + genArgs[i] = new GenericParameterSignature(_owner.Module, GenericParameterType.Type, i); + result = declaringType.MakeGenericInstanceType(genArgs); + } + else + { + result = declaringType.ToTypeSignature(); + } + + if (declaringType.IsValueType) result = result.MakeByReferenceType(); return result; @@ -142,6 +155,18 @@ private void UpdateParameterTypes() return _owner.ParameterDefinitions.FirstOrDefault(p => p.Sequence == sequence); } + internal ParameterDefinition GetOrCreateParameterDefinition(Parameter parameter) + { + if (parameter == ThisParameter) + throw new InvalidOperationException("Cannot retrieve a parameter definition for the virtual this parameter."); + if (parameter.Definition is not null) + return parameter.Definition; + + var parameterDefinition = new ParameterDefinition(parameter.Sequence, Utf8String.Empty, 0); + _owner.ParameterDefinitions.Add(parameterDefinition); + return parameterDefinition; + } + internal void PushParameterUpdateToSignature(Parameter parameter) { if (_owner.Signature is null) diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs index ecb5f4c81..08c7b1703 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs @@ -175,7 +175,7 @@ protected void WriteParametersAndReturnType(BlobSerializationContext context) public int GetTotalParameterCount() { int count = ParameterTypes.Count + SentinelParameterTypes.Count; - if (HasThis || ExplicitThis) + if (HasThis && !ExplicitThis) count++; return count; } diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index f29083c29..0c4b8f333 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -26,7 +26,7 @@ public GenericParameterSignature(GenericParameterType parameterType, int index) /// The module in which this generic parameter signature resides. /// Indicates the parameter signature is declared by a type or a method. /// The index of the referenced parameter. - public GenericParameterSignature(ModuleDefinition module, GenericParameterType parameterType, int index) + public GenericParameterSignature(ModuleDefinition? module, GenericParameterType parameterType, int index) { Scope = module; ParameterType = parameterType; diff --git a/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs b/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs index 847c046a1..8dda148a2 100644 --- a/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs +++ b/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Collections @@ -24,6 +25,13 @@ private static MethodDefinition ObtainInstanceTestMethod(string name) return type.Methods.First(m => m.Name == name); } + private static MethodDefinition ObtainGenericInstanceTestMethod(string name) + { + var module = ModuleDefinition.FromFile(typeof(GenericInstanceMethods<,>).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == "GenericInstanceMethods`2"); + return type.Methods.First(m => m.Name == name); + } + [Fact] public void ReadEmptyParametersFromStaticMethod() { @@ -111,6 +119,23 @@ public void ReadMultipleParametersFromInstanceMethod() Assert.Equal(nameof(InstanceMethods), method.Parameters.ThisParameter.ParameterType.Name); } + [Fact] + public void ReadEmptyParametersFromGenericInstanceMethod() + { + var method = ObtainGenericInstanceTestMethod(nameof(GenericInstanceMethods.InstanceParameterlessMethod)); + Assert.Empty(method.Parameters); + Assert.NotNull(method.Parameters.ThisParameter); + var genericInstanceType = Assert.IsAssignableFrom(method.Parameters.ThisParameter.ParameterType); + Assert.Equal("GenericInstanceMethods`2", genericInstanceType.GenericType.Name); + Assert.Equal(2, genericInstanceType.TypeArguments.Count); + Assert.All(genericInstanceType.TypeArguments, (typeArgument, i) => + { + var genericParameterSignature = Assert.IsAssignableFrom(typeArgument); + Assert.Equal(GenericParameterType.Type, genericParameterSignature.ParameterType); + Assert.Equal(i, genericParameterSignature.Index); + }); + } + [Fact] public void ReadReturnTypeFromStaticParameterlessMethod() { @@ -190,5 +215,48 @@ public void UnnamedParameterShouldResultInDummyName() param.Name = null; Assert.All(method.Parameters, p => Assert.Equal(p.Name, $"A_{p.MethodSignatureIndex}")); } + + [Fact] + public void GetOrCreateDefinitionShouldCreateNewDefinition() + { + var dummyModule = new ModuleDefinition("TestModule"); + var corLibTypesFactory = dummyModule.CorLibTypeFactory; + var method = new MethodDefinition("TestMethodNoParameterDefinitions", + MethodAttributes.Public | MethodAttributes.Static, + MethodSignature.CreateStatic(corLibTypesFactory.Void, corLibTypesFactory.Int32)); + + var param = Assert.Single(method.Parameters); + + Assert.Null(param.Definition); + var definition = param.GetOrCreateDefinition(); + + Assert.Equal(param.Sequence, definition.Sequence); + Assert.Equal(Utf8String.Empty, definition.Name); + Assert.Equal((ParameterAttributes)0, definition.Attributes); + Assert.Contains(definition, method.ParameterDefinitions); + Assert.Same(definition, param.Definition); + } + + [Fact] + public void GetOrCreateDefinitionShouldReturnExistingDefinition() + { + var method = ObtainStaticTestMethod(nameof(MultipleMethods.SingleParameterMethod)); + + var param = Assert.Single(method.Parameters); + + var existingDefinition = param.Definition; + Assert.NotNull(existingDefinition); + var definition = param.GetOrCreateDefinition(); + Assert.Same(existingDefinition, definition); + } + + [Fact] + public void GetOrCreateDefinitionThrowsOnVirtualThisParameter() + { + var method = ObtainInstanceTestMethod(nameof(InstanceMethods.InstanceParameterlessMethod)); + + Assert.NotNull(method.Parameters.ThisParameter); + Assert.Throws(() => method.Parameters.ThisParameter.GetOrCreateDefinition()); + } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs new file mode 100644 index 000000000..d48a2bb4f --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Methods +{ + public class GenericInstanceMethods + { + public void InstanceParameterlessMethod() + { + } + } +}