-
Notifications
You must be signed in to change notification settings - Fork 460
[FLUSS-2091][common] Introduced Cycle Detection During Exception Suppression #2112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
5e31aaf
9e4c844
6586dc7
4a6a2c3
33b581c
3fa12d1
61c395e
78b3692
197cdc7
28cbf79
8d49f75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,8 +32,12 @@ | |
| import java.io.PrintWriter; | ||
| import java.io.StringWriter; | ||
| import java.lang.reflect.Field; | ||
| import java.util.ArrayDeque; | ||
| import java.util.Deque; | ||
| import java.util.HashSet; | ||
| import java.util.Locale; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import java.util.concurrent.CompletionException; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.function.Function; | ||
|
|
@@ -365,10 +369,65 @@ public static <T extends Throwable> T firstOrSuppressed(T newException, @Nullabl | |
|
|
||
| if (previous == null || previous == newException) { | ||
| return newException; | ||
| } else { | ||
| previous.addSuppressed(newException); | ||
| } | ||
|
|
||
| // If the exceptions already reference each other through the suppression or cause chains, | ||
| // return the previous exception to avoid introducing cycles. | ||
| if (existsInExceptionChain(newException, previous) | ||
| || existsInExceptionChain(previous, newException)) { | ||
| return previous; | ||
| } | ||
|
|
||
| previous.addSuppressed(newException); | ||
| return previous; | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether the given {@code exception} throwable exception exists anywhere within the | ||
| * exception chain of {@code previous}. This includes both the cause chain and all suppressed | ||
| * exceptions. A visited set is used to avoid cycles and redundant traversal. | ||
| * | ||
| * @param exception The throwable exception to search for. | ||
| * @param previous The previous throwable exception chain to search in. | ||
|
||
| * @return True, if the exception is found within the suppressed chain, false otherwise. | ||
rionmonster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| private static boolean existsInExceptionChain(Throwable exception, Throwable previous) { | ||
| if (exception == null || previous == null) { | ||
| return false; | ||
| } | ||
| if (exception == previous) { | ||
| return true; | ||
| } | ||
|
|
||
| // Apply cycle prevention through a graph-like traversal of existing | ||
| // suppressed or cause chain exceptions | ||
| Set<Throwable> previousExceptions = new HashSet<>(); | ||
| Deque<Throwable> exceptionStack = new ArrayDeque<>(); | ||
| exceptionStack.push(previous); | ||
|
|
||
| while (!exceptionStack.isEmpty()) { | ||
| Throwable currentException = exceptionStack.pop(); | ||
| if (!previousExceptions.add(currentException)) { | ||
rionmonster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| continue; | ||
| } | ||
|
|
||
| if (currentException == exception) { | ||
| return true; | ||
| } | ||
|
|
||
| // Traverse suppression chain | ||
| for (Throwable suppressed : currentException.getSuppressed()) { | ||
| exceptionStack.push(suppressed); | ||
| } | ||
|
|
||
| // Traverse cause-chain | ||
| Throwable cause = currentException.getCause(); | ||
| if (cause != null) { | ||
| exceptionStack.push(cause); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.