blob: a48e45e13568134d44a5a080757c5838407823a6 [file] [log] [blame]
//===--- DeclRefExprUtils.cpp - clang-tidy---------------------------------===//
//
// 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 "DeclRefExprUtils.h"
#include "Matchers.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <cassert>
namespace clang::tidy::utils::decl_ref_expr {
using namespace ::clang::ast_matchers;
using llvm::SmallPtrSet;
namespace {
template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) {
for (auto E : S1)
if (S2.count(E) == 0)
return false;
return true;
}
// Extracts all Nodes keyed by ID from Matches and inserts them into Nodes.
template <typename Node>
void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
SmallPtrSet<const Node *, 16> &Nodes) {
for (const auto &Match : Matches)
Nodes.insert(Match.getNodeAs<Node>(ID));
}
// A matcher that matches DeclRefExprs that are used in ways such that the
// underlying declaration is not modified.
// If the declaration is of pointer type, `Indirections` specifies the level
// of indirection of the object whose mutations we are tracking.
//
// For example, given:
// ```
// int i;
// int* p;
// p = &i; // (A)
// *p = 3; // (B)
// ```
//
// `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches
// (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))`
// matches (A).
//
AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
// We walk up the parents of the DeclRefExpr recursively until we end up on a
// parent that cannot modify the underlying object. There are a few kinds of
// expressions:
// - Those that cannot be used to mutate the underlying object. We can stop
// recursion there.
// - Those that can be used to mutate the underlying object in analyzable
// ways (such as taking the address or accessing a subobject). We have to
// examine the parents.
// - Those that we don't know how to analyze. In that case we stop there and
// we assume that they can mutate the underlying expression.
struct StackEntry {
StackEntry(const Expr *E, int Indirections)
: E(E), Indirections(Indirections) {}
// The expression to analyze.
const Expr *E;
// The number of pointer indirections of the object being tracked (how
// many times an address was taken).
int Indirections;
};
llvm::SmallVector<StackEntry, 4> Stack;
Stack.emplace_back(&Node, Indirections);
ASTContext &Ctx = Finder->getASTContext();
while (!Stack.empty()) {
const StackEntry Entry = Stack.back();
Stack.pop_back();
// If the expression type is const-qualified at the appropriate indirection
// level then we can not mutate the object.
QualType Ty = Entry.E->getType().getCanonicalType();
for (int I = 0; I < Entry.Indirections; ++I) {
assert(Ty->isPointerType());
Ty = Ty->getPointeeType().getCanonicalType();
}
if (Ty.isConstQualified())
continue;
// Otherwise we have to look at the parents to see how the expression is
// used.
const DynTypedNodeList Parents = Ctx.getParents(*Entry.E);
// Note: most nodes have a single parents, but there exist nodes that have
// several parents, such as `InitListExpr` that have semantic and syntactic
// forms.
for (const auto &Parent : Parents) {
if (Parent.get<CompoundStmt>()) {
// Unused block-scope statement.
continue;
}
const Expr *const P = Parent.get<Expr>();
if (P == nullptr) {
// `Parent` is not an expr (e.g. a `VarDecl`).
// The case of binding to a `const&` or `const*` variable is handled by
// the fact that there is going to be a `NoOp` cast to const below the
// `VarDecl`, so we're not even going to get there.
// The case of copying into a value-typed variable is handled by the
// rvalue cast.
// This triggers only when binding to a mutable reference/ptr variable.
// FIXME: When we take a mutable reference we could keep checking the
// new variable for const usage only.
return false;
}
// Cosmetic nodes.
if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
Stack.emplace_back(P, Entry.Indirections);
continue;
}
if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
switch (Cast->getCastKind()) {
// NoOp casts are used to add `const`. We'll check whether adding that
// const prevents modification when we process the cast.
case CK_NoOp:
// These do nothing w.r.t. to mutability.
case CK_BaseToDerived:
case CK_DerivedToBase:
case CK_UncheckedDerivedToBase:
case CK_Dynamic:
case CK_BaseToDerivedMemberPointer:
case CK_DerivedToBaseMemberPointer:
Stack.emplace_back(Cast, Entry.Indirections);
continue;
case CK_ToVoid:
case CK_PointerToBoolean:
// These do not mutate the underlying variable.
continue;
case CK_LValueToRValue: {
// An rvalue is immutable.
if (Entry.Indirections == 0)
continue;
Stack.emplace_back(Cast, Entry.Indirections);
continue;
}
default:
// Bail out on casts that we cannot analyze.
return false;
}
}
if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
if (const auto *const Method =
dyn_cast<CXXMethodDecl>(Member->getMemberDecl())) {
if (Method->isConst() || Method->isStatic()) {
// The method call cannot mutate our variable.
continue;
}
return false;
}
Stack.emplace_back(Member, 0);
continue;
}
if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
switch (Op->getOpcode()) {
case UO_AddrOf:
Stack.emplace_back(Op, Entry.Indirections + 1);
continue;
case UO_Deref:
assert(Entry.Indirections > 0);
Stack.emplace_back(Op, Entry.Indirections - 1);
continue;
default:
// Bail out on unary operators that we cannot analyze.
return false;
}
}
// Assume any other expression can modify the underlying variable.
return false;
}
}
// No parent can modify the variable.
return true;
}
} // namespace
SmallPtrSet<const DeclRefExpr *, 16>
constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
ASTContext &Context, int Indirections) {
auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))),
doesNotMutateObject(Indirections))
.bind("declRef")),
Stmt, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}
bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
ASTContext &Context, int Indirections) {
// Collect all DeclRefExprs to the loop variable and all CallExprs and
// CXXConstructExprs where the loop variable is used as argument to a const
// reference parameter.
// If the difference is empty it is safe for the loop variable to be a const
// reference.
auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context);
auto ConstReferenceDeclRefs =
constReferenceDeclRefExprs(Var, Stmt, Context, Indirections);
return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs);
}
SmallPtrSet<const DeclRefExpr *, 16>
allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
auto Matches = match(
findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
Stmt, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}
SmallPtrSet<const DeclRefExpr *, 16>
allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) {
auto Matches = match(
decl(forEachDescendant(
declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"))),
Decl, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}
bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
ASTContext &Context) {
auto UsedAsConstRefArg = forEachArgumentWithParam(
declRefExpr(equalsNode(&DeclRef)),
parmVarDecl(hasType(matchers::isReferenceToConst())));
auto Matches = match(
decl(hasDescendant(
cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(cxxConstructorDecl(
isCopyConstructor())))
.bind("constructExpr"))),
Decl, Context);
return !Matches.empty();
}
bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
ASTContext &Context) {
auto UsedAsConstRefArg = forEachArgumentWithParam(
declRefExpr(equalsNode(&DeclRef)),
parmVarDecl(hasType(matchers::isReferenceToConst())));
auto Matches = match(
decl(hasDescendant(
cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName("="),
callee(cxxMethodDecl(isCopyAssignmentOperator())))
.bind("operatorCallExpr"))),
Decl, Context);
return !Matches.empty();
}
} // namespace clang::tidy::utils::decl_ref_expr