Skip to content

Commit 418707b

Browse files
authored
[Serialized diagnostics] Add support for handling the category URL (#501)
The category URL is encoded in the existing "category" record, making use of the fact that the category name length is already explicitly specified in the record (but was ignored by some readers). Handle the <category name>@<category URL> encoding.
1 parent d4ec771 commit 418707b

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

Sources/TSCUtility/SerializedDiagnostics.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ extension SerializedDiagnostics {
7070
public var level: Level
7171
/// The location the diagnostic was emitted at in the source file.
7272
public var location: SourceLocation?
73-
/// The diagnostic category. Currently only Clang emits this.
73+
/// The diagnostic category.
7474
public var category: String?
75+
/// The diagnostic category documentation URL.
76+
public var categoryURL: String?
7577
/// The corresponding diagnostic command-line flag. Currently only Clang emits this.
7678
public var flag: String?
7779
/// Ranges in the source file associated with the diagnostic.
@@ -82,11 +84,12 @@ extension SerializedDiagnostics {
8284
fileprivate init(records: [SerializedDiagnostics.OwnedRecord],
8385
filenameMap: [UInt64: String],
8486
flagMap: [UInt64: String],
85-
categoryMap: [UInt64: String]) throws {
87+
categoryMap: CategoryMap) throws {
8688
var text: String? = nil
8789
var level: Level? = nil
8890
var location: SourceLocation? = nil
8991
var category: String? = nil
92+
var categoryURL: String? = nil
9093
var flag: String? = nil
9194
var ranges: [(SourceLocation, SourceLocation)] = []
9295
var fixIts: [FixIt] = []
@@ -103,7 +106,14 @@ extension SerializedDiagnostics {
103106
level = Level(rawValue: record.fields[0])
104107
location = SourceLocation(fields: record.fields[1...4],
105108
filenameMap: filenameMap)
106-
category = categoryMap[record.fields[5]]
109+
110+
if let categoryEntry = categoryMap[record.fields[5]] {
111+
category = categoryEntry.text
112+
categoryURL = categoryEntry.url
113+
} else {
114+
category = nil
115+
categoryURL = nil
116+
}
107117
flag = flagMap[record.fields[6]]
108118

109119
case .sourceRange:
@@ -142,6 +152,7 @@ extension SerializedDiagnostics {
142152
self.level = level
143153
self.location = location
144154
self.category = category
155+
self.categoryURL = categoryURL
145156
self.flag = flag
146157
self.fixIts = fixIts
147158
self.ranges = ranges
@@ -185,6 +196,8 @@ extension SerializedDiagnostics.Diagnostic: UnsafeSendable {}
185196
#endif
186197

187198
extension SerializedDiagnostics {
199+
typealias CategoryMap = [UInt64: (text: String, url: String?)]
200+
188201
private struct Reader: BitstreamVisitor {
189202
var diagnosticRecords: [[OwnedRecord]] = []
190203
var activeBlocks: [BlockID] = []
@@ -194,7 +207,7 @@ extension SerializedDiagnostics {
194207
var versionNumber: Int? = nil
195208
var filenameMap = [UInt64: String]()
196209
var flagMap = [UInt64: String]()
197-
var categoryMap = [UInt64: String]()
210+
var categoryMap = CategoryMap()
198211

199212
func validate(signature: Bitcode.Signature) throws {
200213
guard signature == .init(string: "DIAG") else { throw Error.badMagic }
@@ -244,9 +257,28 @@ extension SerializedDiagnostics {
244257
case .blob(let categoryBlob) = record.payload
245258
else { throw Error.malformedRecord }
246259

247-
let categoryText = String(decoding: categoryBlob, as: UTF8.self)
260+
let categoryTextBlob = String(decoding: categoryBlob, as: UTF8.self)
248261
let categoryID = record.fields[0]
249-
categoryMap[categoryID] = categoryText
262+
263+
let categoryTextLength = Int(record.fields[1])
264+
if categoryTextLength > categoryTextBlob.count {
265+
throw Error.malformedRecord
266+
}
267+
268+
let categoryText = String(categoryTextBlob.prefix(categoryTextLength))
269+
let afterCategoryText = categoryTextBlob.index(
270+
categoryTextBlob.startIndex,
271+
offsetBy: categoryTextLength
272+
)
273+
let categoryURL: String?
274+
if afterCategoryText < categoryTextBlob.endIndex &&
275+
categoryTextBlob[afterCategoryText] == "@" {
276+
categoryURL = String(categoryTextBlob[afterCategoryText...].dropFirst())
277+
} else {
278+
categoryURL = nil
279+
}
280+
281+
categoryMap[categoryID] = (categoryText, categoryURL)
250282
case .flag:
251283
guard record.fields.count == 2,
252284
case .blob(let flagBlob) = record.payload
800 Bytes
Binary file not shown.

Tests/TSCUtilityTests/SerializedDiagnosticsTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,20 @@ final class SerializedDiagnosticsTests: XCTestCase {
193193
XCTAssertEqual(one.ranges.count, 4)
194194
XCTAssertEqual(one.fixIts.count, 2)
195195
}
196+
197+
func testReadSerializedDiagCatagoryURL() throws {
198+
let serializedDiagnosticsPath = AbsolutePath(#file).parentDirectory
199+
.appending(components: "Inputs", "category-url.dia")
200+
let contents = try localFileSystem.readFileContents(serializedDiagnosticsPath)
201+
let serializedDiags = try SerializedDiagnostics(bytes: contents)
202+
203+
XCTAssertEqual(serializedDiags.versionNumber, 2)
204+
XCTAssertEqual(serializedDiags.diagnostics.count, 2)
205+
206+
let one = serializedDiags.diagnostics[0]
207+
XCTAssertEqual(one.text, "expression uses unsafe constructs but is not marked with 'unsafe'")
208+
XCTAssertEqual(one.level, .warning)
209+
XCTAssertEqual(one.category, "StrictMemorySafety")
210+
XCTAssertEqual(one.categoryURL, "https://www.swift.org/documentation/compiler/diagnostics/strict-memory-safety.md")
211+
}
196212
}

0 commit comments

Comments
 (0)