Debugging Understand with Understand

Abstract: Tracking down an issue with nested typedef structs in the Data Members graph using Understand.

Today’s bug report: Nested typedefs not displayed beyond the first level in the “Data Members (Simplified Variant)” graph. The sample code:

#include <iostream>

typedef struct
{
  int day,
      month,
      year;
} Date;

typedef struct
{
  Date menuDate,
       purDate;
  double purPrice;
} Car;

And a picture with the problem:

The Data Members graph for the sample code doesn’t allow expanding menuDate and purDate in the simplified graph variant (shown on the right).

Since I maintain the graph code, I have a head start on this bug. I know that both the simplified variant and the classic variant use the same configuration to determine the content of the graph. The main difference between the two styles is the layout engine used. Since the classic variant is working correctly, the problem is likely with how the simplified variant is using the configuration. 

Tree-like graphs in Understand tend to be based on a reference kind string. For instance, a basic calls graph is something like this:

QList<Udb::Entity*> curLevel = {startEntity};
QList<Udb::Entity*> nextLevel;
for (int i = 0; i < NUM_LEVELS; i++) {
  foreach (Udb::Entity* ent, curLevel) {
    foreach (Udb::Reference ref, ent->refs("call")) {
      // add edge from ent to ref.entity()
      nextLevel.append(ref.entity());
    }
  }
  curLevel = nextLevel;
  nextLevel.clear();
}

For the data members graph, instead of the reference kind string “call”, use the reference kind string “define, declare, type”. So, for the sample code, Car defines menuDate, purDate, and purPrice. They’re visible as members in the Information Browser:

The Information Browser entry for Car

Here’s where the Information Browser can be misleading. The Information Browser has a layer of logic that interprets the information in the database. So, the information browser shows how a programmer typically understands the code whereas the database stores how a compiler would understand the code. Let’s look at the interactive report “API Info” to see how Car is represented in the database.

The Interactive Report “API Info” for Car

The database has only two references for Car, and neither is a “define,declare,type” reference. “Typed” and “Definein” are the reverse direction. So, how did the information browser come up with three members? The information browser automatically follows the “typed” reference and uses those references too. Here’s the API Info for the referenced entity Car.

The API Information for the struct “Car”

Notice that the kind of this second “Car” is now a “struct” instead of a “typedef.” For the database, the struct and the typedef are two different entities. This same technicality has tripped me up before. Last time it was also a graph (the UML Class Diagram). These kinds of language-specific problems aren’t uncommon. The graph configuration has properties for the general situation of getting references from another entity. The property name is “skip relation.” I didn’t name it, but the idea is you skip the referenced relation (don’t display it as an edge) and inherit the references from the referenced entity. One of the skip relations for the Data Members graph is the “typed” relation. That’s why the initial view of the graph shows the members even though Car is the typedef, not the struct.

So if it worked the first time, why didn’t it work for the Dates menuDate and purDate? 

Understand makes it easy to find how the property is used. I’ll use my favorite setup: the Information Browser and Previewer. I’m starting with the code for the simplified graph variant since I’m more familiar with it. The property I’m interested in is “PROP_SKIPRELATION”.

I need to figure out what the classic variant is doing with the property. The Data Members graph is a tree graph, so tree.cpp is the most promising reference to follow. Clicking it in the information browser updates the previewer:

The value is copied into tdiag->def_skiprelations. So I need to see where that variable is used. Ctrl+Click loads it in the information browser and previewer:

I have references sorted by kind so I can skip down to just the “Use” references since I want to see how the value is used. Abort and Load aren’t likely to be related. Expand Node Relations seems promising. Clicking the first reference in that function:

I have to do a little more reference following to determine that tree_nodeExpandSkip returns true if any entities were added to the list. But the key is already visible. In the classic layout, the skip relation is applied recursively. The simplified version only applies the skip relation once:

      // Skip relations define entities that should be treated as if references
      // from them are references from the start node.
      if (!skipRefs.isEmpty()) {
        foreach (Udb::Reference ref, ent->refs(skipRefs, skipEnts, true))
          start.insert(ref.entity());
      }
      if (!skipRefs2.isEmpty()) {
        foreach (Udb::Reference ref, ent->refs(skipRefs2, skipEnts2, true))
          start.insert(ref.entity());
      }

      // Now expand references directly on the node
      foreach (Udb::Entity * entity, start) {
        allrefs << entity->refs(refs, ents, unique);
      }

So, when the classic variant encounters the member menuDate, it follows menuDate’s typed relation to the typedef Date and then follows the typedef Date’s typed relation to the struct Date. The simplified version stopped at the typedef and therefore didn’t find the edges out. Using a while loop for the simplified variant fixes the problem.

The fixed simplified graph variant

Look forward to the fix in the next build, 1089.

Learn more about the Science of Debugging.