Skip to content

Commit 8149318

Browse files
chadrakoPaul Hohensee
authored andcommitted
8277444: Data race between JvmtiClassFileReconstituter::copy_bytecodes and class linking
Backport-of: 46ae1ee87152742082e6047d0556944d7ae4567d
1 parent 4bc1dc3 commit 8149318

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,11 @@ void JvmtiClassFileReconstituter::write_u8(u8 x) {
996996

997997
void JvmtiClassFileReconstituter::copy_bytecodes(const methodHandle& mh,
998998
unsigned char* bytecodes) {
999+
// We must copy bytecodes only from linked classes.
1000+
// Being linked guarantees we are not getting bytecodes at
1001+
// the same time the linking process is rewriting them.
1002+
guarantee(mh->method_holder()->is_linked(), "Bytecodes must be copied from a linked class");
1003+
9991004
// use a BytecodeStream to iterate over the bytecodes. JVM/fast bytecodes
10001005
// and the breakpoint bytecode are converted to their original bytecodes.
10011006

src/hotspot/share/prims/jvmtiEnv.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,18 @@ JvmtiEnv::RetransformClasses(jint class_count, const jclass* classes) {
451451

452452
InstanceKlass* ik = InstanceKlass::cast(klass);
453453
if (ik->get_cached_class_file_bytes() == nullptr) {
454+
// Link the class to avoid races with the rewriter. This will call the verifier also
455+
// on the class. Linking is also done in VM_RedefineClasses below, but we need
456+
// to keep that for other VM_RedefineClasses callers.
457+
JavaThread* THREAD = current_thread;
458+
ik->link_class(THREAD);
459+
if (HAS_PENDING_EXCEPTION) {
460+
// Retransform/JVMTI swallows error messages. Using this class will rerun the verifier in a context
461+
// that propagates the VerifyError, if thrown.
462+
CLEAR_PENDING_EXCEPTION;
463+
return JVMTI_ERROR_INVALID_CLASS;
464+
}
465+
454466
// Not cached, we need to reconstitute the class file from the
455467
// VM representation. We don't attach the reconstituted class
456468
// bytes to the InstanceKlass here because they have not been
@@ -3428,7 +3440,8 @@ jvmtiError
34283440
JvmtiEnv::GetBytecodes(Method* method, jint* bytecode_count_ptr, unsigned char** bytecodes_ptr) {
34293441
NULL_CHECK(method, JVMTI_ERROR_INVALID_METHODID);
34303442

3431-
methodHandle mh(Thread::current(), method);
3443+
JavaThread* current_thread = JavaThread::current();
3444+
methodHandle mh(current_thread, method);
34323445
jint size = (jint)mh->code_size();
34333446
jvmtiError err = allocate(size, bytecodes_ptr);
34343447
if (err != JVMTI_ERROR_NONE) {
@@ -3437,6 +3450,13 @@ JvmtiEnv::GetBytecodes(Method* method, jint* bytecode_count_ptr, unsigned char**
34373450

34383451
(*bytecode_count_ptr) = size;
34393452
// get byte codes
3453+
// Make sure the class is verified and rewritten first.
3454+
JavaThread* THREAD = current_thread;
3455+
mh->method_holder()->link_class(THREAD);
3456+
if (HAS_PENDING_EXCEPTION) {
3457+
CLEAR_PENDING_EXCEPTION;
3458+
return JVMTI_ERROR_INVALID_CLASS;
3459+
}
34403460
JvmtiClassFileReconstituter::copy_bytecodes(mh, *bytecodes_ptr);
34413461

34423462
return JVMTI_ERROR_NONE;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
/*
26+
* @test
27+
* @bug 8277444
28+
*
29+
* @library /test/lib
30+
* @compile SimpleIdentityTransformer.java
31+
* @run shell MakeJAR.sh retransformAgent
32+
* @run main/othervm -javaagent:retransformAgent.jar RetransformBigClassTest
33+
*/
34+
35+
import jdk.test.lib.compiler.InMemoryJavaCompiler;
36+
37+
/*
38+
* JvmtiClassFileReconstituter::copy_bytecodes restores bytecodes rewritten
39+
* by the linking process. It is used by RetransformClasses.
40+
* JDK-8277444 is a data race between copy_bytecodes and the linking process.
41+
* This test puts the linking process in one thread and the retransforming process
42+
* in another thread. The test uses Class.forName("BigClass", false, classLoader)
43+
* which does not link the class. When the class is used, the linking process starts.
44+
* In another thread retransforming of the class is happening.
45+
* We generate a class with big methods. A number of methods and their size are
46+
* chosen to make the linking and retransforming processes run concurrently.
47+
* We delay the retransforming process to follow the linking process.
48+
* If there is no synchronization between the processes, a data race will happen.
49+
*/
50+
public class RetransformBigClassTest extends AInstrumentationTestCase {
51+
52+
private static final Object LOCK = new Object();
53+
private static final int COUNTER_INC_COUNT = 2000; // A number of 'c+=1;' statements in methods of a class.
54+
private static final int MIN_LINK_TIME_MS = 60; // Large enough so the linking and retransforming processes run in parallel.
55+
private static final int RETRANSFORM_CLASSES_DELAY_MS = 37; // We manage to create a data race when a delay is in the range 0.52x - 0.62x of MIN_LINK_TIME_MS.
56+
57+
private static Class<?> bigClass;
58+
private static byte[] bigClassBytecode;
59+
60+
private Thread retransformThread;
61+
62+
RetransformBigClassTest() {
63+
super("RetransformBigClassTest");
64+
}
65+
66+
public static void main(String[] args) throws Throwable {
67+
new RetransformBigClassTest().runTest();
68+
}
69+
70+
protected final void doRunTest() throws Throwable {
71+
ClassLoader classLoader = new ClassLoader() {
72+
@Override
73+
protected Class<?> findClass(String name) throws ClassNotFoundException {
74+
if (name.equals("BigClass")) {
75+
return defineClass(name, bigClassBytecode, 0, bigClassBytecode.length);
76+
}
77+
78+
return super.findClass(name);
79+
}
80+
};
81+
synchronized (LOCK) {
82+
bigClass = Class.forName("BigClass", false, classLoader);
83+
LOCK.notify();
84+
}
85+
// Make a use of the BigClass
86+
assertTrue(bigClass.getConstructor().newInstance().hashCode() != 0);
87+
retransformThread.join();
88+
}
89+
90+
private byte[] createClassBytecode(String className, int methodCount) throws Exception {
91+
String methodBody = "";
92+
for (int j = 0; j < COUNTER_INC_COUNT; j++) {
93+
methodBody += "c+=1;";
94+
}
95+
96+
String classSrc = "public class " + className + " { int c;";
97+
98+
for (int i = 0; i < methodCount; i++) {
99+
classSrc += "\npublic void m" + i + "(){";
100+
classSrc += methodBody;
101+
classSrc += "\n}";
102+
}
103+
classSrc += "\n}";
104+
105+
return InMemoryJavaCompiler.compile(className, classSrc);
106+
}
107+
108+
// We need a number of methods such that the linking time is greater than
109+
// or equal to MIN_LINK_TIME_MS.
110+
// We create a class having 5 methods and trigger the linking process.
111+
// We measure the time taken and use it to calculate the needed number.
112+
private int findMethodCount() throws Exception {
113+
int methodCount = 5;
114+
final String className = "BigClass" + methodCount;
115+
final byte[] bytecode = createClassBytecode(className, methodCount);
116+
ClassLoader classLoader = new ClassLoader() {
117+
@Override
118+
protected Class<?> findClass(String name) throws ClassNotFoundException {
119+
if (name.equals(className)) {
120+
return defineClass(name, bytecode, 0, bytecode.length);
121+
}
122+
123+
return super.findClass(name);
124+
}
125+
};
126+
var bigClass = Class.forName(className, false, classLoader);
127+
long startTime = System.nanoTime();
128+
assertTrue(bigClass.getConstructor().newInstance().hashCode() != 0);
129+
double linkTimeMs = (System.nanoTime() - startTime) / 1000000.0;
130+
System.out.println("Link time for a class with " + methodCount + " methods each having " + COUNTER_INC_COUNT + " counter increments: " + Math.round(linkTimeMs));
131+
if (linkTimeMs < MIN_LINK_TIME_MS) {
132+
methodCount = (int)Math.round((MIN_LINK_TIME_MS * methodCount) / linkTimeMs);
133+
}
134+
System.out.println("The number of methods to exceed " + MIN_LINK_TIME_MS + " ms linking time: " + methodCount);
135+
return methodCount;
136+
}
137+
138+
@Override
139+
protected void setUp() throws Exception {
140+
super.setUp();
141+
142+
bigClassBytecode = createClassBytecode("BigClass", findMethodCount());
143+
fInst.addTransformer(new SimpleIdentityTransformer());
144+
retransformThread = new Thread(() -> {
145+
try {
146+
synchronized (LOCK) {
147+
while (bigClass == null) {
148+
System.out.println("[retransformThread]: Waiting for bigClass");
149+
LOCK.wait();
150+
}
151+
}
152+
Thread.sleep(RETRANSFORM_CLASSES_DELAY_MS);
153+
fInst.retransformClasses(bigClass);
154+
} catch (Exception e) {
155+
e.printStackTrace();
156+
}
157+
});
158+
retransformThread.start();
159+
Thread.sleep(100);
160+
}
161+
}

0 commit comments

Comments
 (0)