Problem formatting class types

Hello,

I’ve got this code:

class Class {
int x = 0;
short y = 1;
char z = ‘z’;
} C;

int main(int argc, char **argv) {
__debugbreak();
return 0;
}

and I run the following LLDB session:

lldb.exe -f foo.exe
(lldb) target create “foo.exe”

Current executable set to ‘foo.exe’ (x86_64).
(lldb) run
Process 24604 launched: ‘foo.exe’ (x86_64)
Process 24604 stopped

  • thread #1, stop reason = Exception 0x80000003 encountered at address 0x7ff70a0b1017
    frame #0: 0x00007ff70a0b1018 foo.exe`main(argc=-1123614720, argv=0x00007ff70a0b1000) at foo.cpp:19
    16
    17 int main(int argc, char **argv) {
    18 __debugbreak();
    → 19 return 0;
    20 }
    (lldb) p C
    (Class) $0 =
    (lldb)

The issue is, of course, that it doesn’t display the members of the class C. The type support in PDB is fine, so it’s not that. For example:

(lldb) type lookup Class
class Class {
int x;
short y;
char z;
}

And it can definitely find C in memory:

(lldb) p &C

(Class *) $1 = 0x00007ff70a0b3000

Instead, the issue seems to be related to the value object formatter. I tried to track this down but this code is pretty complicated. However, there are two issues that I was able to discover:

  1. It’s using the objective C class formatter. Obviously I’m not using objective C, so that seems wrong right off the bat. Specifically, the “Synthetic children front end” is the ObjCClassSyntheticChildrenFrontEnd.

  2. Because of #1, when it calls CalculateNumChildren() in Cocoa.cpp, it returns 0. I would expect it to be calling some function somewhere that returns 3, because there are 3 members of the class.

What’s strange is that I don’t see anything in the CPlusPlusLanguage plugin that provides a SyntheticChildrenFrontEnd that examines the CxxRecordDecl and looks for children, so I don’t know how this is supposed to work anywhere. But I know it must work somewhere, so I assume I’m just missing something and I need to find out the right place to hook A up to B and things will just work.

Any pointers on what the expected code path that this should be taking is, so I can try to figure out where I might be going off path?

Note that I also tried this with a a linux / DWARF executable and had the same result.

The data formatters don't currently have a specified language. We should maybe add that and then we could only load formatters for a language when its runtime gets loaded. Then they could also know to turn themselves on and off based on the language of the current frame, or of the current expression. We have to be careful, because UIKit and AppKit have a bunch of interesting globals that show the state of the UI, and it very common for folks to just interrupt the debugger anywhere and run ObjC expressions (even though the frame language is not ObjC) and expect them and the associated formatters to work. Breaking that expectation would be pretty disruptive.

But at the minimum, not loading formatters for a language that we can determine isn't used in this program seems like something we should try to avoid.

You can work around this in the short term by doing either:

(lldb) type category disable objc
(lldb) expr myClass
(Class) $5 = {
  x = 0
  y = 1
  z = 'z'
}

Or if you don't want to change global settings, use:

(lldb) expr --raw -- myClass
(Class) $3 = {
  x = 0
  y = 1
  z = 'z'
}

Jim

Remove the "not"...

Jim

So, the second command works, but the first one doesn’t. It doesn’t give any error, but on the other hand, it doesn’t change the results of printing the variable. When I run type category list though, I get this:

(lldb) type category list
Category: default (enabled)
Category: VectorTypes (enabled, applicable for language(s): objective-c++)
Category: system (enabled, applicable for language(s): objective-c++)

So it looks like the behavior I’m seeing is already with the default category. Does this sound right? Which code path is supposed to get executed to format it as a C++ class?

Most C++ classes and C structs don't have data formatters, particularly not classes that you write yourself.

The way value printing works in lldb is that we start by making the ValueObject for the value from its Type, so at that stage it is just a direct view of the members of the object. That is done without help of the data formatters, reading instead directly from the object's type. Then we consult our type match -> summary/synthetic children registries and we construct a summary or a set of "synthetic children" (or both) for the object if we find any matches there. Then the ValueObjectPrinter prints the object using the Type based ValueObject, the Summary and the Synthetic Children, and there's a print options object that says whether to use the raw view, the summary and/or the synthetic children.

But for a type lldb knows nothing about, there won't be any entries in the formatter maps, so you should just see the direct Type based children in that case.

--raw sets the right options in the print option object to get the printer to just use the strict Type based view of the object, with no formatters applied.

In your case, you used "Class" as your type name and Class is a special name in ObjC and there happens to be a formatter for that. You can always figure out what formatters apply to the result of an expression with the "type {summary/synthetic} info" command. For your example, I see (my variable of type Class was called myClass):

(lldb) type summary info myClass
summary applied to (Class) myClass is: (not cascading) (hide value) (skip pointers) (skip references) Class summary provider
(lldb) type synthetic info myClass
synthetic applied to (Class) myClass is: Class synthetic children

On macOS those summary/synthetic child providers are in the objc category. The info output should really print the category as well, that would be helpful. But you can do "type summary list" and then find the summary in that list and go from there to the category. Ditto for "type synthetic".

What do you get from that?

Jim

Ok that was it, it was because my type was called Class. Oops!