Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple cloner listeners #461

Merged
merged 2 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 46 additions & 10 deletions docs/guides/dotnet/cloning.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ cloner.Include(rectangleType);
cloner.Include(vectorType);
```

`Include` returns the same `MemberCloner` instance. It is therefore also
possible to create a long method chain of members to include in the
cloning process.
`Include` returns the same `MemberCloner` instance, allowing for more fluent
syntax:

``` csharp
cloner
Expand Down Expand Up @@ -255,7 +254,24 @@ var cloner = new MemberCloner(destinationModule, (original, cloned) => {
});
```

## Injecting the cloned members
Multiple listeners can also be chained together using the `AddListener` method:

```csharp
var cloner = new MemberCloner(destinationModule);
cloner.AddListener(new MyListener1());
cloner.AddListener(new MyListener2());
```

This can also be used in the fluent syntax form:

```csharp
var cloner = new MemberCloner(destinationModule)
.AddListener(new MyListener1())
.AddListener(new MyListener2());
```


## Injecting cloned members

The `Clone` method returns a `MemberCloneResult`, which contains a
register of all members cloned by the member cloner.
Expand Down Expand Up @@ -288,17 +304,37 @@ foreach (var clonedType in clonedTypes)
destinationModule.TopLevelTypes.Add(clonedType);
```

However, since injecting the cloned top level types is a very common
use-case for the cloner, AsmResolver defines the
`InjectTypeClonerListener` class that implements a cloner listener that
injects all top-level types automatically into the destination module.
In such a case, the code can be reduced to the following:
Injecting the cloned top level types is a very common use-case for the cloner.
AsmResolver defines the `InjectTypeClonerListener` class that implements a
cloner listener that injects all top-level types automatically into
the destination module. In such a case, the code can be reduced to the following:

``` csharp
new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule))
new MemberCloner(destinationModule)
.Include(rectangleType)
.Include(vectorType)
.AddListener(new InjectTypeClonerListener(destinationModule))
.Clone();

// `destinationModule` now contains copies of `rectangleType` and `vectorType`.
```

AsmResolver provides another built-in listener `AssignTokensClonerListener` that
also allows for preemptively assigning new metadata tokens automatically, should
this be necessary.

``` csharp
new MemberCloner(destinationModule)
.Include(rectangleType)
.Include(vectorType)
.AddListener(new InjectTypeClonerListener(destinationModule))
.AddListener(new AssignTokensClonerListener(destinationModule))
.Clone();

// `destinationModule` now contains copies of `rectangleType` and `vectorType`
// and all its members have been assigned a metadata token preemptively.
```

The `AssignTokensClonerListener` uses the target module's `TokenAllocator`. See
[Metadata Token Allocation](token-allocation.md) for more information on how this
class operates.
33 changes: 33 additions & 0 deletions src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace AsmResolver.DotNet.Cloning
{
/// <summary>
/// Provides an implementation of a <see cref="IMemberClonerListener"/> that preemptively assigns new metadata
/// tokens to the cloned metadata members using the target module's <see cref="TokenAllocator"/>.
/// </summary>
public class AssignTokensClonerListener : MemberClonerListener
{
/// <summary>
/// Creates a new instance of the token allocator listener.
/// </summary>
/// <param name="targetModule">The module that will contain the cloned members.</param>
public AssignTokensClonerListener(ModuleDefinition targetModule)
{
TargetModule = targetModule;
}

/// <summary>
/// Gets the module that will contain the cloned members.
/// </summary>
public ModuleDefinition TargetModule
{
get;
}

/// <inheritdoc />
public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned)
{
TargetModule.TokenAllocator.AssignNextAvailableToken((MetadataMember) cloned);
base.OnClonedMember(original, cloned);
}
}
}
4 changes: 2 additions & 2 deletions src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ private void DeepCopyFields(MemberCloneContext context)
{
DeepCopyField(context, field);
var clonedMember = (FieldDefinition)context.ClonedMembers[field];
_clonerListener.OnClonedMember(field, clonedMember);
_clonerListener.OnClonedField(field, clonedMember);
_listeners.OnClonedMember(field, clonedMember);
_listeners.OnClonedField(field, clonedMember);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ private void DeepCopyMethods(MemberCloneContext context)
{
DeepCopyMethod(context, method);
var clonedMember = (MethodDefinition)context.ClonedMembers[method];
_clonerListener.OnClonedMember(method, clonedMember);
_clonerListener.OnClonedMethod(method, clonedMember);
_listeners.OnClonedMember(method, clonedMember);
_listeners.OnClonedMethod(method, clonedMember);
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ private void DeepCopyProperties(MemberCloneContext context)
declaringType.Properties.Add(clonedProperty);
}
var clonedMember = clonedProperty;
_clonerListener.OnClonedMember(property, clonedMember);
_clonerListener.OnClonedProperty(property, clonedMember);
_listeners.OnClonedMember(property, clonedMember);
_listeners.OnClonedProperty(property, clonedMember);
}
}

