Skip to content
5 changes: 5 additions & 0 deletions docs/changelog/133604.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 133604
summary: Update `DefBootstrap` to handle Error from `ClassValue`
area: Infra/Scripting
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ protected MethodHandle computeValue(Class<?> receiverType) {
try {
return lookup(flavor, name, receiverType).asType(type);
} catch (Throwable t) {
Def.rethrow(t);
// ClassValue.getFromHashMap wraps checked exceptions as Error, so we
// use a sentinel class [PainlessWrappedException] here to work around
// this issue and later unwrap the original exception
Def.rethrow(t instanceof Exception ? new PainlessWrappedException((Exception) t) : t);
throw new AssertionError();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public interface PainlessScript {
* @return The generated ScriptException.
*/
default ScriptException convertToScriptException(Throwable t, Map<String, List<String>> extraMetadata) {
if (t instanceof PainlessWrappedException) {
t = t.getCause();
}
// create a script stack: this is just the script portion
List<String> scriptStack = new ArrayList<>();
ScriptException.Position pos = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.painless;

/**
* Checked exceptions are wrapped in {@link ClassValue}#getFromHashMap in Error
* which leads to unexpected behavior in Painless. This class is used as a
* workaround for that exception wrapping.
*/
public class PainlessWrappedException extends Error {

/**
* Constructor.
* @param cause The {@link Exception} cause.
*/
public PainlessWrappedException(final Exception cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.PainlessError;
import org.elasticsearch.painless.PainlessExplainError;
import org.elasticsearch.painless.PainlessWrappedException;
import org.elasticsearch.painless.ScriptClassInfo;
import org.elasticsearch.painless.ScriptClassInfo.MethodArgument;
import org.elasticsearch.painless.ir.BinaryImplNode;
Expand Down Expand Up @@ -415,6 +416,7 @@ protected static void injectSandboxExceptions(FunctionNode irFunctionNode) {

for (Class<? extends Throwable> throwable : List.of(
PainlessError.class,
PainlessWrappedException.class,
LinkageError.class,
OutOfMemoryError.class,
StackOverflowError.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ public void testMegamorphic() throws Throwable {
map.put("a", "b");
assertEquals(2, (int) handle.invokeExact((Object) map));

final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> {
final PainlessWrappedException pwe = expectThrows(PainlessWrappedException.class, () -> {
Integer.toString((int) handle.invokeExact(new Object()));
});
assertTrue(pwe.getCause() instanceof IllegalArgumentException);
IllegalArgumentException iae = (IllegalArgumentException) pwe.getCause();
assertEquals("dynamic method [java.lang.Object, size/0] not found", iae.getMessage());
assertTrue("Does not fail inside ClassValue.computeValue()", Arrays.stream(iae.getStackTrace()).anyMatch(e -> {
return e.getMethodName().equals("computeValue") && e.getClassName().startsWith("org.elasticsearch.painless.DefBootstrap$PIC$");
Expand Down