Skip to content
Prev Previous commit
Next Next commit
fix workaround
  • Loading branch information
jdconrad committed Aug 26, 2025
commit f57386883b96e91c4ee7ed54ab95d8a97174b584
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,10 @@ protected MethodHandle computeValue(Class<?> receiverType) {
try {
return lookup(flavor, name, receiverType).asType(type);
} catch (Throwable t) {
// ClassValue.getFromHashMap wraps checked exceptions as Error, but
// we do not want to crash here because we could not process the type
// correctly as part of a script, so we instead unwrap the Error and
// rethrow the original exception
if (t instanceof Error && ((Error) t).getCause() instanceof Exception) {
t = (Exception) ((Error) t).getCause();
}
Def.rethrow(t);
// ClassValue.getFromHashMap wraps checked exceptions as Error, so we
// use a sentinel class [PainlessWrapperError] here to work around
// this issue and later unwrap the original exception
Def.rethrow(new PainlessWrappedError(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 PainlessWrappedError) {
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 PainlessWrappedError extends Error {

/**
* Constructor.
* @param cause The {@link Throwable} cause.
*/
public PainlessWrappedError(final Throwable 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.PainlessWrappedError;
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,
PainlessWrappedError.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 PainlessWrappedError pwe = expectThrows(PainlessWrappedError.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