Skip to content

RuntimeErrorCatcher + Runtime Elements #7823

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

Open
wants to merge 8 commits into
base: dev/feature
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
59 changes: 59 additions & 0 deletions src/main/java/ch/njol/skript/expressions/ExprCaughtErrors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ch.njol.skript.expressions;

import ch.njol.skript.Skript;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;

@Name("Last Caught Errors")
@Description("Gets the last caught runtime errors from a 'catch runtime errors' section.")
@Example("""
catch runtime errors:
set worldborder center of {_border} to location(0, 0, NaN value)
if last caught runtime errors contains "Your location can't have a NaN value as one of its components":
set worldborder center of {_border} to location(0, 0, 0)
""")
@Since("INSERT VERSION")
public class ExprCaughtErrors extends SimpleExpression<String> {

static {
Skript.registerExpression(ExprCaughtErrors.class, String.class, ExpressionType.SIMPLE,
"last caught [run[ ]time] errors");
}

public static String[] lastErrors;

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
return true;
}

@Override
protected String @Nullable [] get(Event event) {
return lastErrors;
}

@Override
public boolean isSingle() {
return false;
}

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "last caught runtime errors";
}

}
75 changes: 75 additions & 0 deletions src/main/java/ch/njol/skript/sections/SecCatchErrors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ch.njol.skript.sections;

import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.ExprCaughtErrors;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.Section;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.Trigger;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.util.Kleenean;
import com.google.common.collect.Iterables;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.log.runtime.RuntimeError;
import org.skriptlang.skript.log.runtime.RuntimeErrorCatcher;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

@Name("Catch Runtime Errors")
@Description("Catch any runtime errors produced by code within the section. This is an in progress feature")
@Example("""
catch runtime errors:
set worldborder center of {_border} to location(0, 0, NaN value)
if last caught runtime errors contains "Your location can't have a NaN value as one of its components":
set worldborder center of {_border} to location(0, 0, 0)
""")
@Since("INSERT VERSION")
public class SecCatchErrors extends Section {

static {
Skript.registerSection(SecCatchErrors.class, "catch [run[ ]time] error[s]");
}

private Trigger trigger;

@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) {
if (Iterables.size(sectionNode) == 0) {
Skript.error("A catch errors section must contain code.");
return false;
}

AtomicBoolean delayed = new AtomicBoolean(false);
Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse());
trigger = loadCode(sectionNode, "runtime", afterLoading, Event.class);
if (delayed.get()) {
Skript.error("Delays can't be used within a catch errors section.");
return false;
}
return true;
}

