Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -32,7 +32,7 @@ public static String safeGetMessage(Throwable t) {
}
try {
return t.getMessage();
} catch (Exception e) {
} catch (Exception | StackOverflowError e) {
return "(Exception message unavailable for "
+ t.getClass().getSimpleName()
+ ": getMessage() threw "
Expand Down Expand Up @@ -60,8 +60,9 @@ public static String getStackTrace(Throwable t, int maxChars) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
trace = sw.toString();
} catch (Exception ignored) {
// printStackTrace() failed (e.g. getMessage() throws inside toString()).
} catch (Exception | StackOverflowError ignored) {
// printStackTrace() failed (e.g. getMessage() throws inside toString(), or a
// StackOverflowError while formatting an already stack-constrained throwable).
// Reconstruct from getStackTrace() so the call site is still locatable.
try {
trace =
Expand All @@ -70,14 +71,19 @@ public static String getStackTrace(Throwable t, int maxChars) {
+ Arrays.stream(t.getStackTrace())
.map(f -> "\tat " + f)
.collect(Collectors.joining(System.lineSeparator()));
} catch (Exception ignored2) {
trace = t.getClass().getName();
} catch (Exception | StackOverflowError ignored2) {
try {
trace = t.getClass().getName() + ": " + t.getMessage();
} catch (Exception | StackOverflowError ignored3) {
trace = t.getClass().getName();
}
}
}
try {
return truncate(trace, maxChars);
} catch (Exception e) {
// If something goes wrong, return the original trace
} catch (Exception | StackOverflowError e) {
// If something goes wrong (including a further StackOverflowError while the stack is
// still constrained), return the untruncated trace rather than propagate.
return trace;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ void safeGetMessageReturnsNullForNullInput() {
assertNull(StackTraces.safeGetMessage(null));
}

@Test
void getStackTraceFallsBackWhenPrintStackTraceThrowsStackOverflowError() {
String trace =
StackTraces.getStackTrace(TestThrowables.throwingStackOverflowOnPrintStackTrace(), 1000);
assertTrue(trace.contains("TestThrowables"), "must fall back to a locatable trace");
}

@Test
void getStackTraceFallsBackToClassNameAndMessageWhenEverythingElseThrows() {
String trace =
StackTraces.getStackTrace(
TestThrowables.throwingStackOverflowEverywhereExceptGetMessage(), 1000);
assertTrue(trace.contains("TestThrowables"), "must include the throwable's class name");
assertTrue(trace.contains("still readable"), "must include the throwable's message");
}

@Test
void getStackTraceFallsBackToClassNameWhenGetMessageAlsoThrows() {
String trace =
StackTraces.getStackTrace(TestThrowables.throwingStackOverflowEverywhere(), 1000);
assertEquals(trace, TestThrowables.throwingStackOverflowEverywhere().getClass().getName());
}

// --- getStackTrace with broken getMessage ---

@ParameterizedTest(name = "truncation limit {0}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,64 @@ public String getMessage() {
}
};
}

/**
* Returns a {@link RuntimeException} whose {@link Throwable#printStackTrace(java.io.PrintWriter)}
* throws a {@link StackOverflowError} — simulating a second overflow while formatting a throwable
* that was itself caught with little remaining stack margin.
*/
public static RuntimeException throwingStackOverflowOnPrintStackTrace() {
return new RuntimeException() {
@Override
public void printStackTrace(java.io.PrintWriter s) {
throw new StackOverflowError();
}
};
}

/**
* Returns a {@link RuntimeException} whose {@link Throwable#printStackTrace(java.io.PrintWriter)}
* and {@link Throwable#getStackTrace()} both throw {@link StackOverflowError} — simulating a
* throwable caught with essentially no remaining stack margin, where even the array-based
* fallback in {@link StackTraces#getStackTrace} fails and only {@link Throwable#getMessage()}
* remains callable.
*/
public static RuntimeException throwingStackOverflowEverywhereExceptGetMessage() {
return new RuntimeException("still readable") {
@Override
public void printStackTrace(java.io.PrintWriter s) {
throw new StackOverflowError();
}

@Override
public StackTraceElement[] getStackTrace() {
throw new StackOverflowError();
}
};
}

/**
* Returns a {@link RuntimeException} whose {@link
* Throwable#printStackTrace(java.io.PrintWriter)}, {@link Throwable#getStackTrace()}, and {@link
* Throwable#getMessage()} all throw {@link StackOverflowError} — the worst case, where {@link
* StackTraces#getStackTrace} must fall back to just the throwable's class name.
*/
public static RuntimeException throwingStackOverflowEverywhere() {
return new RuntimeException() {
@Override
public void printStackTrace(java.io.PrintWriter s) {
throw new StackOverflowError();
}

@Override
public StackTraceElement[] getStackTrace() {
throw new StackOverflowError();
}

@Override
public String getMessage() {
throw new StackOverflowError();
}
};
}
}
Loading