Index: ELF/Relocations.cpp =================================================================== --- ELF/Relocations.cpp +++ ELF/Relocations.cpp @@ -691,8 +691,65 @@ static std::vector undefs; +static const Symbol *correctSpelling(const Symbol &sym) { + // Build a map of local defined symbols. + DenseMap map; + if (sym.file) + for (const Symbol *s : sym.file->getSymbols()) + if (s->isLocal() && s->isDefined()) + map.try_emplace(s->getName(), s); + + const Symbol *corrected = nullptr; + auto suggest = [&](StringRef newName) { + // Test if the name is defined locally. + const Symbol *s = map.lookup(newName); + if (s) { + corrected = s; + return true; + } + + // Test if the name is in the symbol table and is not undefined. + s = symtab->find(newName); + if (s && !s->isUndefined()) { + corrected = s; + return true; + } + return false; + }; + + StringRef name = sym.getName(); + for (size_t i = 0, e = name.size(); i != e + 1; ++i) { + // Insert a character before name[i]. + std::string newName = (name.substr(0, i) + "0" + name.substr(i)).str(); + for (char c = '0'; c <= 'z'; ++c) { + newName[i] = c; + if (suggest(newName)) + break; + } + if (corrected || i == e) + break; + + // Substitute name[i]. + newName = name; + for (char c = '0'; c <= 'z'; ++c) { + newName[i] = c; + if (suggest(newName)) + break; + } + if (corrected) + break; + + // Delete name[i]. + if (suggest((name.substr(0, i) + name.substr(i + 1)).str())) + break; + } + + return corrected; +} + template -static void reportUndefinedSymbol(const UndefinedDiag &undef) { +static void reportUndefinedSymbol(const UndefinedDiag &undef, + bool spellCorrector) { Symbol &sym = *undef.sym; auto visibility = [&]() -> std::string { @@ -732,6 +789,13 @@ msg += ("\n>>> referenced " + Twine(undef.locs.size() - i) + " more times") .str(); + if (spellCorrector) + if (const Symbol *corrected = correctSpelling(sym)) { + msg += "\n>>> did you mean: " + toString(*corrected); + if (corrected->file) + msg += "\n>>> defined in: " + toString(corrected->file); + } + if (sym.getName().startswith("_ZTV")) msg += "\nthe vtable symbol may be undefined because the class is missing " "its key function (see https://lld.llvm.org/missingkeyfunction)"; @@ -755,10 +819,13 @@ firstRef[undef.sym] = &undef; } - for (const UndefinedDiag &undef : undefs) { - if (!undef.locs.empty()) - reportUndefinedSymbol(undef); - } + size_t i = 0; + for (const UndefinedDiag &undef : undefs) + if (!undef.locs.empty()) { + // Enable spell corrector for the first 2 diagnostics. + reportUndefinedSymbol(undef, i < 2); + ++i; + } undefs.clear(); } Index: test/ELF/undef-spell-corrector.s =================================================================== --- /dev/null +++ test/ELF/undef-spell-corrector.s @@ -0,0 +1,61 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o + +## Insert a character. +## The spell corrector is enabled for the first two "undefined symbol" diagnostics. +# RUN: echo 'call bcde; call abcd; call abde' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=INSERT %s -DFILE=%t.o + +## Symbols defined in DSO can be suggested. +# RUN: ld.lld %t.o -shared -o %t.so +# RUN: not ld.lld %t.so %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=INSERT %s -DFILE=%t.so + +# INSERT: error: undefined symbol: bcde +# INSERT-NEXT: >>> referenced by {{.*}} +# INSERT-NEXT: >>> did you mean: abcde +# INSERT-NEXT: >>> defined in: [[FILE]] +# INSERT: error: undefined symbol: abcd +# INSERT-NEXT: >>> referenced by {{.*}} +# INSERT-NEXT: >>> did you mean: abcde +# INSERT-NEXT: >>> defined in: [[FILE]] +# INSERT: error: undefined symbol: abde +# INSERT-NEXT: >>> referenced by {{.*}} +# INSERT-NOT: >>> + +## Substitute a character. +# RUN: echo 'call bbcde; call abcdd' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=SUBST %s + +# SUBST: error: undefined symbol: bbcde +# SUBST-NEXT: >>> referenced by {{.*}} +# SUBST-NEXT: >>> did you mean: abcde +# SUBST: error: undefined symbol: abcdd +# SUBST-NEXT: >>> referenced by {{.*}} +# SUBST-NEXT: >>> did you mean: abcde + +## Delete a character. +# RUN: echo 'call aabcde; call abcdee' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=DELETE %s + +# DELETE: error: undefined symbol: aabcde +# DELETE-NEXT: >>> referenced by {{.*}} +# DELETE-NEXT: >>> did you mean: abcde +# DELETE: error: undefined symbol: abcdee +# DELETE-NEXT: >>> referenced by {{.*}} +# DELETE-NEXT: >>> did you mean: abcde + +## Missing const qualifier +# RUN: echo 'call _Z3fooPi' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=CONST %s +## Local defined symbols +# RUN: echo '_Z3fooPKi: call _Z3fooPi' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: not ld.lld %t1.o -o /dev/null 2>&1 | FileCheck --check-prefix=CONST %s + +# CONST: error: undefined symbol: foo(int*) +# CONST-NEXT: >>> referenced by {{.*}} +# CONST-NEXT: >>> did you mean: foo(int const*) + +.globl _start, abcde, _Z3fooPKi +_start: +abcde: +_Z3fooPKi: