This is an automated email from the ASF dual-hosted git repository. sergeychugunov pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push: new 87bd626 IGNITE-15716 Fix StackOverflowError in case if exception suppressed with itself. - Fixes #9484. 87bd626 is described below commit 87bd626b08094f197539e4b2ccdbf6c4e49c68f7 Author: Eduard Rakhmankulov <erixon...@gmail.com> AuthorDate: Thu Oct 14 10:02:44 2021 +0300 IGNITE-15716 Fix StackOverflowError in case if exception suppressed with itself. - Fixes #9484. Signed-off-by: Sergey Chugunov <sergey.chugu...@gmail.com> --- .../org/apache/ignite/internal/util/typedef/X.java | 106 +++++++++++++-------- .../java/org/apache/ignite/lang/GridXSelfTest.java | 47 +++++++++ 2 files changed, 115 insertions(+), 38 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/typedef/X.java b/modules/core/src/main/java/org/apache/ignite/internal/util/typedef/X.java index d1db342..c5890df 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/typedef/X.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/typedef/X.java @@ -29,8 +29,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; @@ -461,41 +464,19 @@ public final class X { * * @param t Throwable to check (if {@code null}, {@code false} is returned). * @param msg Message text that should be in cause. - * @param cls Cause classes to check (if {@code null} or empty, {@code false} is returned). + * @param types Cause classes to check (if {@code null} or empty, {@code false} is returned). * @return {@code True} if one of the causing exception is an instance of passed in classes, * {@code false} otherwise. */ - @SafeVarargs - public static boolean hasCause(@Nullable Throwable t, @Nullable String msg, @Nullable Class<?>... cls) { - if (t == null || F.isEmpty(cls)) + public static boolean hasCause(@Nullable Throwable t, @Nullable String msg, @Nullable Class<?>... types) { + if (t == null || F.isEmpty(types)) return false; - assert cls != null; - - for (Throwable th = t; th != null; th = th.getCause()) { - for (Class<?> c : cls) { - if (c.isAssignableFrom(th.getClass())) { - if (msg != null) { - if (th.getMessage() != null && th.getMessage().contains(msg)) - return true; - else - continue; - } - - return true; - } - } - - for (Throwable n : th.getSuppressed()) { - if (hasCause(n, msg, cls)) - return true; - } + Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); - if (th.getCause() == th) - break; - } + Throwable found = searchForCause(t, dejaVu, msg, types); - return false; + return found != null; } /** @@ -548,25 +529,74 @@ public final class X { if (t == null || cls == null) return null; - for (Throwable th = t; th != null; th = th.getCause()) { - if (cls.isAssignableFrom(th.getClass())) - return (T)th; + Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); - for (Throwable n : th.getSuppressed()) { - T found = cause(n, cls); + @SuppressWarnings("unchecked") + T res = (T)searchForCause(t, dejaVu, null, cls); - if (found != null) - return found; - } + return res; + } - if (th.getCause() == th) - break; + /** + * Traverses `tree` of {@link Throwable} to find first cause/suppressed is assignable from any of given types. + * Function is aware of possible circular references through tracking of tested objects. + * + * @param t Throwable. + * @param dejaVu Set of throwable for tracking already tested objects. + * @param types Candidate types. + * @return First throwable meets test condition or {@code null} if none has matched. + */ + @Nullable private static Throwable searchForCause( + Throwable t, + Set<Throwable> dejaVu, + @Nullable String msg, + Class<?>... types + ) { + if (isThrowableValid(t, msg, types)) + return t; + + if (!dejaVu.add(t)) + return null; + + Throwable cause = t.getCause(); + + if (cause != null) { + Throwable found = searchForCause(cause, dejaVu, msg, types); + + if (found != null) + return found; + } + + for (Throwable suppressed : t.getSuppressed()) { + Throwable found = searchForCause(suppressed, dejaVu, msg, types); + + if (found != null) + return found; } return null; } /** + * Checks whether given throwable is assignable from any of candidate throwable type + * and contains given non-null message. + * + * @param thr Throwable. + * @param msg Possible throwable message. + * @param candidateTypes Possible types of throwable. + * @return {@code true} if such type in found, else {@code false}. + */ + private static boolean isThrowableValid(Throwable thr, @Nullable String msg, Class<?>... candidateTypes) { + for (Class<?> c : candidateTypes) { + if (c != null && c.isAssignableFrom(thr.getClass()) + && (msg == null || (thr.getMessage() != null && thr.getMessage().contains(msg)))) + return true; + } + + return false; + } + + /** * @param throwable The exception to examine. * @return The wrapped exception, or {@code null} if not found. */ diff --git a/modules/core/src/test/java/org/apache/ignite/lang/GridXSelfTest.java b/modules/core/src/test/java/org/apache/ignite/lang/GridXSelfTest.java index 4a02402..57b5ff6 100644 --- a/modules/core/src/test/java/org/apache/ignite/lang/GridXSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/lang/GridXSelfTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.ConnectException; import java.util.Arrays; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteException; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.apache.ignite.testframework.junits.common.GridCommonTest; @@ -55,6 +56,52 @@ public class GridXSelfTest extends GridCommonAbstractTest { } /** + * Checks that using function with Throwable that contains circular reference in {@code getSuppressed()} + * does not cause {@link StackOverflowError}. + */ + @Test + public void testCauseDoesNotRaiseStackOverflowWhenCircularReferenceInSuppressed() { + NullPointerException npe = new NullPointerException(); + + IOException ioExc = new IOException(npe); + + IgniteException exc = new IgniteException(ioExc); + + ioExc.addSuppressed(exc); + + assertEquals(npe, X.cause(exc, NullPointerException.class)); + + assertTrue(X.hasCause(exc, NullPointerException.class)); + + assertNull(X.cause(exc, ArithmeticException.class)); + + assertFalse(X.hasCause(exc, ArithmeticException.class)); + } + + /** + * Checks that using function with Throwable that contains circular reference in {@code getCaused()} + * does not cause {@link StackOverflowError}. + */ + @Test + public void testCauseDoesNotRaiseStackOverflowWhenCircularReferenceInCaused() { + NullPointerException npe = new NullPointerException(); + + IOException ioExc = new IOException(npe); + + IgniteException exc = new IgniteException(ioExc); + + npe.initCause(exc); + + assertEquals(npe, X.cause(exc, NullPointerException.class)); + + assertTrue(X.hasCause(exc, NullPointerException.class)); + + assertNull(X.cause(exc, ArithmeticException.class)); + + assertFalse(X.hasCause(exc, ArithmeticException.class)); + } + + /** * Tests string presentation of given time. */ @Test