浏览代码

Update DefBootstrap to handle Error from ClassValue (#133604) (#133662)

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.
Jack Conradson 1 月之前
父节点
当前提交
d155df67bc

+ 5 - 0
docs/changelog/133604.yaml

@@ -0,0 +1,5 @@
+pr: 133604
+summary: Update `DefBootstrap` to handle Error from `ClassValue`
+area: Infra/Scripting
+type: bug
+issues: []

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

@@ -201,7 +201,10 @@ public final class DefBootstrap {
                     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();
                     }
                 }

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

@@ -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;

+ 26 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessWrappedException.java

@@ -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);
+    }
+}

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

@@ -12,6 +12,7 @@ package org.elasticsearch.painless.phase;
 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;
@@ -415,6 +416,7 @@ public class PainlessUserTreeToIRTreePhase extends DefaultUserTreeToIRTreePhase
 
             for (Class<? extends Throwable> throwable : List.of(
                 PainlessError.class,
+                PainlessWrappedException.class,
                 LinkageError.class,
                 OutOfMemoryError.class,
                 StackOverflowError.class,

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

@@ -139,9 +139,11 @@ public class DefBootstrapTests extends ESTestCase {
         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$");