-
Notifications
You must be signed in to change notification settings - Fork 23
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
base: code-reflection
Are you sure you want to change the base?
Conversation
👋 Welcome back mabbay! A progress list of the required criteria for merging this PR into |
@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:
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
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
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() |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
@mabbay this pull request can not be integrated into 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 |
# Conflicts: # src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java # src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
if (q instanceof Quotable2 q2) { | ||
return Optional.of(q2.quoted()); | ||
} | ||
Method method; |
There was a problem hiding this comment.
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 :-) )
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- the module in which capture occurs (e.g. where the quotable lambda is)
java.base
which is where the lambda metafactory isjdk.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
?
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, something more descriptive.
src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java
Outdated
Show resolved
Hide resolved
// 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 -> { |
There was a problem hiding this comment.
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.
In this PR, we make Quotable a marker interface. This PR is based on #301.
Progress
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