Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f02ddab
svm: add JavaThreads.visitStackTrace
zapster Mar 28, 2023
5d0451e
svm: populate StackTraceElement lazily
zapster Mar 16, 2023
590a8c4
svm: improve Uninterruptible reason for RawStackTraceVisitor#visitRaw…
zapster Apr 17, 2023
9f26336
svm: do not statically cache MaxJavaStackTraceDepth
zapster Apr 17, 2023
a6bde1d
svm: make fillInStackTrace return Throwable
zapster Apr 17, 2023
f0c080e
svm: deoptimization not supported in RawStackTraceVisitor
zapster Apr 17, 2023
26e6437
svm: remove RawStackTraceVisitor.hasTrace
zapster Apr 17, 2023
903d534
svm: move CodeInfoAccess.convert into the try block
zapster Apr 17, 2023
4eb0895
svm: move CodeInfoAccess.convert into the try block in RawStackTraceV…
zapster Apr 17, 2023
7261e7e
svm: refactor Throwable#fillInStackTrace substitution
zapster Apr 17, 2023
8503f68
svm: do not trim Throwable#backtrace array
zapster Apr 18, 2023
a870f4d
svm: make Throwable#backtrace private again
zapster Apr 18, 2023
e991b80
svm: fix type in Throwable substitution
zapster Apr 18, 2023
7331096
svm: eager stack trace if Deoptimization is enabled
zapster Apr 18, 2023
be34c52
svm: fix typo in javadoc in fillInStackTrace
zapster Apr 18, 2023
943db32
svm: use FieldValueTransformer for Throwable.stackTrace
zapster Apr 19, 2023
2fb4b39
svm: rename StackTraceUtils.visitStackTrace to visitCurrentThreadStac…
zapster Apr 19, 2023
f7bd76f
svm: rename JavaThreads.visitStackTrace to visitCurrentStackFrames
zapster Apr 19, 2023
0de3dc0
svm: set RawStackTraceVisitor.INITIAL_TRACE_SIZE to 80
zapster Apr 19, 2023
3b251a9
svm: rename RawStackTraceVisitor to RawStackFrameVisitor
zapster Apr 19, 2023
5c7644d
svm: fix JDK 17 substitution of StackTraceElement.of
zapster Apr 19, 2023
e14d23b
svm: move loop out of Uninterruptible code in RawStackFrameVisitor#de…
zapster Apr 20, 2023
3fa41fb
svm: add runtime compilation issue for fillInStackTrace
zapster Apr 20, 2023
53a9087
svm: document RawStackFrameVisitor#getArray trade-off
zapster Apr 20, 2023
c92ed6f
svm: encapsulate MaxJavaStackTraceDepth
zapster Apr 20, 2023
9aac57b
svm: substitute Throwable#fillInStackTrace to fix synchronized issue
zapster Apr 21, 2023
45fac52
svm: handle lazy stack traces in low-level exception logging
zapster Apr 21, 2023
ca03011
svm: Javadoc
zapster Apr 24, 2023
1e476b6
svm: Javadoc
zapster Apr 24, 2023
56ddd36
svm: throw
zapster Apr 24, 2023
5f839cb
svm: move MaxJavaStackTraceDepth to ConcealedOptions
zapster Apr 24, 2023
f063948
svm: remove UNRESTRICTED from RealLog#visitBacktrace
zapster Apr 24, 2023
628a20a
svm: Rename RawStackFrameVisitor to BacktraceVisitor
zapster Apr 24, 2023
e770b48
svm: rename backtrace printing functions in RealLog
zapster Apr 24, 2023
d742339
svm: use FrameInfoCursor for Throwable stack trace printing in RealLog
zapster Apr 25, 2023
fb942f5
svm: RealLog.printBacktrace should also use mutex in VMOperation
zapster Apr 26, 2023
8576c44
svm: rename RealLog.printBacktrackSynchronized to printBacktrackLocked
zapster Apr 26, 2023
655d5d0
svm: introduce BacktraceDecoder and use that for Throwable.getOurStac…
zapster Apr 26, 2023
61e7e10
svm: improve RestrictHeapAccess handling in RealLog.BacktracePrinter
zapster Apr 26, 2023
3c2f638
svm: fix RealLog stack traces wrt Deoptimization [GR-45765]
zapster May 2, 2023
70d9c11
svm: fix method name typos
zapster May 2, 2023
bebea7f
svm: add comment about the race condition in RealLog#exception
zapster May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,21 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
@Option(help = "The size of each internal thread stack, in bytes.", type = OptionType.Expert)//
public static final HostedOptionKey<Long> InternalThreadStackSize = new HostedOptionKey<>(2L * 1024 * 1024);

