diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -697,11 +697,26 @@ static std::vector undefs; +// Check whether the definition name def is a mangled function name that matches +// the reference name ref. +static bool canSuggestExternCForCXX(StringRef ref, StringRef def) { + llvm::ItaniumPartialDemangler d; + if (d.partialDemangle(def.str().c_str())) + return false; + char *buf = d.getFunctionName(nullptr, nullptr); + if (!buf) + return false; + bool ret = ref == buf; + free(buf); + return ret; +} + // Suggest an alternative spelling of an "undefined symbol" diagnostic. Returns // the suggested symbol, which is either in the symbol table, or in the same // file of sym. static const Symbol *getAlternativeSpelling(const Undefined &sym, - std::string &pre_hint) { + std::string &pre_hint, + std::string &post_hint) { // Build a map of local defined symbols. DenseMap map; if (sym.file && !isa(sym.file)) { @@ -774,6 +789,23 @@ return s; } } + } else { + const Symbol *s = nullptr; + for (auto &it : map) + if (canSuggestExternCForCXX(name, it.first)) { + s = it.second; + break; + } + if (!s) + symtab->forEachSymbol([&](Symbol *sym) { + if (!s && canSuggestExternCForCXX(name, sym->getName())) + s = sym; + }); + if (s) { + pre_hint = " to declare "; + post_hint = " as extern \"C\"?"; + return s; + } } return nullptr; @@ -822,10 +854,10 @@ .str(); if (correctSpelling) { - std::string pre_hint = ": "; + std::string pre_hint = ": ", post_hint; if (const Symbol *corrected = - getAlternativeSpelling(cast(sym), pre_hint)) { - msg += "\n>>> did you mean" + pre_hint + toString(*corrected); + getAlternativeSpelling(cast(sym), pre_hint, post_hint)) { + msg += "\n>>> did you mean" + pre_hint + toString(*corrected) + post_hint; if (corrected->file) msg += "\n>>> defined in: " + toString(corrected->file); } diff --git a/lld/test/ELF/undef-suggest-extern-c2.s b/lld/test/ELF/undef-suggest-extern-c2.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/undef-suggest-extern-c2.s @@ -0,0 +1,21 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o + +## The definition is mangled while the reference is not, suggest an arbitrary +## C++ overload. +# RUN: echo '.globl _Z3fooi; _Z3fooi:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck %s + +## Check that we can suggest a local definition. +# RUN: echo '_Z3fooi: call foo' | llvm-mc -filetype=obj -triple=x86_64 - -o %t2.o +# RUN: not ld.lld %t2.o -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: undefined symbol: foo +# CHECK-NEXT: >>> referenced by {{.*}} +# CHECK-NEXT: >>> did you mean to declare foo(int) as extern "C"? + +## Don't suggest nested names whose base name is "foo", e.g. F::foo(). +# RUN: echo '.globl _ZN1F3fooEv; _ZN1F3fooEv:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t3.o +# RUN: not ld.lld %t.o %t3.o -o /dev/null 2>&1 | FileCheck /dev/null --implicit-check-not='did you mean' + +call foo