1 //===-- StructuredData.cpp ------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Utility/StructuredData.h"
10 #include "lldb/Utility/FileSpec.h"
11 #include "lldb/Utility/Status.h"
12 #include "llvm/ADT/StringExtras.h"
13 #include "llvm/Support/MemoryBuffer.h"
14 #include <cerrno>
15 #include <cinttypes>
16 #include <cstdlib>
17 
18 using namespace lldb_private;
19 using namespace llvm;
20 
21 static StructuredData::ObjectSP ParseJSONValue(json::Value &value);
22 static StructuredData::ObjectSP ParseJSONObject(json::Object *object);
23 static StructuredData::ObjectSP ParseJSONArray(json::Array *array);
24 
25 StructuredData::ObjectSP StructuredData::ParseJSON(llvm::StringRef json_text) {
26   llvm::Expected<json::Value> value = json::parse(json_text);
27   if (!value) {
28     llvm::consumeError(value.takeError());
29     return nullptr;
30   }
31   return ParseJSONValue(*value);
32 }
33 
34 StructuredData::ObjectSP
35 StructuredData::ParseJSONFromFile(const FileSpec &input_spec, Status &error) {
36   StructuredData::ObjectSP return_sp;
37 
38   auto buffer_or_error = llvm::MemoryBuffer::getFile(input_spec.GetPath());
39   if (!buffer_or_error) {
40     error.SetErrorStringWithFormatv("could not open input file: {0} - {1}.",
41                                     input_spec.GetPath(),
42                                     buffer_or_error.getError().message());
43     return return_sp;
44   }
45   llvm::Expected<json::Value> value =
46       json::parse(buffer_or_error.get()->getBuffer().str());
47   if (value)
48     return ParseJSONValue(*value);
49   error.SetErrorString(toString(value.takeError()));
50   return StructuredData::ObjectSP();
51 }
52 
53 bool StructuredData::IsRecordType(const ObjectSP object_sp) {
54   return object_sp->GetType() == lldb::eStructuredDataTypeArray ||
55          object_sp->GetType() == lldb::eStructuredDataTypeDictionary;
56 }
57 
58 static StructuredData::ObjectSP ParseJSONValue(json::Value &value) {
59   if (json::Object *O = value.getAsObject())
60     return ParseJSONObject(O);
61 
62   if (json::Array *A = value.getAsArray())
63     return ParseJSONArray(A);
64 
65   if (auto s = value.getAsString())
66     return std::make_shared<StructuredData::String>(*s);
67 
68   if (auto b = value.getAsBoolean())
69     return std::make_shared<StructuredData::Boolean>(*b);
70 
71   if (auto u = value.getAsUINT64())
72     return std::make_shared<StructuredData::UnsignedInteger>(*u);
73 
74   if (auto i = value.getAsInteger())
75     return std::make_shared<StructuredData::SignedInteger>(*i);
76 
77   if (auto d = value.getAsNumber())
78     return std::make_shared<StructuredData::Float>(*d);
79 
80   if (auto n = value.getAsNull())
81     return std::make_shared<StructuredData::Null>();
82 
83   return StructuredData::ObjectSP();
84 }
85 
86 static StructuredData::ObjectSP ParseJSONObject(json::Object *object) {
87   auto dict_up = std::make_unique<StructuredData::Dictionary>();
88   for (auto &KV : *object) {
89     StringRef key = KV.first;
90     json::Value value = KV.second;
91     if (StructuredData::ObjectSP value_sp = ParseJSONValue(value))
92       dict_up->AddItem(key, value_sp);
93   }
94   return std::move(dict_up);
95 }
96 
97 static StructuredData::ObjectSP ParseJSONArray(json::Array *array) {
98   auto array_up = std::make_unique<StructuredData::Array>();
99   for (json::Value &value : *array) {
100     if (StructuredData::ObjectSP value_sp = ParseJSONValue(value))
101       array_up->AddItem(value_sp);
102   }
103   return std::move(array_up);
104 }
105 
106 StructuredData::ObjectSP
107 StructuredData::Object::GetObjectForDotSeparatedPath(llvm::StringRef path) {
108   if (GetType() == lldb::eStructuredDataTypeDictionary) {
109     std::pair<llvm::StringRef, llvm::StringRef> match = path.split('.');
110     llvm::StringRef key = match.first;
111     ObjectSP value = GetAsDictionary()->GetValueForKey(key);
112     if (!value)
113       return {};
114 
115     // Do we have additional words to descend?  If not, return the value
116     // we're at right now.
117     if (match.second.empty())
118       return value;
119 
120     return value->GetObjectForDotSeparatedPath(match.second);
121   }
122 
123   if (GetType() == lldb::eStructuredDataTypeArray) {
124     std::pair<llvm::StringRef, llvm::StringRef> match = path.split('[');
125     if (match.second.empty())
126       return shared_from_this();
127 
128     uint64_t val = 0;
129     if (!llvm::to_integer(match.second, val, /* Base = */ 10))
130       return {};
131 
132     return GetAsArray()->GetItemAtIndex(val);
133   }
134 
135   return shared_from_this();
136 }
137 
138 void StructuredData::Object::DumpToStdout(bool pretty_print) const {
139   json::OStream stream(llvm::outs(), pretty_print ? 2 : 0);
140   Serialize(stream);
141 }
142 
143 void StructuredData::Array::Serialize(json::OStream &s) const {
144   s.arrayBegin();
145   for (const auto &item_sp : m_items) {
146     item_sp->Serialize(s);
147   }
148   s.arrayEnd();
149 }
150 
151 void StructuredData::Float::Serialize(json::OStream &s) const {
152   s.value(m_value);
153 }
154 
155 void StructuredData::Boolean::Serialize(json::OStream &s) const {
156   s.value(m_value);
157 }
158 
159 void StructuredData::String::Serialize(json::OStream &s) const {
160   s.value(m_value);
161 }
162 
163 void StructuredData::Dictionary::Serialize(json::OStream &s) const {
164   s.objectBegin();
165   for (const auto &pair : m_dict) {
166     s.attributeBegin(pair.first.GetStringRef());
167     pair.second->Serialize(s);
168     s.attributeEnd();
169   }
170   s.objectEnd();
171 }
172 
173 void StructuredData::Null::Serialize(json::OStream &s) const {
174   s.value(nullptr);
175 }
176 
177 void StructuredData::Generic::Serialize(json::OStream &s) const {
178   s.value(llvm::formatv("{0:X}", m_object));
179 }
180 
181 void StructuredData::Float::GetDescription(lldb_private::Stream &s) const {
182   s.Printf("%f", m_value);
183 }
184 
185 void StructuredData::Boolean::GetDescription(lldb_private::Stream &s) const {
186   s.Printf(m_value ? "True" : "False");
187 }
188 
189 void StructuredData::String::GetDescription(lldb_private::Stream &s) const {
190   s.Printf("%s", m_value.empty() ? "\"\"" : m_value.c_str());
191 }
192 
193 void StructuredData::Array::GetDescription(lldb_private::Stream &s) const {
194   size_t index = 0;
195   size_t indentation_level = s.GetIndentLevel();
196   for (const auto &item_sp : m_items) {
197     // Sanitize.
198     if (!item_sp)
199       continue;
200 
201     // Reset original indentation level.
202     s.SetIndentLevel(indentation_level);
203     s.Indent();
204 
205     // Print key
206     s.Printf("[%zu]:", index++);
207 
208     // Return to new line and increase indentation if value is record type.
209     // Otherwise add spacing.
210     bool should_indent = IsRecordType(item_sp);
211     if (should_indent) {
212       s.EOL();
213       s.IndentMore();
214     } else {
215       s.PutChar(' ');
216     }
217 
218     // Print value and new line if now last pair.
219     item_sp->GetDescription(s);
220     if (item_sp != *(--m_items.end()))
221       s.EOL();
222 
223     // Reset indentation level if it was incremented previously.
224     if (should_indent)
225       s.IndentLess();
226   }
227 }
228 
229 void StructuredData::Dictionary::GetDescription(lldb_private::Stream &s) const {
230   size_t indentation_level = s.GetIndentLevel();
231   for (const auto &pair : m_dict) {
232     // Sanitize.
233     if (pair.first.IsNull() || pair.first.IsEmpty() || !pair.second)
234       continue;
235 
236     // Reset original indentation level.
237     s.SetIndentLevel(indentation_level);
238     s.Indent();
239 
240     // Print key.
241     s.Printf("%s:", pair.first.AsCString());
242 
243     // Return to new line and increase indentation if value is record type.
244     // Otherwise add spacing.
245     bool should_indent = IsRecordType(pair.second);
246     if (should_indent) {
247       s.EOL();
248       s.IndentMore();
249     } else {
250       s.PutChar(' ');
251     }
252 
253     // Print value and new line if now last pair.
254     pair.second->GetDescription(s);
255     if (pair != *(--m_dict.end()))
256       s.EOL();
257 
258     // Reset indentation level if it was incremented previously.
259     if (should_indent)
260       s.IndentLess();
261   }
262 }
263 
264 void StructuredData::Null::GetDescription(lldb_private::Stream &s) const {
265   s.Printf("NULL");
266 }
267 
268 void StructuredData::Generic::GetDescription(lldb_private::Stream &s) const {
269   s.Printf("%p", m_object);
270 }
271