Skip to content

Commit a313641

Browse files
authored
Update DefBootstrap to handle Error from ClassValue (#133604) (#133660)
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 Painless script, so we instead unwrap the Error and rethrow the original exception.
1 parent 5bcf0bf commit a313641

File tree

6 files changed

+43
-2
lines changed

6 files changed

+43
-2
lines changed

docs/changelog/133604.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 133604
2+
summary: Update `DefBootstrap` to handle Error from `ClassValue`
3+
area: Infra/Scripting
4+
type: bug
5+
issues: []

modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ protected MethodHandle computeValue(Class<?> receiverType) {
201201
try {
202202
return lookup(flavor, name, receiverType).asType(type);
203203
} catch (Throwable t) {
204-
Def.rethrow(t);
204+
// ClassValue.getFromHashMap wraps checked exceptions as Error, so we
205+
// use a sentinel class [PainlessWrappedException] here to work around
206+
// this issue and later unwrap the original exception
207+
Def.rethrow(t instanceof Exception ? new PainlessWrappedException((Exception) t) : t);
205208
throw new AssertionError();
206209
}
207210
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScript.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public interface PainlessScript {
4646
* @return The generated ScriptException.
4747
*/
4848
default ScriptException convertToScriptException(Throwable t, Map<String, List<String>> extraMetadata) {
49+
if (t instanceof PainlessWrappedException) {
50+
t = t.getCause();
51+
}
4952
// create a script stack: this is just the script portion
5053
List<String> scriptStack = new ArrayList<>();
5154
ScriptException.Position pos = null;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.painless;
11+
12+
/**
13+
* Checked exceptions are wrapped in {@link ClassValue}#getFromHashMap in Error
14+
* which leads to unexpected behavior in Painless. This class is used as a
15+
* workaround for that exception wrapping.
16+
*/
17+
public class PainlessWrappedException extends Error {
18+
19+
/**
20+
* Constructor.
21+
* @param cause The {@link Exception} cause.
22+
*/
23+
public PainlessWrappedException(final Exception cause) {
24+
super(cause);
25+
}
26+
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/PainlessUserTreeToIRTreePhase.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.painless.Location;
1313
import org.elasticsearch.painless.PainlessError;
1414
import org.elasticsearch.painless.PainlessExplainError;
15+
import org.elasticsearch.painless.PainlessWrappedException;
1516
import org.elasticsearch.painless.ScriptClassInfo;
1617
import org.elasticsearch.painless.ScriptClassInfo.MethodArgument;
1718
import org.elasticsearch.painless.ir.BinaryImplNode;
@@ -415,6 +416,7 @@ protected static void injectSandboxExceptions(FunctionNode irFunctionNode) {
415416

416417
for (Class<? extends Throwable> throwable : List.of(
417418
PainlessError.class,
419+
PainlessWrappedException.class,
418420
LinkageError.class,
419421
OutOfMemoryError.class,
420422
StackOverflowError.class,

modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ public void testMegamorphic() throws Throwable {
139139
map.put("a", "b");
140140
assertEquals(2, (int) handle.invokeExact((Object) map));
141141

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

0 commit comments

Comments
 (0)