From a79e365e5782f9b41e0d5f701cce9196a74afda5 Mon Sep 17 00:00:00 2001 From: J2ObjC Team Date: Fri, 14 Aug 2026 17:11:47 -0700 Subject: [PATCH] Fix infinite loops in cycle finder. This change fixes several infinite loop conditions in cycle finder: 1. Adds loop detection in GraphBuilder.addSuperclassEdges to prevent infinite loops when type hierarchies have cycles. 2. Fixes ReferenceGraph.findShortestCycle to terminate if there are no more nodes to visit, returning an empty list. 3. Fixes CycleFinder.handleStronglyConnectedComponent to skip cycle extraction and remove the root from unused types if findShortestCycle returns an empty list. PiperOrigin-RevId: 964969489 --- .../java/com/google/devtools/cyclefinder/CycleFinder.java | 4 ++++ .../java/com/google/devtools/cyclefinder/GraphBuilder.java | 3 ++- .../java/com/google/devtools/cyclefinder/ReferenceGraph.java | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/CycleFinder.java b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/CycleFinder.java index ca8c5e7018..efda08cfd1 100644 --- a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/CycleFinder.java +++ b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/CycleFinder.java @@ -220,6 +220,10 @@ private void handleStronglyConnectedComponent(ReferenceGraph subgraph) { TypeNode root = Iterables.getFirst(unusedTypes, null); assert root != null; List cycle = subgraph.findShortestCycle(root); + if (cycle.isEmpty()) { + unusedTypes.remove(root); + continue; + } if (shouldAddCycle(cycle)) { cycles.add(cycle); } diff --git a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/GraphBuilder.java b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/GraphBuilder.java index f6c15d62b1..673a2ec297 100644 --- a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/GraphBuilder.java +++ b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/GraphBuilder.java @@ -135,8 +135,9 @@ private void addSubtypeEdges() { private void addSuperclassEdges() { for (TypeNode type : allTypes.values()) { + Set visited = new HashSet<>(); TypeNode superclassNode = superclasses.get(type); - while (superclassNode != null) { + while (superclassNode != null && visited.add(superclassNode)) { for (Edge e : graph.getEdges(superclassNode)) { addEdge(Edge.newSuperclassEdge(e, type, superclassNode)); } diff --git a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/ReferenceGraph.java b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/ReferenceGraph.java index f05d496efa..55b03b8111 100644 --- a/cycle_finder/src/main/java/com/google/devtools/cyclefinder/ReferenceGraph.java +++ b/cycle_finder/src/main/java/com/google/devtools/cyclefinder/ReferenceGraph.java @@ -67,6 +67,9 @@ public List findShortestCycle(TypeNode root) { Set visited = new HashSet<>(); List toVisit = Lists.newArrayList(root); outer: while (true) { + if (toVisit.isEmpty()) { + return Collections.emptyList(); + } List visitNext = new ArrayList<>(); for (TypeNode source : toVisit) { visited.add(source);