Skip to content

Commit 4815566

Browse files
author
Alex Menkov
committedFeb 9, 2023
8228604: StackMapFrames are missing from redefined class bytes of retransformed classes
Reviewed-by: cjplummer, sspitsyn
1 parent 5147969 commit 4815566

File tree

3 files changed

+349
-6
lines changed

3 files changed

+349
-6
lines changed
 

‎src/hotspot/share/classfile/classFileParser.cpp

+2-6
Original file line numberDiff line numberDiff line change
@@ -1895,7 +1895,6 @@ const ClassFileParser::unsafe_u2* ClassFileParser::parse_localvariable_table(con
18951895

18961896
static const u1* parse_stackmap_table(const ClassFileStream* const cfs,
18971897
u4 code_attribute_length,
1898-
bool need_verify,
18991898
TRAPS) {
19001899
assert(cfs != nullptr, "invariant");
19011900

@@ -1906,12 +1905,9 @@ static const u1* parse_stackmap_table(const ClassFileStream* const cfs,
19061905
const u1* const stackmap_table_start = cfs->current();
19071906
assert(stackmap_table_start != nullptr, "null stackmap table");
19081907

1909-
// check code_attribute_length first
1908+
// check code_attribute_length
19101909
cfs->skip_u1(code_attribute_length, CHECK_NULL);
19111910

1912-
if (!need_verify && !DumpSharedSpaces) {
1913-
return nullptr;
1914-
}
19151911
return stackmap_table_start;
19161912
}
19171913

@@ -2535,7 +2531,7 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs,
25352531
classfile_parse_error("Multiple StackMapTable attributes in class file %s", THREAD);
25362532
return nullptr;
25372533
}
2538-
stackmap_data = parse_stackmap_table(cfs, code_attribute_length, _need_verify, CHECK_NULL);
2534+
stackmap_data = parse_stackmap_table(cfs, code_attribute_length, CHECK_NULL);
25392535
stackmap_data_length = code_attribute_length;
25402536
parsed_stackmap_attribute = true;
25412537
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/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+
* @test
26+
*
27+
* @bug 8228604
28+
*
29+
* @requires vm.jvmti
30+
* @modules java.base/jdk.internal.org.objectweb.asm
31+
* @library /test/lib
32+
*
33+
* @run main/othervm/native -agentlib:MissedStackMapFrames MissedStackMapFrames
34+
*/
35+
36+
import jdk.internal.org.objectweb.asm.ClassReader;
37+
import jdk.internal.org.objectweb.asm.ClassVisitor;
38+
import jdk.internal.org.objectweb.asm.MethodVisitor;
39+
import jdk.internal.org.objectweb.asm.Opcodes;
40+
41+
42+
public class MissedStackMapFrames {
43+
static {
44+
System.loadLibrary("MissedStackMapFrames");
45+
}
46+
47+
/* For each test class:
48+
* - loads class (JNIEnv::FindClass);
49+
* - retransforms class (jvmtiEnv::RetransformClasses).
50+
* Saves class bytes passed to ClassFileLoadHook.
51+
*/
52+
private static native boolean doTest();
53+
54+
/* methods to analyze doTest results */
55+
private static native int testCount();
56+
private static native Class testClass(int idx);
57+
private static native byte[] loadBytes(int idx);
58+
private static native byte[] retransformBytes(int idx);
59+
60+
private static int getStackMapFrameCount(byte[] classfileBuffer) {
61+
ClassReader reader = new ClassReader(classfileBuffer);
62+
final int[] frameCount = {0};
63+
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9) {
64+
@Override
65+
public MethodVisitor visitMethod(int access, String name,
66+
String descriptor, String signature,
67+
String[] exceptions) {
68+
return new MethodVisitor(Opcodes.ASM9) {
69+
private int methodFrames = 0;
70+
@Override
71+
public void visitFrame(int type, int numLocal, Object[] local,
72+
int numStack, Object[] stack) {
73+
methodFrames++;
74+
}
75+
@Override
76+
public void visitEnd() {
77+
log(" method " + name + " - " + methodFrames + " frames");
78+
frameCount[0] += methodFrames;
79+
}
80+
};
81+
}
82+
};
83+
reader.accept(cv, 0);
84+
return frameCount[0];
85+
}
86+
87+
private static int checkStackMapFrames(String mode, byte[] classfileBuffer) {
88+
log(mode + ", len = " + classfileBuffer.length);
89+
int frameCount = getStackMapFrameCount(classfileBuffer);
90+
log(" Has stack map frames: " + frameCount);
91+
if (frameCount == 0) {
92+
throw new RuntimeException(mode + " - no stack frames");
93+
}
94+
return frameCount;
95+
}
96+
97+
private static void checkStackMapFrames(String mode, byte[] classfileBuffer, int expectedCount) {
98+
int actualCount = checkStackMapFrames(mode, classfileBuffer);
99+
if (actualCount != expectedCount) {
100+
throw new RuntimeException(mode + " - unexpected stack frames count: " + actualCount
101+
+ " (expected " + expectedCount + ")");
102+
}
103+
}
104+
105+
public static void main(String[] args) throws Exception {
106+
if (!doTest()) {
107+
throw new RuntimeException("Test failed");
108+
}
109+
110+
// verify results
111+
for (int i = 0; i < testCount(); i++) {
112+
Class cls = testClass(i);
113+
byte[] loadBytes = loadBytes(i);
114+
byte[] retransformBytes = retransformBytes(i);
115+
int loadCount = checkStackMapFrames(cls + "(load)", loadBytes);
116+
checkStackMapFrames(cls + "(retransform)", retransformBytes, loadCount);
117+
}
118+
}
119+
120+
private static void log(Object msg) {
121+
System.out.println(msg);
122+
}
123+
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/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+
#include <jni.h>
25+
#include <jvmti.h>
26+
#include <stdio.h>
27+
#include <string.h>
28+
29+
static void _log(const char* format, ...) {
30+
va_list args;
31+
va_start(args, format);
32+
vprintf(format, args);
33+
va_end(args);
34+
fflush(0);
35+
}
36+
37+
static jvmtiEnv* jvmti = nullptr;
38+
39+
static const char* testClassNames[] = {
40+
"java/util/Date", // JDK class in CDS archive
41+
"java/lang/ProcessBuilder", // JDK class not in CDS
42+
"MissedStackMapFrames" // non-JDK class
43+
};
44+
static const int testClassCount = sizeof(testClassNames) / sizeof(testClassNames[0]);
45+
46+
struct SavedClassBytes {
47+
struct Buffer {
48+
unsigned char* bytes;
49+
jint len;
50+
51+
Buffer() : bytes(nullptr), len(0) {}
52+
53+
void save(const unsigned char *bytes, jint len) {
54+
jvmtiError err = jvmti->Allocate(len, &this->bytes);
55+
if (err != JVMTI_ERROR_NONE) {
56+
_log("ClassFileLoadHook: failed to allocate %ld bytes for saved class bytes: %d\n", len, err);
57+
return;
58+
}
59+
memcpy(this->bytes, bytes, len);
60+
this->len = len;
61+
}
62+
63+
jbyteArray get(JNIEnv *env) {
64+
if (bytes == nullptr) {
65+
_log("SavedClassBytes: NULL\n");
66+
return nullptr;
67+
}
68+
69+
jbyteArray result = env->NewByteArray(len);
70+
if (result == nullptr) {
71+
_log("SavedClassBytes: NewByteArray(%ld) failed\n", len);
72+
} else {
73+
jbyte* arrayPtr = env->GetByteArrayElements(result, nullptr);
74+
if (arrayPtr == nullptr) {
75+
_log("SavedClassBytes: Failed to get array elements\n");
76+
result = nullptr;
77+
} else {
78+
memcpy(arrayPtr, bytes, len);
79+
env->ReleaseByteArrayElements(result, arrayPtr, 0);
80+
}
81+
}
82+
return result;
83+
}
84+
85+
};
86+
87+
jclass klass;
88+
89+
Buffer load;
90+
Buffer retransform;
91+
92+
SavedClassBytes() : klass(nullptr) {}
93+
};
94+
95+
static SavedClassBytes savedBytes[testClassCount];
96+
97+
static int testClassIndex(const char *name) {
98+
if (name != nullptr) {
99+
for (int i = 0; i < testClassCount; i++) {
100+
if (strcmp(name, testClassNames[i]) == 0) {
101+
return i;
102+
}
103+
}
104+
}
105+
return -1;
106+
}
107+
108+
109+
extern "C" {
110+
111+
JNIEXPORT void JNICALL
112+
callbackClassFileLoadHook(jvmtiEnv *jvmti_env,
113+
JNIEnv* jni_env,
114+
jclass class_being_redefined,
115+
jobject loader,
116+
const char* name,
117+
jobject protection_domain,
118+
jint class_data_len,
119+
const unsigned char* class_data,
120+
jint* new_class_data_len,
121+
unsigned char** new_class_data) {
122+
int idx = testClassIndex(name);
123+
if (idx >= 0) {
124+
if (class_being_redefined == nullptr) {
125+
// load
126+
savedBytes[idx].load.save(class_data, class_data_len);
127+
} else {
128+
// retransform/redefine
129+
savedBytes[idx].retransform.save(class_data, class_data_len);
130+
}
131+
}
132+
}
133+
134+
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
135+
jint res = jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1);
136+
if (res != JNI_OK) {
137+
_log("Failed to get JVMTI interface: %ld\n", res);
138+
return JNI_ERR;
139+
}
140+
141+
jvmtiCapabilities caps;
142+
memset(&caps, 0, sizeof(caps));
143+
144+
caps.can_retransform_classes = 1;
145+
jvmtiError err = jvmti->AddCapabilities(&caps);
146+
if (err != JVMTI_ERROR_NONE) {
147+
_log("Failed to add capabilities: %d\n", err);
148+
return JNI_ERR;
149+
}
150+
151+
jvmtiEventCallbacks eventCallbacks;
152+
memset(&eventCallbacks, 0, sizeof(eventCallbacks));
153+
eventCallbacks.ClassFileLoadHook = callbackClassFileLoadHook;
154+
err = jvmti->SetEventCallbacks(&eventCallbacks, sizeof(eventCallbacks));
155+
if (err != JVMTI_ERROR_NONE) {
156+
_log("Error setting event callbacks: %d\n", err);
157+
return JNI_ERR;
158+
}
159+
160+
err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr);
161+
if (err != JVMTI_ERROR_NONE) {
162+
_log("SetEventNotificationMode(JVMTI_ENABLE) error %d\n", err);
163+
return JNI_ERR;
164+
}
165+
166+
return JNI_OK;
167+
}
168+
169+
JNIEXPORT void JNICALL
170+
Agent_OnUnload(JavaVM* jvm) {
171+
return;
172+
}
173+
174+
175+
JNIEXPORT jboolean JNICALL
176+
Java_MissedStackMapFrames_doTest(JNIEnv* env, jclass klass) {
177+
178+
jboolean result = JNI_TRUE;
179+
_log(">>nTest\n");
180+
181+
for (int i = 0; i < testClassCount; i++) {
182+
_log("Loading %s...\n", testClassNames[i]);
183+
184+
savedBytes[i].klass = env->FindClass(testClassNames[i]);
185+
if (savedBytes[i].klass == nullptr) {
186+
_log("Load error\n");
187+
result = JNI_FALSE;
188+
continue;
189+
}
190+
savedBytes[i].klass = (jclass)env->NewGlobalRef(savedBytes[i].klass);
191+
192+
_log("Retransforming %s...\n", testClassNames[i]);
193+
jvmtiError err = jvmti->RetransformClasses(1, &savedBytes[i].klass);
194+
if (err != JVMTI_ERROR_NONE) {
195+
_log("RetransformClasses error %d\n", err);
196+
result = JNI_FALSE;
197+
}
198+
}
199+
_log("<<nTest\n");
200+
return result;
201+
}
202+
203+
JNIEXPORT jint JNICALL
204+
Java_MissedStackMapFrames_testCount(JNIEnv* env, jclass klass) {
205+
return testClassCount;
206+
}
207+
208+
JNIEXPORT jclass JNICALL
209+
Java_MissedStackMapFrames_testClass(JNIEnv* env, jclass klass, jint idx) {
210+
return savedBytes[idx].klass;
211+
}
212+
213+
JNIEXPORT jbyteArray JNICALL
214+
Java_MissedStackMapFrames_loadBytes(JNIEnv* env, jclass klass, jint idx) {
215+
return savedBytes[idx].load.get(env);
216+
}
217+
218+
JNIEXPORT jbyteArray JNICALL
219+
Java_MissedStackMapFrames_retransformBytes(JNIEnv* env, jclass klass, jint idx) {
220+
return savedBytes[idx].retransform.get(env);
221+
}
222+
223+
} // extern "C"

0 commit comments

Comments
 (0)
Please sign in to comment.