@Option(help = "The maximum number of lines in the stack trace for Java exceptions (0 means all)", type = OptionType.User)//
public static final RuntimeOptionKey<Integer> MaxJavaStackTraceDepth = new RuntimeOptionKey<>(1024);
/**
* Cached value of {@link ConcealedOptions#MaxJavaStackTraceDepth}. Also used as default value.
*/
private static int maxJavaStackTraceDepth = 1024;

/**
* Cached accessor for {@link ConcealedOptions#MaxJavaStackTraceDepth}.
*/
public static int maxJavaStackTraceDepth() {
return maxJavaStackTraceDepth;
}

public static void updateMaxJavaStackTraceDepth(EconomicMap<OptionKey<?>, Object> runtimeValues, int newValue) {
ConcealedOptions.MaxJavaStackTraceDepth.update(runtimeValues, newValue);
}

/* Same option name and specification as the Java HotSpot VM. */
@Option(help = "Maximum total size of NIO direct-buffer allocations")//
Expand Down Expand Up @@ -750,6 +763,16 @@ public Boolean getValue(OptionValues values) {
/** Use {@link com.oracle.svm.core.jvmstat.PerfManager#usePerfData()} instead. */
@Option(help = "Flag to disable jvmstat instrumentation for performance testing.")//
public static final RuntimeOptionKey<Boolean> UsePerfData = new RuntimeOptionKey<>(true, Immutable);

/** Use {@link SubstrateOptions#maxJavaStackTraceDepth()} instead. */
@Option(help = "The maximum number of lines in the stack trace for Java exceptions (0 means all)", type = OptionType.User)//
public static final RuntimeOptionKey<Integer> MaxJavaStackTraceDepth = new RuntimeOptionKey<>(maxJavaStackTraceDepth) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Integer oldValue, Integer newValue) {
super.onValueUpdate(values, oldValue, newValue);
maxJavaStackTraceDepth = newValue;
}
};
}

@Option(help = "Overwrites the available number of processors provided by the OS. Any value <= 0 means using the processor count from the OS.")//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.core.jdk;

import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.WordFactory;

import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
import com.oracle.svm.core.code.CodeInfoDecoder;
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.UntetheredCodeInfo;
import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.util.VMError;

public abstract class BacktraceDecoder {

/**
* Visits the backtrace stored in {@code Throwable#backtrace}.
*
* @param backtrace internal backtrace stored in {@link Target_java_lang_Throwable#backtrace}
* @param maxFramesProcessed the maximum number of frames that should be
* {@linkplain #processFrameInfo processed}
* @param maxFramesDecode the maximum number of frames that should be decoded
* @return the number of decoded frames
*/
protected final int visitBacktrace(Object backtrace, int maxFramesProcessed, int maxFramesDecode) {
int framesDecoded = 0;
if (backtrace != null) {
final long[] trace = (long[]) backtrace;
for (long address : trace) {
if (address == 0) {
break;
}
CodePointer ip = WordFactory.pointer(address);
framesDecoded = visitCodePointer(ip, framesDecoded, maxFramesProcessed, maxFramesDecode);
if (framesDecoded == maxFramesDecode) {
break;
}
}
}
return framesDecoded - maxFramesProcessed;
}

@Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.")
private int visitCodePointer(CodePointer ip, int oldFramesDecoded, int maxFramesProcessed, int maxFramesDecode) {
int framesDecoded = oldFramesDecoded;
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip);
if (untetheredInfo.isNull()) {
/* Unknown frame. Must not happen for AOT-compiled code. */
VMError.shouldNotReachHere("Stack walk must walk only frames of known code.");
}

Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
try {
CodeInfo tetheredCodeInfo = CodeInfoAccess.convert(untetheredInfo, tether);
framesDecoded = visitFrame(ip, tetheredCodeInfo, framesDecoded, maxFramesProcessed, maxFramesDecode);
} finally {
CodeInfoAccess.releaseTether(untetheredInfo, tether);
}
return framesDecoded;
}

