mirror of
https://github.com/apple/pkl.git
synced 2026-04-23 00:38:37 +02:00
Fix display of evaluation errors thrown by command convert/transformAll (#1431)
This commit is contained in:
@@ -942,7 +942,52 @@ class CliCommandRunnerTest {
|
|||||||
assertThrows<CliktError> {
|
assertThrows<CliktError> {
|
||||||
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
||||||
}
|
}
|
||||||
assertThat(exc.message).isEqualTo("oops!")
|
assertThat(exc.message).contains("oops!")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `convert with eval error`() {
|
||||||
|
val moduleUri =
|
||||||
|
writePklFile(
|
||||||
|
"cmd.pkl",
|
||||||
|
renderOptions +
|
||||||
|
"""
|
||||||
|
class Options {
|
||||||
|
@Argument { convert = (it) -> it.noSuchMethod() }
|
||||||
|
foo: String
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val exc =
|
||||||
|
assertThrows<CliktError> {
|
||||||
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
||||||
|
}
|
||||||
|
assertThat(exc.message).contains("Cannot find method `noSuchMethod` in class `String`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `convert with stack overflow`() {
|
||||||
|
val moduleUri =
|
||||||
|
writePklFile(
|
||||||
|
"cmd.pkl",
|
||||||
|
renderOptions +
|
||||||
|
"""
|
||||||
|
const function overflow(it) = overflow(it)
|
||||||
|
class Options {
|
||||||
|
@Argument { convert = (it) -> overflow(it) }
|
||||||
|
foo: String
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val exc =
|
||||||
|
assertThrows<CliktError> {
|
||||||
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
||||||
|
}
|
||||||
|
assertThat(exc.message).contains("A stack overflow occurred.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.pkl.core;
|
package org.pkl.core;
|
||||||
|
|
||||||
import com.oracle.truffle.api.TruffleStackTrace;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -286,6 +285,8 @@ public final class EvaluatorImpl implements Evaluator {
|
|||||||
new CommandSpecParser(
|
new CommandSpecParser(
|
||||||
moduleResolver,
|
moduleResolver,
|
||||||
securityManager,
|
securityManager,
|
||||||
|
frameTransformer,
|
||||||
|
color,
|
||||||
(fileOutput) -> new FileOutputImpl(this, fileOutput));
|
(fileOutput) -> new FileOutputImpl(this, fileOutput));
|
||||||
run.accept(commandRunner.parse(module));
|
run.accept(commandRunner.parse(module));
|
||||||
return null;
|
return null;
|
||||||
@@ -358,7 +359,7 @@ public final class EvaluatorImpl implements Evaluator {
|
|||||||
try {
|
try {
|
||||||
evalResult = supplier.get();
|
evalResult = supplier.get();
|
||||||
} catch (VmStackOverflowException e) {
|
} catch (VmStackOverflowException e) {
|
||||||
if (isPklBug(e)) {
|
if (VmUtils.isPklBug(e)) {
|
||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.bug("Stack overflow")
|
.bug("Stack overflow")
|
||||||
.withCause(e.getCause())
|
.withCause(e.getCause())
|
||||||
@@ -387,7 +388,7 @@ public final class EvaluatorImpl implements Evaluator {
|
|||||||
if (e.getClass()
|
if (e.getClass()
|
||||||
.getName()
|
.getName()
|
||||||
.equals("com.oracle.truffle.polyglot.PolyglotEngineImpl$CancelExecution")) {
|
.equals("com.oracle.truffle.polyglot.PolyglotEngineImpl$CancelExecution")) {
|
||||||
// Truffle cancelled evaluation in response to polyglotContext.close(true) triggered by
|
// Truffle canceled evaluation in response to polyglotContext.close(true) triggered by
|
||||||
// TimeoutTask
|
// TimeoutTask
|
||||||
handleTimeout(timeoutTask);
|
handleTimeout(timeoutTask);
|
||||||
throw PklBugException.unreachableCode();
|
throw PklBugException.unreachableCode();
|
||||||
@@ -398,7 +399,7 @@ public final class EvaluatorImpl implements Evaluator {
|
|||||||
try {
|
try {
|
||||||
polyglotContext.leave();
|
polyglotContext.leave();
|
||||||
} catch (IllegalStateException ignored) {
|
} catch (IllegalStateException ignored) {
|
||||||
// happens if evaluation has already been cancelled with polyglotContext.close(true)
|
// happens if evaluation has already been canceled with polyglotContext.close(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,15 +465,6 @@ public final class EvaluatorImpl implements Evaluator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPklBug(VmStackOverflowException e) {
|
|
||||||
// There's no good way to tell if a StackOverflowError came from Pkl, or from our
|
|
||||||
// implementation.
|
|
||||||
// This is a simple heuristic; it's pretty likely that any stack overflow error that occurs
|
|
||||||
// if there's less than 100 truffle frames is due to our own doing.
|
|
||||||
var truffleStackTraceElements = TruffleStackTrace.getStackTrace(e);
|
|
||||||
return truffleStackTraceElements != null && truffleStackTraceElements.size() < 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScheduledFuture.cancel() is problematic, so let's handle cancellation on our own
|
// ScheduledFuture.cancel() is problematic, so let's handle cancellation on our own
|
||||||
private final class TimeoutTask implements Runnable {
|
private final class TimeoutTask implements Runnable {
|
||||||
// both fields guarded by synchronizing on `this`
|
// both fields guarded by synchronizing on `this`
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.graalvm.collections.EconomicMap;
|
import org.graalvm.collections.EconomicMap;
|
||||||
import org.pkl.core.CommandSpec;
|
import org.pkl.core.CommandSpec;
|
||||||
@@ -45,6 +46,7 @@ import org.pkl.core.PNull;
|
|||||||
import org.pkl.core.PklBugException;
|
import org.pkl.core.PklBugException;
|
||||||
import org.pkl.core.SecurityManager;
|
import org.pkl.core.SecurityManager;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
|
import org.pkl.core.StackFrameTransformer;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.ast.VmModifier;
|
import org.pkl.core.ast.VmModifier;
|
||||||
import org.pkl.core.ast.expression.literal.AmendModuleNodeGen;
|
import org.pkl.core.ast.expression.literal.AmendModuleNodeGen;
|
||||||
@@ -71,14 +73,20 @@ public final class CommandSpecParser {
|
|||||||
|
|
||||||
private final ModuleResolver moduleResolver;
|
private final ModuleResolver moduleResolver;
|
||||||
private final SecurityManager securityManager;
|
private final SecurityManager securityManager;
|
||||||
|
private final StackFrameTransformer frameTransformer;
|
||||||
|
private final boolean color;
|
||||||
private final Function<VmTyped, FileOutput> makeFileOutput;
|
private final Function<VmTyped, FileOutput> makeFileOutput;
|
||||||
|
|
||||||
public CommandSpecParser(
|
public CommandSpecParser(
|
||||||
ModuleResolver moduleResolver,
|
ModuleResolver moduleResolver,
|
||||||
SecurityManager securityManager,
|
SecurityManager securityManager,
|
||||||
|
StackFrameTransformer frameTransformer,
|
||||||
|
boolean color,
|
||||||
Function<VmTyped, FileOutput> makeFileOutput) {
|
Function<VmTyped, FileOutput> makeFileOutput) {
|
||||||
this.moduleResolver = moduleResolver;
|
this.moduleResolver = moduleResolver;
|
||||||
this.securityManager = securityManager;
|
this.securityManager = securityManager;
|
||||||
|
this.frameTransformer = frameTransformer;
|
||||||
|
this.color = color;
|
||||||
this.makeFileOutput = makeFileOutput;
|
this.makeFileOutput = makeFileOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,24 +470,13 @@ public final class CommandSpecParser {
|
|||||||
annotation == null
|
annotation == null
|
||||||
? null
|
? null
|
||||||
: VmUtils.readMember(annotation, Identifier.CONVERT) instanceof VmFunction func
|
: VmUtils.readMember(annotation, Identifier.CONVERT) instanceof VmFunction func
|
||||||
? (rawValue, workingDirUri) -> {
|
? (rawValue, workingDirUri) ->
|
||||||
try {
|
handleErrors(() -> handleImports(func.apply(rawValue), workingDirUri))
|
||||||
return handleImports(func.apply(rawValue), workingDirUri);
|
|
||||||
} catch (VmException e) {
|
|
||||||
throw new BadValue(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
: null,
|
||||||
annotation == null
|
annotation == null
|
||||||
? null
|
? null
|
||||||
: VmUtils.readMember(annotation, Identifier.TRANSFORM_ALL) instanceof VmFunction func
|
: VmUtils.readMember(annotation, Identifier.TRANSFORM_ALL) instanceof VmFunction func
|
||||||
? (it) -> {
|
? (it) -> handleErrors(() -> func.apply(VmList.create(it)))
|
||||||
try {
|
|
||||||
return func.apply(VmList.create(it));
|
|
||||||
} catch (VmException e) {
|
|
||||||
throw new BadValue(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
: null,
|
||||||
annotation == null
|
annotation == null
|
||||||
? null
|
? null
|
||||||
@@ -1064,6 +1061,31 @@ public final class CommandSpecParser {
|
|||||||
&& vmTyped.getVmClass() == CommandModule.getImportClass();
|
&& vmTyped.getVmClass() == CommandModule.getImportClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle errors from convert/transformAll and correctly format them for the CLI
|
||||||
|
private Object handleErrors(Supplier<Object> f) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
return f.get();
|
||||||
|
} catch (VmStackOverflowException e) {
|
||||||
|
if (VmUtils.isPklBug(e)) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.bug("Stack overflow")
|
||||||
|
.withCause(e.getCause())
|
||||||
|
.build()
|
||||||
|
.toPklException(frameTransformer, color);
|
||||||
|
}
|
||||||
|
throw e.toPklException(frameTransformer, color);
|
||||||
|
} catch (VmException e) {
|
||||||
|
throw e.toPklException(frameTransformer, color);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PklBugException(e);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// add a newline so this prints nicely under "Error: invalid value for <name>:"
|
||||||
|
throw new BadValue("\n" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// for convert handle imports by replace Command.Import values
|
// for convert handle imports by replace Command.Import values
|
||||||
// with imported module or Mapping<String, Module> values
|
// with imported module or Mapping<String, Module> values
|
||||||
// Command.Import instances in returned Pair, List, Set, or Map values are replaced as well
|
// Command.Import instances in returned Pair, List, Set, or Map values are replaced as well
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.oracle.truffle.api.CompilerDirectives;
|
|||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
import com.oracle.truffle.api.Truffle;
|
import com.oracle.truffle.api.Truffle;
|
||||||
import com.oracle.truffle.api.TruffleLanguage;
|
import com.oracle.truffle.api.TruffleLanguage;
|
||||||
|
import com.oracle.truffle.api.TruffleStackTrace;
|
||||||
import com.oracle.truffle.api.frame.*;
|
import com.oracle.truffle.api.frame.*;
|
||||||
import com.oracle.truffle.api.nodes.*;
|
import com.oracle.truffle.api.nodes.*;
|
||||||
import com.oracle.truffle.api.source.Source;
|
import com.oracle.truffle.api.source.Source;
|
||||||
@@ -975,4 +976,13 @@ public final class VmUtils {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPklBug(VmStackOverflowException e) {
|
||||||
|
// There's no good way to tell if a StackOverflowError came from Pkl, or from our
|
||||||
|
// implementation.
|
||||||
|
// This is a simple heuristic; it's pretty likely that any stack overflow error that occurs
|
||||||
|
// if there's less than 100 truffle frames is due to our own doing.
|
||||||
|
var truffleStackTraceElements = TruffleStackTrace.getStackTrace(e);
|
||||||
|
return truffleStackTraceElements != null && truffleStackTraceElements.size() < 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user