JDK-8283597 : [REDO] Invalid generic signature for redefined classes
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 17
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2022-03-24
  • Updated: 2023-08-11
  • Resolved: 2022-04-04
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17 JDK 19
17.0.5-oracleFixed 19 b17Fixed
Related Reports
Cloners :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8283587 :  
Description
ADDITIONAL SYSTEM INFORMATION :
MacOS 11.6.3 (x64)/ Linux Debian 9 (x64 VM)
Java: Temurin 17.0.2 / Oracle 17.0.2

A DESCRIPTION OF THE PROBLEM :
At New Relic we instrument classes to send telemetry data to our servers. We use ASM for bytecode manipulation.

We instrument CompletableFuture, and since Java 17 it has returned an improper value when Class#getGenericSignature0() is called.
This works fine when the running on Java 16 or lower.

The following has a repro app and more information on the investigation done.
https://github.com/meiao/genericSignature-jdk17-bug

Note that the source code added in the ticket will not reproduce the issue by itself. Use the repro app that will download the New Relic agent and execute the app in a way that the bug will occur.

REGRESSION : Last worked in version 16

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Modify CompletableFuture using a Java agent.
Using reflection, call Class#getGenericSignature0() on CompletableFuture.class.
Check the returned String.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/concurrent/Future<TT;>;Ljava/util/concurrent/CompletionStage<TT;>;
ACTUAL -
java/util/concurrent/CompletableFuture

---------- BEGIN SOURCE ----------
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class Tester {

    public static void main(String... args) throws Throwable {

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandles.Lookup classLookup = MethodHandles.privateLookupIn(Class.class, lookup);
        MethodHandle getGenericSignature0 = classLookup.findVirtual(Class.class, "getGenericSignature0", MethodType.methodType(String.class));
        Object genericSignature = getGenericSignature0.invoke(CompletableFuture.class);

        System.out.println();
        System.out.println("getGenericSignature0: " + genericSignature);
        System.out.println("expected: <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/concurrent/Future<TT;>;Ljava/util/concurrent/CompletionStage<TT;>;");
        System.out.println();

        Type type = Tester.class.getDeclaredMethod("testing").getGenericReturnType();
        System.out.println("TYPE: " + type);
    }

    public CompletableFuture<List<String>> testing() {
        return null;
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Not modifying the CompletableFuture class. Which is not desirable.

FREQUENCY : always



Comments
Fix request [17u] I backport this for parity with 17.0.5-oracle. A small fix but in core functionality, but we should fix this. Clean backport. Test passes. SAP nightly testing passed.
08-06-2022

A pull request was submitted for review. URL: https://git.openjdk.java.net/jdk17u-dev/pull/439 Date: 2022-06-07 11:44:55 +0000
07-06-2022

Changeset: 0cd46f65 Author: Alex Menkov <amenkov@openjdk.org> Date: 2022-04-04 20:39:44 +0000 URL: https://git.openjdk.java.net/jdk/commit/0cd46f655a77662a3ffee057e03025f37530f3aa
04-04-2022

Failure with "@compile" after "@run main RedefineClassHelper" is unclear. I saw the failure in my local environment, but cannot reproduce it anymore (with ~100 runs). With my current understanding this should not affect other tests as it compiles everything to test dir.
30-03-2022

InMemoryJavaCompiler error has the same reason, but the case is more tricky. RedefineClassHelper class has 2 ways to use: 1) ("@run main RedefineClassHelper") - creates redefineagent.jar, requires ClassFileInstaller 2) static methods redefineClass - requires InMemoryJavaCompiler The test failed (TestAddDeleteMethods.java) use both ways. lib directory contains ClassFileInstaller, but does not contain InMemoryJavaCompiler So we have: - TestRedefineObject.java does "@run driver jdk.test.lib.helpers.ClassFileInstaller Agent" this causes compilation ClassFileInstaller to the lib dir; - RedefineGenericSignatureTest does "@run compile", so InMemoryJavaCompiler compiled to test dir - RedefineGenericSignatureTest does ""@run main RedefineClassHelper" this compiles RedefineClassHelper to lib dir, but javac doesn't compile InMemoryJavaCompiler as it's present in test dir - TestAddDeleteMethods does ""@run main RedefineClassHelper" - works fine - TestAddDeleteMethods calls RedefineClassHelper.redefineClass() - it fails with NoClassDefFoundError for InMemoryJavaCompiler
30-03-2022

