package com.oracle.svm.diagnosticsagent;
import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
import static com.oracle.svm.jvmtiagentbase.Support.check;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.UnmanagedMemory;
import org.graalvm.nativeimage.c.struct.SizeOf;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
import org.graalvm.nativeimage.c.type.CIntPointer;
import org.graalvm.nativeimage.c.type.WordPointer;
import org.graalvm.word.Pointer;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
import com.oracle.svm.jni.nativeapi.JNIMethodId;
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
import com.oracle.svm.jvmtiagentbase.JvmtiAgentBase;
import com.oracle.svm.jvmtiagentbase.Support;
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEnv;
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiError;
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiFrameInfo;
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiLineNumberEntry;
public class JavaStackTraceCreator {
private static final int LINE_NUMBER_UNAVAILABLE = -1;
private final JvmtiEnv jvmti;
private final JNIEnvironment jni;
public JavaStackTraceCreator(JvmtiEnv jvmti, JNIEnvironment jni) {
this.jvmti = jvmti;
this.jni = jni;
}
protected void inspectStackTraceElementMethod(@SuppressWarnings("unused") JNIMethodId jMethodID) {
}
private int getCurrentThreadStackFrameCount() {
CIntPointer countPointer = StackValue.get(CIntPointer.class);
check(jvmti.getFunctions().GetFrameCount().invoke(jvmti, nullHandle(), countPointer));
return countPointer.read();
}
private String getSourceFileName(JNIObjectHandle clazz) {
CCharPointerPointer sourceFileNamePointer = StackValue.get(CCharPointerPointer.class);
JvmtiError errorCode = jvmti.getFunctions().GetSourceFileName().invoke(jvmti, clazz, sourceFileNamePointer);
if (errorCode == JvmtiError.JVMTI_ERROR_NONE) {
String sourceFileName = Support.fromCString(sourceFileNamePointer.read());
jvmti.getFunctions().Deallocate().invoke(jvmti, sourceFileNamePointer.read());
return sourceFileName;
} else {
return null;
}
}
private static int getFrameSourceLineNumber(JvmtiEnv jvmti, JvmtiFrameInfo frameInfo) {
CIntPointer entryCountPointer = StackValue.get(CIntPointer.class);
WordPointer lineEntryTablePointer = StackValue.get(WordPointer.class);
JvmtiError errorCode = jvmti.getFunctions().GetLineNumberTable().invoke(jvmti, frameInfo.getMethod(), entryCountPointer, lineEntryTablePointer);
if (errorCode == JvmtiError.JVMTI_ERROR_MUST_POSSESS_CAPABILITY || errorCode == JvmtiError.JVMTI_ERROR_ABSENT_INFORMATION) {
return LINE_NUMBER_UNAVAILABLE;
}
check(errorCode);
int entryCount = entryCountPointer.read();
Pointer lineEntryTable = lineEntryTablePointer.read();
VMError.guarantee(lineEntryTable.isNonNull());
int previousLineNumber = LINE_NUMBER_UNAVAILABLE;
for (int i = 0; i < entryCount; ++i) {
JvmtiLineNumberEntry entry = (JvmtiLineNumberEntry) lineEntryTable.add(i * SizeOf.get(JvmtiLineNumberEntry.class));
if (entry.getStartLocation() > frameInfo.getLocation()) {
break;
}
previousLineNumber = entry.getLineNumber();
}
jvmti.getFunctions().Deallocate().invoke(jvmti, lineEntryTable);
return previousLineNumber;
}
private StackTraceElement constructStackTraceElement(JvmtiFrameInfo frameInfo) {
JNIObjectHandle declaringClass = Support.getMethodDeclaringClass(frameInfo.getMethod());
String methodName = Support.getMethodNameOr(frameInfo.getMethod(), "");
String declaringClassName = Support.getClassNameOr(jni, declaringClass, "", "");
CCharPointer isNativePtr = StackValue.get(CCharPointer.class);
String fileName = null;
int lineNumber = LINE_NUMBER_UNAVAILABLE;
JvmtiError errorCode = jvmti.getFunctions().IsMethodNative().invoke(jvmti, frameInfo.getMethod(), isNativePtr);
if (errorCode == JvmtiError.JVMTI_ERROR_NONE && isNativePtr.read() == 0) {
fileName = getSourceFileName(declaringClass);
lineNumber = getFrameSourceLineNumber(jvmti, frameInfo);
}
return new StackTraceElement(declaringClassName, methodName, fileName, lineNumber);
}
public JNIObjectHandle getStackTraceArray() {
int threadStackFrameCount = getCurrentThreadStackFrameCount();
int frameInfoSize = SizeOf.get(JvmtiFrameInfo.class);
Pointer stackFramesPtr = UnmanagedMemory.malloc(frameInfoSize * threadStackFrameCount);
CIntPointer readStackFramesPtr = StackValue.get(CIntPointer.class);
check(jvmti.getFunctions().GetStackTrace().invoke(jvmti, nullHandle(), 0, threadStackFrameCount, (WordPointer) stackFramesPtr, readStackFramesPtr));
VMError.guarantee(readStackFramesPtr.read() == threadStackFrameCount);
NativeImageDiagnosticsAgent agent = JvmtiAgentBase.singleton();
JNIObjectHandle stackTraceArray = jni.getFunctions().getNewObjectArray().invoke(jni, threadStackFrameCount, agent.handles().javaLangStackTraceElement, nullHandle());
for (int i = 0; i < threadStackFrameCount; ++i) {
JvmtiFrameInfo frameInfo = (JvmtiFrameInfo) stackFramesPtr.add(i * frameInfoSize);
StackTraceElement stackTraceElement = constructStackTraceElement(frameInfo);
JNIObjectHandle classNameHandle = Support.toJniString(jni, stackTraceElement.getClassName());
JNIObjectHandle methodNameHandle = Support.toJniString(jni, stackTraceElement.getMethodName());
JNIObjectHandle sourceFileNameHandle = Support.toJniString(jni, stackTraceElement.getFileName());
int lineNumber = stackTraceElement.getLineNumber();
JNIObjectHandle stackTraceElementHandle = Support.newObjectLLLJ(jni, agent.handles().javaLangStackTraceElement, agent.handles().javaLangStackTraceElementCtor4, classNameHandle,
methodNameHandle,
sourceFileNameHandle, lineNumber);
jni.getFunctions().getSetObjectArrayElement().invoke(jni, stackTraceArray, i, stackTraceElementHandle);
inspectStackTraceElementMethod(frameInfo.getMethod());
}
UnmanagedMemory.free(stackFramesPtr);
return stackTraceArray;
}
}