diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp index 58995c249bd2..b4284f8eb4d1 100644 --- a/clang-tools-extra/clang-doc/JSONGenerator.cpp +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -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; } } diff --git a/clang-tools-extra/test/clang-doc/Inputs/invalid-utf8-comment.cpp b/clang-tools-extra/test/clang-doc/Inputs/invalid-utf8-comment.cpp new file mode 100644 index 000000000000..89f3ff437819 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/Inputs/invalid-utf8-comment.cpp @@ -0,0 +1,2 @@ +// non-UTF-8: � +class A {}; diff --git a/clang-tools-extra/test/clang-doc/json/invalid-utf8-comment.cpp b/clang-tools-extra/test/clang-doc/json/invalid-utf8-comment.cpp new file mode 100644 index 000000000000..b2bd4174832d --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/invalid-utf8-comment.cpp @@ -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: }