|
| 1 | +//===-- SmartPointerAccessorCaching.h ---------------------------*- C++ -*-===// |
| 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 | +// This file defines utilities to help cache accessors for smart pointer |
| 10 | +// like objects. |
| 11 | +// |
| 12 | +// These should be combined with CachedConstAccessorsLattice. |
| 13 | +// Beyond basic const accessors, smart pointers may have the following two |
| 14 | +// additional issues: |
| 15 | +// |
| 16 | +// 1) There may be multiple accessors for the same underlying object, e.g. |
| 17 | +// `operator->`, `operator*`, and `get`. Users may use a mixture of these |
| 18 | +// accessors, so the cache should unify them. |
| 19 | +// |
| 20 | +// 2) There may be non-const overloads of accessors. They are still safe to |
| 21 | +// cache, as they don't modify the container object. |
| 22 | +//===----------------------------------------------------------------------===// |
| 23 | + |
| 24 | +#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H |
| 25 | +#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H |
| 26 | + |
| 27 | +#include <cassert> |
| 28 | + |
| 29 | +#include "clang/AST/Decl.h" |
| 30 | +#include "clang/AST/Expr.h" |
| 31 | +#include "clang/AST/Stmt.h" |
| 32 | +#include "clang/ASTMatchers/ASTMatchers.h" |
| 33 | +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" |
| 34 | +#include "clang/Analysis/FlowSensitive/StorageLocation.h" |
| 35 | +#include "clang/Analysis/FlowSensitive/Value.h" |
| 36 | +#include "llvm/ADT/STLFunctionalExtras.h" |
| 37 | + |
| 38 | +namespace clang::dataflow { |
| 39 | + |
| 40 | +/// Matchers: |
| 41 | +/// For now, these match on any class with an `operator*` or `operator->` |
| 42 | +/// where the return types have a similar shape as std::unique_ptr |
| 43 | +/// and std::optional. |
| 44 | +/// |
| 45 | +/// - `*` returns a reference to a type `T` |
| 46 | +/// - `->` returns a pointer to `T` |
| 47 | +/// - `get` returns a pointer to `T` |
| 48 | +/// - `value` returns a reference `T` |
| 49 | +/// |
| 50 | +/// (1) The `T` should all match across the accessors (ignoring qualifiers). |
| 51 | +/// |
| 52 | +/// (2) The specific accessor used in a call isn't required to be const, |
| 53 | +/// but the class must have a const overload of each accessor. |
| 54 | +/// |
| 55 | +/// For now, we don't have customization to ignore certain classes. |
| 56 | +/// For example, if writing a ClangTidy check for `std::optional`, these |
| 57 | +/// would also match `std::optional`. In order to have special handling |
| 58 | +/// for `std::optional`, we assume the (Matcher, TransferFunction) case |
| 59 | +/// with custom handling is ordered early so that these generic cases |
| 60 | +/// do not trigger. |
| 61 | +ast_matchers::internal::Matcher<Stmt> isSmartPointerLikeOperatorStar(); |
| 62 | +ast_matchers::internal::Matcher<Stmt> isSmartPointerLikeOperatorArrow(); |
| 63 | +ast_matchers::internal::Matcher<Stmt> isSmartPointerLikeValueMethodCall(); |
| 64 | +ast_matchers::internal::Matcher<Stmt> isSmartPointerLikeGetMethodCall(); |
| 65 | + |
| 66 | +// Common transfer functions. |
| 67 | + |
| 68 | +/// Returns the "canonical" callee for smart pointer operators (`*` and `->`) |
| 69 | +/// as a key for caching. |
| 70 | +/// |
| 71 | +/// We choose `operator *` as the canonical one, since it needs a |
| 72 | +/// StorageLocation anyway. |
| 73 | +/// |
| 74 | +/// Note: there may be multiple `operator*` (one const, one non-const) |
| 75 | +/// we pick the const one, which the above provided matchers require to exist. |
| 76 | +const FunctionDecl * |
| 77 | +getCanonicalSmartPointerLikeOperatorCallee(const CallExpr *CE); |
| 78 | + |
| 79 | +/// A transfer function for `operator*` (and `value`) calls. |
| 80 | +/// |
| 81 | +/// Requirements: |
| 82 | +/// |
| 83 | +/// - LatticeT should use the `CachedConstAccessorsLattice` mixin. |
| 84 | +template <typename LatticeT> |
| 85 | +void transferSmartPointerLikeDeref( |
| 86 | + const CallExpr *DerefExpr, RecordStorageLocation *SmartPointerLoc, |
| 87 | + TransferState<LatticeT> &State, |
| 88 | + llvm::function_ref<void(StorageLocation &)> InitializeLoc); |
| 89 | + |
| 90 | +/// A transfer function for `operator->` (and `get`) calls. |
| 91 | +/// |
| 92 | +/// Requirements: |
| 93 | +/// |
| 94 | +/// - LatticeT should use the `CachedConstAccessorsLattice` mixin. |
| 95 | +template <typename LatticeT> |
| 96 | +void transferSmartPointerLikeGet( |
| 97 | + const CallExpr *GetExpr, RecordStorageLocation *SmartPointerLoc, |
| 98 | + TransferState<LatticeT> &State, |
| 99 | + llvm::function_ref<void(StorageLocation &)> InitializeLoc); |
| 100 | + |
| 101 | +template <typename LatticeT> |
| 102 | +void transferSmartPointerLikeDeref( |
| 103 | + const CallExpr *DerefExpr, RecordStorageLocation *SmartPointerLoc, |
| 104 | + TransferState<LatticeT> &State, |
| 105 | + llvm::function_ref<void(StorageLocation &)> InitializeLoc) { |
| 106 | + if (State.Env.getStorageLocation(*DerefExpr) != nullptr) |
| 107 | + return; |
| 108 | + if (SmartPointerLoc == nullptr) |
| 109 | + return; |
| 110 | + |
| 111 | + const FunctionDecl *Callee = DerefExpr->getDirectCallee(); |
| 112 | + if (Callee == nullptr) |
| 113 | + return; |
| 114 | + const FunctionDecl *CanonicalCallee = |
| 115 | + getCanonicalSmartPointerLikeOperatorCallee(DerefExpr); |
| 116 | + // This shouldn't really happen, as we should at least find `Callee` itself. |
| 117 | + assert(CanonicalCallee != nullptr); |
| 118 | + if (CanonicalCallee != Callee) { |
| 119 | + // When using the provided matchers, we should always get a reference to |
| 120 | + // the same type. |
| 121 | + assert(CanonicalCallee->getReturnType()->isReferenceType() && |
| 122 | + Callee->getReturnType()->isReferenceType()); |
| 123 | + assert(CanonicalCallee->getReturnType() |
| 124 | + .getNonReferenceType() |
| 125 | + ->getCanonicalTypeUnqualified() == |
| 126 | + Callee->getReturnType() |
| 127 | + .getNonReferenceType() |
| 128 | + ->getCanonicalTypeUnqualified()); |
| 129 | + } |
| 130 | + |
| 131 | + StorageLocation &LocForValue = |
| 132 | + State.Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 133 | + *SmartPointerLoc, CanonicalCallee, CanonicalCallee->getReturnType(), |
| 134 | + State.Env, InitializeLoc); |
| 135 | + State.Env.setStorageLocation(*DerefExpr, LocForValue); |
| 136 | +} |
| 137 | + |
| 138 | +template <typename LatticeT> |
| 139 | +void transferSmartPointerLikeGet( |
| 140 | + const CallExpr *GetExpr, RecordStorageLocation *SmartPointerLoc, |
| 141 | + TransferState<LatticeT> &State, |
| 142 | + llvm::function_ref<void(StorageLocation &)> InitializeLoc) { |
| 143 | + if (SmartPointerLoc == nullptr) |
| 144 | + return; |
| 145 | + |
| 146 | + const FunctionDecl *CanonicalCallee = |
| 147 | + getCanonicalSmartPointerLikeOperatorCallee(GetExpr); |
| 148 | + |
| 149 | + if (CanonicalCallee != nullptr) { |
| 150 | + auto &LocForValue = |
| 151 | + State.Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 152 | + *SmartPointerLoc, CanonicalCallee, CanonicalCallee->getReturnType(), |
| 153 | + State.Env, InitializeLoc); |
| 154 | + State.Env.setValue(*GetExpr, |
| 155 | + State.Env.template create<PointerValue>(LocForValue)); |
| 156 | + } else { |
| 157 | + // Otherwise, just cache the pointer value as if it was a const accessor. |
| 158 | + Value *Val = State.Lattice.getOrCreateConstMethodReturnValue( |
| 159 | + *SmartPointerLoc, GetExpr, State.Env); |
| 160 | + if (Val == nullptr) |
| 161 | + return; |
| 162 | + State.Env.setValue(*GetExpr, *Val); |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +} // namespace clang::dataflow |
| 167 | + |
| 168 | +#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H |
0 commit comments