tags:

views:

134

answers:

4

Is there a way for GCC to produce a warning while linking libraries that contain classes with the same name? For example

Port.h

class Port {
public:
  std::string me();
};

Port.cpp

#include "Port.h"
std::string Port::me() { return "Port"; }

FakePort.h

class Port {
public:
  std::string me();
};

FakePort.cpp

#include "FakePort.h"
std::string Port::me() { return "FakePort"; }

main.cpp

#include "Port.h"

int main() {
  Port port;
  std::cout << "Hello world from " << port.me() << std::endl;
  return 0;
}

Building

# g++ -c -o Port.o Port.cpp
# ar rc Port.a Port.o
# g++ -c -o FakePort.o FakePort.cpp
# ar rc FakePort.a FakePort.o
# g++ -c -o main.o main.cpp
# g++ main.o Port.a FakePort.a
# ./a.out
  Hello world from Port

Change library order

# g++ main.o FakePort.a Port.a
# ./a.out
  Hello world from FakePort

According to this page:

If a symbol is defined in two different libraries gcc will use the first one it finds and ignore the second one unless the second one is included in an object file which gets included for some other reason.

So the above behavior make sense. Unfortunately I am inheriting a sizable code base that doesn't make use of namespaces (and adding them isn't feasible right now) and uses some generic class names throughout multiple libraries. I would like to automatically detect duplicate names at link time to make sure that the wrong copy of a class isn't accidentally being instantiating. Something like:

# g++ -Wl,--warnLibraryDupSymbols main.o FakePort.a Port.a
  Warning: Duplicate symbol: Port

but I can't find anything in the GCC linker options to do that. Is it possible to get GCC to automatically detect and report such cases?

A: 

I don't see any options to do what you want either. Maybe think laterally and use a code documentation tool like Doxygen to generate documentation for all your classes and manually look for duplicates

Glen
A: 

No there isn't. The reason for this is that a class name is not a symbol as far as the linker is concerned. The C++ compiler will have used the class name to produce mangled function names, but the class name itself is gone by the time the linker gets involved.

anon
+1  A: 

The following might be worth a try (I honestly don't know if it'll do what you want):

--whole-archive

For each archive mentioned on the command line after the --whole-archive option, include every object file in the archive in the link, rather than searching the archive for the required object files. This is normally used to turn an archive file into a shared library, forcing every object to be included in the resulting shared library. This option may be used more than once.

I haven't tried it, but it sounds as if it'll pull in all the items in a library as if they were object files. You'll need to specify the option for each library.

As Neil said, this won't give you class-level conflicts, but if there are class members with the same signature, this might make the linker tell you.

Michael Burr
Not sure if this is the best general case answer, but it works here. # g++ main.cpp -Wl,--whole-archive FakePort.a Port.a -Wl,--no-whole-archive Port.a(Port.o):Port.cpp:(.text+0x0): multiple definition of `Port::me()' FakePort.a(FakePort.o):FakePort.cpp:(.text+0x0): first defined here
joesdiner
If i am not missing anything this may bloat your binaries considerably, so its only useful for a test-run. Or could one take care of that with `--no-whole-archive` at the right place? @joesdiner
Georg Fritzsche
@gf: I think you're right; the intent of this answer was to use this to diagnose problems (or potential problems) at an integration stage - not as a normal build configuration. I think this is what joesdiner was looking to do.
Michael Burr
@Michael: Right, i just wanted to point out a possible pitfall in case of misunderstandings :)
Georg Fritzsche
+1  A: 

Alternatively you could use a script utilizing nm to find candidates, e.g. something like:

import collections, subprocess, os, re, sys

def filt(s):
  p = subprocess.Popen(['c++filt', s],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  return p.communicate()[0].strip()

rx  = re.compile('^[\\d\\w]+ T [\\w]+', re.MULTILINE)
sym = collections.defaultdict(set)

for file in sys.argv[1:]:
  proc = subprocess.Popen(['nm', file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  for s in rx.findall(proc.communicate()[0]):
    sym[s.split()[2]].add(file)

for s in filter(lambda x: len(sym[x])>1, sym):
    print 'Duplicate "%s" in %s' % (filt(s), str(sym[s]))

foo:$ python dups.py *.a  
Duplicate "Port::me()" in set(['Port.a', 'FakePort.a'])
Georg Fritzsche
Use a script like this to create a list of dup candidates and then work on your makefiles. Create makefile templates that focus on a known group of classes in they order the include paths are structured. That way it's easier to keep track of which 'Port' is being linked
Kelly French
Nice script. Works for me, but the linker option provided by Michael Burr seems to be detecting things automatically
joesdiner
@joesdiner: Even better then. Sadly `--whole-archive` doesn't work for me on OSX.
Georg Fritzsche