@@ -392,8 +392,138 @@ void macho::reportPendingDuplicateSymbols() {
392
392
}
393
393
}
394
394
395
+ // Check whether the definition name def is a mangled function name that matches
396
+ // the reference name ref.
397
+ static bool canSuggestExternCForCXX (StringRef ref, StringRef def) {
398
+ llvm::ItaniumPartialDemangler d;
399
+ std::string name = def.str ();
400
+ if (d.partialDemangle (name.c_str ()))
401
+ return false ;
402
+ char *buf = d.getFunctionName (nullptr , nullptr );
403
+ if (!buf)
404
+ return false ;
405
+ bool ret = ref == buf;
406
+ free (buf);
407
+ return ret;
408
+ }
409
+
410
+ // Suggest an alternative spelling of an "undefined symbol" diagnostic. Returns
411
+ // the suggested symbol, which is either in the symbol table, or in the same
412
+ // file of sym.
413
+ static const Symbol *getAlternativeSpelling (const Undefined &sym,
414
+ std::string &pre_hint,
415
+ std::string &post_hint) {
416
+ DenseMap<StringRef, const Symbol *> map;
417
+ if (sym.getFile () && sym.getFile ()->kind () == InputFile::ObjKind) {
418
+ // Build a map of local defined symbols.
419
+ for (const Symbol *s : sym.getFile ()->symbols )
420
+ if (auto *defined = dyn_cast<Defined>(s))
421
+ if (!defined->isExternal ())
422
+ map.try_emplace (s->getName (), s);
423
+ }
424
+
425
+ auto suggest = [&](StringRef newName) -> const Symbol * {
426
+ // If defined locally.
427
+ if (const Symbol *s = map.lookup (newName))
428
+ return s;
429
+
430
+ // If in the symbol table and not undefined.
431
+ if (const Symbol *s = symtab->find (newName))
432
+ if (dyn_cast<Undefined>(s) == nullptr )
433
+ return s;
434
+
435
+ return nullptr ;
436
+ };
437
+
438
+ // This loop enumerates all strings of Levenshtein distance 1 as typo
439
+ // correction candidates and suggests the one that exists as a non-undefined
440
+ // symbol.
441
+ StringRef name = sym.getName ();
442
+ for (size_t i = 0 , e = name.size (); i != e + 1 ; ++i) {
443
+ // Insert a character before name[i].
444
+ std::string newName = (name.substr (0 , i) + " 0" + name.substr (i)).str ();
445
+ for (char c = ' 0' ; c <= ' z' ; ++c) {
446
+ newName[i] = c;
447
+ if (const Symbol *s = suggest (newName))
448
+ return s;
449
+ }
450
+ if (i == e)
451
+ break ;
452
+
453
+ // Substitute name[i].
454
+ newName = std::string (name);
455
+ for (char c = ' 0' ; c <= ' z' ; ++c) {
456
+ newName[i] = c;
457
+ if (const Symbol *s = suggest (newName))
458
+ return s;
459
+ }
460
+
461
+ // Transpose name[i] and name[i+1]. This is of edit distance 2 but it is
462
+ // common.
463
+ if (i + 1 < e) {
464
+ newName[i] = name[i + 1 ];
465
+ newName[i + 1 ] = name[i];
466
+ if (const Symbol *s = suggest (newName))
467
+ return s;
468
+ }
469
+
470
+ // Delete name[i].
471
+ newName = (name.substr (0 , i) + name.substr (i + 1 )).str ();
472
+ if (const Symbol *s = suggest (newName))
473
+ return s;
474
+ }
475
+
476
+ // Case mismatch, e.g. Foo vs FOO.
477
+ for (auto &it : map)
478
+ if (name.equals_insensitive (it.first ))
479
+ return it.second ;
480
+ for (Symbol *sym : symtab->getSymbols ())
481
+ if (dyn_cast<Undefined>(sym) == nullptr &&
482
+ name.equals_insensitive (sym->getName ()))
483
+ return sym;
484
+
485
+ // The reference may be a mangled name while the definition is not. Suggest a
486
+ // missing extern "C".
487
+ if (name.startswith (" __Z" )) {
488
+ std::string buf = name.str ();
489
+ llvm::ItaniumPartialDemangler d;
490
+ if (!d.partialDemangle (buf.c_str ()))
491
+ if (char *buf = d.getFunctionName (nullptr , nullptr )) {
492
+ const Symbol *s = suggest ((Twine (" _" ) + buf).str ());
493
+ free (buf);
494
+ if (s) {
495
+ pre_hint = " : extern \" C\" " ;
496
+ return s;
497
+ }
498
+ }
499
+ } else {
500
+ StringRef name_without_underscore = name;
501
+ name_without_underscore.consume_front (" _" );
502
+ const Symbol *s = nullptr ;
503
+ for (auto &it : map)
504
+ if (canSuggestExternCForCXX (name_without_underscore, it.first )) {
505
+ s = it.second ;
506
+ break ;
507
+ }
508
+ if (!s)
509
+ for (Symbol *sym : symtab->getSymbols ())
510
+ if (canSuggestExternCForCXX (name_without_underscore, sym->getName ())) {
511
+ s = sym;
512
+ break ;
513
+ }
514
+ if (s) {
515
+ pre_hint = " to declare " ;
516
+ post_hint = " as extern \" C\" ?" ;
517
+ return s;
518
+ }
519
+ }
520
+
521
+ return nullptr ;
522
+ }
523
+
395
524
static void reportUndefinedSymbol (const Undefined &sym,
396
- const UndefinedDiag &locations) {
525
+ const UndefinedDiag &locations,
526
+ bool correctSpelling) {
397
527
std::string message = " undefined symbol" ;
398
528
if (config->archMultiple )
399
529
message += (" for arch " + getArchitectureName (config->arch ())).str ();
@@ -426,6 +556,17 @@ static void reportUndefinedSymbol(const Undefined &sym,
426
556
(" \n >>> referenced " + Twine (totalReferences - i) + " more times" )
427
557
.str ();
428
558
559
+ if (correctSpelling) {
560
+ std::string pre_hint = " : " , post_hint;
561
+ if (const Symbol *corrected =
562
+ getAlternativeSpelling (sym, pre_hint, post_hint)) {
563
+ message +=
564
+ " \n >>> did you mean" + pre_hint + toString (*corrected) + post_hint;
565
+ if (corrected->getFile ())
566
+ message += " \n >>> defined in: " + toString (corrected->getFile ());
567
+ }
568
+ }
569
+
429
570
if (config->undefinedSymbolTreatment == UndefinedSymbolTreatment::error)
430
571
error (message);
431
572
else if (config->undefinedSymbolTreatment ==
@@ -436,8 +577,9 @@ static void reportUndefinedSymbol(const Undefined &sym,
436
577
}
437
578
438
579
void macho::reportPendingUndefinedSymbols () {
439
- for (const auto &undef : undefs)
440
- reportUndefinedSymbol (*undef.first , undef.second );
580
+ // Enable spell corrector for the first 2 diagnostics.
581
+ for (const auto &[i, undef] : llvm::enumerate (undefs))
582
+ reportUndefinedSymbol (*undef.first , undef.second , i < 2 );
441
583
442
584
// This function is called multiple times during execution. Clear the printed
443
585
// diagnostics to avoid printing the same things again the next time.
0 commit comments