Fix clang-doc crash when comment contains invalid UTF-8 bytes (#210886)
When a source file comment contains non-UTF-8 bytes (e.g., byte 0x97), clang-doc's JSONGenerator would crash with an assertion failure: Assertion `false && "Invalid UTF-8 in value used as JSON"' failed. This happens because json::Value(StringRef) asserts on invalid UTF-8 input in debug builds. While the release-build path calls fixUTF8() automatically, the assertion crash affects developers and fuzzer runs. Fix by following the same pattern used elsewhere in LLVM (lldb, clangd): check isUTF8() before constructing a json::Value, and call fixUTF8() to sanitize the string if invalid bytes are detected. This avoids an unnecessary copy for the common case of valid UTF-8. The fix covers all paths in serializeComment() where user-written text from source comments enters JSON objects: - CK_TextComment (I.Text) - CK_InlineCommandComment (I.Args) - CK_VerbatimBlockLineComment / CK_VerbatimLineComment (I.Text) - CK_Unknown (I.Text) Also add a regression test with an actual invalid UTF-8 byte in a comment. AI assistance was used for code review analysis and local build verification. Fixes: https://github.com/llvm/llvm-project/issues/210675 --------- Co-authored-by: Chenguang Ding <dingchenguang@kylinos.cn> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8e34b2cbb0
commit
84b931c00f
3 changed files with 58 additions and 4 deletions
|
|
@ -106,6 +106,12 @@ static void insertNonEmpty(StringRef Key, StringRef Value, Object &Obj) {
|
|||
Obj[Key] = Value;
|
||||
}
|
||||
|
||||
static json::Value safeJSONString(StringRef S) {
|
||||
if (LLVM_LIKELY(json::isUTF8(S)))
|
||||
return S;
|
||||
return json::fixUTF8(S);
|
||||
}
|
||||
|
||||
static std::string infoTypeToString(InfoType IT) {
|
||||
switch (IT) {
|
||||
case InfoType::IT_default:
|
||||
|
|
@ -233,7 +239,7 @@ static Object serializeComment(const CommentInfo &I, Object &Description) {
|
|||
switch (I.Kind) {
|
||||
case CommentKind::CK_TextComment: {
|
||||
if (!I.Text.empty())
|
||||
Obj.insert({commentKindToString(I.Kind), I.Text});
|
||||
Obj.insert({commentKindToString(I.Kind), safeJSONString(I.Text)});
|
||||
return Obj;
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +264,7 @@ static Object serializeComment(const CommentInfo &I, Object &Description) {
|
|||
auto &ARef = *ArgsArr.getAsArray();
|
||||
ARef.reserve(I.Args.size());
|
||||
for (const auto &Arg : I.Args)
|
||||
ARef.emplace_back(Arg);
|
||||
ARef.emplace_back(safeJSONString(Arg));
|
||||
Child.insert({"Command", I.Name});
|
||||
Child.insert({"Args", ArgsArr});
|
||||
Child.insert({"Children", ChildArr});
|
||||
|
|
@ -292,7 +298,7 @@ static Object serializeComment(const CommentInfo &I, Object &Description) {
|
|||
|
||||
case CommentKind::CK_VerbatimBlockLineComment:
|
||||
case CommentKind::CK_VerbatimLineComment: {
|
||||
Child.insert({"Text", I.Text});
|
||||
Child.insert({"Text", safeJSONString(I.Text)});
|
||||
Child.insert({"Children", ChildArr});
|
||||
Obj.insert({commentKindToString(I.Kind), ChildVal});
|
||||
return Obj;
|
||||
|
|
@ -333,7 +339,7 @@ static Object serializeComment(const CommentInfo &I, Object &Description) {
|
|||
}
|
||||
|
||||
case CommentKind::CK_Unknown: {
|
||||
Obj.insert({commentKindToString(I.Kind), I.Text});
|
||||
Obj.insert({commentKindToString(I.Kind), safeJSONString(I.Text)});
|
||||
return Obj;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
// non-UTF-8: <20>
|
||||
class A {};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// RUN: rm -rf %t && mkdir -p %t
|
||||
// RUN: clang-doc --pretty-json --output=%t --format=json --executor=standalone %S/../Inputs/invalid-utf8-comment.cpp 2>&1
|
||||
// RUN: cat %t/json/GlobalNamespace/_ZTV1A.json | FileCheck %s
|
||||
|
||||
// Regression test for https://github.com/llvm/llvm-project/issues/210675.
|
||||
// clang-doc should not crash when encountering comments with invalid UTF-8 bytes.
|
||||
// Instead, the invalid bytes should be replaced with valid UTF-8 replacement characters.
|
||||
|
||||
// CHECK: {
|
||||
// CHECK-NEXT: "Contexts": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "DocumentationFileName": "index",
|
||||
// CHECK-NEXT: "End": true,
|
||||
// CHECK-NEXT: "Name": "Global Namespace",
|
||||
// CHECK-NEXT: "QualName": "GlobalNamespace",
|
||||
// CHECK-NEXT: "RelativePath": "./",
|
||||
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "Description": {
|
||||
// CHECK-NEXT: "HasParagraphComments": true,
|
||||
// CHECK-NEXT: "ParagraphComments": [
|
||||
// CHECK-NEXT: [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK: "TextComment":
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "DocumentationFileName": "_ZTV1A",
|
||||
// CHECK-NEXT: "HasContexts": true,
|
||||
// CHECK-NEXT: "InfoType": "record",
|
||||
// CHECK-NEXT: "IsTypedef": false,
|
||||
// CHECK-NEXT: "Location": {
|
||||
// CHECK-NEXT: "Filename": "{{.*}}invalid-utf8-comment.cpp",
|
||||
// CHECK-NEXT: "LineNumber": 2
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "MangledName": "_ZTV1A",
|
||||
// CHECK-NEXT: "Name": "A",
|
||||
// CHECK-NEXT: "Namespace": [
|
||||
// CHECK-NEXT: "GlobalNamespace"
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "Path": "GlobalNamespace",
|
||||
// CHECK-NEXT: "TagType": "class",
|
||||
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
|
||||
// CHECK-NEXT: }
|
||||
Loading…
Reference in a new issue