private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor();

@Uninterruptible(reason = "Wraps the now safe call to the possibly interruptible visitor.", callerMustBe = true, calleeMustBe = false)
private int visitFrame(CodePointer ip, CodeInfo tetheredCodeInfo, int oldFramesDecoded, int maxFramesProcessed, int maxFramesDecode) {
int framesDecoded = oldFramesDecoded;
frameInfoCursor.initialize(tetheredCodeInfo, ip);
while (frameInfoCursor.advance()) {
FrameInfoQueryResult frameInfo = frameInfoCursor.get();
if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) {
/* Always ignore the frame. It is an internal frame of the VM. */
continue;
}
if (framesDecoded == 0 && Throwable.class.isAssignableFrom(frameInfo.getSourceClass())) {
/*
* We are still in the constructor invocation chain at the beginning of the stack
* trace, which is also filtered by the Java HotSpot VM.
*/
continue;
}
if (framesDecoded < maxFramesProcessed) {
processFrameInfo(frameInfo);
}
framesDecoded++;
if (framesDecoded == maxFramesDecode) {
break;
}
}
return framesDecoded;
}

@RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, reason = "Some implementations allocate.")
protected abstract void processFrameInfo(FrameInfoQueryResult frameInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
package com.oracle.svm.core.jdk;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.util.VMError;

public final class JDKUtils {

Expand All @@ -37,7 +39,40 @@ public static String getRawMessage(Throwable ex) {
return SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).detailMessage;
}

/**
* Gets the materialized {@link StackTraceElement} array stored in a {@link Throwable} object.
* Must only be called if {@link #isStackTraceValid} returns (or would return) {@code true}.
*/
public static StackTraceElement[] getRawStackTrace(Throwable ex) {
return SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).stackTrace;
VMError.guarantee(isStackTraceValid(ex));
StackTraceElement[] stackTrace = SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).stackTrace;
if (!DeoptimizationSupport.enabled() || (stackTrace != Target_java_lang_Throwable.UNASSIGNED_STACK && stackTrace != null)) {
return stackTrace;
}
/* Runtime compilation and deoptimized frames are not yet optimized (GR-45765). */
return (StackTraceElement[]) SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).backtrace;
}

/**
* Returns {@code true} if the {@linkplain #getRawStackTrace stack trace} stored in a
* {@link Throwable} object is valid. If not, {@link #getBacktrace} must be used to access the
* Java stack trace frames.
*/
public static boolean isStackTraceValid(Throwable ex) {
StackTraceElement[] stackTrace = SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).stackTrace;
if (stackTrace != Target_java_lang_Throwable.UNASSIGNED_STACK && stackTrace != null) {
return true;
}
/* Runtime compilation and deoptimized frames are not yet optimized (GR-45765). */
return DeoptimizationSupport.enabled() && SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).backtrace instanceof StackTraceElement[];
}

