ninject / Ninject.Extensions.Interception

Interception extension for Ninject
Other
58 stars 27 forks source link

Sequence contains no elements ParameterTypes.Last() #54

Open sw1nn4 opened 4 years ago

sw1nn4 commented 4 years ago

ExtensionsForMethodInfo.GetPropertyFromMethod(Type t) is throwing an exception when called on a property getter.

The method name is returned as "IGenericStateSetter`1[Boolean].get_Value", therefore the substring(0,3) does not equal "get", as expected. Since it's a property getter method.GetParameterTypes() returns an empty list, so the call to Last() throws an invalid operation exception.

I'm not trying to intercept the method, but I presume a check is being performed as to whether it should be intercepted or not. I'm also using a Ninject.MockingKernel to create test mocks for any missing bindings. (The issue happens regardless of which mocking framework is used, I think they all use Castle.Proxies)

I don't believe there's any way to specify not to try intercepting an activation?

Stack trace:

System.Core.dll!System.Linq.Enumerable.Last<System.Type>(System.Collections.Generic.IEnumerable<System.Type> source)
Ninject.Extensions.Interception.dll!Ninject.Extensions.Interception.Infrastructure.Language.ExtensionsForMethodInfo.GetPropertyFromMethod(System.Reflection.MethodInfo method, System.Type implementingType) Line 18    
Ninject.Extensions.Interception.dll!Ninject.Extensions.Interception.Planning.Strategies.InterceptorRegistrationStrategy.Execute(Ninject.Planning.IPlan plan) Line 47    
Ninject.dll!Ninject.Infrastructure.Language.ExtensionsForIEnumerableOfT.Map<Ninject.Planning.Strategies.IPlanningStrategy>(System.Collections.Generic.IEnumerable<Ninject.Planning.Strategies.IPlanningStrategy> series, System.Action<Ninject.Planning.Strategies.IPlanningStrategy> action)   
Ninject.dll!Ninject.Planning.Planner.CreateNewPlan(System.Type type)    
Ninject.dll!Ninject.Planning.Planner.GetPlan(System.Type type)  
Ninject.dll!Ninject.Activation.Context.ResolveInternal(object scope)    
Ninject.dll!Ninject.Activation.Context.Resolve()    
Ninject.dll!Ninject.KernelBase.Resolve(Ninject.Activation.IRequest request, bool handleMissingBindings) 
Ninject.dll!Ninject.Planning.Targets.Target<System.__Canon>.ResolveWithin(Ninject.Activation.IContext parent)   
szaliszali commented 1 year ago

The problem occurs for all explicitly implemented properties In this case, the name does not start with get because it will look like Full.Name.Of.Interface.Type.get_Property.

Location: https://github.com/ninject/Ninject.Extensions.Interception/blob/a2670010b5e622867c66ae0463f8078a6021cbfa/src/Ninject.Extensions.Interception/Infrastructure/Language/ExtensionsForMethodInfo.cs#L48

reproduction:

interface IInterface
{
    int Property { get; }
}

class Class : IInterface
{
    int IInterface.Property => -6; // crash
    public int Property => -6; // no crash
}

class Program
{
    static void Main()
    {
        var t = typeof(Class);
        foreach (var method in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            Console.WriteLine($"Name: '{method.Name}'");
            Console.WriteLine(method.GetPropertyFromMethod(t));
        }
    }
}

My first guess would be look for the last dot in Name first, and only then check for get prefix. Also, the length<4 condition must be revisited.

Naive attempt to fix the method:

public static PropertyInfo GetPropertyFromMethod(this MethodInfo method, Type implementingType)
{
    if (!method.IsSpecialName)
    {
        return null;
    }

    var fullName = method.Name.AsSpan();
    var lastPeriod = fullName.LastIndexOf('.');
    var interfaceNameWithPeriod = fullName[..(lastPeriod + 1)];
    var name = fullName[(lastPeriod + 1)..];

    if (name.Length < 4)
    {
        return null;
    }
    var isGetMethod = name.StartsWith("get", StringComparison.InvariantCulture);

    var returnType = isGetMethod ? method.ReturnType : method.GetParameterTypes().Last();
    var indexerTypes = isGetMethod ? method.GetParameterTypes() : method.GetParameterTypes().SkipLast(1);

    var propertyName = $"{interfaceNameWithPeriod}{name[4..]}";
    return implementingType.GetProperty(propertyName, DefaultBindingFlags, null, returnType, indexerTypes.ToArray(), null);
}