Skip to content

CppDepend: A C++ Dependency Analyser

I evaluated the dependency analyser CppDepend on a real-life embedded application. My goal was to find all dependency cycles between classes in the application. I know that the application contains 50+ dependency cycles. CppDepend only found less than 10% of the cycles. Without rewriting the the source code, CppDepend would have found no cycles at all . My conclusion: I won’t use CppDepend.

My Goal

I am currently working on an embedded Qt/C++ application with over 300 classes grouped into over 30 namespaces. The namespaces and classes are tightly coupled with little cohesion. Namespaces and classes have cyclic dependencies. The abstraction level of classes is low, interfaces hardly exist. Changes take much more time than they should and break the application in unexpected places. The code structure is a mess and in dire need of improvement.

In other words, the application is the ideal test case for evaluating a dependency analyser. My evaluation goal is to find all dependency cycles between classes. The next steps would be to eliminate the cycles, to re-organise the namespaces and to turn the namespaces into mostly independent libraries.

I used CppDepend for the dependency analysis. Here is my evaluation report.

First Try: ????

I installed CppDepend on my development PC and started it. The texts in the UI were so tiny on my 4K monitor that I couldn’t read them from 5cm. CppDepend doesn’t support high-DPI monitors. It should because working with big dependency graphs is so much easier on a 4K monitor. Much less zooming and scrolling needed!

As I didn’t want to change my development setup (QtCreator in full-screen mode with two editor windows and one sidebar), I changed to a laptop. I still had to reduce the resolution from 2.5K to full HD in the Linux VM.

I created my first CppDepend project and loaded my project’s top-level CMakeLists.txt file. CppDepend responded with the error message that this is an invalid CMake project. No hint what to do! Nothing!

At that point, I was close to giving up on the evaluation. It was a very bad start. Two days later I gave CppDepend a second chance.

Second Try: ????

Setup

We must create a JSON compilation database for a CMake project, before it makes sense to start CppDepend. We add the option -DCMAKE_EXPORT_COMPILE_COMMANDS=ON to our normal CMake call and keep all the other options and arguments like the <project-options> and <path-to-source> from the normal CMake call.

$ mkdir build-cppdepend
$ cd build-cppdepend
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON <project-options> <path-to-source>

The extra option makes CMake write a JSON compilation database compile_commands.json to the build directory. The database contains an entry for each compiler call with the current build directory, the compiler call with all options and arguments and the source file.

We start CppDepend with

$ /path/to/CppDepend/VisualCppDepend.sh &

On the dialog Welcome to CppDepend, we click on the button Create a New CppDepend Project. We enter a proper project name (e.g., MyApp) and selected a project directory (e.g., /path/to/build-cppdepend), where CppDepend can store all its generated files.

After we click OK in the welcome dialog, CppDepend shows its main screen. We click on Add CMake project and select compile_command.json in the file dialog. We click the Save Project tab button to save my work so far.

Analysing the Code

We press the Play button in the top-left corner of the main screen to run the code analysis. CppDepend shows the source files parsed in the Linux terminal, from which we started it. It shows the results of the code analysis both in the application and in the web browser. The results for my project are shown on the Dashboard and look as follows.

Many Missing Classes

I had the strange feeling that the 241 types (classes, structs) were too low. In QtCreator, I searched for all class declarations ^class [^ ]+ in the code base and for all forward declarations ^class [^ ;]+;. Subtracting the forward declarations from all declarations and adding some structs resulted in more than 300 types.

Something was wrong here! So, I checked the tab Code | Class Browser | MyApp | Sources in CppDepend. This tree view groups the source code into namespaces, which contain the classes. I checked some namespaces and compared CppDepend’s results with QtCreator’s. For one namespace, CppDepend showed only 2 of the more than 20 classes. For another namespace, it showed 5 of 15 classes. Other namespaces missed out on many classes as well.

Ignoring so many classes explains the low type count. It also explains why many classes never show up in the reports and graphs. The results of CppDepend are incomplete. Performing dependency analysis on an incomplete code base wouldn’t make any sense. So, I looked for another way to make the complete source base known to CppDepend. I gave CppDepend a third chance.

Third Try: ☹️

Setup

