Let’s say we have a large project and we want to find out from which places in our code a particular function is called. You may be wondering why would you want to know? The most common reason is to eliminate dead code; if the function is not called, then it may not be needed anymore. Or maybe you just want to refresh your memory about this area of your code base. The case that triggered this post involved changing one function call to another. I was adding support for composite object ids in ODB and was gradually changing the code to use a more generalized version of a certain function while still maintaining the old version for the code still to be ported. While I knew about most of the areas that needed changing, in the end I needed to verify that nobody was calling the old function and then remove it.
So how do we find out who calls a particular function? The method that I am sure most of you have used before is to comment the function out, recompile, and use the C++ compiler error messages to pin-point the calls. There are a few problems with this approach, however. First of all, depending on your build system, the compilation may stop before showing you all the call sites (make -k
is helpful here but is still not a bulletproof solution). So to make sure that you have seen all the places, you may also have to keep commenting the calls and recompiling until you get no more errors. This is annoying.
This approach will also not work if a call can be resolved to one of the overloaded versions. This was exactly the situation I encountered. I had two functions that looked like this:
class traverser
{
void traverse (type&); // New version.
void traverse (class_&); // Old version.
};
Where class_
derives from type
so if I commented the old version out, the calls were happily resolved to the new version without giving any errors.
Another similar situation is when you have a function in the outer namespace that will be used if you comment a function in the inner namespace:
void f ();
namespace n
{
void f ();
void g ()
{
// Will resolve to outer f() if inner f() is
// commented out.
//
f ();
}
}
What’s worse is that in complex cases involving implicit conversions of arguments, some calls may be successfully resolved to an overloaded or outer version while some will trigger an error. As a result, you may not even realize that you didn’t see all the call sites.
Ok, so that approach didn’t work in my case. What else can we try? Another option is to just comment the definition of the function out and see if we get any unresolved symbol errors during linking. There are many problems with this method as well. First of all, if the function in question is virtual, then this method won’t work because the virtual function table will always contain a reference to the function. Plus, all the calls to this function will go through the vtable
.
If the function is not virtual, then, at best, a linker will tell you that there is an undefined reference in a specific function in a specific translation unit. For example, here is an output from the GNU Binutils ld
:
/tmp/ccXez0jI.o: In function `main':
test.cxx:(.text+0×10): undefined reference to `f()'
test.cxx:(.text+0×15): undefined reference to `f()'
In particular, there will be no line information so if a function calls the function of interest multiple times, we will have no way of knowing which call triggered the undefined symbol.
This approach also won’t work if we are building a shared library (unless we are using the -no-undefined
or equivalent option) because the undefined reference won’t be reported until we link the library to an executable or try to load it (e.g., with dlopen()
). And when that happens all we will get is just a note that there is an undefined reference in a library:
libtest.so: undefined reference to `f()'
In my case, since ODB is implemented as a shared library, all this method did was to confirm that I still had a call to the old version of the function. I, however, had no idea even which file(s) contained these calls.
As it happens, just the day before I was testing ODB with GCC in the C++11 mode. While everything worked fine, I got a few warnings about std::auto_ptr
being deprecated. As I saw them scrolling by, I made an idle note to myself that when compiled in the C++11 mode libstdc++
probably marks auto_ptr
using the GCC deprecated
attribute. A day later this background note went off like a light bulb in my head: I can mark the old version of the function as deprecated and GCC will pin-point with a warning every single place where this function is called:
class traverser
{
void traverse (type&);
void traverse (class_&) __attribute__ ((deprecated));
};
And the diagnostics is:
model.cxx: In function ‘void object_columns::traverse(data_member&)’:
model.cxx:22:9: warning: ‘void traverser::traverse(class_&)’ is
deprecated
This method is also very handy to find out which overloaded version was selected by the compiler without resolving to the runtime test:
void f (bool) __attribute__ ((deprecated));
void f (int) __attribute__ ((deprecated));
void f (double) __attribute__ ((deprecated));
void g ()
{
f (true);
f (123);
f (123.1);
}
And the output is:
test.cxx:7:10: warning: ‘void f(bool)’ is deprecated
test.cxx:8:9: warning: ‘void f(int)’ is deprecated
test.cxx:9:11: warning: ‘void f(double)’ is deprecated
The obvious drawback of this method is that it relies on a GCC-specific extension, though some other compilers (Clang and probably Intel C++ for Linux) also support it. If you know of a similar functionality in other compilers and/or IDE’s, please mention it in the comments.