Skip to content

Commit c9b96ff

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

File tree

16 files changed

+782
-23
lines changed

16 files changed

+782
-23
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
*
99+
* @return JObject represents byte array or ByteBuffer
100+
*/
101+
protected abstract JNIObjectHandle getClassDefinition();
102+
103+
public abstract boolean checkSupported();
104+
105+
protected abstract int getClassDefinitionBytesLength();
106+
107+
public abstract byte[] getClassContents();
108+
109+
protected byte[] getClassContentsFromByteArray() {
110+
// bytes parameter of defineClass method
111+
JNIObjectHandle bytes = getClassDefinition();
112+
// len parameter of defineClass method
113+
int length = getClassDefinitionBytesLength();
114+
// Get generated class' byte array
115+
CCharPointer byteArray = jniFunctions().getGetByteArrayElements().invoke(jni, bytes, WordFactory.nullPointer());
116+
byte[] contents = new byte[length];
117+
try {
118+
CTypeConversion.asByteBuffer(byteArray, length).get(contents);
119+
} finally {
120+
jniFunctions().getReleaseByteArrayElements().invoke(jni, bytes, byteArray, 0);
121+
}
122+
return contents;
123+
}
124+
125+
protected byte[] getClassContentsFromDirectBuffer() {
126+
// DirectBuffer parameter of defineClass
127+
JNIObjectHandle directbuffer = getClassDefinition();
128+
129+
// Get byte array from DirectBuffer
130+
VoidPointer baseAddr = jniFunctions().getGetDirectBufferAddress().invoke(jni, directbuffer);
131+
JNIMethodId limitMId = agent.handles().getMethodId(jni, agent.handles().javaNioByteBuffer, "limit", "()I", false);
132+
int limit = jniFunctions().getCallIntMethod().invoke(jni, directbuffer, limitMId);
133+
ByteBuffer classContentsAsByteBuffer = CTypeConversion.asByteBuffer(baseAddr, limit);
134+
byte[] contents = new byte[classContentsAsByteBuffer.limit()];
135+
classContentsAsByteBuffer.get(contents);
136+
classContentsAsByteBuffer.position(0);
137+
return contents;
138+
}
139+
140+
public int calculateGeneratedClassHashcode() throws IOException {
141+
if (generatedClassHashCode == 0) {
142+
generatedClassHashCode = JavaClassUtil.getHashCodeWithoutSourceFileInfo(getClassContents());
143+
}
144+
return generatedClassHashCode;
145+
}
146+
147+
/**
148+
* Save dynamically defined class to file system.
149+
*
150+
*/
151+
public void dumpDefinedClass() throws IOException {
152+
if (values == null) {
153+
values = getClassContents();
154+
}
155+
156+
// Get name for generated class
157+
String internalName = generatedClassName.replace('.', File.separatorChar);
158+
Path dumpFile = dynclassDumpDir.resolve(internalName + ".class");
159+
160+
// Get directory from package
161+
Path dumpDirs = dumpFile.getParent();
162+
if (!Files.exists(dumpDirs)) {
163+
Files.createDirectories(dumpDirs);
164+
} else if (!Files.isDirectory(dumpDirs)) {
165+
throw new IOException("File " + dumpDirs + " already exists! Cannot create the same name directory for dumping class file.");
166+
}
167+
try (FileOutputStream stream = new FileOutputStream(dumpFile.toFile());) {
168+
stream.write(values);
169+
}
170+
171+
// Dump stack trace for debug usage
172+
Path dumpTraceFile = dynclassDumpDir.resolve(internalName + ".txt");
173+
StringBuilder trace = getStackTrace(jni);
174+
try (FileOutputStream traceStream = new FileOutputStream(dumpTraceFile.toFile());) {
175+
traceStream.write(trace.toString().getBytes());
176+
}
177+
}
178+
179+
public static StringBuilder getStackTrace(JNIEnvironment jni) {
180+
StringBuilder trace = new StringBuilder();
181+
int i = 0;
182+
int maxDepth = 20;
183+
while (i < maxDepth) {
184+
String methodName = getMethodFullNameAtFrame(jni, i++);
185+
if (methodName == null) {
186+
break;
187+
}
188+
trace.append(" ").append(methodName).append("\n");
189+
}
190+
if (i >= maxDepth) {
191+
trace.append(" ").append("...").append("\n");
192+
}
193+
return trace;
194+
}
195+
196+
public String getCallerMethod() {
197+
return callerMethod;
198+
}
199+
}

0 commit comments

Comments
 (0)