Skip to content

Commit 09eb522

Browse files
committed
[Serialized diagnostics] Encode category documentation URL in existing record
Serialized diagnostics support an optional category name, which can be used to group diagnostics together. This grouping is most helpful when a number of diagnostics are placed in a category that is documented together. For example, Clang's warnings are placed into groups that are each documented. However, any relationship between the category name and such documentation is left to the client to establish. This change introduces a convention and APIs to allow diagnostic categories to be associated with a URL at which one can find documentation for the diagnostics in that category. Rather than outright extend the serialized diagnostics format with a new record kind, we utilize some redundancy to extend the existing RECORD_CATEGORY in a backward-compatible way. Specifically, for diagnostic categories that have documentation URLs associated with them, the blob associated with a RECORD_CATEGORY entry will be <category name>@<category URL> The RECORD_CATEGORY already includes a "category name length" field, which specifies the length of the category name. A correct reader would already be ignoring anything beyond that that position in the blob, so we get this extensibility for free: all of the extra information goes into the blob, but the category name length field still describes the length of the category name (i.e., the position of the @). A correct reader (that hasn't been updated for this change) will treat diagnostics files that provide these documentation URLs in the same way as ones that don't, ignoring the URL. Amusingly, the two readers I looked at (the one in Clang and a Swift one we use for other tools) ignore the category name length field. They still fail fairly gracefully: the category name ends up being the whole blob, which is still human-readable and fairly reasonable. This change teaches Clang's serialized diagnostic reader to properly respect the category name length field and, when the blob matches the description above, separately track the category documentation URL. It adds a new libclang diagnostics API `clang_getDiagnosticCategoryURL()` that provides this URL separately from the category. c-index-test prints the category name -> URL mapping separate from the diagnostic so we can test this functionality. This commit does not teach Clang to encode documentation URLs in the diagnostics it generates. That will come separately.
1 parent cf371cd commit 09eb522

10 files changed

+81
-7
lines changed

clang/include/clang-c/CXDiagnostic.h

+8
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,14 @@ clang_getDiagnosticCategoryName(unsigned Category);
338338
*/
339339
CINDEX_LINKAGE CXString clang_getDiagnosticCategoryText(CXDiagnostic);
340340