CppDepend offers a fallback solution to define a project: ProjectMaker. We create a working directory work-cppdepend for the CppDepend project and start CppDepend.

$ mkdir work-cppdepend
$ cd work-cppdepend
$ /path/to/CppDepend/VisualCppDepend.sh &

On CppDepend’s welcome dialog, we select Create a new CppDepend project. We enter the project name (e.g., MyApp) and the project directory (e.g., /home/burkhard/Projects/work-cppdepend). We keep C/C++ as the Language and Logical as the Structure.

Before we can add a ProjectMaker spec, we must define it. Actually, we define two specs: one for the Qt libraries and one for our application. We open the dialog Tools | ProjectMaker from the main menu. We right-click on Solution and select Add Project Specification.

We need a compiler call to fill out the include paths and defines in the project specification dialog. A typical compiler call may look like this.

/usr/bin/g++
    -DQT_CORE_LIB -DQT_GUI_LIB [...]
    -DDEV_HACKS -DVERSION_MAJOR=4 [...]
    -I/path/to/build-myapp/app/src -I/path/to/myapp/app/src 
    -I/path/to/build-myapp/app/src/myapp_autogen/include
    -isystem /path/to/Qt/include -isystem /path/to/Qt/include/QtGui
    -isystem /path/to/Qt/include/QtCore [...]
    -o rel/path/to/ppp/pde.cpp.o 
    /path/to/myapp/app/src/ppp/pde.cpp

We first add the project specification for the Qt libraries. We enter Qt as the Name. We extract the include paths for Qt from the compiler call, remove the option -isystem and separate the paths by semicolons. We enter the result similar to

/path/to/Qt/include;/path/to/Qt/include/QtGui;/path/to/Qt/include/QtCore;...

in the field Include Paths Separated by ;. We prepare the Qt specific defines in a similar way and enter something similar to

QT_CORE_LIB;QT_GUI_LIB;...

in the field Defines Separated by;. We finally tick the box for Third Party and close the dialog with OK. This brings us back to the ProjectMaker dialog, which we leave open.

We then add the project specification for our application. Again, we right-click on Solution and select Add Project Specification. The Name of the project is MyApp. The application-specific include paths are the ones with option -I in the compiler call. We ignore the the two paths starting with /path/to/build-myapp, because they contain generated code (e.g., generated by moc or uic). We enter

/path/to/myapp/app/src

for the Include Paths. Preprocessing the application-specific Defines yields

DEV_HACKS;VERSION_MAJOR=4;...

We do not tick the box for Third Party this time. The filled-out dialog looks as follows.

The ProjectMaker dialog shows the two projects Qt and MyApp.

We save our work in /path/to/work-cppdepend/myapp.sl by pressing the Save button. We right-click on MyApp and select Add Existing Source Directory. We navigate to the base source directory of our project (e.g., /path/to/myapp/app/src) and close the file dialog with OK. The MyApp folder contains a folder src, which contains the subfolders of /path/to/myapp/app/src with all the source and header files. We save the project specification another time and close the ProjectMaker dialog.

Back to CppDepend’s main screen, we click the button Add ProjectMaker spec and select the project specification /path/to/work-cppdepend/myapp.sl. The file is listed in the Project table.

We press the Play button in the top-left corner of the main screen to start the code analysis. CppDepend prints the parsed files in the terminal, from which we started it. The Dashboard with the result looks like this.

The 315 types look a lot better than in the second try. A quick look through Code | Class Browser confirms that the classes missing in the second try are present now. This is a good basis for starting a detailed dependency analysis.

The line count looks odd. Although CppDepend finds 74 more types (including some big classes) than in the first run, it finds 173 lines of code less.

Dependency Analysis with Predefined Queries

When I switch from the Dashboard tab to the Graph tab, CppDepends shows the top-level view of the dependency graph. The MyApp box contains all the application classes and the Externals box contains the elements from the C and C++ standard libraries. The Project box is empty. I guess that it hides the Qt libraries.

When I right-click on MyApp and select View internal dependency cycles on graph from the context menu, CppDepend shows the dependencies between the 32 namespaces. I blackened the names for confidentiality reasons. We can view the dependencies between the classes of a namespace by selecting View internal dependency cycles on graph on a namespace.

