Skip to content
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] Puzzling interaction between type summary and synthetic children provider #92450

Closed
wolfpld opened this issue May 16, 2024 · 4 comments
Closed
Labels
lldb question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!

Comments

@wolfpld
Copy link

wolfpld commented May 16, 2024

I'm trying to add pretty printing to some types in my code. Let's say I have the following program:

struct A { int a; };

int main()
{
    A a = { 2 };
    int b = 0;
}

I also have the following python script:

import lldb

def Summary(value, dict):
    return f"Value: {value.GetChildMemberWithName('a').GetValueAsUnsigned()}"

class Printer:
    def __init__(self, val, dict):
        self.val = val

    def update(self):
        self.a = self.val.GetChildMemberWithName('a').GetValueAsUnsigned()

    def num_children(self):
        return 1

    def get_child_index(self, name):
        return int(name.lstrip('[').rstrip(']'))

    def get_child_at_index(self, index):
        return self.val.CreateValueFromExpression(f'child', f'int v={self.a}; v')

def __lldb_init_module(debugger, dict):
    debugger.HandleCommand('type summary add -F pp.Summary -x A')
    debugger.HandleCommand('type synthetic add -l pp.Printer -x A')

If only the type summary is enabled, I get the following result, as expected:

(lldb) command script import pp.py
(lldb) b a.cpp:6
Breakpoint 1: where = a.out`main + 11 at a.cpp:6:9, address = 0x0000000000001124
(lldb) r
Process 496203 launched: '/home/wolf/test/a.out' (x86_64)
Process 496203 stopped
* thread #1, name = 'a.out', stop reason = breakpoint 1.1
    frame #0: 0x0000555555555124 a.out`main at a.cpp:6:9
   3    int main()
   4    {
   5        A a = { 2 };
-> 6        int b = 0;
   7    }
(lldb) p a
(A) Value: 2
(lldb)

If only the synthetic provider is enabled, I get the following result, as expected:

(lldb) p a
(A)  (child = 2)

But if both type handlers are enabled, the value passed to the summary provider is no longer valid:

(lldb) p a
(A) Value: 0

I don't understand what's happening here. I would expect both summary and synthetic providers to provide valid output even if both are enabled. The summary provider might print out useful data, such as vector's capacity, that's not really related to any of the children that may be synthesized.

The following example suggests that summary and synthetic providers are supposed to get along together:

def StdOptionalSummaryProvider(valobj, dict):
has_value = valobj.GetNumChildren() > 0
# We add wrapping spaces for consistency with the libcxx formatter
return " Has Value=" + ("true" if has_value else "false") + " "
class StdOptionalSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
def update(self):
try:
self.payload = self.valobj.GetChildMemberWithName("_M_payload")
self.value = self.payload.GetChildMemberWithName("_M_payload")
self.has_value = (
self.payload.GetChildMemberWithName("_M_engaged").GetValueAsUnsigned(0)
!= 0
)
except:
self.has_value = False
return False
def num_children(self):
return 1 if self.has_value else 0
def get_child_index(self, name):
return 0
def get_child_at_index(self, index):
# some versions of libstdcpp have an additional _M_value child with the actual value
possible_value = self.value.GetChildMemberWithName("_M_value")
if possible_value.IsValid():
return possible_value.Clone("Value")
return self.value.Clone("Value")

The following example shows that a summary provider should be able to use synthetic provider facilities to access the data:

def stdvector_SummaryProvider(valobj, dict):
prov = stdvector_SynthProvider(valobj, None)
return "size=" + str(prov.num_children())

