Skip to content

Commit bb0dcf0

Browse files
committed
Fix broken proof tree after information flow auto pilot (#3713)
The Full Information Flow Auto Pilot broke the proof tree view with 'IllegalStateException: abstract tree node without parent: Proof Tree'. Root cause: when the macro creates its auxiliary side proof, the StartSideProofMacro handling in macroFinished re-froze the interface via initiateAutoMode(sideProof), which also broadcasts autoModeStarted for the side proof (with no matching autoModeStopped ever following). The ProofTreeView listener trusted any event source and overwrote its modifiedSubtrees bookkeeping with the side proof's goal nodes. At the final autoModeStopped, those foreign (by then disposed) nodes were fed into the tree model of the displayed proof, where the lazily created parent branch node for the foreign root has no parent and a disposed proof has no root() anymore, defeating the existing root check. The fix prevents the poisoning at both ends: - macroFinished only freezes the interface (stopInterface + setInteractive) instead of broadcasting a spurious autoModeStarted for the side proof, - ProofTreeView ignores auto mode events for proofs it does not display and skips foreign nodes when refreshing modified subtrees. GUIAbstractTreeNode.getParent() keeps failing fast on parent-less nodes so that bookkeeping bugs of this kind surface; the previously unexplained root-instance escape ('why can there be another instance of the root node?') is now documented - it stems from stale instances after cache invalidation and from exactly this kind of foreign-node walk. A regression test pins the fail-fast contract. Created with AI tooling
1 parent d3de45c commit bb0dcf0

3 files changed

Lines changed: 79 additions & 3 deletions

File tree

key.ui/src/main/java/de/uka/ilkd/key/gui/prooftree/ProofTreeView.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,13 @@ public synchronized void autoModeStarted(ProofEvent e) {
10661066
LOGGER.debug("delegateModel is null");
10671067
return;
10681068
}
1069+
if (e.getSource() != proof) {
1070+
// Auto mode on a proof this view does not display, e.g. an auxiliary side proof
1071+
// of the information-flow macros (see #3713). Overwriting modifiedSubtrees with
1072+
// the foreign proof's goals would feed its nodes into the displayed proof's tree
1073+
// model in autoModeStopped.
1074+
return;
1075+
}
10691076

10701077
// save goals on which the prover may work
10711078
modifiedSubtrees = e.getSource().openGoals().map(Goal::node);
@@ -1088,7 +1095,10 @@ public synchronized void autoModeStopped(ProofEvent e) {
10881095
setProof(mediator.getSelectedProof());
10891096
if (modifiedSubtrees != null) {
10901097
for (final Node n : modifiedSubtrees) {
1091-
if (proof.openGoals().filter(g -> g.node() == n).isEmpty()) {
1098+
// skip nodes of other proofs: the displayed proof may have changed since the
1099+
// subtrees were recorded in autoModeStarted (see #3713)
1100+
if (n.proof() == proof
1101+
&& proof.openGoals().filter(g -> g.node() == n).isEmpty()) {
10921102
delegateModel.updateTree(n);
10931103
}
10941104
}

key.ui/src/main/java/de/uka/ilkd/key/ui/AbstractMediatorUserInterfaceControl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ public void proofDisposed(ProofDisposedEvent e) {
172172
});
173173
// stop interface again, because it is activated by the proof
174174
// change through startProver; the ProofMacroWorker will activate
175-
// it again at the right time
175+
// it again at the right time.
176176
ThreadUtilities.invokeAndWait(() -> {
177-
getMediator().initiateAutoMode(info.getProof(), true, false);
177+
getMediator().stopInterface(true);
178+
getMediator().setInteractive(false);
178179
});
179180
}
180181
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* This file is part of KeY - https://key-project.org
2+
* KeY is licensed under the GNU General Public License Version 2
3+
* SPDX-License-Identifier: GPL-2.0-only */
4+
package de.uka.ilkd.key.gui.prooftree;
5+
6+
import java.nio.file.Path;
7+
import java.util.Objects;
8+
import javax.swing.tree.TreeNode;
9+
10+
import de.uka.ilkd.key.control.DefaultUserInterfaceControl;
11+
import de.uka.ilkd.key.control.KeYEnvironment;
12+
import de.uka.ilkd.key.proof.Node;
13+
import de.uka.ilkd.key.proof.Proof;
14+
15+
import org.key_project.util.helper.FindResources;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
21+
/**
22+
* Documents the fail-fast contract of the proof tree model for the constellation behind issue
23+
* #3713: nodes of a foreign (here: already disposed) proof must never be fed into another proof's
24+
* {@link GUIProofTreeModel}. The information-flow auto pilot's side proof used to leak into
25+
* {@code ProofTreeView.modifiedSubtrees} this way; the actual fix prevents the leak in
26+
* {@code ProofTreeView} and {@code AbstractMediatorUserInterfaceControl#macroFinished}. The model
27+
* itself deliberately keeps failing fast when the invariant is violated, so that bookkeeping bugs
28+
* of this kind surface instead of being rendered silently.
29+
*/
30+
public class DisposedProofTreeUpdateTest {
31+
private static final Path exampleDir =
32+
Objects.requireNonNull(FindResources.getExampleDirectory());
33+
private static final String EXAMPLE_PROOF =
34+
"standard_key/BookExamples/10UsingKeY/andCommutes.proof";
35+
36+
@Test
37+
void updatingWithNodeOfForeignDisposedProofFailsFast() throws Exception {
38+
KeYEnvironment<DefaultUserInterfaceControl> env =
39+
KeYEnvironment.load(exampleDir.resolve(EXAMPLE_PROOF));
40+
Proof displayed = env.getLoadedProof();
41+
KeYEnvironment<DefaultUserInterfaceControl> env2 =
42+
KeYEnvironment.load(exampleDir.resolve(EXAMPLE_PROOF));
43+
Proof foreign = env2.getLoadedProof();
44+
45+
GUIProofTreeModel model = new GUIProofTreeModel(displayed);
46+
forceExpand((TreeNode) model.getRoot());
47+
48+
// keep a node of the foreign proof (Node keeps its proof reference), then dispose it
49+
Node foreignRoot = foreign.root();
50+
foreign.dispose();
51+
52+
assertThrows(IllegalStateException.class, () -> model.updateTree(foreignRoot),
53+
"walking a foreign disposed proof's node must fail fast");
54+
55+
env.dispose();
56+
env2.dispose();
57+
}
58+
59+
private static void forceExpand(TreeNode node) {
60+
int count = node.getChildCount();
61+
for (int i = 0; i < count; i++) {
62+
forceExpand(node.getChildAt(i));
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)