@Override
protected @Nullable TriggerItem walk(Event event) {
RuntimeErrorCatcher catcher = new RuntimeErrorCatcher().start();
TriggerItem.walk(trigger, event);
ExprCaughtErrors.lastErrors = catcher.getCachedErrors().stream().map(RuntimeError::error).toArray(String[]::new);
catcher.clearCachedErrors()
.clearCachedFrames()
.stop();
return walk(event, false);
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "catch runtime errors";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.skriptlang.skript.log.runtime;

import ch.njol.skript.log.SkriptLogger;
import org.jetbrains.annotations.Unmodifiable;
import org.skriptlang.skript.log.runtime.Frame.FrameOutput;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;

/**
* A {@link RuntimeErrorConsumer} to be used in {@link RuntimeErrorManager} to catch {@link RuntimeError}s.
* This should always be used with {@link #start()} and {@link #stop()}.
*/
public class RuntimeErrorCatcher implements RuntimeErrorConsumer {

private List<RuntimeErrorConsumer> storedConsumers = new ArrayList<>();

private final List<RuntimeError> cachedErrors = new ArrayList<>();

private final List<Entry<FrameOutput, Level>> cachedFrames = new ArrayList<>();

public RuntimeErrorCatcher() {}

/**
* Gets the {@link RuntimeErrorManager}.
*/
private RuntimeErrorManager getManager() {
return RuntimeErrorManager.getInstance();
}

/**
* Starts this {@link RuntimeErrorCatcher}, removing all {@link RuntimeErrorConsumer}s from {@link RuntimeErrorManager}
* and storing them in {@link #storedConsumers}.
* Makes this {@link RuntimeErrorCatcher} the only {@link RuntimeErrorConsumer} in {@link RuntimeErrorManager}
* to catch {@link RuntimeError}s.
* @return This {@link RuntimeErrorCatcher}
*/
public RuntimeErrorCatcher start() {
storedConsumers = getManager().removeAllConsumers();
getManager().addConsumer(this);
return this;
}

/**
* Stops this {@link RuntimeErrorCatcher}, removing from {@link RuntimeErrorManager} and restoring the previous
* {@link RuntimeErrorConsumer}s from {@link #storedConsumers}.
* Prints all cached {@link RuntimeError}s, {@link #cachedErrors}, and cached {@link FrameOutput}s, {@link #cachedFrames}.
*/
public void stop() {
if (!getManager().removeConsumer(this)) {
SkriptLogger.LOGGER.severe("[Skript] A 'RuntimeErrorCatcher' was stopped incorrectly.");
return;
}
getManager().addConsumers(storedConsumers.toArray(RuntimeErrorConsumer[]::new));
for (RuntimeError runtimeError : cachedErrors)
storedConsumers.forEach(consumer -> consumer.printError(runtimeError));
for (Entry<FrameOutput, Level> entry : cachedFrames)
storedConsumers.forEach(consumer -> consumer.printFrameOutput(entry.getKey(), entry.getValue()));
}

/**
* Gets all the cached {@link RuntimeError}s.
*/
public @Unmodifiable List<RuntimeError> getCachedErrors() {
return Collections.unmodifiableList(cachedErrors);
}

/**
* Gets all cached {@link FrameOutput}s stored with its corresponding {@link Level} in an {@link Entry}
*/
public @Unmodifiable List<Entry<FrameOutput, Level>> getCachedFrames() {
return Collections.unmodifiableList(cachedFrames);
}

/**
* Clear all cached {@link RuntimeError}s.
*/
public RuntimeErrorCatcher clearCachedErrors() {
cachedErrors.clear();
return this;
}

/**
* Clears all cached {@link FrameOutput}s.
*/
public RuntimeErrorCatcher clearCachedFrames() {
cachedFrames.clear();
return this;
}

@Override
public void printError(RuntimeError error) {
cachedErrors.add(error);
}

@Override
public void printFrameOutput(FrameOutput output, Level level) {
cachedFrames.add(new Entry<FrameOutput, Level>() {
@Override
public FrameOutput getKey() {
return output;
}

@Override
public Level getValue() {
return level;
}

@Override
public Level setValue(Level value) {
return null;
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.io.Closeable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;

Expand Down Expand Up @@ -132,13 +133,36 @@ public void addConsumer(RuntimeErrorConsumer consumer) {
}
}

/**
* Adds multiple {@link RuntimeErrorConsumer}s that will receive the emitted errors and frame output data.
* Consumers will be maintained when the manager is refreshed.
* @param newConsumers The {@link RuntimeErrorConsumer}s to add.
*/
public void addConsumers(RuntimeErrorConsumer ... newConsumers) {
synchronized (consumers) {
consumers.addAll(Arrays.asList(newConsumers));
}
}

/**
* Removes a {@link RuntimeErrorConsumer} from the tracked list.
* @param consumer The consumer to remove.
*/
public void removeConsumer(RuntimeErrorConsumer consumer) {
public boolean removeConsumer(RuntimeErrorConsumer consumer) {
synchronized (consumers) {
return consumers.remove(consumer);
}
}

/**
* Removes all {@link RuntimeErrorConsumer}s that receive emitted errors and frame output data.
* @return All {@link RuntimeErrorConsumer}s removed.
*/
public List<RuntimeErrorConsumer> removeAllConsumers() {
synchronized (consumers) {
consumers.remove(consumer);
List<RuntimeErrorConsumer> currentConsumers = List.copyOf(consumers);
consumers.clear();
return currentConsumers;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ test "worldborder expand":
assert worldborder radius of {_border} is 100 with "Growing border by None changed the radius"
shrink {_border} to {_None}
assert worldborder radius of {_border} is 100 with "Shrinking border to None changed the radius"
shrink {_border} by NaN value
catch runtime errors:
shrink {_border} by NaN value
assert last caught runtime errors contains "You can't shrink a world border by NaN." with "Shrinking by NaN did not throw error"
assert worldborder radius of {_border} is 100 with "Shrinking border by NaN changed the radius"
grow {_border} to NaN value
catch runtime errors:
grow {_border} to NaN value
assert last caught runtime errors contains "You can't grow a world border to NaN." with "Growing by NaN did not throw error"
assert worldborder radius of {_border} is 100 with "Growing border to NaN changed the radius"

# infinite values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ test "worldborder center":
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "setting location of border to None moved border"
set worldborder center of {_border} to location({_None}, 0, {_None})
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with None components changed the border center"
set worldborder center of {_border} to location(NaN value, 0, 1)
catch runtime errors:
set worldborder center of {_border} to location(NaN value, 0, 1)
assert last caught runtime errors contains "Your location can't have a NaN value as one of its components" with "x-coord NaN value did not throw error"
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with x-coord NaN changed the border center"
set worldborder center of {_border} to location(2, 0, NaN value)
catch runtime errors:
set worldborder center of {_border} to location(2, 0, NaN value)
assert last caught runtime errors contains "Your location can't have a NaN value as one of its components" with "z-coord NaN value did not throw error"
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with z-coord NaN changed the border center"
set worldborder center of {_border} to location(infinity value, 0, infinity value)
assert worldborder center of {_border} is location(29999984, 0, 29999984, "world") with "border center coords were not rounded correctly when using +infinity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ test "worldborder damage amount":
assert worldborder damage amount of {_border} is 1.5 with "Failed to set worldborder damage amount to a float"
set worldborder damage amount of {_border} to {_None}
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to None changed the damage amount"
set worldborder damage amount of {_border} to NaN value
catch runtime errors:
set worldborder damage amount of {_border} to NaN value
assert last caught runtime errors contains "NaN is not a valid world border damage amount" with "NaN damage value did not throw error"
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to NaN value changed the damage amount"
set worldborder damage amount of {_border} to infinity value
catch runtime errors:
set worldborder damage amount of {_border} to infinity value
assert last caught runtime errors contains "World border damage amount cannot be infinite" with "Infinity damage value did not throw error"
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to infinity changed the damage amount"
set worldborder damage amount of {_border} to -infinity value
catch runtime errors:
set worldborder damage amount of {_border} to -infinity value
assert last caught runtime errors contains "World border damage amount cannot be infinite" with "Negative infinity damage value did not throw error"
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to -infinity changed the damage amount"

# add tests
Expand All @@ -27,7 +33,9 @@ test "worldborder damage amount":
assert worldborder damage amount of {_border} is 1.5 with "Failed adding negative integer to damage amount"
add {_None} to worldborder damage amount of {_border}
assert worldborder damage amount of {_border} is 1.5 with "Adding None to worldborder damage amount changed the damage amount"
add NaN value to worldborder damage amount of {_border}
catch runtime errors:
add NaN value to worldborder damage amount of {_border}
assert last caught runtime errors contains "NaN is not a valid world border damage amount" with "Adding NaN damage value did not throw error"
assert worldborder damage amount of {_border} is 1.5 with "Adding NaN value to worldborder damage amount changed the damage amount"
add infinity value to worldborder damage amount of {_border}
assert worldborder damage amount of {_border} is 1.5 with "Adding infinity to worldborder damage amount changed the damage amount"
Expand Down