LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 18074 - Undefined references when using pointer to member functions
Summary: Undefined references when using pointer to member functions
Status: RESOLVED INVALID
Alias: None
Product: libc++
Classification: Unclassified
Component: All Bugs (show other bugs)
Version: unspecified
Hardware: PC All
: P normal
Assignee: Howard Hinnant
URL:
Keywords:
: 24127 (view as bug list)
Depends on:
Blocks:
 
Reported: 2013-11-26 19:58 PST by Rafael Ávila de Espíndola
Modified: 2019-06-26 13:32 PDT (History)
8 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Rafael Ávila de Espíndola 2013-11-26 19:58:28 PST
$ cat test.cpp
#include <locale>

typedef char (std::moneypunct_byname<char,false>::*mbr)() const;
void g(mbr x);

int main() {
  g(&std::moneypunct_byname<char,false>::decimal_point);
}

$ cat test2.cpp
#include <locale>

typedef char (std::moneypunct_byname<char,false>::*mbr)() const;

void g(mbr x) {
}

$ clang++ -o test.so -shared -fPIC test.cpp test2.cpp
Undefined symbols for architecture x86_64:
  "std::__1::moneypunct<char, false>::decimal_point() const", referenced from:
      _main in test-c3f9f0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)


This works with libstdc++.
Comment 1 Jörg Sonnenberger 2013-11-27 02:27:20 PST
Duplicate of 16478?
Comment 2 Rafael Ávila de Espíndola 2013-11-27 08:10:26 PST

*** This bug has been marked as a duplicate of bug 16478 ***
Comment 3 Marshall Clow (home) 2013-12-11 11:04:27 PST
You don't need separate compilation to trigger this.

    #include <locale>

    typedef char (std::moneypunct_byname<char,false>::*mbr)() const;
    void g(mbr x) {}

    int main() {
      g(&std::moneypunct_byname<char,false>::decimal_point);
    }

Fails the same way.
Comment 4 Rafael Ávila de Espíndola 2013-12-11 11:50:12 PST
This still fails on trunk.

As Marshall noticed, it also fails with a single file. Using 2 just makes the test a bit stronger.
Comment 5 Howard Hinnant 2013-12-11 12:18:42 PST
Small repro case:

$ cat Junk.hpp
#define ALWAYS_INLINE  __attribute__ ((__visibility__("hidden"), __always_inline__))
 
template <typename T>
struct Facet
{
    virtual ~Facet();
        ALWAYS_INLINE T InlineMember() const { return T(4); }
};

template <class T>
Facet<T>::~Facet()
{
}
 
extern template class Facet<int>;
$ cat Junk2.cpp
#include "Junk.hpp"
 
typedef int (Facet<int>::*mbr)() const;
int foo (Facet<int> const* t, mbr p );
 
int main () {
    Facet<int> f;
        return foo (&f, &Facet<int>::InlineMember );
        }

int foo (Facet<int> const* t, mbr p )
{
    return (t->*p)();
}
$ cat Junk3.cpp
#include "Junk.hpp"
 
template class Facet<int>;
$ clang++  Junk3.cpp -dynamiclib -o Junk3.dylib
$ clang++ Junk2.cpp Junk3.dylib
Undefined symbols for architecture x86_64:
  "Facet<int>::InlineMember() const", referenced from:
      _main in Junk2-3557a7.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

The issue is that libc++ assumes that a function marked always_inline will not be outlined into a dylib, even with the use of extern template.  clang assumes the opposite.  The standard (I believe) does not specify.  Through in hidden visibility, and you've got a perfect storm.

clang and libc++ need to come to an agreement on the behavior of the set of features: [visibility, always_inline, extern template].  With any luck that behavior might be consistent with a future C++ standard with modules which may specify such behavior.
Comment 6 Rafael Ávila de Espíndola 2013-12-11 12:23:32 PST
> The issue is that libc++ assumes that a function marked always_inline will
> not be outlined into a dylib, even with the use of extern template.  clang
> assumes the opposite.  The standard (I believe) does not specify.  Through
> in hidden visibility, and you've got a perfect storm.
> 
> clang and libc++ need to come to an agreement on the behavior of the set of
> features: [visibility, always_inline, extern template].  With any luck that
> behavior might be consistent with a future C++ standard with modules which
> may specify such behavior.

The clang behavior is pretty much necessary, as bar in the example could be comparing the address with one passed from another shared library.

As I noted in bug 16478:

Why can't we just drop the extern template declarations from libc++ when the
headers are used from user code (as opposed when used from libc++ itself)?

On MachO the linker will even hide these symbols if the address is not taken.
If the address is taken, they will be correctly exposed, which is required
since this  may be part of a library which exposes the address which can then
be compared in another library.

The feature (hiding linkonce_odr symbols) is missing from ELF linkers, but I
think correctness takes precedence. We do hide them when doing LTO, even on
ELF.
Comment 7 Richard Smith 2013-12-11 17:07:07 PST
I think there may be an assumption that "always_inline" means "never generate or require an out-of-line symbol". That's not what it means, and it's not possible -- we cannot inline varargs functions, or functions whose address is taken and used in some way we can't optimize out (whether the address is taken directly, in a vtable, via a pointer-to-member, or whatever else).

Instead, "always_inline" is documented as meaning "inline at any optimization level". It actually means more than that, in practice; it means "inline wherever you can", but unless you can guarantee that every use site is going to be inlineable, it doesn't guarantee that a reference to the symbol will not be generated.

Conversely, the explicit instantiation declaration says that any code seeing that declaration doesn't need to generate a symbol for the member function. And the hidden visibility says that any such symbol reference can be resolved within the same DSO.


To summarize: libc++'s technique is correct if every use site of the relevant symbols is inlineable. Otherwise, it is not correct.

Now... this code has undefined behavior:

  typedef char (std::moneypunct_byname<char,false>::*mbr)() const;
  void g(mbr x);

  int main() {
    g(&std::moneypunct_byname<char,false>::decimal_point);
  }

... because 'std::moneypunct_byname<char,false>::decimal_point' is not required to have the type 'char () const', by [member.functions] (17.6.5.5)/2.

So the code in comment#0 does not prove that libc++'s technique is incorrect. It's not clear to me that there's /any/ way in the current language to make a non-inlineable reference to a (non-virtual) member function in the standard library without exhibiting undefined behavior, since 17.6.5.5's provisions are extremely broad, and basically say that all you can do with a non-virtual member function in the standard library is to directly call it.
Comment 8 Marshall Clow (home) 2015-07-14 20:15:07 PDT
*** Bug 24127 has been marked as a duplicate of this bug. ***
Comment 9 Peter Bindels 2015-08-31 12:53:48 PDT
@Marshall I'd say that 24638 more accurately duplicates 16478, which is marked as "RESOLVED FIXED". My reproduction scenario is identical to the one in that PR - using bind on c_str of a std::string.

Is it possible to either:

- Make LLVM/Clang give at least a warning on using the address of such a function?
- Or make it export it when the address is taken, as a weak symbol (like normal template members would)?
Comment 10 Marshall Clow (home) 2019-06-26 13:32:34 PDT
This has been clarified in the latest draft standard. [namespace.std]/6 now says:

Let F denote a standard library function (16.5.5.4), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function, the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to F. 
[ Note ... end note]
Moreover, the behavior of a C++ program is unspecified (possibly ill-formed) if it attempts to form a reference to F or if it attempts to form a pointer-to-member designating either a standard library non-static member function (16.5.5.5) or an instantiation of a standard library member function template.

The upshot of all that is that the behavior of your example program is "unspecified (possibly ill-formed)".