mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-02-14 18:10:05 +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.computer.TimeoutState;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.core.util.SanitisedError;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.squiddev.cobalt.*;
|
||||
@ -142,7 +143,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
return MachineResult.TIMEOUT;
|
||||
} catch (LuaError e) {
|
||||
close();
|
||||
LOG.warn("Top level coroutine errored", e);
|
||||
LOG.warn("Top level coroutine errored: {}", new SanitisedError(e));
|
||||
return MachineResult.error(e);
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,13 @@ import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
|
||||
/**
|
||||
* Arguments used to construct a {@link ILuaMachine}.
|
||||
*
|
||||
* @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
|
||||
* metrics such as {@link Metrics#COROUTINES_CREATED}
|
||||
* @param metrics A sink to submit metrics to.
|
||||
* @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
|
||||
* (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