-
Notifications
You must be signed in to change notification settings - Fork 14.2k
[lldb] Add lookup by name to SBValue through new member property #118814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[lldb] Add lookup by name to SBValue through new member property #118814
Conversation
@llvm/pr-subscribers-lldb Author: Dave Lee (kastiglione) ChangesFull diff: https://github.com/llvm/llvm-project/pull/118814.diff 2 Files Affected:
diff --git a/lldb/bindings/interface/SBValueExtensions.i b/lldb/bindings/interface/SBValueExtensions.i
index bee9c27775d453..f743b8b9bc786f 100644
--- a/lldb/bindings/interface/SBValueExtensions.i
+++ b/lldb/bindings/interface/SBValueExtensions.i
@@ -7,7 +7,7 @@ STRING_EXTENSION_OUTSIDE(SBValue)
return self.GetDynamicValue (eDynamicCanRunTarget)
class children_access(object):
- '''A helper object that will lazily hand out thread for a process when supplied an index.'''
+ '''A helper object that will lazily hand out child values when supplied an index or name.'''
def __init__(self, sbvalue):
self.sbvalue = sbvalue
@@ -23,6 +23,8 @@ STRING_EXTENSION_OUTSIDE(SBValue)
if -count <= key < count:
key %= count
return self.sbvalue.GetChildAtIndex(key)
+ elif isinstance(key, str):
+ return self.sbvalue.GetChildMemberWithName(key)
return None
def get_child_access_object(self):
@@ -49,7 +51,7 @@ STRING_EXTENSION_OUTSIDE(SBValue)
return self.GetNumChildren()
children = property(get_value_child_list, None, doc='''A read only property that returns a list() of lldb.SBValue objects for the children of the value.''')
- child = property(get_child_access_object, None, doc='''A read only property that returns an object that can access children of a variable by index (child_value = value.children[12]).''')
+ child = property(get_child_access_object, None, doc='''A read only property that returns an object that can access children of a variable by index or by name.''')
name = property(GetName, None, doc='''A read only property that returns the name of this value as a string.''')
type = property(GetType, None, doc='''A read only property that returns a lldb.SBType object that represents the type for this value.''')
size = property(GetByteSize, None, doc='''A read only property that returns the size in bytes of this value.''')
diff --git a/lldb/test/API/python_api/value/TestValueAPI.py b/lldb/test/API/python_api/value/TestValueAPI.py
index 512100912d6fe7..eb6dbfe6362a43 100644
--- a/lldb/test/API/python_api/value/TestValueAPI.py
+++ b/lldb/test/API/python_api/value/TestValueAPI.py
@@ -140,10 +140,8 @@ def test(self):
val_i = target.EvaluateExpression("i")
val_s = target.EvaluateExpression("s")
val_a = target.EvaluateExpression("a")
- self.assertTrue(
- val_s.GetChildMemberWithName("a").GetAddress().IsValid(), VALID_VARIABLE
- )
- self.assertTrue(val_s.GetChildMemberWithName("a").AddressOf(), VALID_VARIABLE)
+ self.assertTrue(val_s.child["a"].GetAddress().IsValid(), VALID_VARIABLE)
+ self.assertTrue(val_s.child["a"].AddressOf(), VALID_VARIABLE)
self.assertTrue(val_a.Cast(val_i.GetType()).AddressOf(), VALID_VARIABLE)
# Test some other cases of the Cast API. We allow casts from one struct type
@@ -210,7 +208,7 @@ def test(self):
weird_cast = f_var.Cast(val_s.GetType())
self.assertSuccess(weird_cast.GetError(), "Can cast from a larger to a smaller")
self.assertEqual(
- weird_cast.GetChildMemberWithName("a").GetValueAsSigned(0),
+ weird_cast.child["a"].GetValueAsSigned(0),
33,
"Got the right value",
)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! LGTM!
I wonder if "member" might be a better name than "child". The base classes of an SBValue representing a C++ object are also children of the value, but you won't be able to look them up in this array. |
@jimingham are you proposing a new property named |
or, should we allow base classes to be looked up by name as well? |
I'd prefer the latter so they can used interchangeably. I like the fact that the subscript can be either an int or a string. |
I updated the PR to support look up of base class children by name. |
Unfortunately, this is legal C++:
So the lookup by name of Foo wouldn't be unique here. |
I think you have to do lookup by name just of the members (and maybe a separate lookup by name of the base classes?) |
The other way to do it would be to use "::Foo" to refer to the base class, and "Foo" the member. Maybe even nicer would be to allow just "Foo" if there's no ambiguity, and only require the :: if there is. |
If we ever come across a language that allows numbers for ivar names, overloading |
for child in self.sbvalue: | ||
if child.name == key: | ||
return child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a good idea, as it means every negative lookup could end up enumerating all children of the object -- and there could be millions of them. If you really want to support looking up base classes (*), I think it should be done by iterating through the base base classes.
(*) I actually think we shouldn't be providing extra functionality here -- at least unless GetChildMemberWithName supports that as well. I think the fact that operator[](string)
is shorthand for GetChildMemberWithName(string)
makes things easy to understand. I think it'd be confusing if one of them provided functionality which is not available in the other one. You'd also have to be very careful about handling data formatters -- they can provide synthetic children, but not "synthetic base classes".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be done by iterating through the base base classes.
Is there even an API for this? Maybe it'd have to be derived, something like:
def base_classes(self):
for child in self:
if self.GetChildMemberWithName(child.name):
# child is a member, stop iteration.
return
yield child
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your opinions. I think I'd like to avoid introducing complexities in interface/consistency.
Should I change this PR to introduce a new member
property? Or should value.child[name]
be documented as meaning only value.GetChildMemberWithName(name)
? cc @jimingham
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have a class with a base class, SBValue::GetChildAtIndex(0)
and var.child[0]
return the base class. So the "child" accessor currently returns both base classes and ivars. Moreover, GetChildMemberWithName actually looks into base classes to pull out ivars (it doesn't however handle duplicate names, it only returns the first one it finds with the matching name which isn't great...)
So GetChildMemberWithName will pull out children that aren't even in the indexed child
array.
That does argue to me that child
is the wrong property to use for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be nice to have both "members" and "bases" so you could easily pull out base class values by name as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll introduce a member
property. I haven't need to access a child base class, by name or otherwise. I'll leave a bases
property for another time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: Base classes will only be returned by GetChildAtIndex(N)
if they have something to show the user, i.e. they have instance variables or have base classes that have instance variables. If a base class has no ivars, it will not be returned. So if we are looking for a reliable way to get base classes, it is better to use the exsting APIs on lldb.SBType
.
The other thing is, do we want to add a SBValue.__get_item__(self, arg)
method to easily access base classes or members where we just return the value of SBValue.GetChildAtIndex(...)
? Then we can do:
var = lldb.frame.FindVariable('my_struct')
first_child = var[0]
Instead of:
first_child = None
if (len(value.bases) > 0)
first_child = value.bases[0]
else:
first_child = value.members[0]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the PR to leave child
alone, and introduced member
. Does this work for everyone? It doesn't provide any means for accessing base classes by name, but that was not my primary intention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine by me, though at some point it would be convenient (and symmetric) to come back and add base class accessors. My only objection to the original patch was that using the child
property for member access was confusing. member
is clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there even an API for this?
I don't think so, but that was sort of my point. If that's what we want to do, then we should have an API for that -- instead of trying to work around its absence in python.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
def __getitem__(self, key): | ||
if isinstance(key, str): | ||
return self.valobj.GetChildMemberWithName(key) | ||
raise TypeError("invalid subscript key") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to be more clear here and say something other than "invalid subscript key"
? Do we want to say "member key values must be string values that specify the name of a member variable to search for"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to member key must be a string
. Since the error is only raised when the key is not a string (a dynamic type check), I left out the semantic part of your suggested message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this.
Introduces a
member
property toSBValue
. This property provides pythonic access to a value's members, by name. The expressionvalue.member["name"]
will be an alternate form form of writingvalue.GetChildMemberWithName("name")
.