Skip too new versioned directories of multi-release JARs when instrumenting#25648
Skip too new versioned directories of multi-release JARs when instrumenting#25648bot-gradle merged 5 commits intomasterfrom
Conversation
|
I think this should have a test that the MRJAR entries are ignored, though I'm not sure how difficult that would be to add. |
e807519 to
90dca7f
Compare
Thanks, I've added unit test coverage. Setting up a proper integration test is harder, I don't think there is enough ROI. |
|
@bot-gradle test RfR |
|
I've triggered the following builds for you. Click here to see all build failures. |
bamboo
left a comment
There was a problem hiding this comment.
It's looking good overall. I made a few suggestions which could be addressed in a follow-up PR as needed.
There was a problem hiding this comment.
🎨 Extract complex expression into something like isMultiReleaseJarManifestEntry(entry)?
There was a problem hiding this comment.
🧪 I wonder if it makes sense to introduce a test to ensure the constant matches the bundled asm 🤔
There was a problem hiding this comment.
ASM doesn't expose the latest supported Java version anywhere but in the release notes. The closest thing I see it to declare a constant with the ASM version in AsmConstants and have a test that checks it against this one. Failing test may be a stronger nagging towards updating the constant.
I'll file an issue upstream in the ASM project, maybe they expose the info themselves - this would make things easier. For the test, it can be added when addressing ##25649.
There was a problem hiding this comment.
🚀 I'd avoid inspecting every entry name by making isMultiReleaseJar a nullable boolean and changing the predicate to isMultiReleaseJar == null && isManifestName(entryName)
There was a problem hiding this comment.
I'd avoid inspecting every entry name by making isMultiReleaseJar a nullable boolean
I think we can even replace the signature file check with manifest check at this point, so we only need to look at the manifest and bail out of the entry for-each immediately. The manifest is typically the first JAR entry, which is nice. But this feels like out of scope for this PR. So far I applied your suggestion.
There was a problem hiding this comment.
🚀 Would it be possible to avoid the check once the manifest has been processed?
There was a problem hiding this comment.
Would it be possible to avoid the check once the manifest has been processed?
We don't care about manifest here when transforming for the agent. We skip unsupported classes always: if the manifest has the attribute because they are unsupported, and if not - because they cannot be loaded as classes, only as resources. Maybe the comment above is a bit confusing and fails to convey this?
There was a problem hiding this comment.
🧪 I'd also include the assertContainsFile("Foo.class") assertion here for clarity.
There was a problem hiding this comment.
🎨 I had to pause to understand the checks multi-release jar manifest so I suggest renaming to something like legacy instrumentation preserves versioned resources in single-release jars (or non multi-release jars or jars without a multi-release attribute).
There was a problem hiding this comment.
❓ Any particular reason not to load the manifest on demand?
There was a problem hiding this comment.
I intended to piggy-back on isManifestPresentAndFirstEntry initially, but discarded. Replaced with a lazy load (and made it possible to manifest even after creating the fixture with checkManifest = false).
This commit encapsulates all transformation logic into a separate class instead of pulling out pieces of it into the policy instance. Policy now serves as a factory for transformations. The refactoring paves way to extracting more info from JAR when preparing the instrumentation strategy.
ASM can only process class files up to a certain version. This can be problematic for multi-release JARs containing newer classes in the appropriate versioned directory. Such JARs can be used on a Gradle-supported JVM, but prior to this change, instrumentation failed even if the unsupported classes were never loaded. This commit makes instrumentation skip unsupported versioned directories. For legacy instrumentation, these directories are filtered out completely. For agent-based instrumentation, classes of the unsupported versions are not instrumented and aren't placed into the transformed archive. Unfortunately, ASM doesn't provide any kind of constant to specify the latest supported class file version, so we have to keep track of this manually.
In some cases JavaVersion class may come from a TAPI client which can be outdated and lack necessary constants.
90dca7f to
80994cb
Compare
|
@bot-gradle test and merge |
This is a backport of #25648, but only the legacy instrumentation part. The agent-based instrumentation is only available in 8+.
ASM library that we use for bytecode instrumentation has an upper limit to the bytecode version it can understand.
When processing multi-release JARs, the restriction caused errors even when running on the supported JVM, because instrumentation was processing all classes inside the JAR regardless of whether it was multi-release or not: e.g. JAR with versioned directories for Java 21 and Java 11 was still problematic even when running on Java 11 JVM, where new version of classes compiled for Java 21 aren't actually used.
This PR rewrites the instrumentation code to skip all versioned directories which aren't supported. The resulting instrumented JARs only contain versioned directories up to latest ASM-supported Java version. This way the cached transformed JAR is independent of the version of the JVM running Gradle.
However, there's a hole left when the build runs on the not-yet-supported-by-asm (and, therefore, Gradle) JVM, as it can then load non-instrumented classes from the versioned directories of the original JARs. This is going to be addressed in some follow-up PR.
Part of #25434.