0

I'm currently writing a library that has some abstract classes. In addition to checking that the library compiles, I'd like to make sure that all pure virtual methods have been defined in classes that are intended to be concrete. I had hoped that I could get this information from nm or objdump, but so far I haven't been able to tell.

Consider the following minimal example:

struct A
{
    void f() {}
};

struct B
{
    virtual void f() = 0;
};

struct C : public B
{
    void f() override {}
};

When I look at the nm output, I get the following. (I've excluded anything that doesn't relate to one of these classes.)

 % nm -C test.so
0000000000001174 W A::f()
000000000000118c W B::B()
000000000000118c W B::B()
0000000000001180 W C::f()
00000000000011aa W C::C()
00000000000011aa W C::C()
0000000000003db0 V typeinfo for B
0000000000003d98 V typeinfo for C
0000000000002003 V typeinfo name for B
0000000000002000 V typeinfo name for C
0000000000003d80 V vtable for B
0000000000003d68 V vtable for C

It's easy to distinguish A (a class with no virtual methods) from B and C. But I want to be able to distinguish the fact that B is an abstract class whereas C is concrete.

Obviously, if I have a list of all pure virtual methods, and a map of the class hierarchy, then I could iterate over them and check whether they are defined. (Above, you can see that C::f is defined, but B::f is not.) But I was hoping that there would be an automatic way of doing this. (I was hoping that the vtable or typeinfo would show up differently above.)

Another way would be to add an extra file where I instantiate one object from every class that I expect to be concrete, and I will get a compiler error if any of them have undefined virtual methods. But this is also annoying.

Clearly, I'm not an expert on ELF or the C object file model in general, so I'd appreciate a useful introduction or reference if the answer turns out to be complicated.

sasquires
  • 356
  • 3
  • 15
  • It’s the job of the compiler to ensure that. Why do you care if what you are being passed is a base class of another class? – Taekahn Apr 08 '22 at 04:00
  • As a programmer, I need to make sure that I have fully defined every class I expect to be instantiated. There are a lot of classes that I need to update, and I don't trust myself to do all of the work without making a single error. It would be nice to check automatically. Unfortunately, the project that I'm working on doesn't have "nice" automated tests and won't in the immediate future. (There is a testing system, but it is very slow and tends to only be launched shortly before release.) I would like to catch these mistakes now rather than have to respond to them later. – sasquires Apr 08 '22 at 04:17
  • I would suggest looking into using the clang ast tooling to accomplish what you’re looking for. You can probably turn it into an automated clang-tidy check – Taekahn Apr 08 '22 at 04:19

2 Answers2

1

As a programmer, I need to make sure that I have fully defined every class I expect to be instantiated.

Only you can know which classes these are.

if you have a list of such classes, you can generate a test program along the lines of:

#include <assert.h>
#include <mylib.h>
int main()
{
  assert(!std::is_abstract<Class1>::value);
...
  assert(!std::is_abstract<ClassN>::value);
}

compile and run it. If it doesn't assert, you are good.

You could in theory also do this at the test.so level. You'll need to locate the vtable for every expected-to-be-concrete class X, and examine the values in that table. If any value is equal to &__cxa_pure_virtual, then the class is not concrete.

This is complicated by the fact that the vtables are relocated at load time. So you'd have to find the vtables, then find relocation records which apply to them, and then search for __cxa_pure_virtual.

Overall, generating a test program is a much easier approach.

Update:

I was hoping that I could do something like nm -C test.so | grep ... | ... to select the names of pure virtual classes.

There is no way to do that.

At a glance I would be able to tell if any of them did not belong on the list.

What you could do is find all classes with virtual tables via nm -D test.so | grep ' _ZTV' | c++filt. Use that list of all classes to generate the test program, but instead of asserting simply print the class name and the result of the test.

Finally filter this list to just the classes where is_abstract<T>::value is true. Voilà: you now have the list you were seeking.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Ok, the second half of your answer convinced me that generating a small test program is the right way to go. – sasquires Apr 09 '22 at 04:17
  • In response to the first sentence of your answer: Obviously, I know that there is no way for `nm` to automatically determine whether I intend a class to be concrete. What I meant is that I was hoping that I could do something like `nm -C test.so | grep ... | ...` to select the names of pure virtual classes. At a glance I would be able to tell if any of them did not belong on the list. – sasquires Apr 09 '22 at 04:17
-1

How about define a macro as below?

#include <iostream>
#include <type_traits>

#define TEST_CC(x) if(std::is_abstract<x>::value) std::cout << #x" is abstract \n"

struct A
{
    void f() {}
};

struct B
{
    virtual void f() = 0;
};

struct C : public B
{
    void f() override {}
};

int main()
{
    TEST_CC(A);
    TEST_CC(B);
    TEST_CC(C);
}
Ramy
  • 72
  • 5
  • 1
    This doesn't answer the original question _at all_. – Employed Russian Apr 09 '22 at 02:45
  • I don't understand, my solution is exactly the same as the above one and mine earlier. In view of the post mentioned: "Another way would be to add an extra file where I instantiate one object from every class that I expect to be concrete, and I will get a compiler error if any of them have undefined virtual methods. But this is also annoying", my solution solves the problem from this side. – Ramy Apr 10 '22 at 02:35