Semantics of SBValue::CreateChildAtOffset

Hello Jim, everyone,

I recently got a question/bug report about python pretty printers (synthetic child providers) that I couldn't answer.

The actual script is of course more complicated, but the essence boils down to this.

There's a class, something like:
struct S {
   ...
   T member;
};

The pretty printer tries to print this type, and it does something like:
      def get_child_at_index(self, index):
          if index == 0:
              child = self.sbvalue.GetChildMemberWithName("child")
              return child.CreateChildAtOffset("[0]", 0, T2)

Now here comes the interesting part. The exact behaviour here depends on the type T. If T (and of course, in the real example this is a template) is plain type, then this behaves a like a bitcast so the synthetic child is essentially *reinterpret_cast<T2*>(&s.member).

*However*, if T is a pointer, then lldb will *dereference* it before performing the cast, giving something like
    *reinterpret_cast<T2*>(s.member) // no &
as a result.

The first question that comes to mind is: Is this behavior intentional or a bug?

At first it seemed like this is too subtle to be a bug, but the more I though about it, the less I was sure about the CreateChildAtOffset function as a whole.

What I mean is, this pretty printer is essentially creating child for a value that it is not printing. That seems like a bad idea in general, although I wasn't able to observe any ill effects (e.g. when I printi s.member directly, I don't see any bonus children). Then I looked at some of the in-tree pretty-printers, and I did find this pattern at least two libc++ printers (libcxx.py:125 and :614), although they don't suffer from this ambiguity, because the values they are printing are always pointers.

However, that means I absolutely don't know what is the expected behavior here:
- Are pretty printers allowed to call CreateChildAtOffset on values they are not printing
- Is CreateChildAtOffset supposed to behave differently for pointer types?

I'd appreciate any insight,
Pavel

Hello Jim, everyone,

I recently got a question/bug report about python pretty printers (synthetic child providers) that I couldn't answer.

The actual script is of course more complicated, but the essence boils down to this.

There's a class, something like:
struct S {
...
T member;
};

The pretty printer tries to print this type, and it does something like:
    def get_child_at_index(self, index):
        if index == 0:
            child = self.sbvalue.GetChildMemberWithName("child")
            return child.CreateChildAtOffset("[0]", 0, T2)

Now here comes the interesting part. The exact behaviour here depends on the type T. If T (and of course, in the real example this is a template) is plain type, then this behaves a like a bitcast so the synthetic child is essentially *reinterpret_cast<T2*>(&s.member).

*However*, if T is a pointer, then lldb will *dereference* it before performing the cast, giving something like
  *reinterpret_cast<T2*>(s.member) // no &
as a result.

The first question that comes to mind is: Is this behavior intentional or a bug?

At first it seemed like this is too subtle to be a bug, but the more I though about it, the less I was sure about the CreateChildAtOffset function as a whole.

What I mean is, this pretty printer is essentially creating child for a value that it is not printing. That seems like a bad idea in general, although I wasn't able to observe any ill effects (e.g. when I printi s.member directly, I don't see any bonus children). Then I looked at some of the in-tree pretty-printers, and I did find this pattern at least two libc++ printers (libcxx.py:125 and :614), although they don't suffer from this ambiguity, because the values they are printing are always pointers.

However, that means I absolutely don't know what is the expected behavior here:
- Are pretty printers allowed to call CreateChildAtOffset on values they are not printing

This should be fine yes AFAIK.

- Is CreateChildAtOffset supposed to behave differently for pointer types?

yes. It all comes down to what a child of a specific type would be. For pointers or references, you do want it to be at an offset from what it is pointing to. If you have an array of bytes, then you want it to be an offset within that array.

I would be really bad to take a pointer and create a child at offset and not follow the pointer by taking the next thing past the pointer. Like if you have:

struct A {
  Foo *ptr1;
  Bar *ptr2;
}

And if we have a "ptr1" in a SBValue, and asked it to CreatChildAtOffset(), we would never really want to access "ptr2" because we could just ask for "ptr2" if we wanted it.