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

Make Quotable a marker interface #302

Open
wants to merge 7 commits into
base: code-reflection
Choose a base branch
from

Conversation

mabbay
Copy link
Member

@mabbay mabbay commented Dec 31, 2024

In this PR, we make Quotable a marker interface. This PR is based on #301.


Progress

  • Change must not contain extraneous whitespace

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/babylon.git pull/302/head:pull/302
$ git checkout pull/302

Update a local copy of the PR:
$ git checkout pull/302
$ git pull https://git.openjdk.org/babylon.git pull/302/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 302

View PR using the GUI difftool:
$ git pr show -t 302

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/babylon/pull/302.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Dec 31, 2024

👋 Welcome back mabbay! A progress list of the required criteria for merging this PR into code-reflection will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Dec 31, 2024

@mabbay This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

Make Quotable a marker interface

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 1 new commit pushed to the code-reflection branch:

  • fec8903: Re organized code builder hierarchy

Please see this link for an up-to-date comparison between the source branch of this pull request and the code-reflection branch.
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the code-reflection branch, type /integrate in a new comment.

@openjdk openjdk bot added ready Pull request is ready to be integrated rfr Pull request is ready for review labels Dec 31, 2024
@mlbridge
Copy link

mlbridge bot commented Dec 31, 2024

Webrevs

if (Quotable.class.isAssignableFrom(fi)) {
return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class<?>[]{fi},
// Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted()
Copy link
Collaborator

@mcimadamore mcimadamore Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic seems a bit expensive. I wonder if we could have a non-exported interface which extends Quoted and adds the quoted() method, which then the implementation knows about, but the outside world can't access. That would probably lead to cleaner code, here and elsewhere (e.g. ofQuotable could be implemented w/o using reflection).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not very concerned about performance in the Interpreter, but i am all for reducing the complexity!
Having an internal interface will reduce the complexity.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not very concerned about performance in the Interpreter, but i am all for reducing the complexity! Having an internal interface will reduce the complexity.

I think the internal interface will also improve performance of the new Op::ofQuotable (which currently uses reflection to discover the magic method it knows to be there)

@openjdk
Copy link

openjdk bot commented Jan 8, 2025

@mabbay this pull request can not be integrated into code-reflection due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout Quotable-no-methods
git fetch https://git.openjdk.org/babylon.git code-reflection
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge code-reflection"
git push

@openjdk openjdk bot added merge-conflict Pull request has merge conflict with target branch and removed ready Pull request is ready to be integrated labels Jan 8, 2025
# Conflicts:
#	src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
#	src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
@openjdk openjdk bot added ready Pull request is ready to be integrated and removed merge-conflict Pull request has merge conflict with target branch labels Jan 8, 2025
if (q instanceof Quotable2 q2) {
return Optional.of(q2.quoted());
}
Method method;
Copy link
Collaborator

@mcimadamore mcimadamore Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this part is not required? E.g. we can only support cases where the instance implements Quotable2. (meaning we know that implementation :-) )

Copy link
Member

@PaulSandoz PaulSandoz Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LambdaMetaFactory needs updating so the FI implementation also implements the internal Quoted2. I don't know if there are any access control issues with the non-exported interface being defined outside of java.base. If so we might need an internal interface in java.base whose quoted method returns Object, and is exported to the code module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FI implementation can't access Quotable2 because it's not exported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so i think we need to create an interface in java.base in a package exported to jdk.incubator.code e.g., create a package in java.base called jdk.internal.code and add:

// Implementations of this interface also implement jdk.incubator.code.Quotable
interface QuotableWithQuotedAccess { // or whatever we call it
    // Implementations return instances of jdk.incubator.code.Quoted
    Object quoted();
}

then modify module-info.java in java.base to export package jdk.internal.code to module jdk.incubator.code.

More generally either this or the current approach will fail if the quotable functional interface is explicitly proxied via Proxy.newProxyInstance or MethodHandleProxies.asInterfaceInstance. And, regardless if we have Quotable::quoted proxying would fail for instances of the following:

Runnable r = (Runnable & Quotable) () -> { ... };

I think we have to specify that such proxying results in inaccessible models. Not great a great answer, but not terrible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually three modules:

  1. the module in which capture occurs (e.g. where the quotable lambda is)
  2. java.base which is where the lambda metafactory is
  3. jdk.incubator.code which is where the code reflection code is

I'm not sure if the problem @mabbay is describing has to do with Quotable2 not being accessible from (1) or (2). Putting the new interface in java.base will obviously address accessibility from (2). But I think there's still issues with respect to (1) ?

E.g. InnerClassLambdaMetafactory, at the end of the day, will call defineClass on the caller lookup (e.g. the one in (1)). If the class being defined contains a symbolic reference to a non-exported interface, wouldn't that be an issue, regardless of whether the non-exported interface is in java.base or jdk.incubator.code ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, that will likely throw an IllegalAccessError. (I seem to recall we ran into this before when trying to add internal marker interfaces to FI implementations?)

Copy link
Member Author

@mabbay mabbay Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcimadamore The issue I'm describing is that the class defined by LambdaMetaFactory will be in unnamed module and can't access to Quotable2.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcimadamore The issue I'm describing is that the class defined by LambdaMetaFactory will be in unnamed module and can't access to Quotable2.

Thanks for the clarification - I suspected that. In that case, I think it is probably best to leave this Quotable2 idea on the side for the time being. Sorry for having mentioned it -- I did not think through the full implications for the metafactory.

We can probably try to revisit this at a separate point: the magic method we try to detect works ok, but I see some risk - e.g. if the client provided its own implementation of Quotable that also exposed its own quoted method. Then the JDK code will try to call that which is why I was reaching for something more robust. But we can separate that concern out of this work.

import jdk.incubator.code.Quotable;
import jdk.incubator.code.Quoted;

public interface Quotable2 extends Quotable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better name would be preferrable. Maybe AccessibleQuotable ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something more descriptive.

// Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted()
// that's why we define an interface that contains the method, so that proxy class has it
// and the code of Op.ofQuotable works
byte[] bytes = ClassFile.of().build(ClassDesc.of("I" + System.nanoTime()), classBuilder -> {
Copy link
Member

@PaulSandoz PaulSandoz Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can potentially generate many many classes that in effect are thrown away but not GC'ed. We should avoid that. Before defining the class we can check if the class is already defined by the lookup class's class loader, and if not define it using the lookup. Ideally we would define a hidden class, but then we cannot query via the class loader if it is defined and would need an alternate tracking mechanism e.g., weak hash map of class loader to class. Regardless this needs to be thread safe. The class name can be "I + [system identify hash code of the class loader]" (perhaps choose a prefix that is not possible to express in source).

There is an alternative simpler compromise that might work (if the accessibility gods are in our favor) where the proxy logic bleeds into the Op:ofQuotable method. Proxy provides methods to check if a class is a proxy class and if so it is possible to obtain the InvocationHandler instance for a proxy whose class is the proxy class. This is currently a lambda expression but we could instead make it an anon inner class and implement the required method.

In Op:ofMethod we would do:

Object oq = q;
if (Proxy.isProxyClass(oq.getClass())) {
    oq = Proxy.getInvocationHandler(oq);
}

try {
    method = oq.getClass().getMethod("quoted");
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}
...

It's not as terrible as i initially thought. If the quotable instance is a proxy we instead operate on the proxy's invocation handler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready Pull request is ready to be integrated rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

3 participants