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

Reply via email to