This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 4060e60f4 Stop ExceptionUtils.removeCommonFrames at the first 
differing frame (#1831)
4060e60f4 is described below

commit 4060e60f45338ff337a145bd55bf1541353d8e3d
Author: alhuda <[email protected]>
AuthorDate: Sun Oct 25 18:22:27 2026 +0530

    Stop ExceptionUtils.removeCommonFrames at the first differing frame (#1831)
    
    Common frames are the trailing run shared by the cause and wrapper
    traces, as folded by Throwable.printStackTrace into "... n more".
    removeCommonFrames kept comparing past the first mismatch and removed
    any frame that matched again at the same offset, so the root-cause
    trace printed by getRootCauseStackTrace lost real frames, including
    the frame where the cause was created when a shared factory method
    builds both exceptions.
---
 .../commons/lang3/exception/ExceptionUtils.java    |  7 ++++---
 .../lang3/exception/ExceptionUtilsTest.java        | 23 ++++++++++++++++++++++
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java 
b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
index ade1eaec4..2c5bed6a2 100644
--- a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
+++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
@@ -923,12 +923,13 @@ public static void removeCommonFrames(final List<String> 
causeFrames, final List
         int wrapperFrameIndex = wrapperFrames.size() - 1;
         while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
             // Remove the frame from the cause trace if it is the same
-            // as in the wrapper trace
+            // as in the wrapper trace; common frames end at the first 
difference
             final String causeFrame = causeFrames.get(causeFrameIndex);
             final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
-            if (causeFrame.equals(wrapperFrame)) {
-                causeFrames.remove(causeFrameIndex);
+            if (!causeFrame.equals(wrapperFrame)) {
+                break;
             }
+            causeFrames.remove(causeFrameIndex);
             causeFrameIndex--;
             wrapperFrameIndex--;
         }
diff --git 
a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
index 533acce5b..a52f34d0d 100644
--- a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
@@ -913,6 +913,29 @@ void testRemoveCommonFrames_ListList() {
         assertNullPointerException(() -> 
ExceptionUtils.removeCommonFrames(null, null));
     }
 
+    @Test
+    void testRemoveCommonFramesStopsAtFirstDifference() {
+        final List<String> causeFrames = new ArrayList<>(Arrays.asList("\tat 
a.Factory.create(Factory.java:1)", "\tat a.B.one(B.java:2)",
+                "\tat a.B.main(B.java:9)"));
+        final List<String> wrapperFrames = Arrays.asList("\tat 
a.Factory.create(Factory.java:1)", "\tat a.B.two(B.java:5)",
+                "\tat a.B.main(B.java:9)");
+        ExceptionUtils.removeCommonFrames(causeFrames, wrapperFrames);
+        assertEquals(Arrays.asList("\tat a.Factory.create(Factory.java:1)", 
"\tat a.B.one(B.java:2)"), causeFrames);
+    }
+
+    @Test
+    void testRootCauseStackTraceKeepsSharedFactoryFrame() {
+        final RuntimeException cause = newRuntimeException("cause", null);
+        final RuntimeException wrapper = newRuntimeException("wrapper", cause);
+        final List<String> frames = 
ExceptionUtils.getRootCauseStackTraceList(wrapper);
+        assertEquals(cause.toString(), frames.get(0));
+        assertTrue(frames.get(1).contains("newRuntimeException"), 
frames.get(1));
+    }
+
+    private static RuntimeException newRuntimeException(final String message, 
final Throwable cause) {
+        return new RuntimeException(message, cause);
+    }
+
     @Test
     void testRethrow() {
         final Exception expected = new InterruptedException();

Reply via email to