Author: Aviral Goel Date: 2026-02-25T09:31:55-08:00 New Revision: ce18f76dc1c0b3c70e5a0d264c9dbce77b5d9210
URL: https://github.com/llvm/llvm-project/commit/ce18f76dc1c0b3c70e5a0d264c9dbce77b5d9210 DIFF: https://github.com/llvm/llvm-project/commit/ce18f76dc1c0b3c70e5a0d264c9dbce77b5d9210.diff LOG: [clang][ssaf] Fix normalization of TUSummary JSON representation and strengthen round-trip tests (#183241) Added: Modified: clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp Removed: ################################################################################ diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp index 287de203cc80c..3ece18d2f724d 100644 --- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp +++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp @@ -27,7 +27,7 @@ using ::testing::HasSubstr; namespace { // ============================================================================ -// PairsEntitySummaryForJSONFormatTest - Simple analysis for testing JSONFormat +// First Test Analysis - Simple analysis for testing JSON serialization. // ============================================================================ struct PairsEntitySummaryForJSONFormatTest final : EntitySummary { @@ -100,6 +100,66 @@ static llvm::Registry<JSONFormat::FormatInfo>::Add< "PairsEntitySummaryForJSONFormatTest", "Format info for PairsArrayEntitySummary"); +// ============================================================================ +// Second Test Analysis - Simple analysis for multi-summary round-trip tests. +// ============================================================================ + +struct TagsEntitySummaryForJSONFormatTest final : EntitySummary { + SummaryName getSummaryName() const override { + return SummaryName("TagsEntitySummaryForJSONFormatTest"); + } + + std::vector<std::string> Tags; +}; + +static json::Object serializeTagsEntitySummaryForJSONFormatTest( + const EntitySummary &Summary, const JSONFormat::EntityIdConverter &) { + const auto &TA = + static_cast<const TagsEntitySummaryForJSONFormatTest &>(Summary); + json::Array TagsArray; + for (const auto &Tag : TA.Tags) { + TagsArray.push_back(Tag); + } + return json::Object{{"tags", std::move(TagsArray)}}; +} + +static Expected<std::unique_ptr<EntitySummary>> +deserializeTagsEntitySummaryForJSONFormatTest( + const json::Object &Obj, EntityIdTable &, + const JSONFormat::EntityIdConverter &) { + auto Result = std::make_unique<TagsEntitySummaryForJSONFormatTest>(); + const json::Array *TagsArray = Obj.getArray("tags"); + if (!TagsArray) { + return createStringError(inconvertibleErrorCode(), + "missing or invalid field 'tags'"); + } + for (const auto &[Index, Value] : llvm::enumerate(*TagsArray)) { + auto Tag = Value.getAsString(); + if (!Tag) { + return createStringError(inconvertibleErrorCode(), + "tags element at index %zu is not a string", + Index); + } + Result->Tags.push_back(Tag->str()); + } + return std::move(Result); +} + +struct TagsEntitySummaryForJSONFormatTestFormatInfo final + : JSONFormat::FormatInfo { + TagsEntitySummaryForJSONFormatTestFormatInfo() + : JSONFormat::FormatInfo( + SummaryName("TagsEntitySummaryForJSONFormatTest"), + serializeTagsEntitySummaryForJSONFormatTest, + deserializeTagsEntitySummaryForJSONFormatTest) {} +}; + +static llvm::Registry<JSONFormat::FormatInfo>::Add< + TagsEntitySummaryForJSONFormatTestFormatInfo> + RegisterTagsEntitySummaryForJSONFormatTest( + "TagsEntitySummaryForJSONFormatTest", + "Format info for TagsEntitySummary"); + // ============================================================================ // NullEntitySummaryForJSONFormatTest - For null data checks // ============================================================================ @@ -200,30 +260,52 @@ class JSONFormatTUSummaryTest : public JSONFormatTest { return JSONFormat().writeTUSummary(Summary, FilePath); } - // Normalize TUSummary JSON by sorting id_table by id field. - static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) { - auto *Obj = Val.getAsObject(); - if (!Obj) { - return createStringError( - inconvertibleErrorCode(), - "Cannot normalize TUSummary JSON: expected an object"); - } + static llvm::Error normalizeIDTable(json::Array &IDTable) { + for (const auto &[Index, Entry] : llvm::enumerate(IDTable)) { + const auto *EntryObj = Entry.getAsObject(); + if (!EntryObj) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: id_table entry at index %zu " + "is not an object", + Index); + } - auto *IDTable = Obj->getArray("id_table"); - if (!IDTable) { - return createStringError(inconvertibleErrorCode(), - "Cannot normalize TUSummary JSON: 'id_table' " - "field is either missing or has the wrong type"); + const auto *IDValue = EntryObj->get("id"); + if (!IDValue) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: id_table entry at index %zu " + "does not contain an 'id' field", + Index); + } + + if (!IDValue->getAsUINT64()) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: id_table entry at index %zu " + "does not contain a valid 'id' uint64_t field", + Index); + } } - // Validate all id_table entries before sorting. - for (const auto &[Index, Entry] : llvm::enumerate(*IDTable)) { + // Safe to dereference: all entries were validated above. + llvm::sort(IDTable, [](const json::Value &A, const json::Value &B) { + return *A.getAsObject()->get("id")->getAsUINT64() < + *B.getAsObject()->get("id")->getAsUINT64(); + }); + + return llvm::Error::success(); + } + + static llvm::Error normalizeLinkageTable(json::Array &LinkageTable) { + for (const auto &[Index, Entry] : llvm::enumerate(LinkageTable)) { const auto *EntryObj = Entry.getAsObject(); if (!EntryObj) { return createStringError( inconvertibleErrorCode(), - "Cannot normalize TUSummary JSON: id_table entry at index %zu is " - "not an object", + "Cannot normalize TUSummary JSON: linkage_table entry at index " + "%zu is not an object", Index); } @@ -231,32 +313,152 @@ class JSONFormatTUSummaryTest : public JSONFormatTest { if (!IDValue) { return createStringError( inconvertibleErrorCode(), - "Cannot normalize TUSummary JSON: id_table entry at index %zu does " - "not contain an 'id' field", + "Cannot normalize TUSummary JSON: linkage_table entry at index " + "%zu does not contain an 'id' field", Index); } - auto EntryID = IDValue->getAsUINT64(); - if (!EntryID) { + if (!IDValue->getAsUINT64()) { return createStringError( inconvertibleErrorCode(), - "Cannot normalize TUSummary JSON: id_table entry at index %zu does " - "not contain a valid 'id' uint64_t field", + "Cannot normalize TUSummary JSON: linkage_table entry at index " + "%zu does not contain a valid 'id' uint64_t field", Index); } } - // Sort id_table entries by the "id" field to ensure deterministic ordering - // for comparison. Use projection-based comparison for strict-weak-ordering. - llvm::sort(*IDTable, [](const json::Value &A, const json::Value &B) { - // Safe to assume these succeed because we validated above. - const auto *AObj = A.getAsObject(); - const auto *BObj = B.getAsObject(); - uint64_t AID = *AObj->get("id")->getAsUINT64(); - uint64_t BID = *BObj->get("id")->getAsUINT64(); - return AID < BID; + // Safe to dereference: all entries were validated above. + llvm::sort(LinkageTable, [](const json::Value &A, const json::Value &B) { + return *A.getAsObject()->get("id")->getAsUINT64() < + *B.getAsObject()->get("id")->getAsUINT64(); }); + return llvm::Error::success(); + } + + static llvm::Error normalizeSummaryData(json::Array &SummaryData, + size_t DataIndex) { + for (const auto &[SummaryIndex, SummaryEntry] : + llvm::enumerate(SummaryData)) { + const auto *SummaryEntryObj = SummaryEntry.getAsObject(); + if (!SummaryEntryObj) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu, " + "summary_data entry at index %zu is not an object", + DataIndex, SummaryIndex); + } + + const auto *EntityIDValue = SummaryEntryObj->get("entity_id"); + if (!EntityIDValue) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu, " + "summary_data entry at index %zu does not contain an " + "'entity_id' field", + DataIndex, SummaryIndex); + } + + if (!EntityIDValue->getAsUINT64()) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu, " + "summary_data entry at index %zu does not contain a valid " + "'entity_id' uint64_t field", + DataIndex, SummaryIndex); + } + } + + // Safe to dereference: all entries were validated above. + llvm::sort(SummaryData, [](const json::Value &A, const json::Value &B) { + return *A.getAsObject()->get("entity_id")->getAsUINT64() < + *B.getAsObject()->get("entity_id")->getAsUINT64(); + }); + + return llvm::Error::success(); + } + + static llvm::Error normalizeData(json::Array &Data) { + for (const auto &[DataIndex, DataEntry] : llvm::enumerate(Data)) { + auto *DataEntryObj = DataEntry.getAsObject(); + if (!DataEntryObj) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu " + "is not an object", + DataIndex); + } + + if (!DataEntryObj->getString("summary_name")) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu " + "does not contain a 'summary_name' string field", + DataIndex); + } + + auto *SummaryData = DataEntryObj->getArray("summary_data"); + if (!SummaryData) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: data entry at index %zu " + "does not contain a 'summary_data' array field", + DataIndex); + } + + if (auto Err = normalizeSummaryData(*SummaryData, DataIndex)) { + return Err; + } + } + + // Safe to dereference: all entries were validated above. + llvm::sort(Data, [](const json::Value &A, const json::Value &B) { + return *A.getAsObject()->getString("summary_name") < + *B.getAsObject()->getString("summary_name"); + }); + + return llvm::Error::success(); + } + + static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) { + auto *Obj = Val.getAsObject(); + if (!Obj) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: expected an object"); + } + + auto *IDTable = Obj->getArray("id_table"); + if (!IDTable) { + return createStringError(inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: 'id_table' " + "field is either missing or has the wrong type"); + } + if (auto Err = normalizeIDTable(*IDTable)) { + return std::move(Err); + } + + auto *LinkageTable = Obj->getArray("linkage_table"); + if (!LinkageTable) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: 'linkage_table' " + "field is either missing or has the wrong type"); + } + if (auto Err = normalizeLinkageTable(*LinkageTable)) { + return std::move(Err); + } + + auto *Data = Obj->getArray("data"); + if (!Data) { + return createStringError(inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: 'data' " + "field is either missing or has the wrong type"); + } + if (auto Err = normalizeData(*Data)) { + return std::move(Err); + } + return Val; } @@ -2168,7 +2370,7 @@ TEST_F(JSONFormatTUSummaryTest, WriteEntitySummaryNoFormatInfo) { // Round-Trip Tests - Serialization Verification // ============================================================================ -TEST_F(JSONFormatTUSummaryTest, Empty) { +TEST_F(JSONFormatTUSummaryTest, RoundTripEmpty) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2180,19 +2382,7 @@ TEST_F(JSONFormatTUSummaryTest, Empty) { })"); } -TEST_F(JSONFormatTUSummaryTest, LinkUnit) { - readWriteCompareTUSummary(R"({ - "tu_namespace": { - "kind": "link_unit", - "name": "libtest.so" - }, - "id_table": [], - "linkage_table": [], - "data": [] - })"); -} - -TEST_F(JSONFormatTUSummaryTest, WithIDTable) { +TEST_F(JSONFormatTUSummaryTest, RoundTripWithTwoSummaryTypes) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2200,9 +2390,9 @@ TEST_F(JSONFormatTUSummaryTest, WithIDTable) { }, "id_table": [ { - "id": 0, + "id": 3, "name": { - "usr": "c:@F@foo", + "usr": "c:@F@qux", "suffix": "", "namespace": [ { @@ -2216,62 +2406,6 @@ TEST_F(JSONFormatTUSummaryTest, WithIDTable) { "id": 1, "name": { "usr": "c:@F@bar", - "suffix": "1", - "namespace": [ - { - "kind": "compilation_unit", - "name": "test.cpp" - }, - { - "kind": "link_unit", - "name": "libtest.so" - } - ] - } - } - ], - "linkage_table": [ - { - "id": 0, - "linkage": { "type": "none" } - }, - { - "id": 1, - "linkage": { "type": "internal" } - } - ], - "data": [] - })"); -} - -TEST_F(JSONFormatTUSummaryTest, WithEmptyDataEntry) { - readWriteCompareTUSummary(R"({ - "tu_namespace": { - "kind": "compilation_unit", - "name": "test.cpp" - }, - "id_table": [], - "linkage_table": [], - "data": [ - { - "summary_name": "PairsEntitySummaryForJSONFormatTest", - "summary_data": [] - } - ] - })"); -} - -TEST_F(JSONFormatTUSummaryTest, RoundTripWithIDTable) { - readWriteCompareTUSummary(R"({ - "tu_namespace": { - "kind": "compilation_unit", - "name": "test.cpp" - }, - "id_table": [ - { - "id": 0, - "name": { - "usr": "c:@F@foo", "suffix": "", "namespace": [ { @@ -2280,29 +2414,11 @@ TEST_F(JSONFormatTUSummaryTest, RoundTripWithIDTable) { } ] } - } - ], - "linkage_table": [ - { - "id": 0, - "linkage": { "type": "none" } - } - ], - "data": [] - })"); -} - -TEST_F(JSONFormatTUSummaryTest, RoundTripPairsEntitySummaryForJSONFormatTest) { - readWriteCompareTUSummary(R"({ - "tu_namespace": { - "kind": "compilation_unit", - "name": "test.cpp" - }, - "id_table": [ + }, { - "id": 0, + "id": 4, "name": { - "usr": "c:@F@main", + "usr": "c:@F@quux", "suffix": "", "namespace": [ { @@ -2313,7 +2429,7 @@ TEST_F(JSONFormatTUSummaryTest, RoundTripPairsEntitySummaryForJSONFormatTest) { } }, { - "id": 1, + "id": 0, "name": { "usr": "c:@F@foo", "suffix": "", @@ -2328,7 +2444,7 @@ TEST_F(JSONFormatTUSummaryTest, RoundTripPairsEntitySummaryForJSONFormatTest) { { "id": 2, "name": { - "usr": "c:@F@bar", + "usr": "c:@F@baz", "suffix": "", "namespace": [ { @@ -2341,34 +2457,92 @@ TEST_F(JSONFormatTUSummaryTest, RoundTripPairsEntitySummaryForJSONFormatTest) { ], "linkage_table": [ { - "id": 0, - "linkage": { "type": "none" } + "id": 3, + "linkage": { "type": "internal" } }, { "id": 1, - "linkage": { "type": "internal" } + "linkage": { "type": "none" } }, { - "id": 2, + "id": 4, "linkage": { "type": "external" } + }, + { + "id": 0, + "linkage": { "type": "none" } + }, + { + "id": 2, + "linkage": { "type": "internal" } } ], "data": [ + { + "summary_name": "TagsEntitySummaryForJSONFormatTest", + "summary_data": [ + { + "entity_id": 4, + "entity_summary": { "tags": ["exported", "hot"] } + }, + { + "entity_id": 1, + "entity_summary": { "tags": ["internal-only"] } + }, + { + "entity_id": 3, + "entity_summary": { "tags": ["internal-only"] } + }, + { + "entity_id": 0, + "entity_summary": { "tags": ["entry-point"] } + }, + { + "entity_id": 2, + "entity_summary": { "tags": [] } + } + ] + }, { "summary_name": "PairsEntitySummaryForJSONFormatTest", "summary_data": [ + { + "entity_id": 1, + "entity_summary": { + "pairs": [ + { "first": 1, "second": 3 } + ] + } + }, + { + "entity_id": 4, + "entity_summary": { + "pairs": [ + { "first": 4, "second": 0 }, + { "first": 4, "second": 2 } + ] + } + }, { "entity_id": 0, + "entity_summary": { + "pairs": [] + } + }, + { + "entity_id": 3, "entity_summary": { "pairs": [ - { - "first": 0, - "second": 1 - }, - { - "first": 1, - "second": 2 - } + { "first": 3, "second": 1 } + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "pairs": [ + { "first": 2, "second": 4 }, + { "first": 2, "second": 3 } ] } } @@ -2378,7 +2552,19 @@ TEST_F(JSONFormatTUSummaryTest, RoundTripPairsEntitySummaryForJSONFormatTest) { })"); } -TEST_F(JSONFormatTUSummaryTest, EmptyLinkageTable) { +TEST_F(JSONFormatTUSummaryTest, RoundTripLinkUnit) { + readWriteCompareTUSummary(R"({ + "tu_namespace": { + "kind": "link_unit", + "name": "libtest.so" + }, + "id_table": [], + "linkage_table": [], + "data": [] + })"); +} + +TEST_F(JSONFormatTUSummaryTest, RoundTripWithEmptyDataEntry) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2386,11 +2572,16 @@ TEST_F(JSONFormatTUSummaryTest, EmptyLinkageTable) { }, "id_table": [], "linkage_table": [], - "data": [] + "data": [ + { + "summary_name": "PairsEntitySummaryForJSONFormatTest", + "summary_data": [] + } + ] })"); } -TEST_F(JSONFormatTUSummaryTest, LinkageTableWithNoneLinkage) { +TEST_F(JSONFormatTUSummaryTest, RoundTripLinkageTableWithNoneLinkage) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2421,7 +2612,7 @@ TEST_F(JSONFormatTUSummaryTest, LinkageTableWithNoneLinkage) { })"); } -TEST_F(JSONFormatTUSummaryTest, LinkageTableWithInternalLinkage) { +TEST_F(JSONFormatTUSummaryTest, RoundTripLinkageTableWithInternalLinkage) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2452,7 +2643,7 @@ TEST_F(JSONFormatTUSummaryTest, LinkageTableWithInternalLinkage) { })"); } -TEST_F(JSONFormatTUSummaryTest, LinkageTableWithExternalLinkage) { +TEST_F(JSONFormatTUSummaryTest, RoundTripLinkageTableWithExternalLinkage) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", @@ -2483,7 +2674,7 @@ TEST_F(JSONFormatTUSummaryTest, LinkageTableWithExternalLinkage) { })"); } -TEST_F(JSONFormatTUSummaryTest, LinkageTableWithMultipleEntries) { +TEST_F(JSONFormatTUSummaryTest, RoundTripLinkageTableWithMultipleEntries) { readWriteCompareTUSummary(R"({ "tu_namespace": { "kind": "compilation_unit", _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
