[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