From 2c4c4e69a514cac2960be02efb73a4f490575146 Mon Sep 17 00:00:00 2001 From: inju2403 <56947879+inju2403@users.noreply.github.com> Date: Tue, 26 May 2026 18:51:56 +0900 Subject: [PATCH 1/2] [SwiftWarningControl] Fix exponential queue growth in addWarningGroupControls The BFS over the inheritance tree used `Array.removeFirst()` (O(N) per call) and only checked `processedGroups` at enqueue time, so a sub-group reachable from multiple already-queued parents would be enqueued (and re-processed) multiple times. On diamond-shaped inheritance DAGs the queue grew as 2^depth, so even modest depths (>= 10) effectively hung. Switch to an index-based queue and de-dupe at dequeue time, making the loop O(N) in the number of distinct sub-groups. Adds a performance test exercising chain, fan, and diamond-DAG workloads. --- .../WarningControlRegions.swift | 31 +++-- ...arningControlRegionsPerformanceTests.swift | 124 ++++++++++++++++++ 2 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 Tests/PerformanceTest/WarningControlRegionsPerformanceTests.swift diff --git a/Sources/SwiftWarningControl/WarningControlRegions.swift b/Sources/SwiftWarningControl/WarningControlRegions.swift index 1d0b9abea87..c9a108f4014 100644 --- a/Sources/SwiftWarningControl/WarningControlRegions.swift +++ b/Sources/SwiftWarningControl/WarningControlRegions.swift @@ -130,22 +130,35 @@ public struct WarningControlRegionTree { guard !controls.isEmpty else { return } let newNode = WarningControlRegionNode(range: range) for (diagnosticGroupIdentifier, control) in controls { - // Handle the control for the added diagnostic group - // and propagate it to all of its subgroups. + // Breadth-first walk over the inheritance tree of `diagnosticGroupIdentifier`, + // applying `control` to every transitive sub-group. + // + // We use an index-based queue (rather than `Array.removeFirst()`, which is O(N)) + // and dedupe at dequeue time. Previously, a sub-group reachable from more than one + // already-queued group would be enqueued multiple times and have its control + // re-applied each time, which was both redundant and made the loop O(N^2) in the + // queue size. For a diamond-shaped inheritance graph this caused the queue to + // grow exponentially, so the function would not terminate in any reasonable time. var groups: [DiagnosticGroupIdentifier] = [diagnosticGroupIdentifier] + var groupIndex = 0 var processedGroups: Set = [] - while !groups.isEmpty { - let groupIdentifier = groups.removeFirst() - processedGroups.insert(groupIdentifier) + while groupIndex < groups.count { + let groupIdentifier = groups[groupIndex] + groupIndex += 1 + // Skip groups we already reached via another path through the inheritance tree. + guard processedGroups.insert(groupIdentifier).inserted else { continue } + newNode.addWarningGroupControl(for: groupIdentifier, control: control) if control != .ignored { enabledGroups.insert(diagnosticGroupIdentifier) } - let newSubGroups = groupInheritanceTree.subgroups(of: groupIdentifier).filter { !processedGroups.contains($0) } - // Ensure we add a corresponding control to each direct and - // transitive sub-group of the one specified on this control. - groups.append(contentsOf: newSubGroups) + // Enqueue not-yet-processed sub-groups so each direct and transitive sub-group + // also receives the control. + for subGroup in groupInheritanceTree.subgroups(of: groupIdentifier) + where !processedGroups.contains(subGroup) { + groups.append(subGroup) + } } } insertIntoSubtree(newNode, parent: rootRegionNode) diff --git a/Tests/PerformanceTest/WarningControlRegionsPerformanceTests.swift b/Tests/PerformanceTest/WarningControlRegionsPerformanceTests.swift new file mode 100644 index 00000000000..b73b79c3336 --- /dev/null +++ b/Tests/PerformanceTest/WarningControlRegionsPerformanceTests.swift @@ -0,0 +1,124 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftIfConfig +import SwiftParser +import SwiftSyntax +import SwiftWarningControl +import XCTest +import _InstructionCounter +import _SwiftSyntaxTestSupport + +/// Linear inheritance chain: g0 -> g1 -> g2 -> ... -> gN. +/// Exercises the BFS but the queue never exceeds 1 element (each level has 1 sub-group). +private func makeChain(depth: Int) -> DiagnosticGroupInheritanceTree { + var subGroups: [DiagnosticGroupIdentifier: [DiagnosticGroupIdentifier]] = [:] + for i in 0.. DiagnosticGroupInheritanceTree { + var children: [DiagnosticGroupIdentifier] = [] + for i in 0.. DiagnosticGroupInheritanceTree { + var subGroups: [DiagnosticGroupIdentifier: [DiagnosticGroupIdentifier]] = [:] + func id(_ level: Int, _ side: Int) -> DiagnosticGroupIdentifier { + DiagnosticGroupIdentifier("l\(level)s\(side)") + } + for level in 0.. Date: Tue, 26 May 2026 21:27:29 +0900 Subject: [PATCH 2/2] Add diamond-inheritance regression test for addWarningGroupControls Exercises the path that previously could re-process a sub-group reachable from multiple parents, complementing the performance test (which proves the queue no longer explodes). --- .../WarningControlTests.swift | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Tests/SwiftWarningControlTest/WarningControlTests.swift b/Tests/SwiftWarningControlTest/WarningControlTests.swift index 3c0db43b211..cd05ee3c7cc 100644 --- a/Tests/SwiftWarningControlTest/WarningControlTests.swift +++ b/Tests/SwiftWarningControlTest/WarningControlTests.swift @@ -346,6 +346,31 @@ public class WarningGroupControlTests: XCTestCase { ) } + /// A sub-group that is reachable from multiple parents (a diamond-shaped + /// inheritance graph) must still have the control applied to it. Previously, + /// the BFS in `addWarningGroupControls` did not de-duplicate at dequeue, so + /// such a sub-group was enqueued (and re-processed) once per incoming path; + /// for deeper diamonds this caused the queue to grow exponentially. + func testDiamondSubGroupInheritance() throws { + try assertWarningGroupControl( + """ + @diagnose(SuperGroup, as: error) + func foo() { + 1️⃣let x = 1 + } + """, + groupInheritanceTree: DiagnosticGroupInheritanceTree(subGroups: [ + "SuperGroup": ["ChildA", "ChildB"], + "ChildA": ["SharedGroup"], + "ChildB": ["SharedGroup"], + ]), + diagnosticGroupID: "SharedGroup", + states: [ + "1️⃣": .error + ] + ) + } + func testInheritanceTreeCycle() throws { XCTAssertThrowsError( try DiagnosticGroupInheritanceTree(subGroups: [