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

Reply via email to