blob: 0712977c4356d440c35fc159883a2504298f1bea [file] [log] [blame] [edit]
//===--- 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