| //===--- FillInMissingMethodStubsFromAbstractClasses.cpp - ---------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Implements the "Add missing abstract class method overrides" refactoring |
| // operation. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "RefactoringOperations.h" |
| #include "SourceLocationUtilities.h" |
| #include "clang/AST/AST.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Expr.h" |
| #include "llvm/ADT/DenseSet.h" |
| |
| using namespace clang; |
| using namespace clang::tooling; |
| |
| namespace { |
| |
| class FillInMissingMethodStubsFromAbstractClassesOperation |
| : public RefactoringOperation { |
| public: |
| FillInMissingMethodStubsFromAbstractClassesOperation( |
| const CXXRecordDecl *Class) |
| : Class(Class) {} |
| |
| const Decl *getTransformedDecl() const override { return Class; } |
| |
| llvm::Expected<RefactoringResult> perform(ASTContext &Context, const Preprocessor &ThePreprocessor, |
| const RefactoringOptionSet &Options, |
| unsigned SelectedCandidateIndex) override; |
| |
| const CXXRecordDecl *Class; |
| }; |
| |
| } // end anonymous namespace |
| |
| static bool hasAbstractBases(const CXXRecordDecl *Class) { |
| for (const CXXBaseSpecifier &Base : Class->bases()) { |
| if (const auto *RD = Base.getType()->getAsCXXRecordDecl()) { |
| if (RD->isAbstract()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| RefactoringOperationResult |
| clang::tooling::initiateFillInMissingMethodStubsFromAbstractClassesOperation( |
| ASTSlice &Slice, ASTContext &Context, SourceLocation Location, |
| SourceRange SelectionRange, bool CreateOperation) { |
| auto SelectedDecl = Slice.innermostSelectedDecl( |
| llvm::makeArrayRef(Decl::CXXRecord), ASTSlice::InnermostDeclOnly); |
| if (!SelectedDecl) |
| return None; |
| const auto *Class = cast<CXXRecordDecl>(SelectedDecl->getDecl()); |
| if (Class->isUnion() || !Class->isThisDeclarationADefinition()) |
| return None; |
| if (!hasAbstractBases(Class)) |
| return RefactoringOperationResult("The class has no abstract bases"); |
| if (!Class->isDependentType() && !Class->isAbstract()) |
| return RefactoringOperationResult( |
| "The class has no missing abstract class methods"); |
| |
| RefactoringOperationResult Result; |
| Result.Initiated = true; |
| if (!CreateOperation) |
| return Result; |
| auto Operation = |
| llvm::make_unique<FillInMissingMethodStubsFromAbstractClassesOperation>( |
| Class); |
| Result.RefactoringOp = std::move(Operation); |
| return Result; |
| } |
| |
| namespace { |
| |
| class PureMethodSet { |
| llvm::DenseMap<const CXXMethodDecl *, int> Methods; |
| |
| void addPureMethodsFromAbstractClasses(const CXXRecordDecl *Class, |
| int &Priority) { |
| for (const CXXBaseSpecifier &Base : Class->bases()) { |
| const auto *RD = Base.getType()->getAsCXXRecordDecl(); |
| if (!RD || !RD->isAbstract()) |
| continue; |
| for (const CXXMethodDecl *M : RD->methods()) { |
| if (M->isPure()) |
| Methods.insert(std::make_pair(M->getCanonicalDecl(), Priority++)); |
| } |
| addPureMethodsFromAbstractClasses(RD, Priority); |
| } |
| } |
| |
| void addPureMethodsFromAbstractClasses(const CXXRecordDecl *Class) { |
| int Priority = 0; |
| addPureMethodsFromAbstractClasses(Class, Priority); |
| } |
| |
| void subtractImplementedPureMethods(const CXXRecordDecl *Class) { |
| for (const CXXMethodDecl *M : Class->methods()) { |
| if (!M->isVirtual() || M->isPure()) |
| continue; |
| for (const CXXMethodDecl *OM : M->overridden_methods()) { |
| OM = OM->getCanonicalDecl(); |
| if (OM->isPure()) |
| Methods.erase(OM); |
| } |
| } |
| for (const CXXBaseSpecifier &Base : Class->bases()) { |
| const auto *RD = Base.getType()->getAsCXXRecordDecl(); |
| if (!RD || !RD->isAbstract()) |
| continue; |
| subtractImplementedPureMethods(RD); |
| } |
| } |
| |
| public: |
| static std::vector<const CXXMethodDecl *> |
| gatherMissingMethods(const CXXRecordDecl *Class) { |
| PureMethodSet MethodSet; |
| MethodSet.addPureMethodsFromAbstractClasses(Class); |
| MethodSet.subtractImplementedPureMethods(Class); |
| // Sort the missing methods. That will place methods from the same abstract |
| // class together in the order in which they were declared. |
| struct MethodInfo { |
| const CXXMethodDecl *M; |
| int Priority; |
| }; |
| std::vector<MethodInfo> MissingMethods; |
| for (const auto &M : MethodSet.Methods) |
| MissingMethods.push_back({M.first, M.second}); |
| std::sort(MissingMethods.begin(), MissingMethods.end(), |
| [](const MethodInfo &LHS, const MethodInfo &RHS) { |
| return LHS.Priority < RHS.Priority; |
| }); |
| std::vector<const CXXMethodDecl *> Result; |
| Result.reserve(MissingMethods.size()); |
| for (const auto &M : MissingMethods) |
| Result.push_back(M.M); |
| return Result; |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| static SourceLocation findInsertionLocationForMethodsFromAbstractClass( |
| const CXXRecordDecl *AbstractClass, const CXXRecordDecl *Class, |
| const SourceManager &SM, const LangOptions &LangOpts) { |
| SourceLocation Loc; |
| for (const CXXMethodDecl *M : Class->methods()) { |
| if (!M->isVirtual() || M->isPure() || M->isImplicit()) |
| continue; |
| for (const CXXMethodDecl *OM : M->overridden_methods()) { |
| OM = OM->getCanonicalDecl(); |
| if (OM->getLexicalDeclContext() == AbstractClass) { |
| SourceLocation EndLoc = M->getLocEnd(); |
| if (EndLoc.isMacroID()) |
| EndLoc = SM.getExpansionRange(EndLoc).second; |
| if (Loc.isInvalid()) |
| Loc = EndLoc; |
| else if (SM.isBeforeInTranslationUnit(Loc, EndLoc)) |
| Loc = EndLoc; |
| break; |
| } |
| } |
| } |
| if (Loc.isInvalid()) |
| return Loc; |
| return getLastLineLocationUnlessItHasOtherTokens(Loc, SM, LangOpts); |
| } |
| |
| /// Returns true if the given \p Class implements the majority of declared |
| /// methods in the class itself. |
| static bool shouldImplementMethodsInClass(const CXXRecordDecl *Class) { |
| // Check if this class implements the methods in the class itself. |
| unsigned NumMethods = 0, NumImplementedMethods = 0; |
| for (const CXXMethodDecl *M : Class->methods()) { |
| if (M->isImplicit()) |
| continue; |
| // Only look at methods/operators. |
| if (isa<CXXConstructorDecl>(M) || isa<CXXDestructorDecl>(M)) |
| continue; |
| ++NumMethods; |
| if (M->hasBody()) |
| ++NumImplementedMethods; |
| } |
| if (!NumMethods) |
| return false; |
| // Use the following arbitrary heuristic: |
| // If the number of method declarations is less than 4, then all of the |
| // methods must have bodies. Otherwise, at least 75% of the methods must |
| // have bodies. |
| return NumMethods < 4 |
| ? NumMethods == NumImplementedMethods |
| : float(NumImplementedMethods) / float(NumMethods) > 0.75; |
| } |
| |
| llvm::Expected<RefactoringResult> |
| FillInMissingMethodStubsFromAbstractClassesOperation::perform( |
| ASTContext &Context, const Preprocessor &ThePreprocessor, |
| const RefactoringOptionSet &Options, unsigned SelectedCandidateIndex) { |
| std::vector<RefactoringReplacement> Replacements; |
| |
| std::vector<const CXXMethodDecl *> MissingMethods = |
| PureMethodSet::gatherMissingMethods(Class); |
| |
| bool GenerateBodyDummies = shouldImplementMethodsInClass(Class); |
| |
| PrintingPolicy PP = Context.getPrintingPolicy(); |
| PP.PolishForDeclaration = true; |
| PP.SuppressStrongLifetime = true; |
| PP.SuppressLifetimeQualifiers = true; |
| PP.SuppressUnwrittenScope = true; |
| |
| std::string EndInsertionOSStr; |
| llvm::raw_string_ostream EndInsertionOS(EndInsertionOSStr); |
| |
| std::string InsertionGroupOSStr; |
| llvm::raw_string_ostream InsertionGroupOS(InsertionGroupOSStr); |
| |
| SourceLocation InsertionLoc = Class->getLocEnd(); |
| const CXXRecordDecl *CurrentAbstractClass = nullptr; |
| SourceLocation CurrentGroupInsertionLoc; |
| for (const auto &I : llvm::enumerate(MissingMethods)) { |
| const CXXMethodDecl *Method = I.value(); |
| const CXXRecordDecl *AbstractClass = Method->getParent(); |
| if (CurrentAbstractClass != AbstractClass) { |
| if (!InsertionGroupOS.str().empty()) { |
| assert(CurrentGroupInsertionLoc.isValid()); |
| Replacements.emplace_back( |
| SourceRange(CurrentGroupInsertionLoc, CurrentGroupInsertionLoc), |
| InsertionGroupOS.str()); |
| } |
| InsertionGroupOSStr.clear(); |
| CurrentAbstractClass = AbstractClass; |
| CurrentGroupInsertionLoc = |
| findInsertionLocationForMethodsFromAbstractClass( |
| CurrentAbstractClass, Class, Context.getSourceManager(), |
| Context.getLangOpts()); |
| } |
| bool IsInsertingAfterRelatedMethods = CurrentGroupInsertionLoc.isValid(); |
| raw_ostream &OS = |
| IsInsertingAfterRelatedMethods ? InsertionGroupOS : EndInsertionOS; |
| |
| if (IsInsertingAfterRelatedMethods && InsertionGroupOS.str().empty()) |
| OS << "\n\n"; |
| // Print the method without the 'virtual' specifier and the pure '= 0' |
| // annotation. |
| auto *MD = const_cast<CXXMethodDecl *>(Method); |
| bool IsVirtual = MD->isVirtualAsWritten(); |
| MD->setVirtualAsWritten(false); |
| bool IsPure = MD->isPure(); |
| MD->setPure(false); |
| MD->print(OS, PP); |
| MD->setVirtualAsWritten(IsVirtual); |
| MD->setPure(IsPure); |
| |
| OS << " override"; |
| if (GenerateBodyDummies) |
| OS << " { \n <#code#>\n}\n"; |
| else |
| OS << ";\n"; |
| // Avoid an additional newline for the last method in an insertion group. |
| if (IsInsertingAfterRelatedMethods) { |
| const CXXRecordDecl *NextAbstractClass = |
| (I.index() + 1) != MissingMethods.size() |
| ? MissingMethods[I.index() + 1]->getParent() |
| : nullptr; |
| if (NextAbstractClass == CurrentAbstractClass) |
| OS << "\n"; |
| } else |
| OS << "\n"; |
| } |
| if (!InsertionGroupOS.str().empty()) { |
| assert(CurrentGroupInsertionLoc.isValid()); |
| Replacements.emplace_back( |
| SourceRange(CurrentGroupInsertionLoc, CurrentGroupInsertionLoc), |
| InsertionGroupOS.str()); |
| } |
| if (!EndInsertionOS.str().empty()) |
| Replacements.emplace_back(SourceRange(InsertionLoc, InsertionLoc), |
| EndInsertionOS.str()); |
| |
| return std::move(Replacements); |
| } |