Background
Recently, a colleague of mine ran into a problem where an old version of a header file for a library was used. The result was that the code generated for calling a virtual function in a C++ referred to the wrong offset in the virtual function lookup table for the class (the vtable).
Unfortunately, this error was not caught during compilation.
Question
All ordinary functions are linked using their mangled names which ensures the correct function (including the correct overload variant) is selected by the linker. Likewise, one could imagine that an object file or a library could contain symbolic information about the functions in the vtable for a C++ class.
Is there any way to let the C++ compiler (say g++
or Visual Studio) type check calls to virtual function during linking?
Example
Here is a simple test example. Imagine this simple header file and the associated implementation:
Base.hpp:
#ifndef BASE_HPP
#define BASE_HPP
namespace Test
{
class Base
{
public:
virtual int f() const = 0;
virtual int g() const = 0;
virtual int h() const = 0;
};
class BaseFactory
{
public:
static const Base* createBase();
};
}
#endif
Derived.cpp:
#include "Base.hpp"
#include <iostream>
using namespace std;
namespace Test
{
class Derived : public Base
{
public:
virtual int f() const
{
cout << "Derived::f()" << endl;
return 1;
}
virtual int g() const
{
cout << "Derived::g()" << endl;
return 2;
}
virtual int h() const
{
cout << "Derived::h()" << endl;
return 3;
}
};
const Base* BaseFactory::createBase()
{
return new Derived();
}
}
Now, imagine that a program uses a wrong/old version of the header file where the virtual function in the middle is missing:
BaseWrong.hpp
#ifndef BASEWRONG_HPP
#define BASEWRONG_HPP
namespace Test
{
class Base
{
public:
virtual int f() const = 0;
// Missing: virtual int g() const = 0;
virtual int h() const = 0;
};
class BaseFactory
{
public:
static const Base* createBase();
};
}
#endif
So here we have the main program:
Main.cpp
// Including the _wrong_ version of the header!
#include "BaseWrong.hpp"
#include <iostream>
using namespace std;
int main()
{
const Test::Base* base = Test::BaseFactory::createBase();
const int fres = base->f();
cout << "f() returned: " << fres << endl;
const int hres = base->h();
cout << "h() returned: " << hres << endl;
return 0;
}
When we compile the “library” using the correct header and then compile and link the main program using the wrong header…
$ g++ -c Derived.cpp
$ g++ Main.cpp Derived.o -o Main
…then the virtual call to h()
uses the wrong index in the vtable so the call actually goes to g()
:
$ ./Main
Derived::f()
f() returned: 1
Derived::g()
h() returned: 2
In this small example the functions g()
and h()
have the same signature so the “only” thing that goes wrong is the wrong function being called (which is bad in itself and could go completely unnoticed), but if the signatures are different this can (and has seen to) lead to stack corruption – e.g., when calling a function in a DLL on Windows where Pascal calling convention is used (caller pushes arguments and callee pops them before returning).
struct X {int a; char b;};
in one TU andstruct X {char b; int a;};
in another TU, do you expect a linker should be able to tell you that? – Crat