[code-reflection] RFR: Add isQuotable attribute to LambdaOp [v6]

Paul Sandoz psandoz at openjdk.org
Wed Sep 10 17:25:02 UTC 2025


On Wed, 10 Sep 2025 11:52:58 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> The change in this PR looks good. But, I'd like to remind ourselves that we have probably several issues when it comes to dealing with lambda expressions. There are a bunch of reasons where javac wants to use the `altMetafactory` instead of the regular one:
>> 
>> 1. when the target of the lambda has one or more "empty markers" -- e.g. `(Runnable & Cloneable) () -> ...`
>> 2. when the target of the lambda is serializable -- e.g. `(Runnable & Serializable) () -> ...
>> 3. when the metafactory needs to also implement one or more "bridge" methods
>> 
>> An example of (3) is as follows:
>> 
>> 
>> interface A<X> {
>>     void m(X x);
>> }
>> 
>> interface B<X extends Number> {
>>     void m(X x);
>> }
>> 
>> interface C extends A<Integer>, B<Integer> { }
>> 
>> C c = (i) -> System.out.println(i);
>> 
>> 
>> In the above example javac will ask the `altMetafactory` to generate an additional bridge implementation for the `(Ljava/lang/Object;)V` signature.
>> 
>> While there's things we can do to detect (1) and (2), I think the kind of analysis required for (3) is rather difficult, and compile-dependent:
>> 
>> https://github.com/openjdk/jdk/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#L927
>> 
>> And I'm quite skeptical we can reproduce this at runtime (as the runtime capabilities to detect overriding based on source-level types are rather limited).
>> 
>> So, more generally, I think both `Interpreter` and `BytecodeGenerator` might have some issues in this area.
>
>> So, more generally, I think both `Interpreter` and `BytecodeGenerator` might have some issues in this area.
> 
> I tried something like this:
> 
> 
> class Test {
>     public interface A<X> {
>         void m(X x);
>     }
> 
>     public interface B<X extends Number> {
>         void m(X x);
>     }
> 
>     public interface C extends A<Integer>, B<Integer> { }
> 
>     @CodeReflection
>     static C getC() {
>         C c = i -> System.out.println(i);
>         return c;
>     }
> 
>     public static void main(String[] args) throws Throwable {
>         var method = Test.class.getDeclaredMethod("getC");
>         var op = (CoreOp.FuncOp) Op.ofMethod(method).get();
>         A<?> a = (A<?>)Interpreter.invoke(MethodHandles.lookup(), op);
>         a.m(null);
>     }
> }
> 
> 
> This (surprisingly) seems to work. I believe the reason is that `Interpreter` uses `MethodHandleProxies::asInterfaceInstance` -- whose javadoc says:
> 
> 
> * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods}
>      * and other corner cases, the interface may also have several abstract methods
>      * with the same name but having distinct descriptors (types of returns and parameters).
>      * In this case, all the methods are bound in common to the one given target.
>      * The type check and effective {@code asType} conversion is applied to each
>      * method type descriptor, and all abstract methods are bound to the target in common.
>      * Beyond this type check, no further checks are made to determine that the
>      * abstract methods are related in any way.
> 
> 
> So, this special "fallback overrides" end up saving the day in this case.
> 
> However, a similar example using `BytecodeGenerator` fails:
> 
> 
> class Test {
>     public interface A<X> {
>         void m(X x);
>     }
> 
>     public interface B<X extends Number> {
>         void m(X x);
>     }
> 
>     public interface C extends A<Integer>, B<Integer> { }
> 
>     @CodeReflection
>     static void run() {
>         C c = i -> System.out.println(i);
>         A<?> a = (A<?>)c;
>         a.m(null);
>     }
> 
>     public static void main(String[] args) throws Throwable {
>         var method = Test.class.getDeclaredMethod("run");
>         var op = (CoreOp.FuncOp) Op.ofMethod(method).get();
>         var handle = BytecodeGenerator.generate(MethodHandles.lookup(), op);
>         handle.invokeExact();
>     }
> }
> 
> 
> The error I see is:
> 
> 
> WARNING: Using incubator modules: jdk.incubator.code
> Exception in thread "main" java.lang.AbstractMethodError: Receiver class run_0x0000000097069000$$Lambda/0x00000000970...

@mcimadamore good points. Complete reverse engineering is hard. I also suspected that the bytecode generator does not produce correct bytecode behaviour for these cases, and i don't know if there is sufficient information held by the lambda op instance in these cases to generate the bytecode. Fixing both the generator and lifter together is important.

-------------

PR Comment: https://git.openjdk.org/babylon/pull/545#issuecomment-3275823722


More information about the babylon-dev mailing list