The red lines with arrows on both sides highlight a dependency cycle between two namespaces. This is wrong! There must not be any dependency cycles between namespaces in a program. These cycles prevent us from turning the namespaces into independent libraries.

As the cycles are between classes from different namespaces, looking at the class dependencies inside one namespace won’t help. Let us start from a class MyApplication that is known to be part of several dependency cycles. MyApplication is a subclass of QApplication and is contained in the global namespace.

We select the action View internal dependencies on graph for GlobalNamespace(MyApp) and select MyApplication in the resulting graph. From the nested context menus, we select Select Types … | … that I use (directly or indirectly). CppDepends shows the query for this action in the upper half of the left-hand column (the query pane) and the types used by MyApplication directly or indirectly in the lower half (the result pane). When we select Export Graph from the combo box in the toolbar of the result pane, CppDepend shows the result as a graph.

The graph doesn’t contain any cycles ☹️ A closer analysis reveals that the graph doesn’t contain any links from MyApplication (the box on the left) to classes that are constructed in MyApplication in a special way.

    auto x = new X();
    _sceneFactory->addScene(SceneFactory::SCENE_X, x);

The code snippet is from a function of MyApplication. So, x is a local variable. _sceneFactory stores the pointer to x in a map for later use.

CppDepend doesn’t ignore constructor calls in general. For example, it picks up this call in the initialiser list of the MyApplication constructor. The pointer to Y is assigned to a member variable.

_y(new Y()),

When we rewrite the creation for X in the way of Y, CppDepend finds the dependency of MyApplication on Y. Of course, we must re-run the analysis by pressing Play on the main screen. Still no cycles ????

There should be a cycle: MyApplication -> X -> MyApplication. It took me some time to figure out that CppDepend doesn’t show this cycle, because MyApplication is used by X through the macro myApp. By the way, myApp is defined in the same way as the standard Qt macro qApp.

// In MyApplication.h
#define myApp (static_cast<MyApplication *>(QCoreApplication::instance()))

If we replace all occurrences of myApp by the static function MyApplication::instance(), CppDepend starts picking up the dependency cycle: MyApplication -> X -> MyApplication.

The program contains several more cycles of the form MyApplication -> X -> Z -> MyApplication, where Z is a dependency path of one or more classes. CppDepend doesn’t even find the dependency chain MyApplication -> X -> Z, although Z is created in the same way as Y in X‘s constructor. I don’t know how to rewrite the code.

Rewriting code to make CppDepend work correctly is not a good use of our time as users. At least we know that CppDepend can detect dependency cycles in principle. The CppDepend development team has some homework to do.

Dependency Analysis with Custom Queries

The one thing that kept me going so far with the CppDepend evaluation is the possibility to write my own queries and display them as graphs. CppDepend uses a query language called CQLinq, which has a close resemblance to SQL. We find a description of the CQLinq syntax here and of CQLinq features here. Note that the documentation of the NDepend website is better than the one on the CppDepend website.

We find the reference manual listing of all the functions provided by CQLinq here. CQLinq is written in C#. A basic knowledge of C# helps to understand the documentation. CQLinq is derived from LINQ created by Microsoft. Hence, general features of the query language can be found in the LINQ documentation.

We want to write a query that finds all cycles containing the class MyApplication. We define a set outgoing containing all classes that can be reached from MyApplication. We define another set incoming containing all classes from which MyApplication can be reached. All classes in the intersection of outgoing and incoming are contained in cycles going through MyApplication. Here is the corresponding CQLinq query.

// <Name>Find cycles through specific class (variant 1)</Name>
let outgoing = 
    from t in Types
    where t.DepthOfIsUsedBy("MyApplication") >= 0
    select t

let incoming = 
    from t in Types
    where t.DepthOfIsUsing("MyApplication") >= 0
    select t

from t in outgoing.Intersect(incoming)
select new { t }

The resulting graph shows only the cycles containing MyApplication and three other classes.

We can rewrite the above query in a more concise notation passing a lambda function to the Where clause.

