Skip to content

Commit

Permalink
8342782: AWTEventMulticaster throws StackOverflowError using AquaButt…
Browse files Browse the repository at this point in the history
…onUI

Reviewed-by: kizune, prr, lbourges
  • Loading branch information
mickleness committed Dec 18, 2024
1 parent edbd76c commit 5b703c7
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 1 deletion.
62 changes: 61 additions & 1 deletion src/java.desktop/share/classes/java/awt/AWTEventMulticaster.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public class AWTEventMulticaster implements
TextListener, InputMethodListener, HierarchyListener,
HierarchyBoundsListener, MouseWheelListener {

private static final int MAX_UNBALANCED_TOP_NODES = 100;

/**
* A variable in the event chain (listener-a)
*/
Expand Down Expand Up @@ -952,14 +954,72 @@ public static MouseWheelListener remove(MouseWheelListener l,
* If listener-b is null, it returns listener-a
* If neither are null, then it creates and returns
* a new AWTEventMulticaster instance which chains a with b.
*
* @param a event listener-a
* @param b event listener-b
* @return the resulting listener
*/
protected static EventListener addInternal(EventListener a, EventListener b) {
if (a == null) return b;
if (b == null) return a;
return new AWTEventMulticaster(a, b);
AWTEventMulticaster n = new AWTEventMulticaster(a, b);
if (!needsRebalance(n)) {
return n;
}

EventListener[] array = getListeners(n, EventListener.class);
return rebalance(array, 0, array.length - 1);
}

/**
* Return true if the argument represents a binary tree that needs to be rebalanced.
*/
private static boolean needsRebalance(AWTEventMulticaster l) {
int level = 0;
while (true) {
// The criteria for when we need a rebalance is subjective. This method checks
// up to a given threshold of the topmost nodes of a AWTEventMulticaster. If
// they all include one leaf node, then this method returns true. This criteria
// will be met after several consecutive iterations of `addInternal(a, b)`
if (++level > MAX_UNBALANCED_TOP_NODES) {
return true;
}
if (l.a instanceof AWTEventMulticaster aMulti) {
if (l.b instanceof AWTEventMulticaster) {
// we reached a node where both children are AWTEventMulticaster: let's assume
// the current node marks the start of a well-balanced subtree
return false;
}
l = aMulti;
} else if (l.b instanceof AWTEventMulticaster bMulti) {
l = bMulti;
} else {
return false;
}
}
}

/**
* Recursively create a balanced tree that includes a given range of EventListeners.
*
* @param array the array of the EventListeners to consult
* @param index0 the lowest index (inclusive) that the return value must include
* @param index1 the highest index (inclusive) that the return value must include.
*
* @return a balanced tree. If index0 equals index1 then this returns an EventListener from
* the array provided. Otherwise this returns an AWTEventMulticaster.
*/
private static EventListener rebalance(EventListener[] array, int index0, int index1) {
if (index0 == index1) {
return array[index0];
}
if (index0 == index1 - 1) {
return new AWTEventMulticaster(array[index0], array[index1]);
}
int mid = (index0 + index1) / 2;
return new AWTEventMulticaster(
rebalance(array, index0, mid),
rebalance(array, mid + 1, index1));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.AWTEventMulticaster;

/*
* @test
* @bug 8342782
* @summary Tests large AWTEventMulticasters for StackOverflowErrors
* @run main LargeAWTEventMulticasterTest
*/
public class LargeAWTEventMulticasterTest {

/**
* This is an empty ActionListener that also has a numeric index.
*/
static class IndexedActionListener implements ActionListener {
private final int index;

public IndexedActionListener(int index) {
this.index = index;
}

@Override
public void actionPerformed(ActionEvent e) {

}

public int getIndex() {
return index;
}

@Override
public String toString() {
return Integer.toString(index);
}
}

public static void main(String[] args) {
int maxA = 0;
try {
for (int a = 1; a < 200_000; a *= 2) {
maxA = a;
testAddingActionListener(a);
}
} finally {
System.out.println("maximum a = " + maxA);
}
}

private static void testAddingActionListener(int numberOfListeners) {
// step 1: create the large AWTEventMulticaster
ActionListener l = null;
for (int a = 0; a < numberOfListeners; a++) {
l = AWTEventMulticaster.add(l, new IndexedActionListener(a));
}

// Prior to 8342782 we could CREATE a large AWTEventMulticaster, but we couldn't
// always interact with it.

// step 2: dispatch an event
// Here we're making sure we don't get a StackOverflowError when we traverse the tree:
l.actionPerformed(null);

// step 3: make sure getListeners() returns elements in the correct order
// The resolution for 8342782 introduced a `rebalance` method; we want to
// double-check that the rebalanced tree preserves the appropriate order.
IndexedActionListener[] array = AWTEventMulticaster.getListeners(l, IndexedActionListener.class);
for (int b = 0; b < array.length; b++) {
if (b != array[b].getIndex())
throw new Error("the listeners are in the wrong order. " + b + " != " + array[b].getIndex());
}
}
}

0 comments on commit 5b703c7

Please sign in to comment.