-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
painless: Fix method references to ctor with the new LambdaBootstrap and cleanup code #24406
Conversation
…od references to ctors. Also remove the get$lambda factory.
Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually? |
…nternal class names (uses ASM Type class instead)
I cleaned up the code a bit more and removed most of the IMHO "bad" conversions between internal/slashed class names and binary class names. Instead I propagated the ASM Type, whcih also makes it type safe. This also removes duplicated method calls. |
I checked the bytecode of a javac-compiled class similar to the failing test and figured out with javap:
As we see the original My code does exactly the same type of adaption (in a static helper method calling the constructor after creating the instance). In our case we just use a static helper method, so we can delegate the whole thing easily to |
I think this is ready now. I moved some code around and it looks elegant to me! 🥇 |
b02bb65
to
c42dfad
Compare
c42dfad
to
ab898aa
Compare
…o officially not allowed)
d8fbf97
to
b547fa9
Compare
I also moved the lambda counter to the classloader so it is per script (makes debugging easier and results more consistent). I also removed the doPrivileged for the constructor, as its not needed (OpenJDK code does it, because it uses setAccessible, because lambda class is package private). |
I also changed the code source of the lambda classes to be the same unprivileged as the script itsself. |
@uschindler I can't thank you enough for fixing all these things! I really appreciate you going through all my code again :). I'll get this checked in today or tomorrow. On the constructors with arguments -- just a really poor assumption on my part that all constructors using ::new would have no parameters, I'm not sure why I was thinking that way. |
No problem. There was also no test about this! Anyhow, the idea you had and how to use invokedynamic to call asType() to do the type adaption is really phantastic! Incredible cool! Congrats! I spent nights to think about how to do this without lots of code redoing the same thing like asType! And on top, this causes no slowdown, as the invokedyanmic is done once and then reused (its a constant callsite).
Very simple reason, they can't do otherwise! The original LambdaMetaFactory has some bootstrapping issues. Because of this it does a lot of stuff only using internal APIs only and doesn't touch public stuff (which is not yet initialized during JVM bootstrapping). In addition, this has less security checks than the public APIs, which prevents them from doing doPrivileged everywhere! E.g. it uses a limited, private-only lookup instance (implLookup, which is a early stage lookup without any security features) and it also creates method handles using that (e.g. the one to the static factory). It also uses Unsafe to define anonymous classes! Because of this it also has to handle security in a very different way, so never ever copy the code from inside the original LambdaMetaFactory. The reason for all this is: While bootstrapping the JVM, they already use lambdas and so there is some chicken and egg problems. Not everything is initialized, so the public APIs may break. Because of this they limit internally to private APIs. And the ctor features need more bootstrapping. Our code uses public APIs and so it can directly return a MethodHandle to call the lambda's class' ctor (internally this calls "newinstance" followed up by an "invokespecial", like your static method). In general, it is a bad idea to look at internal OpenJDK implementations and try to reimplement them in "user code" (it should also not done because of GPL-related issues!). I know that you copypasted some code, because you added the no-op doPrivileged with the same try-catch-structure as the GPLed code. The patch here removes all this stuff that somebody could use to argue that ES was copying GPL code! |
@uschindler Thank you for the explanation here, I think it's clear that I don't deal with the internal APIs in this way very often :) so the chicken and egg problems related to bootstrapping never really occurred to me until you pointed it out. Also I greatly appreciate the help in avoiding any licensing issues. I'm also happy you approved of using invokedynamic to get access to asType for adaptation. I was worried this wasn't a good way to do the adaptation, but I really didn't want to re-write adaptation code. |
Hi, another reason why the original JDK8 code used a static factory was that ctor MethodHandles were not direct. Since Java 8u40 the new LambdaForms made this obsolete, too: the constructor MHs are direct and are as effective as a calls to static methods. The subclass DirectMethodHandle.Constructor just injects an Unsafe.allocateInstance() before invokespecial to the original constructor (identical to the static factory). So the static factory is just a relic from earlier days where performance was an issue. |
@elasticmachine test this please |
Split up a line in LambdaBootstrap that is over 140 characters long.
@elasticmachine test this please |
Does Gradle not show too long lines when running tests? I thought it did in the past...! |
|
Ok, will check next time. BTW: I hate line length limitations. The code is much less readable to me with enforced breaks. 🤐 |
No worries! And I agree with you on the length limits, but it's not up to me. |
…and cleanup code (#24406) * Fix wrong delegation to constructors when compiling lambdas with method references to ctors. Also remove the get$lambda factory. * Cleanup code and remove unneeded transformations between binary and internal class names (uses ASM Type class instead) * Cleanup Exception handling * Simplification by moving the type adaption to the outside * Remove STATIC access flag from our Lambda class (not required and also officially not allowed) * Move the lambda counter to the classloader, so we have a per-script lambda ID * Change Codesource of generated lambdas to be consistent
Thanks again @uschindler for all the help with this! I greatly appreciate your expertise in getting this all working correctly. This was committed to both master and 5.x |
OK. All fine :-) Thanks for committing. I opened another PR for adding two Java 9 related tests 2 days ago! #24405 |
Oops, sorry I missed those. I'll take a look right now. |
* master: (27 commits) Check index sorting with no replica since we cannot ensure that the replica index is ready when forceMerge is called. Closes elastic#24416 Docs: correct indentation on callout Build that java api docs from a test (elastic#24354) Move RemoteClusterService into TransportService (elastic#24424) Fix license header in WildflyIT.java Try not to lose stacktraces (elastic#24426) [DOCS] Update XPack Reference URL for 5.4 (elastic#24425) Painless: Add tests to check for existence and correct detection of the special Java 9 optimizations: Indified String concat and MethodHandles#ArrayLengthHelper() (elastic#24405) Extract a common base class to allow services to listen to remote cluster config updates (elastic#24367) Adds check to snapshot repository incompatible-snapshots blob to delete a pre-existing one before attempting to overwrite it. Added docs for batched_reduce_size Fixes checkstyle errors Allow scripted metric agg to access `_score` (elastic#24295) [Test] Add unit tests for HDR/TDigest PercentilesAggregators (elastic#24245) Fix FieldCaps documentation Upgrade to JUnit 4.12 (elastic#23877) Set available processors for Netty Painless: Fix method references to ctor with the new LambdaBootstrap and cleanup code (elastic#24406) Doc test: use propery regex for file size [DOCS] Tweak doc test to sync_flush ...
This PR fixes #24070 (comment):
I figured out that the special handling of
H_NEWINVOKESPECIAL
is broken. The following test fails:This code fails because the script returns a
Object[]
consisting only of 2 empty Strings. The reason for this is:StringBuilder::new
points to theStringBuilder(CharSequence)
ctor. The code in LambdaBootstrap ignores all parameters and calls the default ctor ofStringBuilder
. The result is mapped back to a String afterwards and is of course empty.To fix this I changed the LamdaBootstrap to create a static proxy/delegator method around the ctor (
delegate$ctor
) with the same parameters as the ctor, returning the newly created instance (somehow similar like the factory for the captured lambda - which I removed, see below). When generating theINVOKEDYNAMIC
to our type-adapting callsite, we change theH_NEWINVOKESPECIAL
to a static method call to our proxy. I tried to fix this without using a static method, but although the JLS/class file format allowsH_NEWINVOKESPECIAL
for a handle, when passing this as aHandle
(in constant pool) toINVOKEDYNAMIC
, it causes a class format error. I am not sure if this is a bug in ASM or ifINVOKEDYNAMIC
does not support this. It has nothing to do with the fact of anewinstance
bytecode may be needed - no it's not for MethodHandles!!!In addition, the static factory as replacement for the constructor of the lambda (
get$lambda
) is not needed. If you uselookup.findConstructor()
everything works as it should. So I also removed it. @jdconrad said (#24070 (comment)) that its needed, but it isn't, becauselookup.findConstructor()
returns a MethodHandle that also doesnewinstance
bytecode.Documentation was updated accordingly.
The final TODO is to check why the direct MH in constant pool leads to a ClassFormatError. I will look into lambdas created by javac, maybe I see something that I was missing!