341+
/**
342+
* Retrieve the diagnostic category's URL for a given diagnostic.
343+
*
344+
* \returns The URL that provides additional documentation for the
345+
* category of this diagnostic.
346+
*/
347+
CINDEX_LINKAGE CXString clang_getDiagnosticCategoryURL(CXDiagnostic);
348+
341349
/**
342350
* Determine the number of source ranges associated with the given
343351
* diagnostic.

clang/include/clang/Frontend/SerializedDiagnosticReader.h

+11
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,21 @@ class SerializedDiagnosticReader {
8989
virtual std::error_code visitEndOfDiagnostic() { return {}; }
9090

9191
/// Visit a category. This associates the category \c ID to a \c Name.
92+
///
93+
/// This entrypoint has been superseded by the overload that follows, which
94+
/// also takes a (possibly-empty) URL providing additional documentation for
95+
/// the category.
9296
virtual std::error_code visitCategoryRecord(unsigned ID, StringRef Name) {
9397
return {};
9498
}
9599

100+
/// Visit a category. This associates the category \c ID to a \c Name with
101+
/// a (possibly empty) URL.
102+
virtual std::error_code visitCategoryRecord(unsigned ID, StringRef Name,
103+
StringRef URL) {
104+
return visitCategoryRecord(ID, Name);
105+
}
106+
96107
/// Visit a flag. This associates the flag's \c ID to a \c Name.
97108
virtual std::error_code visitDiagFlagRecord(unsigned ID, StringRef Name) {
98109
return {};

clang/lib/Frontend/SerializedDiagnosticReader.cpp

+17-2
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,28 @@ SerializedDiagnosticReader::readDiagnosticBlock(llvm::BitstreamCursor &Stream) {
266266
continue;
267267

268268
switch ((RecordIDs)RecID) {
269-
case RECORD_CATEGORY:
269+
case RECORD_CATEGORY: {
270270
// A category has ID and name size.
271271
if (Record.size() != 2)
272272
return SDError::MalformedDiagnosticRecord;
273-
if ((EC = visitCategoryRecord(Record[0], Blob)))
273+
274+
// Make sure the name size makes sense.
275+
unsigned NameLen = Record[1];
276+
if (NameLen > Blob.size())
277+
return SDError::MalformedDiagnosticRecord;
278+
279+
StringRef Name = Blob.substr(0, NameLen);
280+
StringRef URL;
281+
282+
// If the name didn't take up the full blob, and the character that
283+
// follows it is an '@', the rest is the category URL.
284+
if (NameLen < Blob.size() && Blob[NameLen] == '@')
285+
URL = Blob.substr(NameLen + 1);
286+
287+
if ((EC = visitCategoryRecord(Record[0], Name, URL)))
274288
return EC;
275289
continue;
290+
}
276291
case RECORD_DIAG:
277292
// A diagnostic has severity, location (4), category, flag, and message
278293
// size.

clang/tools/c-index-test/c-index-test.c

+10-2
Original file line numberDiff line numberDiff line change
@@ -4823,9 +4823,10 @@ static void printDiagnosticSet(
48234823
CXSourceLocation DiagLoc;
48244824
CXDiagnostic D;
48254825
CXFile File;
4826-
CXString FileName, DiagSpelling, DiagOption, DiagCat;
4826+
CXString FileName, DiagSpelling, DiagOption, DiagCat, DiagCatURL;
48274827
unsigned line, column, offset;
4828-
const char *FileNameStr = 0, *DiagOptionStr = 0, *DiagCatStr = 0;
4828+
const char *FileNameStr = 0, *DiagOptionStr = 0, *DiagCatStr = 0,
4829+
*DiagCatURLStr = 0;
48294830
const char *FileContents = 0;
48304831

48314832
D = clang_getDiagnosticInSet(Diags, i);
@@ -4883,13 +4884,20 @@ static void printDiagnosticSet(
48834884
fprintf(stderr, "%s\nEND CONTENTS OF FILE\n", FileContents);
48844885
}
48854886

4887+
DiagCatURL = clang_getDiagnosticCategoryURL(D);
4888+
DiagCatURLStr = clang_getCString(DiagCatURL);
4889+
if (DiagCatURLStr && DiagCatStr && DiagCatURLStr[0]) {
4890+
fprintf(stderr, "[%s]: <%s>\n", DiagCatStr, DiagCatURLStr);
4891+
}
4892+
48864893
/* Print subdiagnostics. */
48874894
printDiagnosticSet(clang_getChildDiagnostics(D), indent+2, TopDiags);
48884895

48894896
clang_disposeString(FileName);
48904897
clang_disposeString(DiagSpelling);
48914898
clang_disposeString(DiagOption);
48924899
clang_disposeString(DiagCat);
4900+
clang_disposeString(DiagCatURL);
48934901
}
48944902
}
48954903