// <Name>Find cycles through specific class (variant 2)</Name>
let outgoing = Types.Where(t => t.DepthOfIsUsedBy("STouchApplication") >= 0)
let incoming = Types.Where(t => t.DepthOfIsUsing("STouchApplication") >= 0)
from t in outgoing.Intersect(incoming)
select new { t }

The next step would be to iterate over all classes in the application to find all cycles. This fails because the functions DepthOfIsUsedBy and DepthOfIsUsing take only string literals as arguments. We cannot pass s.Name for a type s.

The general solution to find all class-level cycles in the application looks quite different to the solution for a given class above.

// <Name>Find all class-level cycles in application</Name>
from t in Application.Types
let incoming = t.TypesUsingMe.FillIterative
    (ts => ts.SelectMany(t1 => t1.TypesUsingMe)).DefinitionDomain
let outgoing = t.TypesUsed.FillIterative
    (ts => ts.SelectMany(t1 => t1.TypesUsed)).DefinitionDomain
where incoming.Intersect(outgoing).Contains(t)
select t

Let’s dissect the definition of incoming. The function FillIterative starts iterating with an initial set of types, t.TypesUsingMe. This initial set ts1 contains all types from which type t can be reached in one step. The first iteration computes the set ts2 of all types from which a type in ts1 can be reached in one step and from which t can be reached in two steps. The computation for an iteration is done by the lambda function ts => ts.SelectMany(t1 => t1.TypesUsingMe). FillIterative iterates until no new types are added.

FillIterative returns a metric object, which is a hash map from types to depths. The depth of a type is its distance from t. If we removed .DefinitionDomain, we could access the depth of a type s by incoming[s]. The property DefinitionDomain returns the keys of the hash map (the types).

The type set outgoing is calculated similarly to incoming. If t is contained in the intersection of incoming and outgoing, we found a cycle. The result for this generalised query is the same as for the simple query. The application contains 50+ cycles. So, CppDepend found less than 10% of the cycles – probably because it doesn’t parse the source files correctly.

Figuring out the query took me well over a working day and several emails with CppDepend support. CppDepend should provide fundamental queries like finding cycles from its GUI. Knowing cycles in applications can save us a lot of time and tell us where to start our restructuring work. Of course, the query should find all cycles and not just a tiny fraction of them.

Summary

I missed my evaluation goal of finding all class-level dependency cycles in the application. I was able to write a query for finding all dependency cycles between classes in my application. CppDepend found less than 10% of the known cycles, probably because its internal database is incomplete.

I invested three days into the evaluation of CppDepend. Most of my time went into working around basic CppDepend issues. The return of my time investment was meagre. My conclusion is clear: I won’t use CppDepend.

I feel sad about this verdict, because CppDepend has so much potential to be a useful tool. Here is a summary of the problems I encountered.

  • A quarter of the types are missing when the JSON compilation database is created from the CMake call. Setting up the CppDepend project with CMake is so much easier than with ProjectMaker.
  • CppDepend doesn’t find dependencies like the myApp macr, auto x = new X() and also some but not all member initialisations of the form _y(new Y()). I don’t want to rewrite my code to work around CppDepend issues – especially if I don’t know how to rewrite the code.
  • The reference manual for the queries describes the C# functions implementing the CQLinq queries. The CQLinq queries are only described through examples. I don’t want to learn C# and LINQ to work with CppDepend and I don’t want to work out queries by trial and error.
  • CppDepend offers dozens of simple built-in queries. Most of them are provided by good IDEs. Useful built-in queries like computing the call graph for a function don’t work. I’d like CppDepend to focus on half a dozen time-saving queries like computing call graphs, finding dependency cycles or even suggesting groupings of classes into namespaces.
  • CppDepend is often slow in responding, regularly freezes and crashes.
  • Make CppDepend doesn’t work on high-resolution monitors out of the box.

I performed the evaluation on a Linux system (Ubuntu 18.04). CppDepend support told me that the Linux version has many issues the Windows version doesn’t, because the C# implementation on Linux is buggy. As the query language CQLinq uses C#, queries may yield wrong or incomplete results on Linux. Using CppDepend on Windows isn’t an option for me, as I do my embedded software development entirely on Linux.

Leave a Reply

Your email address will not be published. Required fields are marked *