I have tested this with lldb 17.0.6 and 19.0.0git (https://github.com/llvm/llvm-project.git revision 9a7f54b).

@llvmbot
Copy link
Collaborator

llvmbot commented May 16, 2024

@llvm/issue-subscribers-lldb

Author: Bartosz Taudul (wolfpld)

I'm trying to add pretty printing to some types in my code. Let's say I have the following program:
struct A { int a; };

int main()
{
    A a = { 2 };
    int b = 0;
}

I also have the following python script:

import lldb

def Summary(value, dict):
    return f"Value: {value.GetChildMemberWithName('a').GetValueAsUnsigned()}"

class Printer:
    def __init__(self, val, dict):
        self.val = val

    def update(self):
        self.a = self.val.GetChildMemberWithName('a').GetValueAsUnsigned()

    def num_children(self):
        return 1

    def get_child_index(self, name):
        return int(name.lstrip('[').rstrip(']'))

    def get_child_at_index(self, index):
        return self.val.CreateValueFromExpression(f'child', f'int v={self.a}; v')

def __lldb_init_module(debugger, dict):
    debugger.HandleCommand('type summary add -F pp.Summary -x A')
    debugger.HandleCommand('type synthetic add -l pp.Printer -x A')

If only the type summary is enabled, I get the following result, as expected:

(lldb) command script import pp.py
(lldb) b a.cpp:6
Breakpoint 1: where = a.out`main + 11 at a.cpp:6:9, address = 0x0000000000001124
(lldb) r
Process 496203 launched: '/home/wolf/test/a.out' (x86_64)
Process 496203 stopped
* thread #<!-- -->1, name = 'a.out', stop reason = breakpoint 1.1
    frame #<!-- -->0: 0x0000555555555124 a.out`main at a.cpp:6:9
   3    int main()
   4    {
   5        A a = { 2 };
-&gt; 6        int b = 0;
   7    }
(lldb) p a
(A) Value: 2
(lldb)

If only the synthetic provider is enabled, I get the following result, as expected:

(lldb) p a
(A)  (child = 2)

But if both type handlers are enabled, the value passed to the summary provider is no longer valid:

(lldb) p a
(A) Value: 0

I don't understand what's happening here. I would expect both summary and synthetic providers to provide valid output even if both are enabled. The summary provider might print out useful data, such as vector's capacity, that's not really related to any of the children that may be synthesized.

The following example suggests that summary and synthetic providers are supposed to get along together:

def StdOptionalSummaryProvider(valobj, dict):
has_value = valobj.GetNumChildren() > 0
# We add wrapping spaces for consistency with the libcxx formatter
return " Has Value=" + ("true" if has_value else "false") + " "
class StdOptionalSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
def update(self):
try:
self.payload = self.valobj.GetChildMemberWithName("_M_payload")
self.value = self.payload.GetChildMemberWithName("_M_payload")
self.has_value = (
self.payload.GetChildMemberWithName("_M_engaged").GetValueAsUnsigned(0)
!= 0
)
except:
self.has_value = False
return False
def num_children(self):
return 1 if self.has_value else 0
def get_child_index(self, name):
return 0
def get_child_at_index(self, index):
# some versions of libstdcpp have an additional _M_value child with the actual value
possible_value = self.value.GetChildMemberWithName("_M_value")
if possible_value.IsValid():
return possible_value.Clone("Value")
return self.value.Clone("Value")

The following example shows that a summary provider should be able to use synthetic provider facilities to access the data:

def stdvector_SummaryProvider(valobj, dict):
prov = stdvector_SynthProvider(valobj, None)
return "size=" + str(prov.num_children())

I have tested this with lldb 17.0.6 and 19.0.0git (https://github.com/llvm/llvm-project.git revision 9a7f54b).

@jimingham
Copy link
Collaborator

jimingham commented May 21, 2024

This is by design. When an type has a synthetic child provider, and you ask "GetChildMemberWithName" of a SBValue of that type, it will return the Synthetic Child (or if no Synthetic child of that name exists it will return a "child not found" error.) That's because we don't want the clients of the API (e.g. Local variables windows in GUI's) to have to make decisions about whether to use synthetic or raw children on a case by case basis. They just set "use_synthetic" and then they can query the SBValue for children, and get the right version automatically.

If you know in some code that you are writing that you are going to be asking for the raw children, you can dial them up explicitly using the SBValue.GetNonSyntheticValue() and then asking for that SBValue's children.

@jimingham
Copy link
Collaborator

So you should be able to fix your issue with:

def Summary(value, dict):
return f"Value: {value.GetNonSyntheticValue().GetChildMemberWithName('a').GetValueAsUnsigned()}"

@wolfpld
Copy link
Author

wolfpld commented May 21, 2024

Thanks. This does indeed fix the issue.

obraz

@wolfpld wolfpld closed this as completed May 21, 2024
@EugeneZelenko EugeneZelenko added the question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead! label May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lldb question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!
Projects
None yet
Development

No branches or pull requests

4 participants