mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-07-05 11:32:53 +00:00
Truncate longer error messages
We could do this in a more concise manner by wrapping Throwable rather than reimplementing printStackTrace. However, doing this way allows us to handle nested exceptions too.
This commit is contained in:
parent
9d16fda266
commit
ebed357f05
@ -14,6 +14,7 @@ import dan200.computercraft.core.asm.LuaMethod;
|
|||||||
import dan200.computercraft.core.asm.ObjectSource;
|
import dan200.computercraft.core.asm.ObjectSource;
|
||||||
import dan200.computercraft.core.computer.TimeoutState;
|
import dan200.computercraft.core.computer.TimeoutState;
|
||||||
import dan200.computercraft.core.util.Nullability;
|
import dan200.computercraft.core.util.Nullability;
|
||||||
|
import dan200.computercraft.core.util.SanitisedError;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.squiddev.cobalt.*;
|
import org.squiddev.cobalt.*;
|
||||||
@ -142,7 +143,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
return MachineResult.TIMEOUT;
|
return MachineResult.TIMEOUT;
|
||||||
} catch (LuaError e) {
|
} catch (LuaError e) {
|
||||||
close();
|
close();
|
||||||
LOG.warn("Top level coroutine errored", e);
|
LOG.warn("Top level coroutine errored: {}", new SanitisedError(e));
|
||||||
return MachineResult.error(e);
|
return MachineResult.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,13 @@ import dan200.computercraft.api.lua.ILuaAPI;
|
|||||||
import dan200.computercraft.api.lua.ILuaContext;
|
import dan200.computercraft.api.lua.ILuaContext;
|
||||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
import dan200.computercraft.core.computer.TimeoutState;
|
import dan200.computercraft.core.computer.TimeoutState;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
|
||||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments used to construct a {@link ILuaMachine}.
|
* Arguments used to construct a {@link ILuaMachine}.
|
||||||
*
|
*
|
||||||
* @param context The Lua context to execute main-thread tasks with.
|
* @param context The Lua context to execute main-thread tasks with.
|
||||||
* @param metrics A sink to submit metrics to. You do not need to submit task timings here, it should only be for additional
|
* @param metrics A sink to submit metrics to.
|
||||||
* metrics such as {@link Metrics#COROUTINES_CREATED}
|
|
||||||
* @param timeout The current timeout state. This should be used by the machine to interrupt its execution.
|
* @param timeout The current timeout state. This should be used by the machine to interrupt its execution.
|
||||||
* @param apis APIs to inject into the global environment. Each API should be converted into a Lua object
|
* @param apis APIs to inject into the global environment. Each API should be converted into a Lua object
|
||||||
* (following the same rules as any other value), and then set to all names in {@link ILuaAPI#getNames()}.
|
* (following the same rules as any other value), and then set to all names in {@link ILuaAPI#getNames()}.
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.core.util;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps {@link Throwable}, which attempts to sanitise error messages.
|
||||||
|
* <p>
|
||||||
|
* This is intended for logging errors where the message content is supplied from untrusted sources. This isn't a
|
||||||
|
* perfect escaping mechanism, but ensures basic "unsafe" strings (i.e. ANSI escape sequences, long lines) are escaped.
|
||||||
|
*
|
||||||
|
* <h2>Example:</h2>
|
||||||
|
* <pre>{@code
|
||||||
|
* LOG.error("Some error occurred: {}", new TruncatedError(error));
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @param error The error to wrap.
|
||||||
|
*/
|
||||||
|
public record SanitisedError(Throwable error) {
|
||||||
|
private static final int MAX_LENGTH = 200;
|
||||||
|
private static final String CAUSED_BY = "Caused by: ";
|
||||||
|
private static final String SUPPRESSED = "Suppressed: ";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var output = new StringBuilder();
|
||||||
|
printStackTrace(output, error);
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printStackTrace(StringBuilder output, Throwable error) {
|
||||||
|
appendMessage(output, error);
|
||||||
|
|
||||||
|
var trace = error.getStackTrace();
|
||||||
|
for (var traceElement : trace) output.append("\tat ").append(traceElement).append("\n");
|
||||||
|
|
||||||
|
var seen = new HashSet<Throwable>();
|
||||||
|
seen.add(error);
|
||||||
|
|
||||||
|
printAdditionalErrors(output, error, trace, "", seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendMessage(StringBuilder output, Throwable self) {
|
||||||
|
var message = self.toString();
|
||||||
|
var length = message.length();
|
||||||
|
for (int i = 0, limit = Math.min(MAX_LENGTH, length); i < limit; i++) {
|
||||||
|
var c = message.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '\\' -> output.append("\\\\");
|
||||||
|
case '\n' -> output.append("\\n");
|
||||||
|
case '\r' -> output.append("\\r");
|
||||||
|
case '\t' -> output.append("\\t");
|
||||||
|
default -> {
|
||||||
|
if (c >= ' ') {
|
||||||
|
output.append(c);
|
||||||
|
} else {
|
||||||
|
output.append("\\u{").append(Integer.toHexString(c)).append("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > MAX_LENGTH) output.append("... (message truncated)");
|
||||||
|
output.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printAdditionalErrors(
|
||||||
|
StringBuilder output, Throwable self, StackTraceElement[] trace, String indent, Set<Throwable> seen
|
||||||
|
) {
|
||||||
|
// Print suppressed exceptions, if any
|
||||||
|
for (var se : self.getSuppressed()) printAdditionalError(output, se, trace, SUPPRESSED, indent + "\t", seen);
|
||||||
|
|
||||||
|
// Print cause, if any
|
||||||
|
var ourCause = self.getCause();
|
||||||
|
if (ourCause != null) printAdditionalError(output, ourCause, trace, CAUSED_BY, indent, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printAdditionalError(
|
||||||
|
StringBuilder output, Throwable self, StackTraceElement[] parent, String label, String indent, Set<Throwable> seen
|
||||||
|
) {
|
||||||
|
if (!seen.add(self)) {
|
||||||
|
output.append("[DUPLICATE ERROR: ");
|
||||||
|
appendMessage(output, self);
|
||||||
|
output.append("]\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(indent).append(label);
|
||||||
|
appendMessage(output, self);
|
||||||
|
|
||||||
|
// Find a common prefix with the parent exception and just print that.
|
||||||
|
var trace = self.getStackTrace();
|
||||||
|
int traceIdx, parentIdx;
|
||||||
|
for (
|
||||||
|
traceIdx = trace.length - 1, parentIdx = parent.length - 1;
|
||||||
|
traceIdx >= 0 && parentIdx >= 0;
|
||||||
|
traceIdx--, parentIdx--
|
||||||
|
) {
|
||||||
|
if (!trace[traceIdx].equals(parent[parentIdx])) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i <= traceIdx; i++) output.append(indent).append("\tat ").append(trace[traceIdx]).append("\n");
|
||||||
|
|
||||||
|
var remaining = trace.length - traceIdx - 1;
|
||||||
|
if (remaining >= 0) output.append(indent).append("\t... ").append(remaining).append(" more\n");
|
||||||
|
|
||||||
|
printAdditionalErrors(output, self, trace, indent + "\t", seen);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.core.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.hamcrest.text.CharSequenceLength.hasLength;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class SanitisedErrorTest {
|
||||||
|
private void assertEquivalent(Throwable t) {
|
||||||
|
var truncatedOutput = new SanitisedError(t).toString();
|
||||||
|
|
||||||
|
var writer = new StringWriter();
|
||||||
|
try (var printWriter = new PrintWriter(writer)) {
|
||||||
|
t.printStackTrace(printWriter);
|
||||||
|
}
|
||||||
|
var actualOutput = writer.toString();
|
||||||
|
|
||||||
|
assertEquals(actualOutput, truncatedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicException() {
|
||||||
|
assertEquivalent(new RuntimeException("A problem occurred"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textExceptionWithCause() {
|
||||||
|
var inner = new RuntimeException("Inner error");
|
||||||
|
var outer = new RuntimeException("Outer error", inner);
|
||||||
|
assertEquivalent(outer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textExceptionWithSuppressed() {
|
||||||
|
var inner = new RuntimeException("Inner error");
|
||||||
|
var outer = new RuntimeException("Outer error");
|
||||||
|
outer.addSuppressed(inner);
|
||||||
|
assertEquivalent(outer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTruncates() {
|
||||||
|
var error = new RuntimeException("Some message".repeat(100));
|
||||||
|
error.setStackTrace(new StackTraceElement[0]);
|
||||||
|
|
||||||
|
var message = new SanitisedError(error).toString();
|
||||||
|
assertThat(message, containsString("message truncated"));
|
||||||
|
assertThat(message, hasLength(lessThanOrEqualTo(250)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStrips() {
|
||||||
|
var error = new RuntimeException("Some message\n\r\t\033");
|
||||||
|
error.setStackTrace(new StackTraceElement[0]);
|
||||||
|
|
||||||
|
var message = new SanitisedError(error).toString();
|
||||||
|
assertThat(message, startsWith("java.lang.RuntimeException: Some message\\n\\r\\t\\u{1b}\n"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user