[~amenkov] Thanks for the additional information. I thought "@library /test/lib" was supposed to solve that kind of problem. We never explicitly build the test library using @build. We have other failure modes e.g. "java.lang.NoClassDefFoundError: jdk/test/lib/compiler/InMemoryJavaCompiler". So IIUC compiling RedefineGenericSignatureTest.java causes compilation of dependencies from the test library which get put into the test directory; whereas if "@run main RedefineClassHelper" executes first, those same dependencies get compiled into the lib directory. But if a later test uses the same lib file it should also be implicitly compiled into that tests directory, as it would not be found in the other tests directory, and is not present in the lib directory - so I don't completely follow what is going on here. I guess I would need to look at each failure in relation to the execution sequence relative to the RedefineGenericSignatureTest. Regardless we still seem to have this problem that test lib files are not getting compiled as needed and placed where needed.
30-03-2022

I tried to move "@run compile -g RedefineGenericSignatureTest.java" to be executed after "@run main RedefineClassHelper", but got NoClassDefFoundError for some other tests. I believe the reason is the same - "@run compile" compiles some classes from libs to test-specific location and later we get NoClassDefFoundError for other test because JTReg cannot detect that implicit dependency.
29-03-2022

[~dholmes] JTReg uses 2 locations for .class files: for libraries and for test itself: "run compile -g RedefineGenericSignatureTest.java" causes compilation of the test and all their dependent classes to test-specific directory. So I see jdk/test/lib/helpers/ClassFileInstaller.class in test-support/.../classes/2/serviceability/jvmti/RedefineClasses/RedefineGenericSignatureTest.d Then "@run main RedefineClassHelper" causes compilation of RedefineClassHelper to library directories, but this action includes test classpath, so ClassFileInstaller is not recompiled to lib directory. Later other test is started and does "@run main RedefineClassHelper" JTReg sees that RedefineClassHelper.class is present in lib directory and does not compile it: =============================== command: build RedefineClassHelper reason: Named class compiled on demand elapsed time (seconds): 0.001 result: Passed. All files up to date =============================== But when it tries to executes it we get NoClassDefFoundError.
29-03-2022

[~amenkov] I'm still not understanding the failure modes previously caused by the test. Even if the explicit compile command is not present, the RedefineGenericSignatureTest.java file will be compiled implicitly by the "@run ... RedefineGenericSignatureTest". How does the explicit compilation cause a difference that then results in other tests failing? I'm concerned this has exposed a bigger problem that we will now hide again without fully understanding it.
29-03-2022

A pull request was submitted for review. URL: https://git.openjdk.java.net/jdk/pull/8007 Date: 2022-03-28 22:19:49 +0000
28-03-2022

After pushing fix for JDK-8282241 random tests from serviceability/jvmti/RedefineClasses start to fail with java.lang.NoClassDefFoundError: jdk/test/lib/helpers/ClassFileInstaller$Manifest This is caused by JTReg classpath directories sharing between tests. JTReg recommends to add @build tags for all used libraries, but RedefineClasses don't do it (and this approach doesn't look as a good to me). Comparing new test from JDK-8282241 fix with other tests in RedefineClasses the only significant difference is new test uses run compile -g RedefineGenericSignatureTest.java to include additional debug info, but actually it's not needed as the test only needs source file data and it's included by default.
28-03-2022