diff --git a/lldb/include/lldb/Core/StructuredDataImpl.h b/lldb/include/lldb/Core/StructuredDataImpl.h index e755c53aaa9f6..16dbc5263b285 100644 --- a/lldb/include/lldb/Core/StructuredDataImpl.h +++ b/lldb/include/lldb/Core/StructuredDataImpl.h @@ -80,7 +80,7 @@ class StructuredDataImpl { error.SetErrorString("No data to describe."); return error; } - m_data_sp->Dump(stream, true); + m_data_sp->GetDescription(stream); return error; } // Get the data's description. diff --git a/lldb/include/lldb/Utility/StructuredData.h b/lldb/include/lldb/Utility/StructuredData.h index 9f6300f4f115b..5420c0dcf8d5a 100644 --- a/lldb/include/lldb/Utility/StructuredData.h +++ b/lldb/include/lldb/Utility/StructuredData.h @@ -158,6 +158,12 @@ class StructuredData { Serialize(jso); } + virtual void GetDescription(lldb_private::Stream &s) const { + s.IndentMore(); + Dump(s, false); + s.IndentLess(); + } + private: lldb::StructuredDataType m_type; }; @@ -277,6 +283,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: typedef std::vector collection; collection m_items; @@ -295,6 +303,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: uint64_t m_value; }; @@ -312,6 +322,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: double m_value; }; @@ -329,6 +341,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: bool m_value; }; @@ -345,6 +359,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: std::string m_value; }; @@ -524,6 +540,8 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + protected: typedef std::map collection; collection m_dict; @@ -538,6 +556,8 @@ class StructuredData { bool IsValid() const override { return false; } void Serialize(llvm::json::OStream &s) const override; + + void GetDescription(lldb_private::Stream &s) const override; }; class Generic : public Object { @@ -553,12 +573,15 @@ class StructuredData { void Serialize(llvm::json::OStream &s) const override; + void GetDescription(lldb_private::Stream &s) const override; + private: void *m_object; }; static ObjectSP ParseJSON(const std::string &json_text); static ObjectSP ParseJSONFromFile(const FileSpec &file, Status &error); + static bool IsRecordType(const ObjectSP object_sp); }; } // namespace lldb_private diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp index 28a99ea3d94a5..92544c564e532 100644 --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -1537,8 +1537,9 @@ class CommandObjectProcessStatus : public CommandObjectParsed { StructuredData::DictionarySP crash_info_sp = *expected_crash_info; if (crash_info_sp) { + strm.EOL(); strm.PutCString("Extended Crash Information:\n"); - crash_info_sp->Dump(strm); + crash_info_sp->GetDescription(strm); } } diff --git a/lldb/source/Utility/StructuredData.cpp b/lldb/source/Utility/StructuredData.cpp index 2e023344f3ddb..acc09289e6b98 100644 --- a/lldb/source/Utility/StructuredData.cpp +++ b/lldb/source/Utility/StructuredData.cpp @@ -50,6 +50,11 @@ StructuredData::ParseJSONFromFile(const FileSpec &input_spec, Status &error) { return StructuredData::ObjectSP(); } +bool StructuredData::IsRecordType(const ObjectSP object_sp) { + return object_sp->GetType() == lldb::eStructuredDataTypeArray || + object_sp->GetType() == lldb::eStructuredDataTypeDictionary; +} + static StructuredData::ObjectSP ParseJSONValue(json::Value &value) { if (json::Object *O = value.getAsObject()) return ParseJSONObject(O); @@ -175,3 +180,98 @@ void StructuredData::Null::Serialize(json::OStream &s) const { void StructuredData::Generic::Serialize(json::OStream &s) const { s.value(llvm::formatv("{0:X}", m_object)); } + +void StructuredData::Integer::GetDescription(lldb_private::Stream &s) const { + s.Printf("%" PRId64, static_cast(m_value)); +} + +void StructuredData::Float::GetDescription(lldb_private::Stream &s) const { + s.Printf("%f", m_value); +} + +void StructuredData::Boolean::GetDescription(lldb_private::Stream &s) const { + s.Printf(m_value ? "True" : "False"); +} + +void StructuredData::String::GetDescription(lldb_private::Stream &s) const { + s.Printf("%s", m_value.empty() ? "\"\"" : m_value.c_str()); +} + +void StructuredData::Array::GetDescription(lldb_private::Stream &s) const { + size_t index = 0; + size_t indentation_level = s.GetIndentLevel(); + for (const auto &item_sp : m_items) { + // Sanitize. + if (!item_sp) + continue; + + // Reset original indentation level. + s.SetIndentLevel(indentation_level); + s.Indent(); + + // Print key + s.Printf("[%zu]:", index++); + + // Return to new line and increase indentation if value is record type. + // Otherwise add spacing. + bool should_indent = IsRecordType(item_sp); + if (should_indent) { + s.EOL(); + s.IndentMore(); + } else { + s.PutChar(' '); + } + + // Print value and new line if now last pair. + item_sp->GetDescription(s); + if (item_sp != *(--m_items.end())) + s.EOL(); + + // Reset indentation level if it was incremented previously. + if (should_indent) + s.IndentLess(); + } +} + +void StructuredData::Dictionary::GetDescription(lldb_private::Stream &s) const { + size_t indentation_level = s.GetIndentLevel(); + for (const auto &pair : m_dict) { + // Sanitize. + if (pair.first.IsNull() || pair.first.IsEmpty() || !pair.second) + continue; + + // Reset original indentation level. + s.SetIndentLevel(indentation_level); + s.Indent(); + + // Print key. + s.Printf("%s:", pair.first.AsCString()); + + // Return to new line and increase indentation if value is record type. + // Otherwise add spacing. + bool should_indent = IsRecordType(pair.second); + if (should_indent) { + s.EOL(); + s.IndentMore(); + } else { + s.PutChar(' '); + } + + // Print value and new line if now last pair. + pair.second->GetDescription(s); + if (pair != *(--m_dict.end())) + s.EOL(); + + // Reset indentation level if it was incremented previously. + if (should_indent) + s.IndentLess(); + } +} + +void StructuredData::Null::GetDescription(lldb_private::Stream &s) const { + s.Printf("NULL"); +} + +void StructuredData::Generic::GetDescription(lldb_private::Stream &s) const { + s.Printf("%p", m_object); +} diff --git a/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py b/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py index 30190e7c4df9b..659539c28a795 100644 --- a/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py +++ b/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py @@ -37,7 +37,9 @@ def test_cli(self): patterns=["Process .* launched: .*a.out"]) self.expect('process status --verbose', - patterns=["\"message\".*pointer being freed was not allocated"]) + patterns=["Extended Crash Information", + "crash-info annotations", + "pointer being freed was not allocated"]) @skipIfAsan # The test process intentionally hits a memory bug. diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt index d697464600ff5..848a36215aa67 100644 --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -54,6 +54,10 @@ add_lldb_unittest(UtilityTests Support ) -add_unittest_inputs(UtilityTests +set(test_inputs StructuredData-basic.json + StructuredData-nested.json + StructuredData-full.json ) + +add_unittest_inputs(UtilityTests "${test_inputs}") diff --git a/lldb/unittests/Utility/Inputs/StructuredData-full.json b/lldb/unittests/Utility/Inputs/StructuredData-full.json new file mode 100644 index 0000000000000..4e4945cd6a280 --- /dev/null +++ b/lldb/unittests/Utility/Inputs/StructuredData-full.json @@ -0,0 +1,15 @@ +{ + "Array": [ + 3.14, + { + "key": "val" + } + ], + "Dictionary": { + "FalseBool": false + }, + "Integer": 1, + "Null": null, + "String": "value", + "TrueBool": true +} diff --git a/lldb/unittests/Utility/Inputs/StructuredData-nested.json b/lldb/unittests/Utility/Inputs/StructuredData-nested.json new file mode 100644 index 0000000000000..facf461bb6c1f --- /dev/null +++ b/lldb/unittests/Utility/Inputs/StructuredData-nested.json @@ -0,0 +1,14 @@ +{ + "my_dict": [ + { + "three": 3, + "two": 2 + }, + { + "four": { + "val": 4 + } + }, + 1 + ] +} diff --git a/lldb/unittests/Utility/StructuredDataTest.cpp b/lldb/unittests/Utility/StructuredDataTest.cpp index cb5e418cd958e..e732016fe43db 100644 --- a/lldb/unittests/Utility/StructuredDataTest.cpp +++ b/lldb/unittests/Utility/StructuredDataTest.cpp @@ -31,6 +31,73 @@ TEST(StructuredDataTest, StringDump) { } } +TEST(StructuredDataTest, GetDescriptionEmpty) { + Status status; + auto object_sp = StructuredData::ParseJSON("{}"); + ASSERT_NE(nullptr, object_sp); + + StreamString S; + object_sp->GetDescription(S); + EXPECT_EQ(0, S.GetSize()); +} + +TEST(StructuredDataTest, GetDescriptionBasic) { + Status status; + std::string input = GetInputFilePath("StructuredData-basic.json"); + auto object_sp = StructuredData::ParseJSONFromFile(FileSpec(input), status); + ASSERT_NE(nullptr, object_sp); + + const std::string expected = "[0]: 1\n" + "[1]: 2\n" + "[2]: 3"; + + StreamString S; + object_sp->GetDescription(S); + EXPECT_EQ(expected, S.GetString()); +} + +TEST(StructuredDataTest, GetDescriptionNested) { + Status status; + std::string input = GetInputFilePath("StructuredData-nested.json"); + auto object_sp = StructuredData::ParseJSONFromFile(FileSpec(input), status); + ASSERT_NE(nullptr, object_sp); + + const std::string expected = "my_dict:\n" + " [0]:\n" + " three: 3\n" + " two: 2\n" + " [1]:\n" + " four:\n" + " val: 4\n" + " [2]: 1"; + + StreamString S; + object_sp->GetDescription(S); + EXPECT_EQ(expected, S.GetString()); +} + +TEST(StructuredDataTest, GetDescriptionFull) { + Status status; + std::string input = GetInputFilePath("StructuredData-full.json"); + auto object_sp = StructuredData::ParseJSONFromFile(FileSpec(input), status); + ASSERT_NE(nullptr, object_sp); + + const std::string expected = "Array:\n" + " [0]: 3.140000\n" + " [1]:\n" + " key: val\n" + "Dictionary:\n" + " FalseBool: False\n" + "Integer: 1\n" + "Null: NULL\n" + "String: value\n" + "TrueBool: True"; + + StreamString S; + object_sp->GetDescription(S); + EXPECT_EQ(expected, S.GetString()); +} + TEST(StructuredDataTest, ParseJSONFromFile) { Status status; auto object_sp = StructuredData::ParseJSONFromFile(