-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
8335619: Add an @apiNote to j.l.i.ClassFileTransformer to warn about recursive class loading and ClassCircularityErrors #20011
Conversation
…recursive class loading and ClassCircularityErrors
👋 Welcome back simonis! A progress list of the required criteria for merging this PR into |
@simonis 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 97 new commits pushed to the
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 |
Webrevs
|
src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java
Outdated
Show resolved
Hide resolved
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.
Small nit, otherwise ok.
As usual, excellent bug description.
src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java
Outdated
Show resolved
Hide resolved
…other reference to the JVMS and fixed its section number
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.
LGTM
Notice that according to the CSR FAQ, I don't think that this change requires a CSR because it is not changing the specification but merely describes the actual behavior in some more detail:
|
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.
Looks good. We can always file a retroactive CSR if people find this clarification worth of archiving/recording.
@AlanBateman would you mind having a quick look at this trivial, documentation-only PR and let me know if you're OK with it? Thank you and best regards, |
(Catching up as I was away for a few days). I agree it would be useful to have an API note on this topic. There were several reports of deadlocks, ClassCircularityError, and other hard to diagnose issues when support for instrumentation was originally introduced in JDK 5. I think agent maintainers learned to exclude many of the core classes in java.base but that is always a moving target and there have been a few more sightings/reports recently (maybe due to newer features doing more in Java rather than in the VM). I agree that the ClassFileTransformer class description is the best place for this. My initial reaction was to wonder about the j.l.instrument package description but it is more focused on starting agents and the JAR file attributes so I think your proposed location is best.. As regards the text then it might be better to start with a sentence to say that the "transform" method may be called from critical points in JDK core classes or called to transform JDK core classes. You've got some of this in second sentence but I think better to lead with it before revealing one of the several possible things that can happen. I think part of the lead in needs to say "great care must be taken" or something like that to get across the point that the ClassFileTransformer implementor has the potential to cause many possible issues. For the same class and linkage error then probably best to not use the phrase "transitively requires" as it's too close "requires transitive" used in module declarations. Note that the |
Thanks a lot for looking into this @AlanBateman. I've tried to integrate your suggestions into the new version of the PR. I already used the Please let me know if this sounds better now or if you see further room for improvement. |
src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java
Outdated
Show resolved
Hide resolved
src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java
Outdated
Show resolved
Hide resolved
* {@code D} will permanently fail with the same error at any subsequent attempt. | ||
* This means that a {@link LinkageError} triggered during transformation of | ||
* {@code C} in a class {@code D} not directly related to {@code C} can repeatedly | ||
* occur later in arbitrary user code which uses {@code D}. |
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 paragraph looks okay but I can't help thinking we should have something in normative text to reference that specifies the reentrancy behavior. Maybe I missed it but I thought we have something in the API docs on this.
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 haven't found anything either. The only specification-relevant mentioning of the issue I found is in the JVMTI Specification referenced at the beginning of the PR:
Care must be taken to avoid perturbing dependencies, especially when instrumenting core classes.
The example that follows describes an infinite recursion when instrumenting the the j.l.Object()
constructor.
I think the exact reentrancy behavior isn't specified anywhere. Not even the exact that should be thrown in such a case is specified (see 8164165: JVM throws incorrect exception when ClassFileTransformer.transform() triggers class loading of class already being loaded for a discussion of different scenarios).
I think the real problem is that the JVMS predates the JVMTI specification and the interaction between instrumentation and class loading isn't clearly defined. I think it might even be possible to treat class loading errors during transformation differently, such that they will not lead to a permanent resolution error for the corresponding constant pool entries. I know that this will violate the current section § 5.4.3 Resolution (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3) of the JVM specification which mandates that "if an attempt by the Java Virtual Machine to resolve a symbolic reference fails because an error is thrown that is an instance of LinkageError (or a subclass), then subsequent attempts to resolve the reference always fail with the same error that was thrown as a result of the initial resolution attempt". But as I wrote, that predates JVMTI and when JVMTI was added, we missed the opportunity to specify its exact impact on class loading and resolution.
But all this is a much bigger discussion. Maybe we should open another issue for it?
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've created JDK-8336296) for the spec issues.
Last version still looks good. |
Thanks everybody for the quick and helpful reviews and @AlanBateman for opening JDK-8336296 |
/integrate |
Going to push as commit eec0e15.
Your commit was automatically rebased without conflicts. |
Since Java 5 the
java.lang.instrument
package provides services that allow Java programming language agents to instrument (i.e. modify the bytecode) of programs running on the Java Virtual Machine. Thejava.lang.instrument
functionality is based and implemented on top of the native Java Virtual Machine Tool Interface (JVMTI) also introduced in Java 5. But because thejava.lang.instrument
API is a pure Java API and uses Java classes to instrument Java classes it imposes some usage restrictions which are not very well documented in its API specification.E.g. the section on "Bytecode Instrumentation" in the JVMTI specification explicitly warns that special "Care must be taken to avoid perturbing dependencies, especially when instrumenting core classes". The risk of such "perturbing dependencies" is obviously much higher in a Java API like
java.lang.instrument
, but a more detailed explanation and warning is missing from its API documentation.The most evident class file transformation restriction is that while a class A is being loaded and transformed it is not possible to use this same class directly or transitively from the
ClassFileTransformer::transform()
method. Violating this rule will result in aClassCircularityError
(the exact error type is disputable as can be seen in 8164165: JVM throws incorrect exception when ClassFileTransformer.transform() triggers class loading of class already being loaded, but the result would be a `LinkageError in any case).The risk to run into such a
ClassCircularityError
error increases with the amount of code a transforming agent is transitively using from thetransform()
method. Using popular libraries like ASM, ByteBuddy, etc. for transformation further increases the probability of running into such issues, especially if the agent aims to transform core JDK library classes.By default, the occurrence of a
ClassCircularityError
inClassFileTransformer::transform()
will be handled gracefully with the only consequence that the current transformation target will be loaded unmodified (seeClassFileTransformer
API spec: "throwing an exception has the same effect as returning null"). But unfortunately, it can also have a subtle but at the same time much more far-reaching consequence. If theClassCircularityError
occurs during the resolution of a constant pool entry in another, unrelated class, that class will remain in an error state forever due to §5.4.3 Resolution of the Java Virtual Machine Specification which mandates that "if an attempt by the Java Virtual Machine to resolve a symbolic reference fails because an error is thrown that is an instance of LinkageError (or a subclass), then subsequent attempts to resolve the reference always fail with the same error that was thrown as a result of the initial resolution attempt.". This means that theClassCircularityError
can repeatedly be thrown much later, in user code which is completely unrelated to class file transformation if that code happens to use the same class that failed to resolve a reference during instrumentation. A good example for this scenario are the sporadicClassCircularityError
that were seen in user code while usingConcurrentHashMap
s caused by a change in the popular ByteBuddy library (see ByteBuddy #1666 for more details).I'd therefor like to propose to add an
@apiNote
to j.l.i.ClassFileTransformer which makes users of the API aware of these potential issues:Please feel free to wordsmith the suggested
@apiNote
text :)Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/20011/head:pull/20011
$ git checkout pull/20011
Update a local copy of the PR:
$ git checkout pull/20011
$ git pull https://git.openjdk.org/jdk.git pull/20011/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 20011
View PR using the GUI difftool:
$ git pr show -t 20011
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/20011.diff
Webrev
Link to Webrev Comment