yrnkrn / zapcc

zapcc is a caching C++ compiler based on clang, designed to perform faster compilations
Other
1.25k stars 60 forks source link

Linker symbols leak to unrelated translation units, leading to linker errors #12

Closed peterazmanov closed 6 years ago

peterazmanov commented 6 years ago

Linker fails with "multiple definition of ..." when compiling code that uses Boost.Serialization.

Steps to reproduce:

Output:

[ 33%] Building CXX object CMakeFiles/zapcc-test.dir/a.cpp.o
[ 66%] Building CXX object CMakeFiles/zapcc-test.dir/main.cpp.o
[100%] Linking CXX executable zapcc-test
CMakeFiles/zapcc-test.dir/main.cpp.o:(.rodata+0x10): multiple definition of `typeinfo for A'
CMakeFiles/zapcc-test.dir/a.cpp.o:(.rodata+0x10): first defined here
CMakeFiles/zapcc-test.dir/main.cpp.o:(.rodata+0x0): multiple definition of `typeinfo name for A'
CMakeFiles/zapcc-test.dir/a.cpp.o:(.rodata+0x0): first defined here
zapcc: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [CMakeFiles/zapcc-test.dir/build.make:100: zapcc-test] Error 1
make[1]: *** [CMakeFiles/Makefile2:68: CMakeFiles/zapcc-test.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

System: Linux archlinux 4.17.2-1-ARCH #1 SMP PREEMPT Sat Jun 16 11:08:59 UTC 2018 x86_64 GNU/Linux ZapCC is built from commit 01ff39e6e0278432e562987ebf8a5a99869cd747 (2018.06.21) Boost version: 1.67.0

The example compiles and links fine with Clang 5.0/6.0 and GCC 8.1.1

The problem occures when serializing polymorphic base class. This causes registration in internal singleton of Boost.Serialization. Somehow vtable-related symbols from previous translation units are included in subsequent unrelated translation units.

Example has the following structure:

a.h:

#pragma once

#include <boost/serialization/access.hpp>

class B
{
public:
    virtual ~B() = default;

private:
    friend class boost::serialization::access;

    template< class Archive >
    void serialize( Archive & /* ar */, unsigned int const /* version */ )
    {}
};

class A
    : public B
{
public:
    A();

    virtual ~A();

private:
    friend class boost::serialization::access;

    template< class Archive >
    void serialize( Archive & ar, unsigned int version );
};

a.cpp:

#include "a.h"

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

A::A() = default;
A::~A() = default;

template< class Archive >
void A::serialize( Archive & ar, unsigned int /* version */ )
{
    ar & boost::serialization::base_object<B>(*this);
}

template void A::serialize( boost::archive::binary_oarchive & ar, unsigned int version );
template void A::serialize( boost::archive::binary_iarchive & ar, unsigned int version );

main.cpp:

#include <boost/serialization/access.hpp>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

struct C
{
private:
    friend class boost::serialization::access;

    template< class Archive >
    void serialize( Archive & /* ar */, unsigned int const /* version */ )
    {}
};

template void C::serialize( boost::archive::binary_oarchive & ar, unsigned int const version );
template void C::serialize( boost::archive::binary_iarchive & ar, unsigned int const version );

int main()
{
    return 0;
}

zapcc_test.zip

yrnkrn commented 6 years ago

Deciding whether to emit template instantiation-related symbols with cached includes is a very complex problem. Boost had already provided zapcc lots of good tests in tools/clang/test/zapcc/multi , this is probably another... this example will need to be completely c-reduced first, will look into this.

yrnkrn commented 6 years ago

Could you c-reduce (https://embed.cs.utah.edu/creduce) this example code to independent header and source files? final version would be few lines each, similar to https://github.com/yrnkrn/zapcc/tree/master/tools/clang/test/zapcc/multi/template-function-local-var also reduced from Boost. Thanks!

peterazmanov commented 6 years ago

I managed to reduce (with the help of creduce) the test to the following: zapcc_test_reduced.zip

reduced_boost.h:

#pragma once

template <class A, class B>
void void_cast_register()  __attribute__ ((__used__));

template <class A, class B>
void void_cast_register()
{
    (void)dynamic_cast<A *>((B *)nullptr);
}

a.h:

#pragma once

struct B
{
  virtual ~B() = default;
};

struct A : B
{
  ~A();
};

a.cpp:

#include "a.h"
#include "reduced_boost.h"

A::~A()
{
  void_cast_register<A, B>();
}

main.cpp:

#include "reduced_boost.h"

int main()
{}
yrnkrn commented 6 years ago

Nice & clean reduce, looking at the problem.

yrnkrn commented 6 years ago

https://guides.github.com/features/mastering-markdown/ 12.zip

Normalized to our usual test script.

yrnkrn commented 6 years ago

Fix reduced + new LIT test in 194583e73b0dde257b49f41426c5bc66229baba5. If original problem still persists, please open a new issue.

peterazmanov commented 6 years ago

Fix solved the problem. Checked both reduced and non-reduced versions.