clang/tools/libclang/CIndexDiagnostic.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class CXDiagnosticCustomNoteImpl : public CXDiagnosticImpl {
7070

7171
unsigned getCategory() const override { return 0; }
7272
CXString getCategoryText() const override { return cxstring::createEmpty(); }
73+
CXString getCategoryURL() const override { return cxstring::createEmpty(); }
7374

7475
unsigned getNumRanges() const override { return 0; }
7576
CXSourceRange getRange(unsigned Range) const override {
@@ -431,7 +432,13 @@ CXString clang_getDiagnosticCategoryText(CXDiagnostic Diag) {
431432
return D->getCategoryText();
432433
return cxstring::createEmpty();
433434
}
434-
435+
436+
CXString clang_getDiagnosticCategoryURL(CXDiagnostic Diag) {
437+
if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
438+
return D->getCategoryURL();
439+
return cxstring::createEmpty();
440+
}
441+
435442
unsigned clang_getDiagnosticNumRanges(CXDiagnostic Diag) {
436443
if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
437444
return D->getNumRanges();

clang/tools/libclang/CIndexDiagnostic.h

+6
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ class CXDiagnosticImpl {
9999
/// Return the category string of the diagnostic.
100100
virtual CXString getCategoryText() const = 0;
101101

102+
/// Return the category URL of the diagnostic.
103+
virtual CXString getCategoryURL() const = 0;
104+
102105
/// Return the number of source ranges for the diagnostic.
103106
virtual unsigned getNumRanges() const = 0;
104107

@@ -160,6 +163,9 @@ struct CXStoredDiagnostic : public CXDiagnosticImpl {
160163
/// Return the category string of the diagnostic.
161164
CXString getCategoryText() const override;
162165

166+
/// Return the category URL of the diagnostic.
167+
CXString getCategoryURL() const override;
168+
163169
/// Return the number of source ranges for the diagnostic.
164170
unsigned getNumRanges() const override;
165171

clang/tools/libclang/CXLoadedDiagnostic.cpp

+11-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl {
4141

4242
llvm::BumpPtrAllocator Alloc;
4343
Strings Categories;
44+
Strings CategoryURLs;
4445
Strings WarningFlags;
4546
Strings FileNames;
4647

@@ -126,6 +127,10 @@ CXString CXLoadedDiagnostic::getCategoryText() const {
126127
return cxstring::createDup(CategoryText);
127128
}
128129

130+
CXString CXLoadedDiagnostic::getCategoryURL() const {
131+
return cxstring::createDup(CategoryURL);
132+
}
133+
129134
unsigned CXLoadedDiagnostic::getNumRanges() const {
130135
return Ranges.size();
131136
}
@@ -215,7 +220,8 @@ class DiagLoader : serialized_diags::SerializedDiagnosticReader {
215220
std::error_code visitStartOfDiagnostic() override;
216221
std::error_code visitEndOfDiagnostic() override;
217222

218-
std::error_code visitCategoryRecord(unsigned ID, StringRef Name) override;
223+
std::error_code visitCategoryRecord(unsigned ID, StringRef Name,
224+
StringRef URL) override;
219225

220226
std::error_code visitDiagFlagRecord(unsigned ID, StringRef Name) override;
221227

@@ -345,11 +351,13 @@ std::error_code DiagLoader::visitEndOfDiagnostic() {
345351
return std::error_code();
346352
}
347353

348-
std::error_code DiagLoader::visitCategoryRecord(unsigned ID, StringRef Name) {
354+
std::error_code DiagLoader::visitCategoryRecord(unsigned ID, StringRef Name,
355+
StringRef URL) {
349356
// FIXME: Why do we care about long strings?
350357
if (Name.size() > 65536)
351358
return reportInvalidFile("Out-of-bounds string in category");
352359
TopDiags->Categories[ID] = TopDiags->copyString(Name);
360+
TopDiags->CategoryURLs[ID] = TopDiags->copyString(URL);
353361
return std::error_code();
354362
}
355363

@@ -431,6 +439,7 @@ std::error_code DiagLoader::visitDiagnosticRecord(
431439
D.category = Category;
432440
D.DiagOption = Flag ? TopDiags->WarningFlags[Flag] : "";
433441
D.CategoryText = Category ? TopDiags->Categories[Category] : "";
442+
D.CategoryURL = Category ? TopDiags->CategoryURLs[Category] : "";
434443
D.Spelling = TopDiags->copyString(Message);
435444
return std::error_code();
436445
}

clang/tools/libclang/CXLoadedDiagnostic.h

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class CXLoadedDiagnostic : public CXDiagnosticImpl {
4949
/// Return the category string of the diagnostic.
5050
CXString getCategoryText() const override;
5151

52+
/// Return the category URL of the diagnostic.
53+
CXString getCategoryURL() const override;
54+
5255
/// Return the number of source ranges for the diagnostic.
5356
unsigned getNumRanges() const override;
5457

@@ -89,6 +92,7 @@ class CXLoadedDiagnostic : public CXDiagnosticImpl {
8992
const char *Spelling;
9093
llvm::StringRef DiagOption;
9194
llvm::StringRef CategoryText;
95+
llvm::StringRef CategoryURL;
9296
unsigned severity;
9397
unsigned category;
9498
};

clang/tools/libclang/CXStoredDiagnostic.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ CXString CXStoredDiagnostic::getCategoryText() const {
7878
return cxstring::createRef(DiagnosticIDs::getCategoryNameFromID(catID));
7979
}
8080

81+
CXString CXStoredDiagnostic::getCategoryURL() const {
82+
// Clang does not currently provide URLs for its own diagnostics.
83+
return cxstring::createEmpty();
84+
}
85+
8186
unsigned CXStoredDiagnostic::getNumRanges() const {
8287
if (Diag.getLocation().isInvalid())
8388
return 0;

clang/tools/libclang/libclang.map

+1
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ LLVM_13 {
339339
clang_getDiagnosticCategory;
340340
clang_getDiagnosticCategoryName;
341341
clang_getDiagnosticCategoryText;
342+
clang_getDiagnosticCategoryURL;
342343
clang_getDiagnosticFixIt;
343344
clang_getDiagnosticInSet;
344345
clang_getDiagnosticLocation;

0 commit comments

Comments
 (0)