| //===--- FblAtomicCheck.cpp - clang-tidy ----------------------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "FblAtomicCheck.h" |
| #include "../utils/LexerUtils.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Lex/Lexer.h" |
| #include "llvm/ADT/Optional.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace zircon { |
| |
| class FblAtomicPPCallbacks : public PPCallbacks { |
| public: |
| explicit FblAtomicPPCallbacks(FblAtomicCheck &Check, SourceManager &SM) |
| : Check(Check), SM(SM) {} |
| |
| void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| StringRef FileName, bool IsAngled, |
| CharSourceRange FilenameRange, const FileEntry *File, |
| StringRef SearchPath, StringRef RelativePath, |
| const Module *Imported, |
| SrcMgr::CharacteristicKind FileType) override; |
| void EndOfMainFile() override; |
| |
| private: |
| bool found = false; |
| CharSourceRange IncludeRange; |
| |
| FblAtomicCheck &Check; |
| SourceManager &SM; |
| }; |
| |
| void FblAtomicPPCallbacks::InclusionDirective( |
| SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, |
| bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, |
| StringRef SearchPath, StringRef RelativePath, const Module *Imported, |
| SrcMgr::CharacteristicKind FileType) { |
| if (FileName == "fbl/atomic.h") { |
| unsigned End = std::strcspn(SM.getCharacterData(HashLoc), "\n") + 1; |
| IncludeRange = |
| CharSourceRange::getCharRange(HashLoc, HashLoc.getLocWithOffset(End)); |
| found = true; |
| } |
| } |
| |
| void FblAtomicPPCallbacks::EndOfMainFile() { |
| // Any instance of <fbl/atomic.h> should be removed, since the decls in it |
| // will be replaced in this check and <atomic> added. |
| if (found) { |
| if (!SM.isInMainFile(IncludeRange.getBegin())) { |
| Check.diag(IncludeRange.getBegin(), |
| "including fbl/atomic.h is deprecated, transitively " |
| "included from %0") |
| << SM.getFilename(IncludeRange.getBegin()) |
| << FixItHint::CreateRemoval(IncludeRange); |
| return; |
| } |
| Check.diag(IncludeRange.getBegin(), "including fbl/atomic.h is deprecated") |
| << FixItHint::CreateRemoval(IncludeRange); |
| } |
| } |
| |
| void FblAtomicCheck::registerMatchers(MatchFinder *Finder) { |
| |
| // enum memory_order constants |
| Finder->addMatcher(declRefExpr(hasDeclaration(enumConstantDecl(allOf( |
| hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_relaxed"))))) |
| .bind("mo_const"), |
| this); |
| Finder->addMatcher(declRefExpr(hasDeclaration(enumConstantDecl(allOf( |
| hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_acquire"))))) |
| .bind("mo_const"), |
| this); |
| Finder->addMatcher(declRefExpr(hasDeclaration(enumConstantDecl(allOf( |
| hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_release"))))) |
| .bind("mo_const"), |
| this); |
| Finder->addMatcher(declRefExpr(hasDeclaration(enumConstantDecl(allOf( |
| hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_acq_rel"))))) |
| .bind("mo_const"), |
| this); |
| Finder->addMatcher(declRefExpr(hasDeclaration(enumConstantDecl(allOf( |
| hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_seq_cst"))))) |
| .bind("mo_const"), |
| this); |
| |
| // enum memory_order type |
| Finder->addMatcher( |
| valueDecl(hasType(enumDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("memory_order"))))) |
| .bind("valdecl"), |
| this); |
| |
| Finder->addMatcher(valueDecl(hasType(enumConstantDecl( |
| allOf(hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_relaxed"))))) |
| .bind("valdecl"), |
| this); |
| Finder->addMatcher(valueDecl(hasType(enumConstantDecl( |
| allOf(hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_acquire"))))) |
| .bind("valdecl"), |
| this); |
| Finder->addMatcher(valueDecl(hasType(enumConstantDecl( |
| allOf(hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_release"))))) |
| .bind("valdecl"), |
| this); |
| Finder->addMatcher(valueDecl(hasType(enumConstantDecl( |
| allOf(hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_acq_rel"))))) |
| .bind("valdecl"), |
| this); |
| Finder->addMatcher(valueDecl(hasType(enumConstantDecl( |
| allOf(hasDeclContext(enumDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("memory_order_seq_cst"))))) |
| .bind("valdecl"), |
| this); |
| |
| // decls of fbl::atomic<T> and fbl::atomic<T*> and aliases. |
| Finder->addMatcher( |
| valueDecl(hasType(namedDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic"))))) |
| .bind("valdecl"), |
| this); |
| Finder->addMatcher( |
| cxxTemporaryObjectExpr(hasDeclaration(functionDecl(allOf( |
| hasDeclContext(cxxRecordDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("atomic"))))) |
| .bind("expr"), |
| this); |
| |
| #define ALIAS_MATCHER(name) \ |
| Finder->addMatcher( \ |
| valueDecl(hasType(typeAliasDecl( \ |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), \ |
| hasName(name))))) \ |
| .bind("valdecl"), \ |
| this); |
| |
| ALIAS_MATCHER("atomic_char") |
| ALIAS_MATCHER("atomic_schar") |
| ALIAS_MATCHER("atomic_uchar") |
| ALIAS_MATCHER("atomic_short") |
| ALIAS_MATCHER("atomic_ushort") |
| ALIAS_MATCHER("atomic_int") |
| ALIAS_MATCHER("atomic_uint") |
| ALIAS_MATCHER("atomic_long") |
| ALIAS_MATCHER("atomic_ulong") |
| ALIAS_MATCHER("atomic_llong") |
| ALIAS_MATCHER("atomic_ullong") |
| |
| ALIAS_MATCHER("atomic_intptr_t") |
| ALIAS_MATCHER("atomic_uintptr_t") |
| ALIAS_MATCHER("atomic_size_t") |
| ALIAS_MATCHER("atomic_ptrdiff_t") |
| ALIAS_MATCHER("atomic_intmax_t") |
| ALIAS_MATCHER("atomic_uintmax_t") |
| |
| ALIAS_MATCHER("atomic_int8_t") |
| ALIAS_MATCHER("atomic_uint8_t") |
| ALIAS_MATCHER("atomic_int16_t") |
| ALIAS_MATCHER("atomic_uint16_t") |
| ALIAS_MATCHER("atomic_int32_t") |
| ALIAS_MATCHER("atomic_uint32_t") |
| ALIAS_MATCHER("atomic_int64_t") |
| ALIAS_MATCHER("atomic_uint64_t") |
| |
| ALIAS_MATCHER("atomic_int_least8_t") |
| ALIAS_MATCHER("atomic_uint_least8_t") |
| ALIAS_MATCHER("atomic_int_least16_t") |
| ALIAS_MATCHER("atomic_uint_least16_t") |
| ALIAS_MATCHER("atomic_int_least32_t") |
| ALIAS_MATCHER("atomic_uint_least32_t") |
| ALIAS_MATCHER("atomic_int_least64_t") |
| ALIAS_MATCHER("atomic_uint_least64_t") |
| ALIAS_MATCHER("atomic_int_fast8_t") |
| ALIAS_MATCHER("atomic_uint_fast8_t") |
| ALIAS_MATCHER("atomic_int_fast16_t") |
| ALIAS_MATCHER("atomic_uint_fast16_t") |
| ALIAS_MATCHER("atomic_int_fast32_t") |
| ALIAS_MATCHER("atomic_uint_fast32_t") |
| ALIAS_MATCHER("atomic_int_fast64_t") |
| ALIAS_MATCHER("atomic_uint_fast64_t") |
| |
| ALIAS_MATCHER("atomic_bool") |
| |
| #undef ALIAS_MATCHER |
| |
| // compare_exchange_weak and _strong |
| Finder->addMatcher( |
| cxxMemberCallExpr(callee(cxxMethodDecl( |
| allOf(hasDeclContext(cxxRecordDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("compare_exchange_weak"))))) |
| .bind("method"), |
| this); |
| Finder->addMatcher( |
| cxxMemberCallExpr(callee(cxxMethodDecl( |
| allOf(hasDeclContext(cxxRecordDecl(hasDeclContext( |
| namespaceDecl(hasName("::fbl"))))), |
| hasName("compare_exchange_strong"))))) |
| .bind("method"), |
| this); |
| |
| // Functions |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_store"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_load"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_exchange"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_compare_exchange_weak"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_compare_exchange_strong"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_fetch_add"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_fetch_sub"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_fetch_and"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_fetch_or"))))) |
| .bind("func"), |
| this); |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_fetch_xor"))))) |
| .bind("func"), |
| this); |
| |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_init"))))) |
| .bind("func"), |
| this); |
| |
| // atomic_thread_fence |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_thread_fence"))))) |
| .bind("func_with_default"), |
| this); |
| // atomic_signal_fence |
| Finder->addMatcher( |
| callExpr(callee(functionDecl( |
| allOf(hasDeclContext(namespaceDecl(hasName("::fbl"))), |
| hasName("atomic_signal_fence"))))) |
| .bind("func_with_default"), |
| this); |
| } |
| |
| void FblAtomicCheck::registerPPCallbacks(CompilerInstance &Compiler) { |
| Inserter = llvm::make_unique<utils::IncludeInserter>( |
| Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle); |
| Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); |
| Compiler.getPreprocessor().addPPCallbacks( |
| llvm::make_unique<FblAtomicPPCallbacks>(*this, |
| Compiler.getSourceManager())); |
| } |
| |
| StringRef FblAtomicCheck::getStatement(SourceManager &SM, SourceLocation &Start, |
| SourceLocation &End, bool check_fbl) { |
| // If it's a macro, we want to extract information about the macro instead |
| // of the expr. |
| if (Start.isMacroID()) { |
| Start = SM.getSpellingLoc(Start); |
| End = SM.getSpellingLoc(End); |
| } |
| |
| // Extract the source text. |
| bool Invalid = false; |
| StringRef Statement = Lexer::getSourceText( |
| CharSourceRange::getCharRange(Start, End), SM, getLangOpts(), &Invalid); |
| if (Invalid) |
| return ""; |
| if (check_fbl && !Statement.startswith("fbl")) |
| return ""; |
| return Statement; |
| } |
| |
| void FblAtomicCheck::emitWarning(SourceManager &SM, StringRef Name, |
| StringRef ReplacementName, |
| SourceLocation Start, SourceLocation End, |
| FixItHint *ExtraFixit) { |
| Name = Name.ltrim("fbl::"); |
| ReplacementName = ReplacementName.ltrim("fbl::"); |
| // Emit the appropriate diagnostic. |
| DiagnosticBuilder Diag = |
| diag(Start, "use of fbl::%0 is deprecated, use " |
| "std::%1 instead") |
| << Name << ReplacementName |
| << FixItHint::CreateReplacement(SourceRange(Start, End), |
| ("std::" + ReplacementName).str()); |
| |
| // Add in header if it hasn't been added already. |
| if (llvm::Optional<FixItHint> IncludeFixit = |
| Inserter->CreateIncludeInsertion(SM.getFileID(Start), "optional", |
| /*IsAngled=*/true)) |
| Diag << *IncludeFixit; |
| |
| if (ExtraFixit) |
| Diag << *ExtraFixit; |
| } |
| |
| void FblAtomicCheck::check(const MatchFinder::MatchResult &Result) { |
| SourceManager &SM = *Result.SourceManager; |
| |
| // For memory_order_const, we just emit the basic warning and replacements. |
| if (const auto *V = Result.Nodes.getNodeAs<DeclRefExpr>("mo_const")) { |
| std::string Name = V->getDecl()->getNameAsString(); |
| SourceLocation Start = V->getBeginLoc(); |
| SourceLocation End = V->getEndLoc(); |
| emitWarning(SM, Name, Name, Start, End); |
| } |
| |
| // For the methods, we have to fix the reference to pointer conversion, but no |
| // name replacement is necessary. |
| if (const auto *E = Result.Nodes.getNodeAs<CXXMemberCallExpr>("method")) { |
| if (E->getNumArgs() < 1) |
| return; |
| |
| // Get the first arg. |
| const Expr *A = E->getArg(0); |
| |
| DiagnosticBuilder Diag = |
| diag(A->getBeginLoc(), |
| "std::atomic requires references instead of pointers"); |
| |
| SourceLocation Start = A->getBeginLoc(); |
| SourceLocation End = A->getEndLoc(); |
| StringRef Statement = getStatement(SM, Start, End, /*check_fbl=*/false); |
| |
| size_t Amp = Statement.find("&"); |
| if (Amp != std::string::npos) |
| Diag << FixItHint::CreateRemoval(A->getBeginLoc()); |
| else |
| Diag << FixItHint::CreateInsertion(A->getBeginLoc(), "*"); |
| } |
| |
| // For functions, we need to replace the name and add '_explicit' if it calls |
| // out the default parameter. |
| if (const auto *E = Result.Nodes.getNodeAs<CallExpr>("func")) { |
| |
| std::string Name = E->getDirectCallee()->getNameAsString(); |
| std::string ReplacementName = Name; |
| // if there's three args, then memory order is set explicitly and the |
| // equivalent std function calls that out. |
| if (E->getNumArgs() == 3 && !dyn_cast<CXXDefaultArgExpr>(E->getArg(2))) |
| ReplacementName += "_explicit"; |
| |
| SourceManager &SM = *Result.SourceManager; |
| SourceLocation Start = E->getBeginLoc(); |
| SourceLocation End = E->getEndLoc(); |
| StringRef Statement = getStatement(SM, Start, End, /*check_fbl=*/true); |
| |
| // Find the template bracket, or find the lparen if the bracket isn't there. |
| size_t LParen = Statement.find('('); |
| size_t LAngle = Statement.find('<'); |
| if (LAngle == std::string::npos && LParen == std::string::npos) |
| return; |
| else if (LParen < LAngle) |
| End = Start.getLocWithOffset(LParen - 1); |
| else |
| End = Start.getLocWithOffset(LAngle - 1); |
| |
| emitWarning(SM, Name, ReplacementName, Start, End); |
| } |
| |
| // For these functions, we need to add in the default argument if it's relied |
| // on. |
| if (const auto *E = Result.Nodes.getNodeAs<CallExpr>("func_with_default")) { |
| |
| std::string Name = E->getDirectCallee()->getNameAsString(); |
| |
| // Find the end of the name of the call. |
| SourceManager &SM = *Result.SourceManager; |
| SourceLocation Start = E->getBeginLoc(); |
| SourceLocation End = E->getEndLoc(); |
| StringRef Statement = getStatement(SM, Start, End, /*check_fbl=*/true); |
| |
| // Find the template bracket, or find the lparen if the bracket isn't there. |
| size_t LParen = Statement.find('('); |
| size_t LAngle = Statement.find('<'); |
| if (LAngle == std::string::npos && LParen == std::string::npos) |
| return; |
| else if (LParen < LAngle) |
| End = Start.getLocWithOffset(LParen - 1); |
| else |
| End = Start.getLocWithOffset(LAngle - 1); |
| |
| if (E->getNumArgs() == 1 && dyn_cast<CXXDefaultArgExpr>(E->getArg(0))) { |
| // std::memory_order_seq_cst is the default value provided by fbl. |
| FixItHint DefaultArgFixit = FixItHint::CreateInsertion( |
| E->getEndLoc(), "std::memory_order_seq_cst"); |
| emitWarning(SM, Name, Name, Start, End, &DefaultArgFixit); |
| return; |
| } |
| emitWarning(SM, Name, Name, Start, End); |
| } |
| |
| // Otherwise, it's an cxxconstructexpr or a valdecl, and we want to replace |
| // the name. |
| SourceLocation Start; |
| SourceLocation End; |
| std::string Name; |
| if (const auto *V = Result.Nodes.getNodeAs<ValueDecl>("valdecl")) { |
| Start = V->getBeginLoc(); |
| End = V->getEndLoc(); |
| Name = V->getType().getAsString(); |
| } else if (const auto *V = |
| Result.Nodes.getNodeAs<CXXTemporaryObjectExpr>("expr")) { |
| Start = V->getBeginLoc(); |
| End = V->getEndLoc(); |
| Name = V->getType().getAsString(); |
| } else |
| return; |
| |
| // If it's a macro, we want to extract information about the macro instead |
| // of the expr. |
| if (Start.isMacroID()) |
| Start = SM.getSpellingLoc(Start); |
| |
| if (!StringRef(Name).startswith("fbl")) |
| return; |
| End = Start.getLocWithOffset(Name.size() - 1); |
| emitWarning(SM, Name, Name, Start, End); |
| } |
| |
| void FblAtomicCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "IncludeStyle", IncludeStyle); |
| } |
| |
| } // namespace zircon |
| } // namespace tidy |
| } // namespace clang |