/**
* Gets the internal backtrace of a {@link Throwable} object. Must only be called if
* {@link #isStackTraceValid} returns (or would return) {@code false}.
*/
public static Object getBacktrace(Throwable ex) {
VMError.guarantee(!isStackTraceValid(ex));
return SubstrateUtil.cast(ex, Target_java_lang_Throwable.class).backtrace;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.TargetElement;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.hub.ClassForNameSupport;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.jdk.JavaLangSubstitutions.ClassValueSupport;
import com.oracle.svm.core.monitor.MonitorSupport;
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.util.ReflectionUtil;

Expand Down Expand Up @@ -279,27 +281,145 @@ final class Target_java_lang_StringUTF16 {
final class Target_java_lang_Throwable {

@Alias @RecomputeFieldValue(kind = Reset)//
private Object backtrace;
Object backtrace;

@Alias @RecomputeFieldValue(kind = Reset)//
@Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = ThrowableStackTraceFieldValueTransformer.class)//
StackTraceElement[] stackTrace;

@Alias String detailMessage;

// Checkstyle: stop
@Alias//
static StackTraceElement[] UNASSIGNED_STACK;
// Checkstyle: resume

/**
* Fills in the execution stack trace. {@link Throwable#fillInStackTrace()} cannot be
* {@code synchronized}, because it might be called in a {@link VMOperation} (via one of the
* {@link Throwable} constructors), where we are not allowed to block. To work around that, we
* do the following:
* <ul>
* <li>If we are not in a {@link VMOperation}, it executes {@link #fillInStackTrace(int)} in a
* block {@code synchronized} by the supplied {@link Throwable}. This is the default case.
* <li>If we are in a {@link VMOperation}, it checks if the {@link Throwable} is currently
* locked. If not, {@link #fillInStackTrace(int)} is called without synchronization, which is
* safe in a {@link VMOperation}. If it is locked, we do not do any filling (and thus do not
* collect the stack trace).
* </ul>
*/
@Substitute
@NeverInline("Starting a stack walk in the caller frame")
private Object fillInStackTrace() {
stackTrace = JavaThreads.getStackTrace(true, Thread.currentThread());
public Target_java_lang_Throwable fillInStackTrace() {
if (VMOperation.isInProgress()) {
if (MonitorSupport.singleton().isLockedByAnyThread(this)) {
/*
* The Throwable is locked. We cannot safely fill in the stack trace. Do nothing and
* accept that we will not get a stack track.
*/
} else {
/*
* The Throwable is not locked. We can safely fill the stack trace without
* synchronization because we VMOperation is single threaded.
*/

/* Copy of `Throwable#fillInStackTrace()` */
if (stackTrace != null || backtrace != null) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
}
} else {
synchronized (this) {
/* Copy of `Throwable#fillInStackTrace()` */
if (stackTrace != null || backtrace != null) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
}
}
return this;
}

/**
* Records the execution stack in an internal format. The information is transformed into a
* {@link StackTraceElement} array in
* {@link Target_java_lang_StackTraceElement#of(Object, int)}.
*
* @param dummy to change signature
*/
@Substitute
@NeverInline("Starting a stack walk in the caller frame")
Target_java_lang_Throwable fillInStackTrace(int dummy) {
/*
* Start out by clearing the backtrace for this object, in case the VM runs out of memory
* while allocating the stack trace.
*/
backtrace = null;

if (DeoptimizationSupport.enabled()) {
/*
* Runtime compilation and deoptimized frames are not yet optimized (GR-45765). Eagerly
* construct a stack trace and store it in backtrace. We cannot directly use
* `stackTrace` because it is overwritten by the caller.
*/
backtrace = JavaThreads.getStackTrace(true, Thread.currentThread());
return this;
}

BacktraceVisitor visitor = new BacktraceVisitor();
JavaThreads.visitCurrentStackFrames(visitor);
backtrace = visitor.getArray();
return this;
}
}

final class ThrowableStackTraceFieldValueTransformer implements FieldValueTransformer {

private static final StackTraceElement[] UNASSIGNED_STACK = ReflectionUtil.readStaticField(Throwable.class, "UNASSIGNED_STACK");

@Override
public Object transform(Object receiver, Object originalValue) {
if (originalValue == null) { // Immutable stack
return null;
}
return UNASSIGNED_STACK;
}
}

@TargetClass(java.lang.StackTraceElement.class)
@Platforms(InternalPlatform.NATIVE_ONLY.class)
final class Target_java_lang_StackTraceElement {
/**
* Constructs the {@link StackTraceElement} array from a backtrace.
*
* @param x backtrace stored in {@link Target_java_lang_Throwable#backtrace}
* @param depth ignored
*/
@Substitute
private StackTraceElement[] getOurStackTrace() {
if (stackTrace != null) {
@TargetElement(onlyWith = JDK19OrLater.class)
static StackTraceElement[] of(Object x, int depth) {
if (x instanceof StackTraceElement[] stackTrace) {
/* Stack trace eagerly created. */
return stackTrace;
}
return StackTraceBuilder.build(x);
}

/**
* Constructs the {@link StackTraceElement} array from a {@link Throwable}.
*
* @param t the {@link Throwable} object
* @param depth ignored
*/
@Substitute
@TargetElement(onlyWith = JDK17OrEarlier.class)
static StackTraceElement[] of(Target_java_lang_Throwable t, int depth) {
Object x = t.backtrace;
if (x instanceof StackTraceElement[] stackTrace) {
/* Stack trace eagerly created. */
return stackTrace;
} else {
return new StackTraceElement[0];
}
return StackTraceBuilder.build(x);
}
}

Expand Down
Loading