| 1 | //===--- DeclRefExprUtils.cpp - clang-tidy---------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "DeclRefExprUtils.h" |
| 10 | #include "Matchers.h" |
| 11 | #include "clang/AST/ASTContext.h" |
| 12 | #include "clang/AST/DeclCXX.h" |
| 13 | #include "clang/AST/ExprCXX.h" |
| 14 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 15 | #include <cassert> |
| 16 | |
| 17 | namespace clang::tidy::utils::decl_ref_expr { |
| 18 | |
| 19 | using namespace ::clang::ast_matchers; |
| 20 | using llvm::SmallPtrSet; |
| 21 | |
| 22 | namespace { |
| 23 | |
| 24 | template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) { |
| 25 | for (auto E : S1) |
| 26 | if (S2.count(E) == 0) |
| 27 | return false; |
| 28 | return true; |
| 29 | } |
| 30 | |
| 31 | // Extracts all Nodes keyed by ID from Matches and inserts them into Nodes. |
| 32 | template <typename Node> |
| 33 | void (ArrayRef<BoundNodes> Matches, StringRef ID, |
| 34 | SmallPtrSet<const Node *, 16> &Nodes) { |
| 35 | for (const auto &Match : Matches) |
| 36 | Nodes.insert(Match.getNodeAs<Node>(ID)); |
| 37 | } |
| 38 | |
| 39 | // Returns true if both types refer to the same type, |
| 40 | // ignoring the const-qualifier. |
| 41 | bool isSameTypeIgnoringConst(QualType A, QualType B) { |
| 42 | A = A.getCanonicalType(); |
| 43 | B = B.getCanonicalType(); |
| 44 | A.addConst(); |
| 45 | B.addConst(); |
| 46 | return A == B; |
| 47 | } |
| 48 | |
| 49 | // Returns true if `D` and `O` have the same parameter types. |
| 50 | bool hasSameParameterTypes(const CXXMethodDecl &D, const CXXMethodDecl &O) { |
| 51 | if (D.getNumParams() != O.getNumParams()) |
| 52 | return false; |
| 53 | for (int I = 0, E = D.getNumParams(); I < E; ++I) { |
| 54 | if (!isSameTypeIgnoringConst(D.getParamDecl(I)->getType(), |
| 55 | O.getParamDecl(I)->getType())) |
| 56 | return false; |
| 57 | } |
| 58 | return true; |
| 59 | } |
| 60 | |
| 61 | // If `D` has a const-qualified overload with otherwise identical |
| 62 | // ref-qualifiers and parameter types, returns that overload. |
| 63 | const CXXMethodDecl *findConstOverload(const CXXMethodDecl &D) { |
| 64 | assert(!D.isConst()); |
| 65 | |
| 66 | DeclContext::lookup_result LookupResult = |
| 67 | D.getParent()->lookup(Name: D.getNameInfo().getName()); |
| 68 | if (LookupResult.isSingleResult()) { |
| 69 | // No overload. |
| 70 | return nullptr; |
| 71 | } |
| 72 | for (const Decl *Overload : LookupResult) { |
| 73 | const auto *O = dyn_cast<CXXMethodDecl>(Overload); |
| 74 | if (O && !O->isDeleted() && O->isConst() && |
| 75 | O->getRefQualifier() == D.getRefQualifier() && |
| 76 | hasSameParameterTypes(D, *O)) |
| 77 | return O; |
| 78 | } |
| 79 | return nullptr; |
| 80 | } |
| 81 | |
| 82 | // Returns true if both types are pointers or reference to the same type, |
| 83 | // ignoring the const-qualifier. |
| 84 | bool pointsToSameTypeIgnoringConst(QualType A, QualType B) { |
| 85 | assert(A->isPointerType() || A->isReferenceType()); |
| 86 | assert(B->isPointerType() || B->isReferenceType()); |
| 87 | return isSameTypeIgnoringConst(A: A->getPointeeType(), B: B->getPointeeType()); |
| 88 | } |
| 89 | |
| 90 | // Return true if non-const member function `M` likely does not mutate `*this`. |
| 91 | // |
| 92 | // Note that if the member call selects a method/operator `f` that |
| 93 | // is not const-qualified, then we also consider that the object is |
| 94 | // not mutated if: |
| 95 | // - (A) there is a const-qualified overload `cf` of `f` that has |
| 96 | // the |
| 97 | // same ref-qualifiers; |
| 98 | // - (B) * `f` returns a value, or |
| 99 | // * if `f` returns a `T&`, `cf` returns a `const T&` (up to |
| 100 | // possible aliases such as `reference` and |
| 101 | // `const_reference`), or |
| 102 | // * if `f` returns a `T*`, `cf` returns a `const T*` (up to |
| 103 | // possible aliases). |
| 104 | // - (C) the result of the call is not mutated. |
| 105 | // |
| 106 | // The assumption that `cf` has the same semantics as `f`. |
| 107 | // For example: |
| 108 | // - In `std::vector<T> v; const T t = v[...];`, we consider that |
| 109 | // expression `v[...]` does not mutate `v` as |
| 110 | // `T& std::vector<T>::operator[]` has a const overload |
| 111 | // `const T& std::vector<T>::operator[] const`, and the |
| 112 | // result expression of type `T&` is only used as a `const T&`; |
| 113 | // - In `std::map<K, V> m; V v = m.at(...);`, we consider |
| 114 | // `m.at(...)` to be an immutable access for the same reason. |
| 115 | // However: |
| 116 | // - In `std::map<K, V> m; const V v = m[...];`, We consider that |
| 117 | // `m[...]` mutates `m` as `V& std::map<K, V>::operator[]` does |
| 118 | // not have a const overload. |
| 119 | // - In `std::vector<T> v; T& t = v[...];`, we consider that |
| 120 | // expression `v[...]` mutates `v` as the result is kept as a |
| 121 | // mutable reference. |
| 122 | // |
| 123 | // This function checks (A) ad (B), but the caller should make sure that the |
| 124 | // object is not mutated through the return value. |
| 125 | bool isLikelyShallowConst(const CXXMethodDecl &M) { |
| 126 | assert(!M.isConst()); |
| 127 | // The method can mutate our variable. |
| 128 | |
| 129 | // (A) |
| 130 | const CXXMethodDecl *ConstOverload = findConstOverload(D: M); |
| 131 | if (ConstOverload == nullptr) { |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | // (B) |
| 136 | const QualType CallTy = M.getReturnType().getCanonicalType(); |
| 137 | const QualType OverloadTy = ConstOverload->getReturnType().getCanonicalType(); |
| 138 | if (CallTy->isReferenceType()) { |
| 139 | return OverloadTy->isReferenceType() && |
| 140 | pointsToSameTypeIgnoringConst(A: CallTy, B: OverloadTy); |
| 141 | } |
| 142 | if (CallTy->isPointerType()) { |
| 143 | return OverloadTy->isPointerType() && |
| 144 | pointsToSameTypeIgnoringConst(A: CallTy, B: OverloadTy); |
| 145 | } |
| 146 | return isSameTypeIgnoringConst(A: CallTy, B: OverloadTy); |
| 147 | } |
| 148 | |
| 149 | // A matcher that matches DeclRefExprs that are used in ways such that the |
| 150 | // underlying declaration is not modified. |
| 151 | // If the declaration is of pointer type, `Indirections` specifies the level |
| 152 | // of indirection of the object whose mutations we are tracking. |
| 153 | // |
| 154 | // For example, given: |
| 155 | // ``` |
| 156 | // int i; |
| 157 | // int* p; |
| 158 | // p = &i; // (A) |
| 159 | // *p = 3; // (B) |
| 160 | // ``` |
| 161 | // |
| 162 | // `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches |
| 163 | // (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))` |
| 164 | // matches (A). |
| 165 | // |
| 166 | AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) { |
| 167 | // We walk up the parents of the DeclRefExpr recursively. There are a few |
| 168 | // kinds of expressions: |
| 169 | // - Those that cannot be used to mutate the underlying variable. We can stop |
| 170 | // recursion there. |
| 171 | // - Those that can be used to mutate the underlying variable in analyzable |
| 172 | // ways (such as taking the address or accessing a subobject). We have to |
| 173 | // examine the parents. |
| 174 | // - Those that we don't know how to analyze. In that case we stop there and |
| 175 | // we assume that they can modify the expression. |
| 176 | |
| 177 | struct StackEntry { |
| 178 | StackEntry(const Expr *E, int Indirections) |
| 179 | : E(E), Indirections(Indirections) {} |
| 180 | // The expression to analyze. |
| 181 | const Expr *E; |
| 182 | // The number of pointer indirections of the object being tracked (how |
| 183 | // many times an address was taken). |
| 184 | int Indirections; |
| 185 | }; |
| 186 | |
| 187 | llvm::SmallVector<StackEntry, 4> Stack; |
| 188 | Stack.emplace_back(Args: &Node, Args: Indirections); |
| 189 | ASTContext &Ctx = Finder->getASTContext(); |
| 190 | |
| 191 | while (!Stack.empty()) { |
| 192 | const StackEntry Entry = Stack.back(); |
| 193 | Stack.pop_back(); |
| 194 | |
| 195 | // If the expression type is const-qualified at the appropriate indirection |
| 196 | // level then we can not mutate the object. |
| 197 | QualType Ty = Entry.E->getType().getCanonicalType(); |
| 198 | for (int I = 0; I < Entry.Indirections; ++I) { |
| 199 | assert(Ty->isPointerType()); |
| 200 | Ty = Ty->getPointeeType().getCanonicalType(); |
| 201 | } |
| 202 | if (Ty->isVoidType() || Ty.isConstQualified()) |
| 203 | continue; |
| 204 | |
| 205 | // Otherwise we have to look at the parents to see how the expression is |
| 206 | // used. |
| 207 | const DynTypedNodeList Parents = Ctx.getParents(Node: *Entry.E); |
| 208 | // Note: most nodes have a single parents, but there exist nodes that have |
| 209 | // several parents, such as `InitListExpr` that have semantic and syntactic |
| 210 | // forms. |
| 211 | for (const auto &Parent : Parents) { |
| 212 | if (Parent.get<CompoundStmt>()) { |
| 213 | // Unused block-scope statement. |
| 214 | continue; |
| 215 | } |
| 216 | const Expr *const P = Parent.get<Expr>(); |
| 217 | if (P == nullptr) { |
| 218 | // `Parent` is not an expr (e.g. a `VarDecl`). |
| 219 | // The case of binding to a `const&` or `const*` variable is handled by |
| 220 | // the fact that there is going to be a `NoOp` cast to const below the |
| 221 | // `VarDecl`, so we're not even going to get there. |
| 222 | // The case of copying into a value-typed variable is handled by the |
| 223 | // rvalue cast. |
| 224 | // This triggers only when binding to a mutable reference/ptr variable. |
| 225 | // FIXME: When we take a mutable reference we could keep checking the |
| 226 | // new variable for const usage only. |
| 227 | return false; |
| 228 | } |
| 229 | // Cosmetic nodes. |
| 230 | if (isa<ParenExpr>(Val: P) || isa<MaterializeTemporaryExpr>(Val: P)) { |
| 231 | Stack.emplace_back(Args: P, Args: Entry.Indirections); |
| 232 | continue; |
| 233 | } |
| 234 | if (const auto *const Cast = dyn_cast<CastExpr>(Val: P)) { |
| 235 | switch (Cast->getCastKind()) { |
| 236 | // NoOp casts are used to add `const`. We'll check whether adding that |
| 237 | // const prevents modification when we process the cast. |
| 238 | case CK_NoOp: |
| 239 | // These do nothing w.r.t. to mutability. |
| 240 | case CK_BaseToDerived: |
| 241 | case CK_DerivedToBase: |
| 242 | case CK_UncheckedDerivedToBase: |
| 243 | case CK_Dynamic: |
| 244 | case CK_BaseToDerivedMemberPointer: |
| 245 | case CK_DerivedToBaseMemberPointer: |
| 246 | Stack.emplace_back(Args: Cast, Args: Entry.Indirections); |
| 247 | continue; |
| 248 | case CK_ToVoid: |
| 249 | case CK_PointerToBoolean: |
| 250 | // These do not mutate the underlying variable. |
| 251 | continue; |
| 252 | case CK_LValueToRValue: { |
| 253 | // An rvalue is immutable. |
| 254 | if (Entry.Indirections == 0) |
| 255 | continue; |
| 256 | Stack.emplace_back(Args: Cast, Args: Entry.Indirections); |
| 257 | continue; |
| 258 | } |
| 259 | default: |
| 260 | // Bail out on casts that we cannot analyze. |
| 261 | return false; |
| 262 | } |
| 263 | } |
| 264 | if (const auto *const Member = dyn_cast<MemberExpr>(Val: P)) { |
| 265 | if (const auto *const Method = |
| 266 | dyn_cast<CXXMethodDecl>(Val: Member->getMemberDecl())) { |
| 267 | if (Method->isConst() || Method->isStatic()) { |
| 268 | // The method call cannot mutate our variable. |
| 269 | continue; |
| 270 | } |
| 271 | if (isLikelyShallowConst(M: *Method)) { |
| 272 | // We still have to check that the object is not modified through |
| 273 | // the method's return value (C). |
| 274 | const auto MemberParents = Ctx.getParents(Node: *Member); |
| 275 | assert(MemberParents.size() == 1); |
| 276 | const auto *Call = MemberParents[0].get<CallExpr>(); |
| 277 | // If `o` is an object of class type and `f` is a member function, |
| 278 | // then `o.f` has to be used as part of a call expression. |
| 279 | assert(Call != nullptr && "member function has to be called" ); |
| 280 | Stack.emplace_back( |
| 281 | Call, |
| 282 | Method->getReturnType().getCanonicalType()->isPointerType() |
| 283 | ? 1 |
| 284 | : 0); |
| 285 | continue; |
| 286 | } |
| 287 | return false; |
| 288 | } |
| 289 | Stack.emplace_back(Args: Member, Args: 0); |
| 290 | continue; |
| 291 | } |
| 292 | if (const auto *const OpCall = dyn_cast<CXXOperatorCallExpr>(Val: P)) { |
| 293 | // Operator calls have function call syntax. The `*this` parameter |
| 294 | // is the first parameter. |
| 295 | if (OpCall->getNumArgs() == 0 || OpCall->getArg(0) != Entry.E) { |
| 296 | return false; |
| 297 | } |
| 298 | const auto *const Method = |
| 299 | dyn_cast_or_null<CXXMethodDecl>(OpCall->getDirectCallee()); |
| 300 | |
| 301 | if (Method == nullptr) { |
| 302 | // This is not a member operator. Typically, a friend operator. These |
| 303 | // are handled like function calls. |
| 304 | return false; |
| 305 | } |
| 306 | |
| 307 | if (Method->isConst() || Method->isStatic()) { |
| 308 | continue; |
| 309 | } |
| 310 | if (isLikelyShallowConst(*Method)) { |
| 311 | // We still have to check that the object is not modified through |
| 312 | // the operator's return value (C). |
| 313 | Stack.emplace_back( |
| 314 | OpCall, |
| 315 | Method->getReturnType().getCanonicalType()->isPointerType() ? 1 |
| 316 | : 0); |
| 317 | continue; |
| 318 | } |
| 319 | return false; |
| 320 | } |
| 321 | |
| 322 | if (const auto *const Op = dyn_cast<UnaryOperator>(Val: P)) { |
| 323 | switch (Op->getOpcode()) { |
| 324 | case UO_AddrOf: |
| 325 | Stack.emplace_back(Args: Op, Args: Entry.Indirections + 1); |
| 326 | continue; |
| 327 | case UO_Deref: |
| 328 | assert(Entry.Indirections > 0); |
| 329 | Stack.emplace_back(Args: Op, Args: Entry.Indirections - 1); |
| 330 | continue; |
| 331 | default: |
| 332 | // Bail out on unary operators that we cannot analyze. |
| 333 | return false; |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | // Assume any other expression can modify the underlying variable. |
| 338 | return false; |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | // No parent can modify the variable. |
| 343 | return true; |
| 344 | } |
| 345 | |
| 346 | } // namespace |
| 347 | |
| 348 | SmallPtrSet<const DeclRefExpr *, 16> |
| 349 | constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, |
| 350 | ASTContext &Context, int Indirections) { |
| 351 | auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))), |
| 352 | doesNotMutateObject(Indirections)) |
| 353 | .bind("declRef" )), |
| 354 | Stmt, Context); |
| 355 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
| 356 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
| 357 | |
| 358 | return DeclRefs; |
| 359 | } |
| 360 | |
| 361 | bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, |
| 362 | ASTContext &Context, int Indirections) { |
| 363 | // Collect all DeclRefExprs to the loop variable and all CallExprs and |
| 364 | // CXXConstructExprs where the loop variable is used as argument to a const |
| 365 | // reference parameter. |
| 366 | // If the difference is empty it is safe for the loop variable to be a const |
| 367 | // reference. |
| 368 | auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context); |
| 369 | auto ConstReferenceDeclRefs = |
| 370 | constReferenceDeclRefExprs(Var, Stmt, Context, Indirections); |
| 371 | return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs); |
| 372 | } |
| 373 | |
| 374 | SmallPtrSet<const DeclRefExpr *, 16> |
| 375 | allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) { |
| 376 | auto Matches = match( |
| 377 | findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef" )), |
| 378 | Stmt, Context); |
| 379 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
| 380 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
| 381 | return DeclRefs; |
| 382 | } |
| 383 | |
| 384 | SmallPtrSet<const DeclRefExpr *, 16> |
| 385 | allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) { |
| 386 | auto Matches = match( |
| 387 | decl(forEachDescendant( |
| 388 | declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef" ))), |
| 389 | Decl, Context); |
| 390 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
| 391 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
| 392 | return DeclRefs; |
| 393 | } |
| 394 | |
| 395 | bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, |
| 396 | ASTContext &Context) { |
| 397 | auto UsedAsConstRefArg = forEachArgumentWithParam( |
| 398 | declRefExpr(equalsNode(&DeclRef)), |
| 399 | parmVarDecl(hasType(InnerMatcher: matchers::isReferenceToConst()))); |
| 400 | auto Matches = match( |
| 401 | decl(hasDescendant( |
| 402 | cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(InnerMatcher: cxxConstructorDecl( |
| 403 | isCopyConstructor()))) |
| 404 | .bind("constructExpr" ))), |
| 405 | Decl, Context); |
| 406 | return !Matches.empty(); |
| 407 | } |
| 408 | |
| 409 | bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, |
| 410 | ASTContext &Context) { |
| 411 | auto UsedAsConstRefArg = forEachArgumentWithParam( |
| 412 | declRefExpr(equalsNode(&DeclRef)), |
| 413 | parmVarDecl(hasType(InnerMatcher: matchers::isReferenceToConst()))); |
| 414 | auto Matches = match( |
| 415 | decl(hasDescendant( |
| 416 | cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName(Name: "=" ), |
| 417 | callee(InnerMatcher: cxxMethodDecl(isCopyAssignmentOperator()))) |
| 418 | .bind("operatorCallExpr" ))), |
| 419 | Decl, Context); |
| 420 | return !Matches.empty(); |
| 421 | } |
| 422 | |
| 423 | } // namespace clang::tidy::utils::decl_ref_expr |
| 424 | |