JBR-6771 BoxLayout throws mysterious NPEs due to previous exceptions

The checkRequests method only does layout initialization
if it isn't initialized already. However, when an exception
is thrown during the initialization, the layout may end up
in a half-initialized state.

Fix this by using the field that is initialized the last to check
if the layout is initialized. If that field is null, it may mean
that the layout isn't initialized or that the last attempt
failed midway. Then we try again. This attempt can,
of course, break for the same reason as the previous one,
but in that case we'll at least get a stack trace pointing
to a real cause of the error and not some mysterious NPE
that seems to be impossible from the logic.

The bug is that if we add a component that throws an exception
in one of its methods called by BoxLayout, then the layout may
end up in a half-initialized state that would mistakenly be considered
fully initialized. Then it would try to access some fields
and throw NPE with a stack trace that tells exactly nothing
about what went wrong and where.

This test checks for the presence of this bug by adding a broken
component to a BoxLayout and then un-breaking this component
and checking that an exception is thrown even though the component
is no longer broken.
This commit is contained in:
Sergei Tachenov
2024-03-01 17:38:35 +02:00
committed by Maxim Kartashev
parent 035ccf385d
commit 35da2d269e
2 changed files with 88 additions and 1 deletions

View File

@@ -459,7 +459,8 @@ public class BoxLayout implements LayoutManager2, Serializable {
}
void checkRequests() {
if (xChildren == null || yChildren == null) {
// check yTotal, as it's assigned the last, to ensure initialization completed without exceptions:
if (yTotal == null) {
// The requests have been invalidated... recalculate
// the request information.
int n = target.getComponentCount();

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2024 JetBrains s.r.o.
* 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.
*/
/*
* @test
* @summary BoxLayout doesn't ignore invisible components
* @key headful
* @run main NPECheckRequests
*/
import java.awt.BorderLayout;
import java.lang.reflect.InvocationTargetException;
import java.awt.Dimension;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class NPECheckRequests {
JFrame frame;
JPanel p;
BrokenComponent foo;
public void init() {
frame = new JFrame();
p = new JPanel();
BoxLayout boxLayout = new BoxLayout(p, BoxLayout.X_AXIS);
p.setLayout(boxLayout);
foo = new BrokenComponent();
p.add(foo);
frame.setLayout(new BorderLayout());
frame.add(p, BorderLayout.CENTER);
try {
frame.pack();
} catch (RuntimeException ignored) {
// our broken component threw an exception
}
}
public void start() {
foo.broken = false;
// check that the layout isn't in a broken state because of that exception
frame.pack();
}
public static void main(String[] args) throws InterruptedException,
InvocationTargetException {
NPECheckRequests test = new NPECheckRequests();
SwingUtilities.invokeAndWait(test::init);
SwingUtilities.invokeAndWait(test::start);
}
private class BrokenComponent extends JPanel {
boolean broken = true;
@Override
public Dimension getPreferredSize() {
if (broken) {
throw new RuntimeException("Broken component");
}
return super.getPreferredSize();
}
}
}