227 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- C++ -*-===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "IncludeCleaner.h"
 | 
						|
#include "support/Logger.h"
 | 
						|
#include "clang/AST/RecursiveASTVisitor.h"
 | 
						|
#include "clang/Basic/SourceLocation.h"
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace clangd {
 | 
						|
namespace {
 | 
						|
 | 
						|
/// Crawler traverses the AST and feeds in the locations of (sometimes
 | 
						|
/// implicitly) used symbols into \p Result.
 | 
						|
class ReferencedLocationCrawler
 | 
						|
    : public RecursiveASTVisitor<ReferencedLocationCrawler> {
 | 
						|
public:
 | 
						|
  ReferencedLocationCrawler(ReferencedLocations &Result) : Result(Result) {}
 | 
						|
 | 
						|
  bool VisitDeclRefExpr(DeclRefExpr *DRE) {
 | 
						|
    add(DRE->getDecl());
 | 
						|
    add(DRE->getFoundDecl());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitMemberExpr(MemberExpr *ME) {
 | 
						|
    add(ME->getMemberDecl());
 | 
						|
    add(ME->getFoundDecl().getDecl());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitTagType(TagType *TT) {
 | 
						|
    add(TT->getDecl());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitFunctionDecl(FunctionDecl *FD) {
 | 
						|
    // Function definition will require redeclarations to be included.
 | 
						|
    if (FD == FD->getDefinition())
 | 
						|
      add(FD);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitCXXConstructExpr(CXXConstructExpr *CCE) {
 | 
						|
    add(CCE->getConstructor());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitTemplateSpecializationType(TemplateSpecializationType *TST) {
 | 
						|
    if (isNew(TST)) {
 | 
						|
      add(TST->getTemplateName().getAsTemplateDecl()); // Primary template.
 | 
						|
      add(TST->getAsCXXRecordDecl());                  // Specialization
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitTypedefType(TypedefType *TT) {
 | 
						|
    add(TT->getDecl());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Consider types of any subexpression used, even if the type is not named.
 | 
						|
  // This is helpful in getFoo().bar(), where Foo must be complete.
 | 
						|
  // FIXME(kirillbobyrev): Should we tweak this? It may not be desirable to
 | 
						|
  // consider types "used" when they are not directly spelled in code.
 | 
						|
  bool VisitExpr(Expr *E) {
 | 
						|
    TraverseType(E->getType());
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool TraverseType(QualType T) {
 | 
						|
    if (isNew(T.getTypePtrOrNull())) { // don't care about quals
 | 
						|
      Base::TraverseType(T);
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  bool VisitUsingDecl(UsingDecl *D) {
 | 
						|
    for (const auto *Shadow : D->shadows()) {
 | 
						|
      add(Shadow->getTargetDecl());
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Enums may be usefully forward-declared as *complete* types by specifying
 | 
						|
  // an underlying type. In this case, the definition should see the declaration
 | 
						|
  // so they can be checked for compatibility.
 | 
						|
  bool VisitEnumDecl(EnumDecl *D) {
 | 
						|
    if (D->isThisDeclarationADefinition() && D->getIntegerTypeSourceInfo())
 | 
						|
      add(D);
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  using Base = RecursiveASTVisitor<ReferencedLocationCrawler>;
 | 
						|
 | 
						|
  void add(const Decl *D) {
 | 
						|
    if (!D || !isNew(D->getCanonicalDecl())) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    for (const Decl *Redecl : D->redecls()) {
 | 
						|
      Result.insert(Redecl->getLocation());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  bool isNew(const void *P) { return P && Visited.insert(P).second; }
 | 
						|
 | 
						|
  ReferencedLocations &Result;
 | 
						|
  llvm::DenseSet<const void *> Visited;
 | 
						|
};
 | 
						|
 | 
						|
// Given a set of referenced FileIDs, determines all the potentially-referenced
 | 
						|
// files and macros by traversing expansion/spelling locations of macro IDs.
 | 
						|
// This is used to map the referenced SourceLocations onto real files.
 | 
						|
struct ReferencedFiles {
 | 
						|
  ReferencedFiles(const SourceManager &SM) : SM(SM) {}
 | 
						|
  llvm::DenseSet<FileID> Files;
 | 
						|
  llvm::DenseSet<FileID> Macros;
 | 
						|
  const SourceManager &SM;
 | 
						|
 | 
						|
  void add(SourceLocation Loc) { add(SM.getFileID(Loc), Loc); }
 | 
						|
 | 
						|
  void add(FileID FID, SourceLocation Loc) {
 | 
						|
    if (FID.isInvalid())
 | 
						|
      return;
 | 
						|
    assert(SM.isInFileID(Loc, FID));
 | 
						|
    if (Loc.isFileID()) {
 | 
						|
      Files.insert(FID);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Don't process the same macro FID twice.
 | 
						|
    if (!Macros.insert(FID).second)
 | 
						|
      return;
 | 
						|
    const auto &Exp = SM.getSLocEntry(FID).getExpansion();
 | 
						|
    // For token pasting operator in macros, spelling and expansion locations
 | 
						|
    // can be within a temporary buffer that Clang creates (scratch space or
 | 
						|
    // ScratchBuffer). That is not a real file we can include.
 | 
						|
    if (!SM.isWrittenInScratchSpace(Exp.getSpellingLoc()))
 | 
						|
      add(Exp.getSpellingLoc());
 | 
						|
    if (!SM.isWrittenInScratchSpace(Exp.getExpansionLocStart()))
 | 
						|
      add(Exp.getExpansionLocStart());
 | 
						|
    if (!SM.isWrittenInScratchSpace(Exp.getExpansionLocEnd()))
 | 
						|
      add(Exp.getExpansionLocEnd());
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
ReferencedLocations findReferencedLocations(ParsedAST &AST) {
 | 
						|
  ReferencedLocations Result;
 | 
						|
  ReferencedLocationCrawler Crawler(Result);
 | 
						|
  Crawler.TraverseAST(AST.getASTContext());
 | 
						|
  // FIXME(kirillbobyrev): Handle macros.
 | 
						|
  return Result;
 | 
						|
}
 | 
						|
 | 
						|
llvm::DenseSet<FileID>
 | 
						|
findReferencedFiles(const llvm::DenseSet<SourceLocation> &Locs,
 | 
						|
                    const SourceManager &SM) {
 | 
						|
  std::vector<SourceLocation> Sorted{Locs.begin(), Locs.end()};
 | 
						|
  llvm::sort(Sorted); // Group by FileID.
 | 
						|
  ReferencedFiles Result(SM);
 | 
						|
  for (auto It = Sorted.begin(); It < Sorted.end();) {
 | 
						|
    FileID FID = SM.getFileID(*It);
 | 
						|
    Result.add(FID, *It);
 | 
						|
    // Cheaply skip over all the other locations from the same FileID.
 | 
						|
    // This avoids lots of redundant Loc->File lookups for the same file.
 | 
						|
    do
 | 
						|
      ++It;
 | 
						|
    while (It != Sorted.end() && SM.isInFileID(*It, FID));
 | 
						|
  }
 | 
						|
  return std::move(Result.Files);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<const Inclusion *>
 | 
						|
getUnused(const IncludeStructure &Structure,
 | 
						|
          const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles) {
 | 
						|
  std::vector<const Inclusion *> Unused;
 | 
						|
  for (const Inclusion &MFI : Structure.MainFileIncludes) {
 | 
						|
    // FIXME: Skip includes that are not self-contained.
 | 
						|
    if (!MFI.HeaderID) {
 | 
						|
      elog("File {0} not found.", MFI.Written);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
 | 
						|
    if (!ReferencedFiles.contains(IncludeID)) {
 | 
						|
      Unused.push_back(&MFI);
 | 
						|
    }
 | 
						|
    dlog("{0} is {1}", MFI.Written,
 | 
						|
         ReferencedFiles.contains(IncludeID) ? "USED" : "UNUSED");
 | 
						|
  }
 | 
						|
  return Unused;
 | 
						|
}
 | 
						|
 | 
						|
llvm::DenseSet<IncludeStructure::HeaderID>
 | 
						|
translateToHeaderIDs(const llvm::DenseSet<FileID> &Files,
 | 
						|
                     const IncludeStructure &Includes,
 | 
						|
                     const SourceManager &SM) {
 | 
						|
  llvm::DenseSet<IncludeStructure::HeaderID> TranslatedHeaderIDs;
 | 
						|
  TranslatedHeaderIDs.reserve(Files.size());
 | 
						|
  for (FileID FID : Files) {
 | 
						|
    const FileEntry *FE = SM.getFileEntryForID(FID);
 | 
						|
    assert(FE);
 | 
						|
    const auto File = Includes.getID(FE);
 | 
						|
    assert(File);
 | 
						|
    TranslatedHeaderIDs.insert(*File);
 | 
						|
  }
 | 
						|
  return TranslatedHeaderIDs;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<const Inclusion *> computeUnusedIncludes(ParsedAST &AST) {
 | 
						|
  const auto &SM = AST.getSourceManager();
 | 
						|
 | 
						|
  auto Refs = findReferencedLocations(AST);
 | 
						|
  auto ReferencedFiles = translateToHeaderIDs(findReferencedFiles(Refs, SM),
 | 
						|
                                              AST.getIncludeStructure(), SM);
 | 
						|
  return getUnused(AST.getIncludeStructure(), ReferencedFiles);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace clangd
 | 
						|
} // namespace clang
 |