8301703: java.base jdk.internal.foreign.abi.BindingSpecializer uses ASM to generate classes#13247
8301703: java.base jdk.internal.foreign.abi.BindingSpecializer uses ASM to generate classes#13247JornVernee wants to merge 28 commits intoopenjdk:masterfrom
Conversation
Co-authored-by: Paul Sandoz <paul.d.sandoz@googlemail.com>
Co-authored-by: Paul Sandoz <paul.d.sandoz@googlemail.com>
|
👋 Welcome back jvernee! A progress list of the required criteria for merging this PR into |
|
@JornVernee The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java
Outdated
Show resolved
Hide resolved
|
|
||
| if (PERFORM_VERIFICATION) { | ||
| boolean printResults = false; // only print in case of exception | ||
| CheckClassAdapter.verify(new ClassReader(bytes), null, printResults, new PrintWriter(System.err)); |
There was a problem hiding this comment.
Classfile API provides verification functionality as well, as seen here:
There was a problem hiding this comment.
Does this provide additional verification over what is already done just by generating the class?
For instance, IIRC the ASM verifier could catch e.g. stack underflow, but that seems to be caught already by the new implementation without running the verifier.
There was a problem hiding this comment.
Yes, for instance, the class generation doesn't check operand stack underflow, that you can generate code with invalid pops. The classfile verifier catches this:
There was a problem hiding this comment.
This seems to be caught without running the verifier as well:
Caused by: java.lang.IllegalStateException: Operand stack underflow at bytecode offset 79 of method invoke(SegmentAllocator,MemorySegment,MemorySegment)
...
at java.base/jdk.internal.classfile.impl.StackMapGenerator.generatorError(StackMapGenerator.java:876)
at java.base/jdk.internal.classfile.impl.StackMapGenerator.generatorError(StackMapGenerator.java:832)
at java.base/jdk.internal.classfile.impl.StackMapGenerator$Frame.decStack(StackMapGenerator.java:1024)
at java.base/jdk.internal.classfile.impl.StackMapGenerator.processBlock(StackMapGenerator.java:600)
at java.base/jdk.internal.classfile.impl.StackMapGenerator.processMethod(StackMapGenerator.java:420)
at java.base/jdk.internal.classfile.impl.StackMapGenerator.generate(StackMapGenerator.java:293)
at java.base/jdk.internal.classfile.impl.StackMapGenerator.<init>(StackMapGenerator.java:232)
at java.base/jdk.internal.classfile.impl.DirectCodeBuilder$4.writeBody(DirectCodeBuilder.java:333)
at java.base/jdk.internal.classfile.impl.UnboundAttribute$AdHocAttribute.writeTo(UnboundAttribute.java:914)
at java.base/jdk.internal.classfile.impl.AttributeHolder.writeTo(AttributeHolder.java:56)
at java.base/jdk.internal.classfile.impl.DirectMethodBuilder.writeTo(DirectMethodBuilder.java:136)
at java.base/jdk.internal.classfile.impl.BufWriterImpl.writeList(BufWriterImpl.java:194)
at java.base/jdk.internal.classfile.impl.DirectClassBuilder.build(DirectClassBuilder.java:176)
at java.base/jdk.internal.classfile.Classfile.build(Classfile.java:218)
at java.base/jdk.internal.classfile.Classfile.build(Classfile.java:200)
at java.base/jdk.internal.classfile.Classfile.build(Classfile.java:186)
at java.base/jdk.internal.foreign.abi.BindingSpecializer.specializeHelper(BindingSpecializer.java:186)
...
(I think ASM will just throw an array index OOB exception when processing a subsequent frame)
There was a problem hiding this comment.
Just to clarify: I'm looking for the kind of errors that don't get caught by just generating the class, but are also more informative than the default VerifyError you would get from loading an invalid class.
There was a problem hiding this comment.
Classfile API does have some sort of error reporting mechanism in verification; at least it tracks the error context.
The method dumping is providing a yaml tree representation of the class file to the provided Consumer, and the VerifyError itself already includes the error context. What exact specific information are you looking for that's provided by ASM? We might be able to add it to Classfile API too.
There was a problem hiding this comment.
I'm not really looking for anything specific. I'm just trying to figure out if it's worth it to keep the PERFORM_VERIFICATION flag, and change it to call the verifier in the new impl. i.e. does it catch more errors than just generating and loading the class would (or does it output the errors in a better format).
I had a brief look at the implementation, and it seems that the consumer is for detailed logging of the verification process. I think in this case we're just interested in catching errors.
I already tried switching the code to call Classfile.parse(newBytes).verify(null).forEach(System.err::println), but the error I artificially introduced was already being caught during class generation. So I'm wondering if having a separate verification pass will actually catch any additional errors.
There was a problem hiding this comment.
I believe that, in order to generate the actual bytecodes, the classfile API does a full verification pass (as it needs to infer the stackmap information). This leads me to believe that, yes, most (but probably all) errors would be detected simply by generating code. Maybe @asotona can clarify.
There was a problem hiding this comment.
Stackmap generator does not perform full verification, it only performs fast pass through the code and hits only errors preventing to construct valid stack maps (as for example stack underflow).
Verifyier on the other hand does full verification similar to when the class is loaded.
There was a problem hiding this comment.
Classfile API allows to pass-through complete class members or attributes, so resulting class might be invalid even the particular code and stack maps have been generated.
This is brief list of verifications performed when verify is explicitly called:
- class version
- descriptors
- instruction opcodes
- CP entries matching instructions (matching types, array dimensions limits, etc...)
- stack map frames and assignability between reference types
- flow control (branches, exception handlers, falling through code end, falling through initialised, etc..)
- exception tables (offsets, throwing Throwables, etc...)
- local variable tables
- switches (low/high, number of keys, etc...)
- method calls (arguments, operands, constructor call target, constructors returning void, etc...)
- return values
| } | ||
| } | ||
|
|
||
| private void emitInvokeStatic(Class<?> owner, String methodName, String descriptor) { |
There was a problem hiding this comment.
It is great to see these ad-hoc routines (most of which exist in one form or another in all our JDK code generators) go away!
| ConstantDescs.DEFAULT_NAME, | ||
| OBJECT_DESC, | ||
| BSM_CLASS_DATA); | ||
| private static final ClassDesc CD_Arena = desc(Arena.class); |
There was a problem hiding this comment.
I love that we're able to describe what we need to use at a higher-level of abstraction than just strings.
asotona
left a comment
There was a problem hiding this comment.
It looks good. I would only consider keeping the PERFORM_VERIFICATION option.
|
Thanks for the reviews. I've now re-implemented the |
|
The parent pull request that this pull request depends on has now been integrated and the target branch of this pull request has been updated. This means that changes from the dependent pull request can start to show up as belonging to this pull request, which may be confusing for reviewers. To remedy this situation, simply merge the latest changes from the new target branch into this pull request by running commands similar to these in the local repository for your personal fork: git checkout CFA_jdk
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# if there are conflicts, follow the instructions given by git merge
git commit -m "Merge master"
git push |
|
@JornVernee this pull request can not be integrated into git checkout CFA_jdk
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
|
@JornVernee 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 |
|
/integrate |
|
Going to push as commit b39a9bf.
Your commit was automatically rebased without conflicts. |
|
@JornVernee Pushed as commit b39a9bf. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Rewrite BindingSpecializer to use the new class file API.
Note: There is a big try/catch/finally block generated in the
specializemethod that currently uses labels. I looked at replacing this with a call toCodeBuilder::tryingbut it would require threading the nested code builders through all theemit*methods, which currently access the 'global' CodeBuilder instance attached to the BindingSpecializer instance. Since there didn't really seem to be a big benefit to this, I've kept that try/catch/finally block as is, using labels.The current implementation could also use
CheckClassAdapterto do additional verification on the generated bytecode (ahead of the VM's verifier). I'm not sure if the new API has a replacement for that?Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/13247/head:pull/13247$ git checkout pull/13247Update a local copy of the PR:
$ git checkout pull/13247$ git pull https://git.openjdk.org/jdk.git pull/13247/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 13247View PR using the GUI difftool:
$ git pr show -t 13247Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/13247.diff
Webrev
Link to Webrev Comment