Skip to content

Commit 2112045

Browse files
authored
gracefully fail workspace state loading when duplicate entries found (#3974)
motivation: some older versions of SwiftPM wrote duplicate entries into workspace-state.json which can cause a crash due to use of Dictionary::uniquingKeysWith changes: * do not use Dictionary::uniquingKeysWith for non-santized input such as workspace state * add tests rdar://86857825
1 parent e0aadff commit 2112045

File tree

4 files changed

+437
-226
lines changed

4 files changed

+437
-226
lines changed

Sources/Workspace/ManagedArtifact.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,22 @@ extension Workspace {
115115
AnyCollection(self.artifactMap.values.lazy.flatMap{ $0.values })
116116
}
117117

118-
init(_ artifacts: [ManagedArtifact] = []) {
118+
init() {
119+
self.artifactMap = [:]
120+
}
121+
122+
init(_ artifacts: [ManagedArtifact]) throws {
119123
let artifactsByPackagePath = Dictionary(grouping: artifacts, by: { $0.packageRef.identity })
120-
self.artifactMap = artifactsByPackagePath.mapValues{ artifacts in
121-
Dictionary(uniqueKeysWithValues: artifacts.map{ ($0.targetName, $0) })
124+
self.artifactMap = try artifactsByPackagePath.mapValues{ artifacts in
125+
// rdar://86857825 do not use Dictionary(uniqueKeysWithValues:) as it can crash the process when input is incorrect such as in older versions of SwiftPM
126+
var map = [String: ManagedArtifact]()
127+
for artifact in artifacts {
128+
if map[artifact.targetName] != nil {
129+
throw StringError("binary artifact for '\(artifact.targetName)' already exists in managed artifacts")
130+
}
131+
map[artifact.targetName] = artifact
132+
}
133+
return map
122134
}
123135
}
124136

Sources/Workspace/ManagedDependency.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,21 @@ extension Workspace.ManagedDependency: CustomStringConvertible {
171171
extension Workspace {
172172
/// A collection of managed dependencies.
173173
final public class ManagedDependencies {
174-
// FIXME: this should be identity based
175174
private var dependencies: [PackageIdentity: ManagedDependency]
176175

177-
init(_ dependencies: [ManagedDependency] = []) {
178-
self.dependencies = Dictionary(uniqueKeysWithValues: dependencies.map{ ($0.packageRef.identity, $0) })
176+
init() {
177+
self.dependencies = [:]
178+
}
179+
180+
init(_ dependencies: [ManagedDependency]) throws {
181+
// rdar://86857825 do not use Dictionary(uniqueKeysWithValues:) as it can crash the process when input is incorrect such as in older versions of SwiftPM
182+
self.dependencies = [:]
183+
for dependency in dependencies {
184+
if self.dependencies[dependency.packageRef.identity] != nil {
185+
throw StringError("\(dependency.packageRef.identity) already exists in managed dependencies")
186+
}
187+
self.dependencies[dependency.packageRef.identity] = dependency
188+
}
179189
}
180190

181191
public subscript(identity: PackageIdentity) -> ManagedDependency? {

Sources/Workspace/WorkspaceState.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ fileprivate struct WorkspaceStateStorage {
9696
let v4 = try self.decoder.decode(path: self.path, fileSystem: self.fileSystem, as: V4.self)
9797
let dependencies = try v4.object.dependencies.map{ try Workspace.ManagedDependency($0) }
9898
let artifacts = try v4.object.artifacts.map{ try Workspace.ManagedArtifact($0) }
99-
return (dependencies: .init(dependencies), artifacts: .init(artifacts))
99+
return try (dependencies: .init(dependencies), artifacts: .init(artifacts))
100100
case 5:
101101
let v5 = try self.decoder.decode(path: self.path, fileSystem: self.fileSystem, as: V5.self)
102102
let dependencies = try v5.object.dependencies.map{ try Workspace.ManagedDependency($0) }
103103
let artifacts = try v5.object.artifacts.map{ try Workspace.ManagedArtifact($0) }
104-
return (dependencies: .init(dependencies), artifacts: .init(artifacts))
104+
return try (dependencies: .init(dependencies), artifacts: .init(artifacts))
105105
default:
106106
throw StringError("unknown 'WorkspaceStateStorage' version '\(version.version)' at '\(self.path)'")
107107
}

0 commit comments

Comments
 (0)