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

Incorrect byte code generated for nested lambda with closure #51

Open
BladeWise opened this issue Mar 9, 2022 · 0 comments
Open

Incorrect byte code generated for nested lambda with closure #51

BladeWise opened this issue Mar 9, 2022 · 0 comments

Comments

@BladeWise
Copy link

I have encountered an issue compiling a lambda using a closure in a nested lambda.

This is the expression I am trying to generate and compile (the getOrNull method, included below, is basically a null-conditional get accessor):
(Ctx ctx) -> getOrNull(ctx, x -> getOrNull(ctx.items.get("key_2"), k -> k.value))

This is the a test to replicate the issue:

public class CustomTests {
    public static <T, R> R getOrNull(final T instance, @NotNull final Func1<T, R> getter) {
        if (instance == null) {
            return null;
        }
        return getter.apply(instance);
    }

    @Test
    public void nestedLambdaWithClosureCompilation() {
        final Type<Ctx> ctxType = Type.of(Ctx.class);
        final Type<Ctx.Item> itemType = Type.of(Ctx.Item.class);
        final MethodInfo getOrNullMethod =
            Type.of(CustomTests.class).getMethod("getOrNull", BindingFlags.PublicStatic, Types.Object, Type.of(Func1.class));

        // Reference expression: (Ctx ctx) -> getOrNull(ctx, x -> getOrNull(ctx.items.get("key_2"), k -> k.value))
        // 1. Create parameter [(Ctx ctx)]
        final ParameterExpression ctxParameter = Expression.parameter(ctxType, "ctx");

        // 2. Create value lambda getter [(Ctx.Item k) -> k.value]
        final ParameterExpression kParameter = Expression.parameter(itemType, "k");
        final MemberExpression itemGetterBody = Expression.field(kParameter, itemType.getField("value"));
        final Type<Func1<Ctx.Item, Object>> itemGetterDelegateType = Type.of(Func1.class).makeGenericType(itemType, Types.Object);
        final LambdaExpression<Func1<Ctx.Item, Object>> itemGetter = Expression.lambda(itemGetterDelegateType, itemGetterBody, kParameter);

        // 3. Create item accessor [ctx.items.get("key_2")]
        final MemberExpression itemsAccessor = Expression.field(ctxParameter, ctxType.getField("items"));
        final MethodCallExpression getValueByKey =
            Expression.call(itemsAccessor, itemsAccessor.getType().getMethod("get", Types.String), Expression.constant("key_2"));

        // 4. Create conditional getter [getOrNull(ctx.items.get("key_2"), k -> k.value)]
        final MethodCallExpression getValueConditional =
            Expression.call(getOrNullMethod.makeGenericMethod(getValueByKey.getType(), Types.Object), getValueByKey, itemGetter);

        // 5. Create conditional getter as lambda getter [(Ctx x) -> getOrNull(ctx.items.get("key_2"), k -> k.value)] (A closure is used instead of the parameter)
        final ParameterExpression xParameter = Expression.parameter(ctxType, "x");
        final Type<Func1<Ctx, Object>> getValueConditionalDelegateType = Type.of(Func1.class).makeGenericType(ctxType, Types.Object);
        final LambdaExpression<Func1<Ctx, Object>> getValueConditionalLambda =
            Expression.lambda(getValueConditionalDelegateType, getValueConditional, xParameter);

        // 6. Create lambda [getOrNull(ctx, x -> getOrNull(ctx.items.get("key_2"), k -> k.value))]
        final MethodCallExpression body = Expression.call(getOrNullMethod.makeGenericMethod(ctxType, Types.Object), ctxParameter, getValueConditionalLambda);
        final LambdaExpression<Func1<Ctx, Object>> lambda =
            Expression.lambda(Type.of(Func1.class).<Func1<Ctx, Object>>makeGenericType(ctxType, Types.Object), body, ctxParameter);

        final Func1<Ctx, Object> delegate = lambda.compile();
        assertNotNull(delegate);
    }

    private static class Ctx {
        public Map<String, Item> items = new LinkedHashMap<>();
        public Double number = 23.3;

        public Ctx() {
            items.put("key_2", new Item());
        }

        public static class Item {
            public Integer value = 55;
        }
    }
}

The compiled byte code fails verification phase (VerifyError).
I used ASM to have a better validation error, and analyzed the attached f__Lambda$0x0002.class and it seems that the generated bytecode passes the Closure as a parameter to the actual invoke method, while it should be passed to the constructor of the delegate (if I am not wrong).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant