diff --git a/CHANGELOG.md b/CHANGELOG.md index 821adfa63433e..26b4287f0ab0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Onboarding new maven snapshots publishing to s3 ([#19619](https://github.com/opensearch-project/OpenSearch/pull/19619)) - Remove MultiCollectorWrapper and use MultiCollector in Lucene instead ([#19595](https://github.com/opensearch-project/OpenSearch/pull/19595)) - Change implementation for `percentiles` aggregation for latency improvement ([#19648](https://github.com/opensearch-project/OpenSearch/pull/19648)) +- Wrap checked exceptions in painless.DefBootstrap to support JDK-25 ([#19706](https://github.com/opensearch-project/OpenSearch/pull/19706)) - Refactor the ThreadPoolStats.Stats class to use the Builder pattern instead of constructors ([#19306](https://github.com/opensearch-project/OpenSearch/pull/19306)) ### Fixed diff --git a/modules/lang-painless/src/main/java/org/opensearch/painless/DefBootstrap.java b/modules/lang-painless/src/main/java/org/opensearch/painless/DefBootstrap.java index 0726881b1297f..89d3d62baf714 100644 --- a/modules/lang-painless/src/main/java/org/opensearch/painless/DefBootstrap.java +++ b/modules/lang-painless/src/main/java/org/opensearch/painless/DefBootstrap.java @@ -112,6 +112,17 @@ private DefBootstrap() {} // no instance! */ public static final int OPERATOR_EXPLICIT_CAST = 1 << 2; + /** + * ClassValue.getFromHashMap will wrap checked exceptions with Errors. To get + * around that we wrap any checked exceptions with this RuntimeException and + * unwrap them later. + */ + static final class WrappedCheckedException extends RuntimeException { + private WrappedCheckedException(Exception cause) { + super(cause); + } + } + /** * CallSite that implements the polymorphic inlining cache (PIC). */ @@ -204,7 +215,10 @@ protected MethodHandle computeValue(Class receiverType) { try { return lookup(flavor, name, receiverType).asType(type); } catch (Throwable t) { - Def.rethrow(t); + switch (t) { + case Exception e -> throw new WrappedCheckedException(e); + default -> Def.rethrow(t); + } throw new AssertionError(); } } diff --git a/modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScript.java b/modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScript.java index 766d9baaa0b1a..885282cead156 100644 --- a/modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScript.java +++ b/modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScript.java @@ -65,14 +65,18 @@ public interface PainlessScript { /** * Adds stack trace and other useful information to exceptions thrown * from a Painless script. - * @param t The throwable to build an exception around. + * @param originalThrowable The throwable to build an exception around. * @return The generated ScriptException. */ - default ScriptException convertToScriptException(Throwable t, Map> extraMetadata) { + default ScriptException convertToScriptException(Throwable originalThrowable, Map> extraMetadata) { + final Throwable unwrapped = switch (originalThrowable) { + case DefBootstrap.WrappedCheckedException w -> w.getCause(); + default -> originalThrowable; + }; // create a script stack: this is just the script portion List scriptStack = new ArrayList<>(); ScriptException.Position pos = null; - for (StackTraceElement element : t.getStackTrace()) { + for (StackTraceElement element : unwrapped.getStackTrace()) { if (WriterConstants.CLASS_NAME.equals(element.getClassName())) { // found the script portion int originalOffset = element.getLineNumber(); @@ -106,7 +110,14 @@ default ScriptException convertToScriptException(Throwable t, Map> entry : extraMetadata.entrySet()) { scriptException.addMetadata(entry.getKey(), entry.getValue()); } diff --git a/modules/lang-painless/src/test/java/org/opensearch/painless/DefBootstrapTests.java b/modules/lang-painless/src/test/java/org/opensearch/painless/DefBootstrapTests.java index ae860d177cce1..58c1c35f15b42 100644 --- a/modules/lang-painless/src/test/java/org/opensearch/painless/DefBootstrapTests.java +++ b/modules/lang-painless/src/test/java/org/opensearch/painless/DefBootstrapTests.java @@ -46,6 +46,8 @@ import java.util.Collections; import java.util.HashMap; +import static org.hamcrest.Matchers.instanceOf; + public class DefBootstrapTests extends OpenSearchTestCase { private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromAllowlists(Allowlist.BASE_ALLOWLISTS); @@ -157,9 +159,11 @@ public void testMegamorphic() throws Throwable { map.put("a", "b"); assertEquals(2, (int) handle.invokeExact((Object) map)); - final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> { + final DefBootstrap.WrappedCheckedException wrapped = expectThrows(DefBootstrap.WrappedCheckedException.class, () -> { Integer.toString((int) handle.invokeExact(new Object())); }); + assertThat(wrapped.getCause(), instanceOf(IllegalArgumentException.class)); + final IllegalArgumentException iae = (IllegalArgumentException) wrapped.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.opensearch.painless.DefBootstrap$PIC$");