Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.21.2] Adjust how throwables and stack trace are preserved #330

Open
wants to merge 1 commit into
base: 1.21.2
Choose a base branch
from
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
63 changes: 58 additions & 5 deletions src/main/java/io/wispforest/owo/mixin/DataResultMixin.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package io.wispforest.owo.mixin;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.Lifecycle;
import io.wispforest.owo.Owo;
import io.wispforest.owo.util.StackTraceSupplier;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

Expand All @@ -35,40 +43,85 @@ private static <R> void wrapMessageWithStacktrace(CallbackInfoReturnable<Optiona
var ogClass = ogSupplier.getClass();
if (ogSupplier instanceof StackTraceSupplier) return;

var stackTrace = Thread.currentThread().getStackTrace();
StackTraceSupplier stackTraceSupplier = null;

if (ogClass.isSynthetic()) {
try {
for (var field : ogClass.getDeclaredFields()) {
if (!Throwable.class.isAssignableFrom(field.getType())) continue;

field.setAccessible(true);
if (field.get(ogSupplier) instanceof Throwable e) stackTrace = e.getStackTrace().clone();
if (field.get(ogSupplier) instanceof Throwable e) {
stackTraceSupplier = StackTraceSupplier.of(e, ogSupplier);
}
break;
}
} catch (IllegalArgumentException | IllegalAccessException ignore) {}
}

messageSupplier.set(new StackTraceSupplier(stackTrace, ogSupplier));
if (stackTraceSupplier == null) stackTraceSupplier = StackTraceSupplier.of(ogSupplier.get());

messageSupplier.set(stackTraceSupplier);
}

@Mixin(value = DataResult.Error.class, remap = false)
abstract class DataResultErrorMixin<R> {

@Unique
private static final Logger LOGGER = LogUtils.getLogger();

@Shadow(remap = false)
public abstract Supplier<String> messageSupplier();

@Shadow @Final
private Supplier<String> messageSupplier;

@Inject(method = {"getOrThrow", "getPartialOrThrow"}, at = @At(value = "HEAD"), remap = false)
private <E extends Throwable> void addStackTraceToException(CallbackInfoReturnable<R> cir, @Local(argsOnly = true) LocalRef<Function<String, E>> exceptionSupplier) {
final var funcToWrap = exceptionSupplier.get();

exceptionSupplier.set(s -> {
var exception = funcToWrap.apply(s);
if (this.messageSupplier() instanceof StackTraceSupplier stackTraceSupplier) {
exception.setStackTrace(stackTraceSupplier.stackTrace());
exception.setStackTrace(stackTraceSupplier.getFullStackTrace());
}

return exception;
});
}

@WrapOperation(method ={
"resultOrPartial(Ljava/util/function/Consumer;)Ljava/util/Optional;",
"promotePartial"
}, at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"))
private <T> void printStackTrace(Consumer<T> instance, T t, Operation<Void> original) {
original.call(instance, t);

if (Owo.DEBUG && this.messageSupplier instanceof StackTraceSupplier supplier) {
LOGGER.error("An error has occurred within DFU: ", supplier.throwable());
}
}

@WrapOperation(method = {
"ap(Lcom/mojang/serialization/DataResult;)Lcom/mojang/serialization/DataResult$Error;",
"flatMap(Ljava/util/function/Function;)Lcom/mojang/serialization/DataResult$Error;"
}, at = @At(value = "NEW", target = "(Ljava/util/function/Supplier;Ljava/util/Optional;Lcom/mojang/serialization/Lifecycle;)Lcom/mojang/serialization/DataResult$Error;", ordinal = 1))
private DataResult.Error preserveStackTrace1(Supplier<String> messageSupplier, Optional partialValue, Lifecycle lifecycle, Operation<DataResult.Error> original) {
if (this.messageSupplier instanceof StackTraceSupplier supplier) {
messageSupplier = StackTraceSupplier.of(supplier.throwable(), messageSupplier);
}

return original.call(messageSupplier, partialValue, lifecycle);
}

@WrapOperation(method = {
"mapError(Ljava/util/function/UnaryOperator;)Lcom/mojang/serialization/DataResult$Error;"
}, at = @At(value = "NEW", target = "(Ljava/util/function/Supplier;Ljava/util/Optional;Lcom/mojang/serialization/Lifecycle;)Lcom/mojang/serialization/DataResult$Error;"))
private DataResult.Error preserveStackTrace2(Supplier<String> messageSupplier, Optional partialValue, Lifecycle lifecycle, Operation<DataResult.Error> original) {
if (this.messageSupplier instanceof StackTraceSupplier supplier) {
messageSupplier = StackTraceSupplier.of(supplier.throwable(), messageSupplier);
}

return original.call(messageSupplier, partialValue, lifecycle);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.wispforest.owo.serialization.format.nbt.NbtEndec;
import io.wispforest.owo.serialization.format.nbt.NbtSerializer;
import io.wispforest.owo.util.Scary;
import io.wispforest.owo.util.StackTraceSupplier;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtOps;
Expand Down Expand Up @@ -434,7 +435,7 @@ private static <T> DataResult<T> captureThrows(Supplier<T> action) {
try {
return DataResult.success(action.get());
} catch (Exception e) {
return DataResult.error(e::getMessage);
return DataResult.error(StackTraceSupplier.of(e));
}
}

Expand Down
45 changes: 43 additions & 2 deletions src/main/java/io/wispforest/owo/util/StackTraceSupplier.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,51 @@
package io.wispforest.owo.util;

import org.jetbrains.annotations.Nullable;

import java.util.function.Supplier;

public record StackTraceSupplier(StackTraceElement[] stackTrace, Supplier<String> message) implements Supplier<String> {
public final class StackTraceSupplier implements Supplier<String> {
private final Throwable throwable;
private final @Nullable Supplier<String> message;

private StackTraceSupplier(@Nullable Throwable throwable, @Nullable Supplier<String> message) {
this.throwable = throwable;
this.message = message;
}

public static StackTraceSupplier of(Throwable throwable) {
return new StackTraceSupplier(throwable, null);
}

public static StackTraceSupplier of(Throwable throwable, Supplier<String> supplier) {
return new StackTraceSupplier(throwable, supplier);
}

public static StackTraceSupplier of(String message) {
return new StackTraceSupplier(new IllegalStateException(message), null);
}

@Override
public String get() {
return message.get();
return message != null ? message.get() : throwable.getMessage();
}

public StackTraceElement[] getFullStackTrace() {
var innerThrowable = throwable();
while (innerThrowable.getCause() != null) {
innerThrowable = throwable().getCause();
}
return innerThrowable.getStackTrace();
}

public Throwable throwable() {
return throwable;
}

@Override
public String toString() {
return "StackTraceSupplier[" +
"throwable=" + throwable + ", " +
"message=" + message + ']';
}
}
Loading