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()
+ {
+ }
+ }
+}