Expand Down Expand Up @@ -55,8 +55,8 @@ private void DeepCopyEvents(MemberCloneContext context)
declaringType.Events.Add(clonedEvent);
}
var clonedMember = clonedEvent;
_clonerListener.OnClonedMember(@event, clonedMember);
_clonerListener.OnClonedEvent(@event, clonedMember);
_listeners.OnClonedMember(@event, clonedMember);
_listeners.OnClonedEvent(@event, clonedMember);
}
}

Expand Down
34 changes: 29 additions & 5 deletions src/AsmResolver.DotNet/Cloning/MemberCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace AsmResolver.DotNet.Cloning
/// </remarks>
public partial class MemberCloner
{
private readonly IMemberClonerListener _clonerListener;
private readonly MemberClonerListenerList _listeners;
private readonly Func<MemberCloneContext, CloneContextAwareReferenceImporter>? _importerFactory;
private readonly ModuleDefinition _targetModule;

Expand All @@ -32,7 +32,7 @@ public partial class MemberCloner
/// </summary>
/// <param name="targetModule">The target module to copy the members into.</param>
public MemberCloner(ModuleDefinition targetModule)
: this(targetModule, (original, cloned) => { })
: this(targetModule, null, null)
{
}

Expand Down Expand Up @@ -79,7 +79,9 @@ public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listene
{
_targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule));
_importerFactory = importerFactory;
_clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance;
_listeners = new MemberClonerListenerList();
if (clonerListener is not null)
_listeners.Add(clonerListener);
}

/// <summary>
Expand Down Expand Up @@ -262,6 +264,28 @@ public MemberCloner Include(EventDefinition @event)
return this;
}

/// <summary>
/// Adds a member cloner listener to the cloner.
/// </summary>
/// <param name="listener">The listener to add.</param>
/// <returns>The metadata cloner that the listener is added to.</returns>
public MemberCloner AddListener(Action<IMemberDefinition, IMemberDefinition> listener)
{
_listeners.Add(new CallbackClonerListener(listener));
return this;
}

/// <summary>
/// Adds a member cloner listener to the cloner.
/// </summary>
/// <param name="listener">The listener to add.</param>
/// <returns>The metadata cloner that the listener is added to.</returns>
public MemberCloner AddListener(IMemberClonerListener listener)
{
_listeners.Add(listener);
return this;
}

/// <summary>
/// Clones all included members.
/// </summary>
Expand Down Expand Up @@ -313,8 +337,8 @@ private void DeepCopyTypes(MemberCloneContext context)
{
DeepCopyType(context, type);
var clonedMember = (TypeDefinition)context.ClonedMembers[type];
_clonerListener.OnClonedMember(type, clonedMember);
_clonerListener.OnClonedType(type, clonedMember);
_listeners.OnClonedMember(type, clonedMember);
_listeners.OnClonedType(type, clonedMember);
}
}

Expand Down
52 changes: 52 additions & 0 deletions src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.ObjectModel;

namespace AsmResolver.DotNet.Cloning
{
/// <summary>
/// Wraps a list of <see cref="IMemberClonerListener"/>s into a single instance of <see cref="IMemberClonerListener"/>.
/// </summary>
public class MemberClonerListenerList : Collection<IMemberClonerListener>, IMemberClonerListener
{
/// <inheritdoc />
public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedMember(original, cloned);
}

/// <inheritdoc />
public void OnClonedType(TypeDefinition original, TypeDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedType(original, cloned);
}

/// <inheritdoc />
public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedMethod(original, cloned);
}

/// <inheritdoc />
public void OnClonedField(FieldDefinition original, FieldDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedField(original, cloned);
}

/// <inheritdoc />
public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedProperty(original, cloned);
}

/// <inheritdoc />
public void OnClonedEvent(EventDefinition original, EventDefinition cloned)
{
for (int i = 0; i < Items.Count; i++)
Items[i].OnClonedEvent(original, cloned);
}
}
}
25 changes: 21 additions & 4 deletions test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef
return clonedMethod;
}

private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef)
private static FieldDefinition CloneInitializerField(FieldInfo field, out FieldDefinition originalFieldDef)
{
var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location);
originalFieldDef = (FieldDefinition) sourceModule.LookupMember(field.MetadataToken);
Expand Down Expand Up @@ -279,10 +279,10 @@ public void CloneConstant()
public void CloneFieldRva()
{
var clonedInitializerField =
CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field);
CloneInitializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field);

var originalData = ((IReadableSegment) field.FieldRva).ToArray();
var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray();
var originalData = ((IReadableSegment) field.FieldRva!).ToArray();
var newData = ((IReadableSegment) clonedInitializerField.FieldRva!).ToArray();

Assert.Equal(originalData, newData);
}
Expand Down Expand Up @@ -391,5 +391,22 @@ public void CloneAndInject()
Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes));
}

[Fact]
public void CloneAndInjectAndAssignToken()
{
var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location);
var targetModule = PrepareTempModule();

var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous));

var result = new MemberCloner(targetModule)
.Include(type)
.AddListener(new InjectTypeClonerListener(targetModule))
.AddListener(new AssignTokensClonerListener(targetModule))
.Clone();

Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes));
Assert.All(result.ClonedMembers, m => Assert.NotEqual(0u, ((IMetadataMember) m).MetadataToken.Rid));
}
}
}