Skip to content

Commit 5b703c7

Browse files
committed
8342782: AWTEventMulticaster throws StackOverflowError using AquaButtonUI
Reviewed-by: kizune, prr, lbourges
1 parent edbd76c commit 5b703c7

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

src/java.desktop/share/classes/java/awt/AWTEventMulticaster.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public class AWTEventMulticaster implements
110110
TextListener, InputMethodListener, HierarchyListener,
111111
HierarchyBoundsListener, MouseWheelListener {
112112

113+
private static final int MAX_UNBALANCED_TOP_NODES = 100;
114+
113115
/**
114116
* A variable in the event chain (listener-a)
115117
*/
@@ -952,14 +954,72 @@ public static MouseWheelListener remove(MouseWheelListener l,
952954
* If listener-b is null, it returns listener-a
953955
* If neither are null, then it creates and returns
954956
* a new AWTEventMulticaster instance which chains a with b.
957+
*
955958
* @param a event listener-a
956959
* @param b event listener-b
957960
* @return the resulting listener
958961
*/
959962
protected static EventListener addInternal(EventListener a, EventListener b) {
960963
if (a == null) return b;
961964
if (b == null) return a;
962-
return new AWTEventMulticaster(a, b);
965+
AWTEventMulticaster n = new AWTEventMulticaster(a, b);
966+
if (!needsRebalance(n)) {
967+
return n;
968+
}
969+
970+
EventListener[] array = getListeners(n, EventListener.class);
971+
return rebalance(array, 0, array.length - 1);
972+
}
973+
974+
/**
975+
* Return true if the argument represents a binary tree that needs to be rebalanced.
976+
*/
977+
private static boolean needsRebalance(AWTEventMulticaster l) {
978+
int level = 0;
979+
while (true) {
980+
// The criteria for when we need a rebalance is subjective. This method checks
981+
// up to a given threshold of the topmost nodes of a AWTEventMulticaster. If
982+
// they all include one leaf node, then this method returns true. This criteria
983+
// will be met after several consecutive iterations of `addInternal(a, b)`
984+
if (++level > MAX_UNBALANCED_TOP_NODES) {
985+
return true;
986+
}
987+
if (l.a instanceof AWTEventMulticaster aMulti) {
988+
if (l.b instanceof AWTEventMulticaster) {
989+
// we reached a node where both children are AWTEventMulticaster: let's assume
990+
// the current node marks the start of a well-balanced subtree
991+
return false;
992+
}
993+
l = aMulti;
994+
} else if (l.b instanceof AWTEventMulticaster bMulti) {
995+
l = bMulti;
996+
} else {
997+
return false;
998+
}
999+
}
1000+
}
1001+
1002+
/**
1003+
* Recursively create a balanced tree that includes a given range of EventListeners.
1004+
*
1005+
* @param array the array of the EventListeners to consult
1006+
* @param index0 the lowest index (inclusive) that the return value must include
1007+
* @param index1 the highest index (inclusive) that the return value must include.
1008+
*
1009+
* @return a balanced tree. If index0 equals index1 then this returns an EventListener from
1010+
* the array provided. Otherwise this returns an AWTEventMulticaster.
1011+
*/
1012+
private static EventListener rebalance(EventListener[] array, int index0, int index1) {
1013+
if (index0 == index1) {
1014+
return array[index0];
1015+
}
1016+
if (index0 == index1 - 1) {
1017+
return new AWTEventMulticaster(array[index0], array[index1]);
1018+
}
1019+
int mid = (index0 + index1) / 2;
1020+
return new AWTEventMulticaster(
1021+
rebalance(array, index0, mid),
1022+
rebalance(array, mid + 1, index1));
9631023
}
9641024

9651025
/**
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.awt.event.ActionListener;
25+
import java.awt.event.ActionEvent;
26+
import java.awt.AWTEventMulticaster;
27+
28+
/*
29+
* @test
30+
* @bug 8342782
31+
* @summary Tests large AWTEventMulticasters for StackOverflowErrors
32+
* @run main LargeAWTEventMulticasterTest
33+
*/
34+
public class LargeAWTEventMulticasterTest {
35+
36+
/**
37+
* This is an empty ActionListener that also has a numeric index.
38+
*/
39+
static class IndexedActionListener implements ActionListener {
40+
private final int index;
41+
42+
public IndexedActionListener(int index) {
43+
this.index = index;
44+
}
45+
46+
@Override
47+
public void actionPerformed(ActionEvent e) {
48+
49+
}
50+
51+
public int getIndex() {
52+
return index;
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return Integer.toString(index);
58+
}
59+
}
60+
61+
public static void main(String[] args) {
62+
int maxA = 0;
63+
try {
64+
for (int a = 1; a < 200_000; a *= 2) {
65+
maxA = a;
66+
testAddingActionListener(a);
67+
}
68+
} finally {
69+
System.out.println("maximum a = " + maxA);
70+
}
71+
}
72+
73+
private static void testAddingActionListener(int numberOfListeners) {
74+
// step 1: create the large AWTEventMulticaster
75+
ActionListener l = null;
76+
for (int a = 0; a < numberOfListeners; a++) {
77+
l = AWTEventMulticaster.add(l, new IndexedActionListener(a));
78+
}
79+
80+
// Prior to 8342782 we could CREATE a large AWTEventMulticaster, but we couldn't
81+
// always interact with it.
82+
83+
// step 2: dispatch an event
84+
// Here we're making sure we don't get a StackOverflowError when we traverse the tree:
85+
l.actionPerformed(null);
86+
87+
// step 3: make sure getListeners() returns elements in the correct order
88+
// The resolution for 8342782 introduced a `rebalance` method; we want to
89+
// double-check that the rebalanced tree preserves the appropriate order.
90+
IndexedActionListener[] array = AWTEventMulticaster.getListeners(l, IndexedActionListener.class);
91+
for (int b = 0; b < array.length; b++) {
92+
if (b != array[b].getIndex())
93+
throw new Error("the listeners are in the wrong order. " + b + " != " + array[b].getIndex());
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)