teak-llvm/clang-tools-extra/clang-tidy/misc/UseAfterMoveCheck.cpp
Mandeep Singh Grang 7c7ea7d0ae [clang-tools-extra] Format sources with clang-format. NFC.
Summary:
Ran clang-format on all .c/.cpp/.h files in clang-tools-extra.
Excluded the test, unittests, clang-reorder-fields, include-fixer, modularize and pptrace directories.

Reviewers: klimek, alexfh

Subscribers: nemanjai

Tags: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D26329

llvm-svn: 286221
2016-11-08 07:50:19 +00:00

684 lines
27 KiB
C++

//===--- UseAfterMoveCheck.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 "UseAfterMoveCheck.h"
#include "clang/Analysis/CFG.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace misc {
namespace {
/// Provides information about the evaluation order of (sub-)expressions within
/// a `CFGBlock`.
///
/// While a `CFGBlock` does contain individual `CFGElement`s for some
/// sub-expressions, the order in which those `CFGElement`s appear reflects
/// only one possible order in which the sub-expressions may be evaluated.
/// However, we want to warn if any of the potential evaluation orders can lead
/// to a use-after-move, not just the one contained in the `CFGBlock`.
///
/// This class implements only a simplified version of the C++ sequencing rules
/// that is, however, sufficient for the purposes of this check. The main
/// limitation is that we do not distinguish between value computation and side
/// effect -- see the "Implementation" section for more details.
///
/// Note: `SequenceChecker` from SemaChecking.cpp does a similar job (and much
/// more thoroughly), but using it would require
/// - Pulling `SequenceChecker` out into a header file (i.e. making it part of
/// the API),
/// - Removing the dependency of `SequenceChecker` on `Sema`, and
/// - (Probably) modifying `SequenceChecker` to make it suitable to be used in
/// this context.
/// For the moment, it seems preferable to re-implement our own version of
/// sequence checking that is special-cased to what we need here.
///
/// Implementation
/// --------------
///
/// `ExprSequence` uses two types of sequencing edges between nodes in the AST:
///
/// - Every `Stmt` is assumed to be sequenced after its children. This is
/// overly optimistic because the standard only states that value computations
/// of operands are sequenced before the value computation of the operator,
/// making no guarantees about side effects (in general).
///
/// For our purposes, this rule is sufficient, however, because this check is
/// interested in operations on objects, which are generally performed through
/// function calls (whether explicit and implicit). Function calls guarantee
/// that the value computations and side effects for all function arguments
/// are sequenced before the execution fo the function.
///
/// - In addition, some `Stmt`s are known to be sequenced before or after
/// their siblings. For example, the `Stmt`s that make up a `CompoundStmt`are
/// all sequenced relative to each other. The function
/// `getSequenceSuccessor()` implements these sequencing rules.
class ExprSequence {
public:
/// Initializes this `ExprSequence` with sequence information for the given
/// `CFG`.
ExprSequence(const CFG *TheCFG, ASTContext *TheContext);
/// Returns whether \p Before is sequenced before \p After.
bool inSequence(const Stmt *Before, const Stmt *After) const;
/// Returns whether \p After can potentially be evaluated after \p Before.
/// This is exactly equivalent to `!inSequence(After, Before)` but makes some
/// conditions read more naturally.
bool potentiallyAfter(const Stmt *After, const Stmt *Before) const;
private:
// Returns the sibling of \p S (if any) that is directly sequenced after \p S,
// or nullptr if no such sibling exists. For example, if \p S is the child of
// a `CompoundStmt`, this would return the Stmt that directly follows \p S in
// the `CompoundStmt`.
//
// As the sequencing of many constructs that change control flow is already
// encoded in the `CFG`, this function only implements the sequencing rules
// for those constructs where sequencing cannot be inferred from the `CFG`.
const Stmt *getSequenceSuccessor(const Stmt *S) const;
const Stmt *resolveSyntheticStmt(const Stmt *S) const;
ASTContext *Context;
llvm::DenseMap<const Stmt *, const Stmt *> SyntheticStmtSourceMap;
};
/// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be
/// contained in more than one `CFGBlock`; in this case, they are mapped to the
/// innermost block (i.e. the one that is furthest from the root of the tree).
class StmtToBlockMap {
public:
/// Initializes the map for the given `CFG`.
StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext);
/// Returns the block that \p S is contained in. Some `Stmt`s may be contained
/// in more than one `CFGBlock`; in this case, this function returns the
/// innermost block (i.e. the one that is furthest from the root of the tree).
const CFGBlock *blockContainingStmt(const Stmt *S) const;
private:
ASTContext *Context;
llvm::DenseMap<const Stmt *, const CFGBlock *> Map;
};
/// Contains information about a use-after-move.
struct UseAfterMove {
// The DeclRefExpr that constituted the use of the object.
const DeclRefExpr *DeclRef;
// Is the order in which the move and the use are evaluated undefined?
bool EvaluationOrderUndefined;
};
/// Finds uses of a variable after a move (and maintains state required by the
/// various internal helper functions).
class UseAfterMoveFinder {
public:
UseAfterMoveFinder(ASTContext *TheContext);
// Within the given function body, finds the first use of 'MovedVariable' that
// occurs after 'MovingCall' (the expression that performs the move). If a
// use-after-move is found, writes information about it to 'TheUseAfterMove'.
// Returns whether a use-after-move was found.
bool find(Stmt *FunctionBody, const Expr *MovingCall,
const ValueDecl *MovedVariable, UseAfterMove *TheUseAfterMove);
private:
bool findInternal(const CFGBlock *Block, const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove);
void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *MovedVariable,
llvm::SmallVectorImpl<const DeclRefExpr *> *Uses,
llvm::SmallPtrSetImpl<const Stmt *> *Reinits);
void getDeclRefs(const CFGBlock *Block, const Decl *MovedVariable,
llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
void getReinits(const CFGBlock *Block, const ValueDecl *MovedVariable,
llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
ASTContext *Context;
std::unique_ptr<ExprSequence> Sequence;
std::unique_ptr<StmtToBlockMap> BlockMap;
llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
};
} // namespace
// Returns the Stmt nodes that are parents of 'S', skipping any potential
// intermediate non-Stmt nodes.
//
// In almost all cases, this function returns a single parent or no parents at
// all.
//
// The case that a Stmt has multiple parents is rare but does actually occur in
// the parts of the AST that we're interested in. Specifically, InitListExpr
// nodes cause ASTContext::getParent() to return multiple parents for certain
// nodes in their subtree because RecursiveASTVisitor visits both the syntactic
// and semantic forms of InitListExpr, and the parent-child relationships are
// different between the two forms.
static SmallVector<const Stmt *, 1> getParentStmts(const Stmt *S,
ASTContext *Context) {
SmallVector<const Stmt *, 1> Result;
ASTContext::DynTypedNodeList Parents = Context->getParents(*S);
SmallVector<ast_type_traits::DynTypedNode, 1> NodesToProcess(Parents.begin(),
Parents.end());
while (!NodesToProcess.empty()) {
ast_type_traits::DynTypedNode Node = NodesToProcess.back();
NodesToProcess.pop_back();
if (const auto *S = Node.get<Stmt>()) {
Result.push_back(S);
} else {
Parents = Context->getParents(Node);
NodesToProcess.append(Parents.begin(), Parents.end());
}
}
return Result;
}
bool isDescendantOrEqual(const Stmt *Descendant, const Stmt *Ancestor,
ASTContext *Context) {
if (Descendant == Ancestor)
return true;
for (const Stmt *Parent : getParentStmts(Descendant, Context)) {
if (isDescendantOrEqual(Parent, Ancestor, Context))
return true;
}
return false;
}
ExprSequence::ExprSequence(const CFG *TheCFG, ASTContext *TheContext)
: Context(TheContext) {
for (const auto &SyntheticStmt : TheCFG->synthetic_stmts()) {
SyntheticStmtSourceMap[SyntheticStmt.first] = SyntheticStmt.second;
}
}
bool ExprSequence::inSequence(const Stmt *Before, const Stmt *After) const {
Before = resolveSyntheticStmt(Before);
After = resolveSyntheticStmt(After);
// If 'After' is in the subtree of the siblings that follow 'Before' in the
// chain of successors, we know that 'After' is sequenced after 'Before'.
for (const Stmt *Successor = getSequenceSuccessor(Before); Successor;
Successor = getSequenceSuccessor(Successor)) {
if (isDescendantOrEqual(After, Successor, Context))
return true;
}
// If 'After' is a parent of 'Before' or is sequenced after one of these
// parents, we know that it is sequenced after 'Before'.
for (const Stmt *Parent : getParentStmts(Before, Context)) {
if (Parent == After || inSequence(Parent, After))
return true;
}
return false;
}
bool ExprSequence::potentiallyAfter(const Stmt *After,
const Stmt *Before) const {
return !inSequence(After, Before);
}
const Stmt *ExprSequence::getSequenceSuccessor(const Stmt *S) const {
for (const Stmt *Parent : getParentStmts(S, Context)) {
if (const auto *BO = dyn_cast<BinaryOperator>(Parent)) {
// Comma operator: Right-hand side is sequenced after the left-hand side.
if (BO->getLHS() == S && BO->getOpcode() == BO_Comma)
return BO->getRHS();
} else if (const auto *InitList = dyn_cast<InitListExpr>(Parent)) {
// Initializer list: Each initializer clause is sequenced after the
// clauses that precede it.
for (unsigned I = 1; I < InitList->getNumInits(); ++I) {
if (InitList->getInit(I - 1) == S)
return InitList->getInit(I);
}
} else if (const auto *Compound = dyn_cast<CompoundStmt>(Parent)) {
// Compound statement: Each sub-statement is sequenced after the
// statements that precede it.
const Stmt *Previous = nullptr;
for (const auto *Child : Compound->body()) {
if (Previous == S)
return Child;
Previous = Child;
}
} else if (const auto *TheDeclStmt = dyn_cast<DeclStmt>(Parent)) {
// Declaration: Every initializer expression is sequenced after the
// initializer expressions that precede it.
const Expr *PreviousInit = nullptr;
for (const Decl *TheDecl : TheDeclStmt->decls()) {
if (const auto *TheVarDecl = dyn_cast<VarDecl>(TheDecl)) {
if (const Expr *Init = TheVarDecl->getInit()) {
if (PreviousInit == S)
return Init;
PreviousInit = Init;
}
}
}
} else if (const auto *ForRange = dyn_cast<CXXForRangeStmt>(Parent)) {
// Range-based for: Loop variable declaration is sequenced before the
// body. (We need this rule because these get placed in the same
// CFGBlock.)
if (S == ForRange->getLoopVarStmt())
return ForRange->getBody();
} else if (const auto *TheIfStmt = dyn_cast<IfStmt>(Parent)) {
// If statement: If a variable is declared inside the condition, the
// expression used to initialize the variable is sequenced before the
// evaluation of the condition.
if (S == TheIfStmt->getConditionVariableDeclStmt())
return TheIfStmt->getCond();
}
}
return nullptr;
}
const Stmt *ExprSequence::resolveSyntheticStmt(const Stmt *S) const {
if (SyntheticStmtSourceMap.count(S))
return SyntheticStmtSourceMap.lookup(S);
else
return S;
}
StmtToBlockMap::StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext)
: Context(TheContext) {
for (const auto *B : *TheCFG) {
for (const auto &Elem : *B) {
if (Optional<CFGStmt> S = Elem.getAs<CFGStmt>())
Map[S->getStmt()] = B;
}
}
}
const CFGBlock *StmtToBlockMap::blockContainingStmt(const Stmt *S) const {
while (!Map.count(S)) {
SmallVector<const Stmt *, 1> Parents = getParentStmts(S, Context);
if (Parents.empty())
return nullptr;
S = Parents[0];
}
return Map.lookup(S);
}
// Matches nodes that are
// - Part of a decltype argument or class template argument (we check this by
// seeing if they are children of a TypeLoc), or
// - Part of a function template argument (we check this by seeing if they are
// children of a DeclRefExpr that references a function template).
// DeclRefExprs that fulfill these conditions should not be counted as a use or
// move.
static StatementMatcher inDecltypeOrTemplateArg() {
return anyOf(hasAncestor(typeLoc()),
hasAncestor(declRefExpr(
to(functionDecl(ast_matchers::isTemplateInstantiation())))));
}
UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
: Context(TheContext) {}
bool UseAfterMoveFinder::find(Stmt *FunctionBody, const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove) {
// Generate the CFG manually instead of through an AnalysisDeclContext because
// it seems the latter can't be used to generate a CFG for the body of a
// labmda.
//
// We include implicit and temporary destructors in the CFG so that
// destructors marked [[noreturn]] are handled correctly in the control flow
// analysis. (These are used in some styles of assertion macros.)
CFG::BuildOptions Options;
Options.AddImplicitDtors = true;
Options.AddTemporaryDtors = true;
std::unique_ptr<CFG> TheCFG =
CFG::buildCFG(nullptr, FunctionBody, Context, Options);
if (!TheCFG)
return false;
Sequence.reset(new ExprSequence(TheCFG.get(), Context));
BlockMap.reset(new StmtToBlockMap(TheCFG.get(), Context));
Visited.clear();
const CFGBlock *Block = BlockMap->blockContainingStmt(MovingCall);
if (!Block)
return false;
return findInternal(Block, MovingCall, MovedVariable, TheUseAfterMove);
}
bool UseAfterMoveFinder::findInternal(const CFGBlock *Block,
const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove) {
if (Visited.count(Block))
return false;
// Mark the block as visited (except if this is the block containing the
// std::move() and it's being visited the first time).
if (!MovingCall)
Visited.insert(Block);
// Get all uses and reinits in the block.
llvm::SmallVector<const DeclRefExpr *, 1> Uses;
llvm::SmallPtrSet<const Stmt *, 1> Reinits;
getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits);
// Ignore all reinitializations where the move potentially comes after the
// reinit.
llvm::SmallVector<const Stmt *, 1> ReinitsToDelete;
for (const Stmt *Reinit : Reinits) {
if (MovingCall && Sequence->potentiallyAfter(MovingCall, Reinit))
ReinitsToDelete.push_back(Reinit);
}
for (const Stmt *Reinit : ReinitsToDelete) {
Reinits.erase(Reinit);
}
// Find all uses that potentially come after the move.
for (const DeclRefExpr *Use : Uses) {
if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) {
// Does the use have a saving reinit? A reinit is saving if it definitely
// comes before the use, i.e. if there's no potential that the reinit is
// after the use.
bool HaveSavingReinit = false;
for (const Stmt *Reinit : Reinits) {
if (!Sequence->potentiallyAfter(Reinit, Use))
HaveSavingReinit = true;
}
if (!HaveSavingReinit) {
TheUseAfterMove->DeclRef = Use;
// Is this a use-after-move that depends on order of evaluation?
// This is the case if the move potentially comes after the use (and we
// already know that use potentially comes after the move, which taken
// together tells us that the ordering is unclear).
TheUseAfterMove->EvaluationOrderUndefined =
MovingCall != nullptr &&
Sequence->potentiallyAfter(MovingCall, Use);
return true;
}
}
}
// If the object wasn't reinitialized, call ourselves recursively on all
// successors.
if (Reinits.empty()) {
for (const auto &Succ : Block->succs()) {
if (Succ && findInternal(Succ, nullptr, MovedVariable, TheUseAfterMove))
return true;
}
}
return false;
}
void UseAfterMoveFinder::getUsesAndReinits(
const CFGBlock *Block, const ValueDecl *MovedVariable,
llvm::SmallVectorImpl<const DeclRefExpr *> *Uses,
llvm::SmallPtrSetImpl<const Stmt *> *Reinits) {
llvm::SmallPtrSet<const DeclRefExpr *, 1> DeclRefs;
llvm::SmallPtrSet<const DeclRefExpr *, 1> ReinitDeclRefs;
getDeclRefs(Block, MovedVariable, &DeclRefs);
getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs);
// All references to the variable that aren't reinitializations are uses.
Uses->clear();
for (const DeclRefExpr *DeclRef : DeclRefs) {
if (!ReinitDeclRefs.count(DeclRef))
Uses->push_back(DeclRef);
}
// Sort the uses by their occurrence in the source code.
std::sort(Uses->begin(), Uses->end(),
[](const DeclRefExpr *D1, const DeclRefExpr *D2) {
return D1->getExprLoc() < D2->getExprLoc();
});
}
bool isStandardSmartPointer(const ValueDecl *VD) {
const Type *TheType = VD->getType().getTypePtrOrNull();
if (!TheType)
return false;
const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl();
if (!RecordDecl)
return false;
const IdentifierInfo *ID = RecordDecl->getIdentifier();
if (!ID)
return false;
StringRef Name = ID->getName();
if (Name != "unique_ptr" && Name != "shared_ptr" && Name != "weak_ptr")
return false;
return RecordDecl->getDeclContext()->isStdNamespace();
}
void UseAfterMoveFinder::getDeclRefs(
const CFGBlock *Block, const Decl *MovedVariable,
llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
DeclRefs->clear();
for (const auto &Elem : *Block) {
Optional<CFGStmt> S = Elem.getAs<CFGStmt>();
if (!S)
continue;
auto addDeclRefs = [this, Block,
DeclRefs](const ArrayRef<BoundNodes> Matches) {
for (const auto &Match : Matches) {
const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
const auto *Operator = Match.getNodeAs<CXXOperatorCallExpr>("operator");
if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) {
// Ignore uses of a standard smart pointer that don't dereference the
// pointer.
if (Operator || !isStandardSmartPointer(DeclRef->getDecl())) {
DeclRefs->insert(DeclRef);
}
}
}
};
auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
unless(inDecltypeOrTemplateArg()))
.bind("declref");
addDeclRefs(match(findAll(DeclRefMatcher), *S->getStmt(), *Context));
addDeclRefs(match(
findAll(cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("*"),
hasOverloadedOperatorName("->"),
hasOverloadedOperatorName("[]")),
hasArgument(0, DeclRefMatcher))
.bind("operator")),
*S->getStmt(), *Context));
}
}
void UseAfterMoveFinder::getReinits(
const CFGBlock *Block, const ValueDecl *MovedVariable,
llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
auto DeclRefMatcher =
declRefExpr(hasDeclaration(equalsNode(MovedVariable))).bind("declref");
auto StandardContainerTypeMatcher = hasType(cxxRecordDecl(
hasAnyName("::std::basic_string", "::std::vector", "::std::deque",
"::std::forward_list", "::std::list", "::std::set",
"::std::map", "::std::multiset", "::std::multimap",
"::std::unordered_set", "::std::unordered_map",
"::std::unordered_multiset", "::std::unordered_multimap")));
auto StandardSmartPointerTypeMatcher = hasType(cxxRecordDecl(
hasAnyName("::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr")));
// Matches different types of reinitialization.
auto ReinitMatcher =
stmt(anyOf(
// Assignment. In addition to the overloaded assignment operator,
// test for built-in assignment as well, since template functions
// may be instantiated to use std::move() on built-in types.
binaryOperator(hasOperatorName("="), hasLHS(DeclRefMatcher)),
cxxOperatorCallExpr(hasOverloadedOperatorName("="),
hasArgument(0, DeclRefMatcher)),
// Declaration. We treat this as a type of reinitialization too,
// so we don't need to treat it separately.
declStmt(hasDescendant(equalsNode(MovedVariable))),
// clear() and assign() on standard containers.
cxxMemberCallExpr(
on(allOf(DeclRefMatcher, StandardContainerTypeMatcher)),
// To keep the matcher simple, we check for assign() calls
// on all standard containers, even though only vector,
// deque, forward_list and list have assign(). If assign()
// is called on any of the other containers, this will be
// flagged by a compile error anyway.
callee(cxxMethodDecl(hasAnyName("clear", "assign")))),
// reset() on standard smart pointers.
cxxMemberCallExpr(
on(allOf(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
callee(cxxMethodDecl(hasName("reset")))),
// Passing variable to a function as a non-const pointer.
callExpr(forEachArgumentWithParam(
unaryOperator(hasOperatorName("&"),
hasUnaryOperand(DeclRefMatcher)),
unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))),
// Passing variable to a function as a non-const lvalue reference
// (unless that function is std::move()).
callExpr(forEachArgumentWithParam(
DeclRefMatcher,
unless(parmVarDecl(hasType(
references(qualType(isConstQualified())))))),
unless(callee(functionDecl(hasName("::std::move")))))))
.bind("reinit");
Stmts->clear();
DeclRefs->clear();
for (const auto &Elem : *Block) {
Optional<CFGStmt> S = Elem.getAs<CFGStmt>();
if (!S)
continue;
SmallVector<BoundNodes, 1> Matches =
match(findAll(ReinitMatcher), *S->getStmt(), *Context);
for (const auto &Match : Matches) {
const auto *TheStmt = Match.getNodeAs<Stmt>("reinit");
const auto *TheDeclRef = Match.getNodeAs<DeclRefExpr>("declref");
if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) {
Stmts->insert(TheStmt);
// We count DeclStmts as reinitializations, but they don't have a
// DeclRefExpr associated with them -- so we need to check 'TheDeclRef'
// before adding it to the set.
if (TheDeclRef)
DeclRefs->insert(TheDeclRef);
}
}
}
}
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
const UseAfterMove &Use, ClangTidyCheck *Check,
ASTContext *Context) {
SourceLocation UseLoc = Use.DeclRef->getExprLoc();
SourceLocation MoveLoc = MovingCall->getExprLoc();
Check->diag(UseLoc, "'%0' used after it was moved")
<< MoveArg->getDecl()->getName();
Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note);
if (Use.EvaluationOrderUndefined) {
Check->diag(UseLoc,
"the use and move are unsequenced, i.e. there is no guarantee "
"about the order in which they are evaluated",
DiagnosticIDs::Note);
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
Check->diag(UseLoc,
"the use happens in a later loop iteration than the move",
DiagnosticIDs::Note);
}
}
void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus11)
return;
auto CallMoveMatcher =
callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1),
hasArgument(0, declRefExpr().bind("arg")),
anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")),
hasAncestor(functionDecl().bind("containing-func"))),
unless(inDecltypeOrTemplateArg()))
.bind("call-move");
Finder->addMatcher(
// To find the Stmt that we assume performs the actual move, we look for
// the direct ancestor of the std::move() that isn't one of the node
// types ignored by ignoringParenImpCasts().
stmt(forEach(expr(ignoringParenImpCasts(CallMoveMatcher))),
unless(expr(ignoringParenImpCasts(equalsBoundNode("call-move")))))
.bind("moving-call"),
this);
}
void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ContainingLambda =
Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");
const auto *ContainingFunc =
Result.Nodes.getNodeAs<FunctionDecl>("containing-func");
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
if (!MovingCall)
MovingCall = CallMove;
Stmt *FunctionBody = nullptr;
if (ContainingLambda)
FunctionBody = ContainingLambda->getBody();
else if (ContainingFunc)
FunctionBody = ContainingFunc->getBody();
else
return;
// Ignore the std::move if the variable that was passed to it isn't a local
// variable.
if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())
return;
UseAfterMoveFinder finder(Result.Context);
UseAfterMove Use;
if (finder.find(FunctionBody, MovingCall, Arg->getDecl(), &Use))
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context);
}
} // namespace misc
} // namespace tidy
} // namespace clang