Skip to content

Commit

Permalink
implemented support for method info caching also for open generic types
Browse files Browse the repository at this point in the history
  • Loading branch information
dd committed Oct 20, 2021
1 parent a7080eb commit a21c3cc
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void IfGenericClassIsWeavedForOnException_ThenExceptionThrownBetweenAwait
}

[Fact]
public void IfGenericClassIsWeavedForOnExit_ThenTaskIsPasedToAspectReturnValue()
public void IfGenericClassIsWeavedForOnExit_ThenTaskIsPassedToAspectReturnValue()
{
// Act
WeaveAssemblyClassAndLoad(OpenClass);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using FluentAssertions;
using MethodBoundaryAspect.Fody.UnitTests.TestAssembly.NetFramework;
using Xunit;

namespace MethodBoundaryAspect.Fody.UnitTests.NetFramework
{
public class GenericClassWithOpenTypeTests : MethodBoundaryAspectNetFrameworkTestBase
{
[Fact]
public void IfCGenericClassWithOpenType_ThenTheAssemblyShouldBeValid()
{
// Arrange
const string testMethodName = nameof(GenericClassWithOpenType.CallOpenTypeMethod);
var testClassType = typeof(GenericClassWithOpenType);

// Act
WeaveAssemblyMethodAndLoad(testClassType, nameof(GenericClassWithOpenType.OpenTypeMethod));
var result = AssemblyLoader.InvokeMethod(testClassType.TypeInfo(), testMethodName, 42);

// Assert
result.Should().Be("OpenTypeMethod");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using MethodBoundaryAspect.Fody.Attributes;

namespace MethodBoundaryAspect.Fody.UnitTests.TestAssembly.NetFramework.Aspects
{
public class GenericClassWithOpenTypeSetReturnValueAspect : OnMethodBoundaryAspect
{
public override void OnExit(MethodExecutionArgs arg)
{
GenericClassWithOpenType.Result = arg.Method.Name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using MethodBoundaryAspect.Fody.UnitTests.TestAssembly.NetFramework.Aspects;

namespace MethodBoundaryAspect.Fody.UnitTests.TestAssembly.NetFramework
{
public class GenericClassWithOpenType
{
public static object Result { get; set; }

[GenericClassWithOpenTypeSetReturnValueAspect]
public static T OpenTypeMethod<T>(T arg)
{
return default;
}

public static void CallOpenTypeMethod(int value)
{
OpenTypeMethod(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public NamedInstructionBlockChain CreateMethodExecutionArgsInstance(
var methodBaseVariable = _creator.CreateVariable(methodBaseTypeRef);
InstructionBlock callGetCurrentMethodBlock;
var variablePersistable = new VariablePersistable(methodBaseVariable);
if (methodInfoCompileTimeWeaver?.IsEnabled != true || !methodInfoCompileTimeWeaver.CanWeave(method))
if (methodInfoCompileTimeWeaver?.IsEnabled != true)
{
// fallback: slow GetCurrentMethod
var methodBaseGetCurrentMethod = _referenceFinder.GetMethodReference(methodBaseTypeRef,
Expand Down
60 changes: 41 additions & 19 deletions src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,39 +65,49 @@ public void AddMethod(MethodDefinition method)
public InstructionBlock PushMethodInfoOnStack(MethodDefinition method, VariablePersistable variablePersistable)
{
var fieldDefinition = _fieldsCache[method];
var instruction = Instruction.Create(OpCodes.Ldsfld, fieldDefinition);
var getMethodFromHandle2 = ImportGetMethodFromHandleArg2();

var instructions = new List<Instruction>();
if (ContainsOpenTypeRecursive(method))
{
instructions.Add(Instruction.Create(OpCodes.Ldtoken, method));
instructions.Add(Instruction.Create(OpCodes.Ldtoken, method.DeclaringType));
instructions.Add(Instruction.Create(OpCodes.Call, getMethodFromHandle2));
}
else
instructions.Add(Instruction.Create(OpCodes.Ldsfld, fieldDefinition));

var store = variablePersistable.Store(
new InstructionBlock("", instruction),
new InstructionBlock("", instructions),
variablePersistable.PersistedType);
return new InstructionBlock($"Load method info for '{method.Name}'", store.Instructions);
}

public void Finish()
{
if (_fieldsCache.Any())
CreateStaticCtor();
CreateStaticCtor();
}
public bool CanWeave(MethodDefinition method)

private static bool ContainsOpenTypeRecursive(MethodDefinition method)
{
// no support for open generic types
if (IsOpenType(method))
return false;
if (ContainsOpenType(method))
return true;

var parentType = method.DeclaringType;
while (parentType != null)
{
if (IsOpenType(parentType))
return false;
if (ContainsOpenType(parentType))
return true;

parentType = parentType.DeclaringType;
}

return true;
return false;
}

private static bool IsOpenType(IGenericParameterProvider method)
private static bool ContainsOpenType(IGenericParameterProvider method)
{
return method.GenericParameters.Any(x => x.IsGenericParameter);
return method.GenericParameters.Any(x => x.ContainsGenericParameter);
}

private string CreateIdentifier(MemberReference method)
Expand All @@ -120,15 +130,21 @@ private void CreateStaticCtor()
var cctor = new MethodDefinition(".cctor", staticConstructorAttributes, typeReferenceVoid);

// taken from https://gist.github.com/jbevain/390902
var getMethodFromHandle = ImportGetMethodFromHandle();
var getMethodFromHandle = ImportGetMethodFromHandleArg1();
var cctorInstructions = new List<Instruction>();
foreach (var entry in _fieldsCache)
{
if (ContainsOpenTypeRecursive(entry.Key))
continue; // method info has to be resolved during runtime so we don't need a cache entry

cctorInstructions.Add(Instruction.Create(OpCodes.Ldtoken, entry.Key));
cctorInstructions.Add(Instruction.Create(OpCodes.Call, getMethodFromHandle));
cctorInstructions.Add(Instruction.Create(OpCodes.Stsfld, entry.Value));
}

if (!cctorInstructions.Any())
return;

cctorInstructions.Add(Instruction.Create(OpCodes.Ret));

foreach (var methodInstruction in cctorInstructions)
Expand All @@ -153,18 +169,24 @@ private TypeReference GetTypeReference(string name)
return _mainModule.TypeSystem.Void;
default:
{
var splitted = name.Split('.');
var namespaceName = string.Join(".", splitted.Take(splitted.Length - 1));
var typeName = splitted.Last();
var split = name.Split('.');
var namespaceName = string.Join(".", split.Take(split.Length - 1));
var typeName = split.Last();
return new TypeReference(namespaceName, typeName, _mainModule, _mainModule.TypeSystem.CoreLibrary);
}
}
}

private MethodReference ImportGetMethodFromHandle()
private MethodReference ImportGetMethodFromHandleArg1()
{
return _mainModule.ImportReference(typeof(MethodBase)
.GetMethod("GetMethodFromHandle", new[] {typeof(RuntimeMethodHandle)}));
}

private MethodReference ImportGetMethodFromHandleArg2()
{
return _mainModule.ImportReference(typeof(MethodBase)
.GetMethod("GetMethodFromHandle", new[] {typeof(RuntimeMethodHandle),typeof(RuntimeTypeHandle)}));
}
}
}

0 comments on commit a21c3cc

Please sign in to comment.