pamidur / aspect-injector

AOP framework for .NET (c#, vb, etc)
Apache License 2.0
745 stars 112 forks source link

Aspect Injection throws AI_ERR0 with inner NotSupportedException on compilation #123

Closed backfromexile closed 4 years ago

backfromexile commented 4 years ago

The following code is part of an ASP.NET Core application and throws this error:

1>AspectInjector|2.3.0: Found 1 aspects, 2 injections
1>AspectInjector|2.3.0 : error AI_ERR0: Processing failure: System.NotSupportedException: No instruction for ValueType
1>   at FluentIL.Arrays.GetStoreOpcode(TypeReference elementType)
1>   at FluentIL.Arrays.SetByIndex(Cut pc, TypeReference elementType, Int32 index, PointCut value)
1>   at FluentIL.Arrays.CreateArray(Cut pc, TypeReference elementType, PointCut[] elements)
1>   at FluentIL.Values.AttributeArgument(Cut pc, CustomAttributeArgument argument)
1>   at FluentIL.Values.Value(Cut pc, Object value)
1>   at AspectInjector.Core.Advice.Weavers.Processes.AdviceWeaveProcessBase`1.<>c__DisplayClass16_0.<LoadInjectionsArgument>b__2(Cut ilc)
1>   at FluentIL.Cut.Here(PointCut pc)
1>   at FluentIL.Statements.Call(Cut cut, MethodReference method, PointCut args)
1>   at AspectInjector.Core.Advice.Weavers.Processes.AdviceWeaveProcessBase`1.<>c__DisplayClass16_0.<LoadInjectionsArgument>b__1(Cut il)
1>   at FluentIL.Cut.Here(PointCut pc)
1>   at FluentIL.Arrays.SetByIndex(Cut pc, TypeReference elementType, Int32 index, PointCut value)
1>   at FluentIL.Arrays.CreateArray(Cut pc, TypeReference elementType, PointCut[] elements)
1>   at AspectInjector.Core.Advice.Weavers.Processes.AdviceWeaveProcessBase`1.LoadInjectionsArgument(Cut pc, AdviceArgument parameter)
1>   at AspectInjector.Core.Advice.Weavers.Processes.AdviceWeaveProcessBase`1.LoadAdviceArgs(Cut cut)
1>   at FluentIL.Cut.Here(PointCut pc)
1>   at FluentIL.Statements.Call(Cut cut, MethodReference method, PointCut args)
1>   at AspectInjector.Core.Advice.Weavers.Processes.AdviceBeforeProcess.<Execute>b__1_0(Cut e)
1>   at FluentIL.Cut.Here(PointCut pc)
1>   at FluentIL.MethodEditor.BeforeInstruction(MethodBody body, Instruction instruction, PointCut action)
1>   at AspectInjector.Core.Processor.PatchAssembly(AssemblyDefinition assembly, Boolean optimize, Boolean verbose)
1>   at FluentIL.PatcherBase.Process(String assemblyFile, IAssemblyResolver resolver, Boolean optimize, Boolean verbose)
1>   at AspectInjector.Compiler.Execute(String filename, IReadOnlyList`1 references, Boolean optimize, Boolean verbose). Please submit an issue to https://github.com/pamidur/aspect-injector
1>AspectInjector : error AI_FAIL: Aspect Injector processing has failed. See other errors.

This is the code:

public enum UserRole
    {
        Guest,
        Normal,
        Admin,
    }

    public class User
    {
        public IReadOnlyList<UserRole> Roles { get; }

        public User(params UserRole[] roles)
        {
            Roles = new ReadOnlyCollection<UserRole>(roles);
        }

        public bool HasRole(UserRole role)
        {
            return Roles.Contains(role);
        }
    }

    public static class Context
    {
        public static AsyncLocal<User> User { get; } = new AsyncLocal<User>();
    }

    public class HomeController : Controller
    {
        public async Task<IActionResult> Index()
        {
            User user = new User(UserRole.Admin);
            Context.User.Value = user;

            await SomeIOOperation();

            await ActionOnlyAdminsCanDo();

            return View();
        }

        [CheckPrivileges(UserRole.Admin)]
        private async Task ActionOnlyAdminsCanDo()
        {
            await Task.Delay(100);
        }

        [CheckPrivileges(UserRole.Guest)]
        private async Task ActionEverybodyCanDo()
        {
            await Task.Delay(0);
        }

        public async Task<IActionResult> Privacy()
        {
            User user = new User(UserRole.Guest);
            Context.User.Value = user;

            await SomeIOOperation();

            return View();
        }

        private async Task SomeIOOperation()
        {
            await Task.Factory.StartNew(async () => { await Task.Delay(1000); }, TaskCreationOptions.LongRunning);
        }
    }

    [Injection(typeof(CheckPrivilegesAspect))]
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class CheckPrivileges : Attribute
    {
        public UserRole[] Roles { get; }

        public CheckPrivileges(params UserRole[] roles)
        {
            Roles = roles;
        }
    }

    [Aspect(Scope.PerInstance)]
    public class CheckPrivilegesAspect
    {
        [Advice(Kind.Before)]
        public void Before([Argument(Source.Triggers)] Attribute[] attributes)
        {
            CheckPrivileges attr = attributes.OfType<CheckPrivileges>().FirstOrDefault();

            if (attr.Roles.Any(role => !Context.User.Value.HasRole(role)))
                throw new MissingPrivilegesException();
        }
    }

    [Serializable]
    internal class MissingPrivilegesException : Exception
    {
        public MissingPrivilegesException()
        {
        }

        public MissingPrivilegesException(string message) : base(message)
        {
        }

        public MissingPrivilegesException(string message, Exception innerException) : base(message, innerException)
        {
        }

        protected MissingPrivilegesException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
    }

Am I doing something wrong here or can't I inject values into the attributes? Also is there a reason I have to create two different classes for aspect and attribute if I want the attribute to have a not empty constructor?

pamidur commented 4 years ago

Hi @backfromexile , thank you for the report! Issue should be fixed in 2.3.1

pamidur commented 4 years ago

FYI the issue was that when one uses params with enums the compiler creates an array of int instead of array of enums(value types), this totally makes sense however we haven't tested this scenario before