Skip to content

Commit 2092075

Browse files
authored
Add a precondition to prevent the parsing of a command that has itself as its subcommand (#197)
* Add a precondition to prevent the parsing of a command that has itself as its subcommand. This avoids the infinite recursion that causes a crash and shows the user a meaningful error message. Fixes: #192 * Fix the detection of a command that has itself as its subcommand - The recursion detection now works for both the root command and its subcommands - Add a test to shows that the fix works * Fix typo in TreeTests
1 parent eb51f94 commit 2092075

File tree

3 files changed

+33
-3
lines changed

3 files changed

+33
-3
lines changed

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ struct CommandParser {
3131
}
3232

3333
init(_ rootCommand: ParsableCommand.Type) {
34-
self.commandTree = Tree(root: rootCommand)
34+
do {
35+
self.commandTree = try Tree(root: rootCommand)
36+
} catch Tree<ParsableCommand.Type>.InitializationError.recursiveSubcommand(let command) {
37+
fatalError("The ParsableCommand \"\(command)\" can't have itself as its own subcommand.")
38+
} catch {
39+
fatalError("Unexpected error: \(error).")
40+
}
3541
self.currentNode = commandTree
3642

3743
// A command tree that has a depth greater than zero gets a `help`

Sources/ArgumentParser/Utilities/Tree.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,17 @@ extension Tree where Element == ParsableCommand.Type {
8888
children.first(where: { $0.element._commandName == name })
8989
}
9090

91-
convenience init(root command: ParsableCommand.Type) {
91+
convenience init(root command: ParsableCommand.Type) throws {
9292
self.init(command)
9393
for subcommand in command.configuration.subcommands {
94-
addChild(Tree(root: subcommand))
94+
if subcommand == command {
95+
throw InitializationError.recursiveSubcommand(subcommand)
96+
}
97+
try addChild(Tree(root: subcommand))
9598
}
9699
}
100+
101+
enum InitializationError: Error {
102+
case recursiveSubcommand(ParsableCommand.Type)
103+
}
97104
}

Tests/ArgumentParserUnitTests/TreeTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,20 @@ extension TreeTests {
5353
XCTAssertTrue(tree.path(toFirstWhere: { $0 < 0 }).isEmpty)
5454
}
5555
}
56+
57+
extension TreeTests {
58+
struct A: ParsableCommand {
59+
static let configuration = CommandConfiguration(subcommands: [A.self])
60+
}
61+
struct Root: ParsableCommand {
62+
static let configuration = CommandConfiguration(subcommands: [Sub.self])
63+
}
64+
struct Sub: ParsableCommand {
65+
static let configuration = CommandConfiguration(subcommands: [Sub.self])
66+
}
67+
68+
func testInitializationWithRecursiveSubcommand() {
69+
XCTAssertThrowsError(try Tree(root: A.asCommand))
70+
XCTAssertThrowsError(try Tree(root: Root.asCommand))
71+
}
72+
}

0 commit comments

Comments
 (0)