Skip to content

Commit d289ff7

Browse files
authored
[lldb] Reland: Add Pythonic API to SBStructuredData extension (#156771)
* Adds `dynamic` property to automatically convert `SBStructuredData` instances to the associated Python type (`str`, `int`, `float`, `bool`, `NoneType`, etc) * Implements `__getitem__` for Pythonic array and dictionary subscripting * Subscripting return the result of the `dynamic` property * Updates `__iter__` to support dictionary instances (supporting `for` loops) * Adds `__str__`, `__int__`, and `__float__` With these changes, these two expressions are equal: ```py data["name"] == data.GetValueForKey("name").GetStringValue(1024) ``` **Note**: Unlike the original commit (#155061), this re-commit removes the `__bool__` implementation, which broke crashlog. Somewhere in the crashlog execution, it depends on `__bool__` meaning only `IsValid()`. Additionally did some cleanup in TestStructuredDataAPI.py.
1 parent 1cb47c1 commit d289ff7

File tree

2 files changed

+186
-30
lines changed

2 files changed

+186
-30
lines changed

lldb/bindings/interface/SBStructuredDataExtensions.i

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,119 @@ STRING_EXTENSION_OUTSIDE(SBStructuredData)
33
%extend lldb::SBStructuredData {
44
#ifdef SWIGPYTHON
55
%pythoncode%{
6-
def __int__(self):
7-
return self.GetSignedInteger()
8-
96
def __len__(self):
107
'''Return the number of element in a lldb.SBStructuredData object.'''
118
return self.GetSize()
129

1310
def __iter__(self):
1411
'''Iterate over all the elements in a lldb.SBStructuredData object.'''
15-
return lldb_iter(self, 'GetSize', 'GetItemAtIndex')
12+
data_type = self.GetType()
13+
if data_type == eStructuredDataTypeArray:
14+
for i in range(self.GetSize()):
15+
yield self.GetItemAtIndex(i).dynamic
16+
return
17+
elif data_type == eStructuredDataTypeDictionary:
18+
keys = SBStringList()
19+
self.GetKeys(keys)
20+
return iter(keys)
21+
else:
22+
raise TypeError(f"cannot iterate {self.type_name(data_type)} type")
23+
24+
def __getitem__(self, key):
25+
data_type = self.GetType()
26+
if data_type == eStructuredDataTypeArray:
27+
if not isinstance(key, int):
28+
raise TypeError("subscript index must be an integer")
29+
count = len(self)
30+
if -count <= key < count:
31+
key %= count
32+
return self.GetItemAtIndex(key).dynamic
33+
raise IndexError("index out of range")
34+
elif data_type == eStructuredDataTypeDictionary:
35+
if not isinstance(key, str):
36+
raise TypeError("subscript key must be a string")
37+
return self.GetValueForKey(key).dynamic
38+
else:
39+
raise TypeError(f"cannot subscript {self.type_name(data_type)} type")
40+
41+
def __str__(self):
42+
data_type = self.GetType()
43+
if data_type in (
44+
eStructuredDataTypeString,
45+
eStructuredDataTypeInteger,
46+
eStructuredDataTypeSignedInteger,
47+
eStructuredDataTypeFloat,
48+
):
49+
return str(self.dynamic)
50+
else:
51+
return repr(self)
52+
53+
def __int__(self):
54+
data_type = self.GetType()
55+
if data_type in (
56+
eStructuredDataTypeInteger,
57+
eStructuredDataTypeSignedInteger,
58+
):
59+
return self.dynamic
60+
else:
61+
raise TypeError(f"cannot convert {self.type_name(data_type)} to int")
62+
63+
def __float__(self):
64+
data_type = self.GetType()
65+
if data_type in (
66+
eStructuredDataTypeFloat,
67+
eStructuredDataTypeInteger,
68+
eStructuredDataTypeSignedInteger,
69+
):
70+
return float(self.dynamic)
71+
else:
72+
raise TypeError(f"cannot convert {self.type_name(data_type)} to float")
73+
74+
@property
75+
def dynamic(self):
76+
data_type = self.GetType()
77+
if data_type == eStructuredDataTypeNull:
78+
return None
79+
elif data_type == eStructuredDataTypeBoolean:
80+
return self.GetBooleanValue()
81+
elif data_type == eStructuredDataTypeInteger:
82+
return self.GetUnsignedIntegerValue()
83+
elif data_type == eStructuredDataTypeSignedInteger:
84+
return self.GetSignedIntegerValue()
85+
elif data_type == eStructuredDataTypeFloat:
86+
return self.GetFloatValue()
87+
elif data_type == eStructuredDataTypeString:
88+
size = len(self) or 1023
89+
return self.GetStringValue(size + 1)
90+
elif data_type == eStructuredDataTypeGeneric:
91+
return self.GetGenericValue()
92+
else:
93+
return self
94+
95+
@staticmethod
96+
def type_name(t):
97+
if t == eStructuredDataTypeNull:
98+
return "null"
99+
elif t == eStructuredDataTypeBoolean:
100+
return "boolean"
101+
elif t == eStructuredDataTypeInteger:
102+
return "integer"
103+
elif t == eStructuredDataTypeSignedInteger:
104+
return "integer"
105+
elif t == eStructuredDataTypeFloat:
106+
return "float"
107+
elif t == eStructuredDataTypeString:
108+
return "string"
109+
elif t == eStructuredDataTypeArray:
110+
return "array"
111+
elif t == eStructuredDataTypeDictionary:
112+
return "dictionary"
113+
elif t == eStructuredDataTypeGeneric:
114+
return "generic"
115+
elif t == eStructuredDataTypeInvalid:
116+
return "invalid"
117+
else:
118+
raise TypeError(f"unknown structured data type: {t}")
16119
%}
17120
#endif
18121
}

lldb/test/API/python_api/sbstructureddata/TestStructuredDataAPI.py

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ def structured_data_api_test(self):
4848
s.Clear()
4949
error = example.GetDescription(s)
5050
self.assertSuccess(error, "GetDescription works")
51+
# Ensure str() doesn't raise an exception.
52+
self.assertTrue(str(example))
5153
if not "key_float" in s.GetData():
5254
self.fail("FAILED: could not find key_float in description output")
5355

54-
dict_struct = lldb.SBStructuredData()
5556
dict_struct = example.GetValueForKey("key_dict")
5657

5758
# Tests for dictionary data type
@@ -113,18 +114,22 @@ class MyRandomClass:
113114
self.assertSuccess(example.SetFromJSON("1"))
114115
self.assertEqual(example.GetType(), lldb.eStructuredDataTypeInteger)
115116
self.assertEqual(example.GetIntegerValue(), 1)
117+
self.assertEqual(int(example), 1)
116118

117119
self.assertSuccess(example.SetFromJSON("4.19"))
118120
self.assertEqual(example.GetType(), lldb.eStructuredDataTypeFloat)
119121
self.assertEqual(example.GetFloatValue(), 4.19)
122+
self.assertEqual(float(example), 4.19)
120123

121124
self.assertSuccess(example.SetFromJSON('"Bonjour, 123!"'))
122125
self.assertEqual(example.GetType(), lldb.eStructuredDataTypeString)
123126
self.assertEqual(example.GetStringValue(42), "Bonjour, 123!")
127+
self.assertEqual(str(example), "Bonjour, 123!")
124128

125129
self.assertSuccess(example.SetFromJSON("true"))
126130
self.assertEqual(example.GetType(), lldb.eStructuredDataTypeBoolean)
127131
self.assertTrue(example.GetBooleanValue())
132+
self.assertTrue(example)
128133

129134
self.assertSuccess(example.SetFromJSON("null"))
130135
self.assertEqual(example.GetType(), lldb.eStructuredDataTypeNull)
@@ -187,38 +192,35 @@ class MyRandomClass:
187192
self.assertEqual(sb_data, example_arr)
188193

189194
def invalid_struct_test(self, example):
190-
invalid_struct = lldb.SBStructuredData()
191195
invalid_struct = example.GetValueForKey("invalid_key")
192196
if invalid_struct.IsValid():
193197
self.fail("An invalid object should have been returned")
194198

195199
# Check Type API
196-
if not invalid_struct.GetType() == lldb.eStructuredDataTypeInvalid:
200+
if invalid_struct.GetType() != lldb.eStructuredDataTypeInvalid:
197201
self.fail("Wrong type returned: " + str(invalid_struct.GetType()))
198202

199203
def dictionary_struct_test(self, example):
200204
# Check API returning a valid SBStructuredData of 'dictionary' type
201-
dict_struct = lldb.SBStructuredData()
202205
dict_struct = example.GetValueForKey("key_dict")
203206
if not dict_struct.IsValid():
204207
self.fail("A valid object should have been returned")
205208

206209
# Check Type API
207-
if not dict_struct.GetType() == lldb.eStructuredDataTypeDictionary:
210+
if dict_struct.GetType() != lldb.eStructuredDataTypeDictionary:
208211
self.fail("Wrong type returned: " + str(dict_struct.GetType()))
209212

210213
# Check Size API for 'dictionary' type
211-
if not dict_struct.GetSize() == 6:
214+
if dict_struct.GetSize() != 6:
212215
self.fail("Wrong no of elements returned: " + str(dict_struct.GetSize()))
213216

214217
def string_struct_test(self, dict_struct):
215-
string_struct = lldb.SBStructuredData()
216218
string_struct = dict_struct.GetValueForKey("key_string")
217219
if not string_struct.IsValid():
218220
self.fail("A valid object should have been returned")
219221

220222
# Check Type API
221-
if not string_struct.GetType() == lldb.eStructuredDataTypeString:
223+
if string_struct.GetType() != lldb.eStructuredDataTypeString:
222224
self.fail("Wrong type returned: " + str(string_struct.GetType()))
223225

224226
# Check API returning 'string' value
@@ -238,18 +240,17 @@ def uint_struct_test(self, dict_struct):
238240
# Check a valid SBStructuredData containing an unsigned integer.
239241
# We intentionally make this larger than what an int64_t can hold but
240242
# still small enough to fit a uint64_t
241-
uint_struct = lldb.SBStructuredData()
242243
uint_struct = dict_struct.GetValueForKey("key_uint")
243244
if not uint_struct.IsValid():
244245
self.fail("A valid object should have been returned")
245246

246247
# Check Type API
247-
if not uint_struct.GetType() == lldb.eStructuredDataTypeInteger:
248+
if uint_struct.GetType() != lldb.eStructuredDataTypeInteger:
248249
self.fail("Wrong type returned: " + str(uint_struct.GetType()))
249250

250251
# Check API returning unsigned integer value
251252
output = uint_struct.GetUnsignedIntegerValue()
252-
if not output == 0xFFFFFFFF00000000:
253+
if output != 0xFFFFFFFF00000000:
253254
self.fail("wrong output: " + str(output))
254255

255256
# Calling wrong API on a SBStructuredData
@@ -262,18 +263,17 @@ def sint_struct_test(self, dict_struct):
262263
# Check a valid SBStructuredData containing an signed integer.
263264
# We intentionally make this smaller than what an uint64_t can hold but
264265
# still small enough to fit a int64_t
265-
sint_struct = lldb.SBStructuredData()
266266
sint_struct = dict_struct.GetValueForKey("key_sint")
267267
if not sint_struct.IsValid():
268268
self.fail("A valid object should have been returned")
269269

270270
# Check Type API
271-
if not sint_struct.GetType() == lldb.eStructuredDataTypeSignedInteger:
271+
if sint_struct.GetType() != lldb.eStructuredDataTypeSignedInteger:
272272
self.fail("Wrong type returned: " + str(sint_struct.GetType()))
273273

274274
# Check API returning signed integer value
275275
output = sint_struct.GetSignedIntegerValue()
276-
if not output == -42:
276+
if output != -42:
277277
self.fail("wrong output: " + str(output))
278278

279279
# Calling wrong API on a SBStructuredData
@@ -283,28 +283,26 @@ def sint_struct_test(self, dict_struct):
283283
self.fail("Valid string " + output + " returned for an integer object")
284284

285285
def double_struct_test(self, dict_struct):
286-
floating_point_struct = lldb.SBStructuredData()
287286
floating_point_struct = dict_struct.GetValueForKey("key_float")
288287
if not floating_point_struct.IsValid():
289288
self.fail("A valid object should have been returned")
290289

291290
# Check Type API
292-
if not floating_point_struct.GetType() == lldb.eStructuredDataTypeFloat:
291+
if floating_point_struct.GetType() != lldb.eStructuredDataTypeFloat:
293292
self.fail("Wrong type returned: " + str(floating_point_struct.GetType()))
294293

295294
# Check API returning 'double' value
296295
output = floating_point_struct.GetFloatValue()
297-
if not output == 2.99:
296+
if output != 2.99:
298297
self.fail("wrong output: " + str(output))
299298

300299
def bool_struct_test(self, dict_struct):
301-
bool_struct = lldb.SBStructuredData()
302300
bool_struct = dict_struct.GetValueForKey("key_bool")
303301
if not bool_struct.IsValid():
304302
self.fail("A valid object should have been returned")
305303

306304
# Check Type API
307-
if not bool_struct.GetType() == lldb.eStructuredDataTypeBoolean:
305+
if bool_struct.GetType() != lldb.eStructuredDataTypeBoolean:
308306
self.fail("Wrong type returned: " + str(bool_struct.GetType()))
309307

310308
# Check API returning 'bool' value
@@ -314,35 +312,90 @@ def bool_struct_test(self, dict_struct):
314312

315313
def array_struct_test(self, dict_struct):
316314
# Check API returning a valid SBStructuredData of 'array' type
317-
array_struct = lldb.SBStructuredData()
318315
array_struct = dict_struct.GetValueForKey("key_array")
319316
if not array_struct.IsValid():
320317
self.fail("A valid object should have been returned")
321318

322319
# Check Type API
323-
if not array_struct.GetType() == lldb.eStructuredDataTypeArray:
320+
if array_struct.GetType() != lldb.eStructuredDataTypeArray:
324321
self.fail("Wrong type returned: " + str(array_struct.GetType()))
325322

326323
# Check Size API for 'array' type
327-
if not array_struct.GetSize() == 2:
324+
if array_struct.GetSize() != 2:
328325
self.fail("Wrong no of elements returned: " + str(array_struct.GetSize()))
329326

330327
# Check API returning a valid SBStructuredData for different 'array'
331328
# indices
332329
string_struct = array_struct.GetItemAtIndex(0)
333330
if not string_struct.IsValid():
334331
self.fail("A valid object should have been returned")
335-
if not string_struct.GetType() == lldb.eStructuredDataTypeString:
332+
if string_struct.GetType() != lldb.eStructuredDataTypeString:
336333
self.fail("Wrong type returned: " + str(string_struct.GetType()))
337334
output = string_struct.GetStringValue(5)
338-
if not output == "23":
335+
if output != "23":
339336
self.fail("wrong output: " + str(output))
340337

341338
string_struct = array_struct.GetItemAtIndex(1)
342339
if not string_struct.IsValid():
343340
self.fail("A valid object should have been returned")
344-
if not string_struct.GetType() == lldb.eStructuredDataTypeString:
341+
if string_struct.GetType() != lldb.eStructuredDataTypeString:
345342
self.fail("Wrong type returned: " + str(string_struct.GetType()))
346343
output = string_struct.GetStringValue(5)
347-
if not output == "arr":
344+
if output != "arr":
348345
self.fail("wrong output: " + str(output))
346+
347+
def test_round_trip_scalars(self):
348+
for original in (0, 11, -1, 0.0, 4.5, -0.25):
349+
constructor = type(original)
350+
data = lldb.SBStructuredData()
351+
data.SetFromJSON(json.dumps(original))
352+
round_tripped = constructor(data)
353+
self.assertEqual(round_tripped, original)
354+
355+
def test_dynamic(self):
356+
for original in (0, 11, -1, 0.0, 4.5, -0.25, "", "dirk", True, False):
357+
data = lldb.SBStructuredData()
358+
data.SetFromJSON(json.dumps(original))
359+
self.assertEqual(data.dynamic, original)
360+
361+
def test_round_trip_int(self):
362+
for original in (0, 11, -1):
363+
data = lldb.SBStructuredData()
364+
data.SetFromJSON(json.dumps(original))
365+
self.assertEqual(int(data), int(original))
366+
367+
def test_round_trip_float(self):
368+
for original in (0, 11, -1, 0.0, 4.5, -0.25):
369+
data = lldb.SBStructuredData()
370+
data.SetFromJSON(json.dumps(original))
371+
self.assertEqual(float(data), float(original))
372+
373+
def test_iterate_array(self):
374+
array = [0, 1, 2]
375+
data = lldb.SBStructuredData()
376+
data.SetFromJSON(json.dumps(array))
377+
for value in data:
378+
self.assertEqual(value, array.pop(0))
379+
380+
def test_iterate_dictionary(self):
381+
dictionary = {"0": 0, "1": 1, "2": 2}
382+
keys = set(dictionary.keys())
383+
data = lldb.SBStructuredData()
384+
data.SetFromJSON(json.dumps(dictionary))
385+
for key in data:
386+
self.assertIn(key, keys)
387+
keys.remove(key)
388+
389+
def test_getitem_array(self):
390+
array = [1, 2, 3]
391+
data = lldb.SBStructuredData()
392+
data.SetFromJSON(json.dumps(array))
393+
for i in range(len(array)):
394+
self.assertEqual(data[i], array[i])
395+
396+
def test_getitem_dictionary(self):
397+
dictionary = {"one": 1, "two": 2, "three": 3}
398+
data = lldb.SBStructuredData()
399+
data.SetFromJSON(json.dumps(dictionary))
400+
for key in dictionary:
401+
self.assertEqual(data[key], dictionary[key])

0 commit comments

Comments
 (0)