Skip to content

Commit 40eeed9

Browse files
committed
Support dynamic class loading
1 parent 1932f23 commit 40eeed9

File tree

16 files changed

+776
-23
lines changed

16 files changed

+776
-23
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
package com.oracle.svm.agent;
27+
28+
import com.oracle.svm.core.util.JavaClassUtil;
29+
import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions;
30+
import static com.oracle.svm.jvmtiagentbase.Support.getMethodFullNameAtFrame;
31+
32+
import java.io.File;
33+
import java.io.FileOutputStream;
34+
import java.io.IOException;
35+
import java.nio.ByteBuffer;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.nio.file.Paths;
39+
40+
import com.oracle.svm.jni.nativeapi.JNIMethodId;
41+
import org.graalvm.nativeimage.c.type.CCharPointer;
42+
import org.graalvm.nativeimage.c.type.CTypeConversion;
43+
import org.graalvm.nativeimage.c.type.VoidPointer;
44+
import org.graalvm.word.WordFactory;
45+
46+
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
47+
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
48+
49+
public abstract class AbstractDynamicClassGenerationSupport {
50+
51+
protected JNIEnvironment jni;
52+
protected JNIObjectHandle callerClass;
53+
protected final String generatedClassName;
54+
protected TraceWriter traceWriter;
55+
protected NativeImageAgent agent;
56+
protected int generatedClassHashCode = 0;
57+
protected byte[] values = null;
58+
59+
private static Path dynclassDumpDir = null;
60+
private static final Path DEFAULT_DUMP = Paths.get("dynClass");
61+
protected String callerMethod;
62+
63+
public static void setDynClassDumpDir(String dir) {
64+
dynclassDumpDir = Paths.get(dir);
65+
}
66+
67+
public static AbstractDynamicClassGenerationSupport getDynamicClassGenerationSupport(JNIEnvironment jni, JNIObjectHandle callerClass,
68+
String generatedClassName, TraceWriter traceWriter, NativeImageAgent agent) throws IOException {
69+
return new ClassLoaderDefineClassSupport(jni, callerClass, generatedClassName, traceWriter, agent);
70+
}
71+
72+
protected AbstractDynamicClassGenerationSupport(JNIEnvironment jni, JNIObjectHandle callerClass,
73+
String generatedClassName, TraceWriter traceWriter, NativeImageAgent agent) throws IOException {
74+
if (dynclassDumpDir == null) {
75+
System.out.println("Warning: dynamic-class-dump-dir= was not set in -agentlib:native-image-agent=. Dynamically generated classes will be dumped to the default location:" +
76+
DEFAULT_DUMP.toAbsolutePath().toString());
77+
dynclassDumpDir = DEFAULT_DUMP;
78+
}
79+
80+
if (!Files.exists(dynclassDumpDir)) {
81+
Files.createDirectories(dynclassDumpDir);
82+
} else if (!Files.isDirectory(dynclassDumpDir)) {
83+
throw new IOException("File " + dynclassDumpDir + " already exists! Cannot create the same directory for class file dumping.");
84+
}
85+
86+
this.jni = jni;
87+
this.callerClass = callerClass;
88+
// Make sure use qualified name for generatedClassName
89+
this.generatedClassName = generatedClassName.replace('/', '.');
90+
this.traceWriter = traceWriter;
91+
this.agent = agent;
92+
}
93+
94+
public abstract void traceReflects(Object result) throws IOException;
95+
96+
/**
97+
* Get class definition contents from passed in function parameter.
98+
* @return JObject represents byte array or ByteBuffer
99+
*/
100+
protected abstract JNIObjectHandle getClassDefinition();
101+
102+
public abstract boolean checkSupported();
103+
104+
protected abstract int getClassDefinitionBytesLength();
105+
106+
public abstract byte[] getClassContents();
107+
108+
protected byte[] getClassContentsFromByteArray() {
109+
// bytes parameter of defineClass method
110+
JNIObjectHandle bytes = getClassDefinition();
111+
// len parameter of defineClass method
112+
int length = getClassDefinitionBytesLength();
113+
// Get generated class' byte array
114+
CCharPointer byteArray = jniFunctions().getGetByteArrayElements().invoke(jni, bytes, WordFactory.nullPointer());
115+
byte[] contents = new byte[length];
116+
try {
117+
CTypeConversion.asByteBuffer(byteArray, length).get(contents);
118+
} finally {
119+
jniFunctions().getReleaseByteArrayElements().invoke(jni, bytes, byteArray, 0);
120+
}
121+
return contents;
122+
}
123+
124+
protected byte[] getClassContentsFromDirectBuffer() {
125+
// DirectBuffer parameter of defineClass
126+
JNIObjectHandle directbuffer = getClassDefinition();
127+
128+
// Get byte array from DirectBuffer
129+
VoidPointer baseAddr = jniFunctions().getGetDirectBufferAddress().invoke(jni, directbuffer);
130+
JNIMethodId limitMId = agent.handles().getMethodId(jni, agent.handles().javaNioByteBuffer, "limit", "()I", false);
131+
int limit = jniFunctions().getCallIntMethod().invoke(jni, directbuffer, limitMId);
132+
ByteBuffer classContentsAsByteBuffer = CTypeConversion.asByteBuffer(baseAddr, limit);
133+
byte[] contents = new byte[classContentsAsByteBuffer.limit()];
134+
classContentsAsByteBuffer.get(contents);
135+
classContentsAsByteBuffer.position(0);
136+
return contents;
137+
}
138+
139+
public int calculateGeneratedClassHashcode() throws IOException {
140+
if (generatedClassHashCode == 0) {
141+
generatedClassHashCode = JavaClassUtil.getHashCodeWithoutSourceFileInfo(getClassContents());
142+
}
143+
return generatedClassHashCode;
144+
}
145+
146+
/**
147+
* Save dynamically defined class to file system.
148+
*
149+
*/
150+
public void dumpDefinedClass() throws IOException {
151+
if (values == null) {
152+
values = getClassContents();
153+
}
154+
155+
// Get name for generated class
156+
String internalName = generatedClassName.replace('.', File.separatorChar);
157+
Path dumpFile = dynclassDumpDir.resolve(internalName + ".class");
158+
159+
// Get directory from package
160+
Path dumpDirs = dumpFile.getParent();
161+
if (!Files.exists(dumpDirs)) {
162+
Files.createDirectories(dumpDirs);
163+
} else if (!Files.isDirectory(dumpDirs)) {
164+
throw new IOException("File " + dumpDirs + " already exists! Cannot create the same name directory for dumping class file.");
165+
}
166+
try (FileOutputStream stream = new FileOutputStream(dumpFile.toFile());) {
167+
stream.write(values);
168+
}
169+
170+
// Dump stack trace for debug usage
171+
Path dumpTraceFile = dynclassDumpDir.resolve(internalName + ".txt");
172+
StringBuilder trace = getStackTrace(jni);
173+
try (FileOutputStream traceStream = new FileOutputStream(dumpTraceFile.toFile());) {
174+
traceStream.write(trace.toString().getBytes());
175+
}
176+
}
177+
178+
public static StringBuilder getStackTrace(JNIEnvironment jni) {
179+
StringBuilder trace = new StringBuilder();
180+
int i = 0;
181+
int maxDepth = 20;
182+
while (i < maxDepth) {
183+
String methodName = getMethodFullNameAtFrame(jni, i++);
184+
if (methodName == null) {
185+
break;
186+
}
187+
trace.append(" ").append(methodName).append("\n");
188+
}
189+
if (i >= maxDepth) {
190+
trace.append(" ").append("...").append("\n");
191+
}
192+
return trace;
193+
}
194+
195+
public String getCallerMethod() {
196+
return callerMethod;
197+
}
198+
}

0 commit comments

Comments
 (0)