From dae96ca0867680ac06b150af11cd6106aac3b0f9 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 3 Nov 2024 18:10:09 +0100 Subject: [PATCH] BUGFIX: Include and clone semantic method owners in ClonedMembers, add attached methods by default --- .../Cloning/MemberCloner.Semantics.cs | 22 ++++++----- .../Cloning/MemberCloner.cs | 33 +++++++++++++++- .../Cloning/MetadataClonerTest.cs | 39 +++++++++++++++++-- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 53140cfca..bc8564ed0 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -17,9 +17,10 @@ private void DeepCopyProperties(MemberCloneContext context) { declaringType.Properties.Add(clonedProperty); } - var clonedMember = clonedProperty; - _listeners.OnClonedMember(property, clonedMember); - _listeners.OnClonedProperty(property, clonedMember); + + context.ClonedMembers.Add(property, clonedProperty); + _listeners.OnClonedMember(property, clonedProperty); + _listeners.OnClonedProperty(property, clonedProperty); } } @@ -54,9 +55,10 @@ private void DeepCopyEvents(MemberCloneContext context) { declaringType.Events.Add(clonedEvent); } - var clonedMember = clonedEvent; - _listeners.OnClonedMember(@event, clonedMember); - _listeners.OnClonedEvent(@event, clonedMember); + + context.ClonedMembers.Add(@event, clonedEvent); + _listeners.OnClonedMember(@event, clonedEvent); + _listeners.OnClonedEvent(@event, clonedEvent); } } @@ -82,9 +84,11 @@ private static void CloneSemantics(MemberCloneContext context, IHasSemantics sem { foreach (var semantics in semanticsProvider.Semantics) { - clonedProvider.Semantics.Add(new MethodSemantics( - (MethodDefinition) context.ClonedMembers[semantics.Method!], - semantics.Attributes)); + if (context.ClonedMembers.TryGetValue(semantics.Method!, out var m) + && m is MethodDefinition semanticMethod) + { + clonedProvider.Semantics.Add(new MethodSemantics(semanticMethod, semantics.Attributes)); + } } } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 60f1d1227..7e238ba74 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -245,9 +245,19 @@ public MemberCloner Include(FieldDefinition field) /// /// The property to include. /// The metadata cloner that the property is added to. - public MemberCloner Include(PropertyDefinition property) + public MemberCloner Include(PropertyDefinition property) => Include(property, true); + + /// + /// Adds a single property to the list of members to clone. + /// + /// The property to include. + /// Indicates the attached semantic methods (getters and setters) should be included. + /// The metadata cloner that the property is added to. + public MemberCloner Include(PropertyDefinition property, bool recursive) { _propertiesToClone.Add(property); + if (recursive) + IncludeSemantics(property); return this; } @@ -256,12 +266,31 @@ public MemberCloner Include(PropertyDefinition property) /// /// The event to include. /// The metadata cloner that the event is added to. - public MemberCloner Include(EventDefinition @event) + public MemberCloner Include(EventDefinition @event) => Include(@event, true); + + /// + /// Adds a single event to the list of members to clone. + /// + /// The event to include. + /// Indicates the attached semantic methods (add, remove, fire) should be included. + /// The metadata cloner that the property is added to. + public MemberCloner Include(EventDefinition @event, bool recursive) { _eventsToClone.Add(@event); + if (recursive) + IncludeSemantics(@event); return this; } + private void IncludeSemantics(IHasSemantics member) + { + foreach (var semantic in member.Semantics) + { + if (semantic.Method is not null) + _methodsToClone.Add(semantic.Method); + } + } + /// /// Adds a member cloner listener to the cloner. /// diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 8e0da015f..9585779ee 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -9,6 +9,7 @@ using AsmResolver.DotNet.TestCases.Fields; using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.Methods; +using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.DotNet.TestCases.Types; using AsmResolver.PE.DotNet.Cil; using AsmResolver.Tests.Listeners; @@ -38,7 +39,7 @@ private static ModuleDefinition PrepareTempModule() private static TypeDefinition CloneType(Type type, out TypeDefinition originalTypeDef) { var sourceModule = ModuleDefinition.FromFile(type.Module.Assembly.Location, TestReaderParameters); - originalTypeDef= (TypeDefinition) sourceModule.LookupMember(type.MetadataToken); + originalTypeDef= sourceModule.LookupMember(type.MetadataToken); var targetModule = PrepareTempModule(); @@ -60,7 +61,7 @@ private static TypeDefinition CloneType(Type type, out TypeDefinition originalTy private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDefinition originalMethodDef) { var sourceModule = ModuleDefinition.FromFile(methodBase.Module.Assembly.Location, TestReaderParameters); - originalMethodDef = (MethodDefinition) sourceModule.LookupMember(methodBase.MetadataToken); + originalMethodDef = sourceModule.LookupMember(methodBase.MetadataToken); var targetModule = PrepareTempModule(); @@ -68,8 +69,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef .Include(originalMethodDef) .Clone(); - - var clonedMethod = (MethodDefinition) result.ClonedMembers.First(); + var clonedMethod = result.ClonedMembers.OfType().First(); Assert.True(result.ContainsClonedMember(originalMethodDef)); Assert.Equal(clonedMethod, result.GetClonedMember(originalMethodDef)); @@ -77,6 +77,25 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef return clonedMethod; } + private static PropertyDefinition CloneProperty(PropertyInfo property, out PropertyDefinition originalProperty) + { + var sourceModule = ModuleDefinition.FromFile(property.Module.Assembly.Location, TestReaderParameters); + originalProperty = sourceModule.LookupMember(property.MetadataToken); + + var targetModule = PrepareTempModule(); + + var result = new MemberCloner(targetModule) + .Include(originalProperty) + .Clone(); + + var clonedProperty = result.ClonedMembers.OfType().First(); + + Assert.True(result.ContainsClonedMember(originalProperty)); + Assert.Equal(clonedProperty, result.GetClonedMember(originalProperty)); + + return clonedProperty; + } + private static FieldDefinition CloneInitializerField(FieldInfo field, out FieldDefinition originalFieldDef) { var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location, TestReaderParameters); @@ -255,6 +274,18 @@ public void ReferencesToMethodSpecs() Assert.NotSame(originalSpec.Module, newSpec.Module); } + [Fact] + public void CloneSemantics() + { + var clonedProperty = CloneProperty(typeof(SingleProperty).GetProperty(nameof(SingleProperty.IntProperty)), out var originalProperty); + + Assert.Equal(originalProperty.Name, clonedProperty.Name); + Assert.Equal( + originalProperty.Semantics.Select(x => x.Method!.Name), + clonedProperty.Semantics.Select(x => x.Method!.Name) + ); + } + [Fact] public void CloneImplMap() {