1 //===-- CachedConstAccessorsLattice.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 the lattice mixin that additionally maintains a cache of
10 // stable method call return values to model const accessor member functions.
11 //===----------------------------------------------------------------------===//
12
13 #ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
14 #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
15
16 #include "clang/AST/Decl.h"
17 #include "clang/AST/Expr.h"
18 #include "clang/AST/Type.h"
19 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
20 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
21 #include "clang/Analysis/FlowSensitive/StorageLocation.h"
22 #include "clang/Analysis/FlowSensitive/Value.h"
23 #include "llvm/ADT/DenseMap.h"
24 #include "llvm/ADT/STLFunctionalExtras.h"
25
26 namespace clang {
27 namespace dataflow {
28
29 /// A mixin for a lattice that additionally maintains a cache of stable method
30 /// call return values to model const accessors methods. When a non-const method
31 /// is called, the cache should be cleared causing the next call to a const
32 /// method to be considered a different value. NOTE: The user is responsible for
33 /// clearing the cache.
34 ///
35 /// For example:
36 ///
37 /// class Bar {
38 /// public:
39 /// const std::optional<Foo>& getFoo() const;
40 /// void clear();
41 /// };
42 //
43 /// void func(Bar& s) {
44 /// if (s.getFoo().has_value()) {
45 /// use(s.getFoo().value()); // safe (checked earlier getFoo())
46 /// s.clear();
47 /// use(s.getFoo().value()); // unsafe (invalidate cache for s)
48 /// }
49 /// }
50 template <typename Base> class CachedConstAccessorsLattice : public Base {
51 public:
52 using Base::Base; // inherit all constructors
53
54 /// Creates or returns a previously created `Value` associated with a const
55 /// method call `obj.getFoo()` where `RecordLoc` is the
56 /// `RecordStorageLocation` of `obj`.
57 /// Returns nullptr if unable to find or create a value.
58 ///
59 /// Requirements:
60 ///
61 /// - `CE` should return a value (not a reference or record type)
62 Value *
63 getOrCreateConstMethodReturnValue(const RecordStorageLocation &RecordLoc,
64 const CallExpr *CE, Environment &Env);
65
66 /// Creates or returns a previously created `StorageLocation` associated with
67 /// a const method call `obj.getFoo()` where `RecordLoc` is the
68 /// `RecordStorageLocation` of `obj`, `Callee` is the decl for `getFoo`.
69 ///
70 /// The callback `Initialize` runs on the storage location if newly created.
71 ///
72 /// Requirements:
73 ///
74 /// - `Callee` should return a location (return type is a reference type or a
75 /// record type).
76 StorageLocation &getOrCreateConstMethodReturnStorageLocation(
77 const RecordStorageLocation &RecordLoc, const FunctionDecl *Callee,
78 Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize);
79
clearConstMethodReturnValues(const RecordStorageLocation & RecordLoc)80 void clearConstMethodReturnValues(const RecordStorageLocation &RecordLoc) {
81 ConstMethodReturnValues.erase(&RecordLoc);
82 }
83
clearConstMethodReturnStorageLocations(const RecordStorageLocation & RecordLoc)84 void clearConstMethodReturnStorageLocations(
85 const RecordStorageLocation &RecordLoc) {
86 ConstMethodReturnStorageLocations.erase(&RecordLoc);
87 }
88
89 bool operator==(const CachedConstAccessorsLattice &Other) const {
90 return Base::operator==(Other);
91 }
92
93 LatticeJoinEffect join(const CachedConstAccessorsLattice &Other);
94
95 private:
96 // Maps a record storage location and const method to the value to return
97 // from that const method.
98 using ConstMethodReturnValuesType =
99 llvm::SmallDenseMap<const RecordStorageLocation *,
100 llvm::SmallDenseMap<const FunctionDecl *, Value *>>;
101 ConstMethodReturnValuesType ConstMethodReturnValues;
102
103 // Maps a record storage location and const method to the record storage
104 // location to return from that const method.
105 using ConstMethodReturnStorageLocationsType = llvm::SmallDenseMap<
106 const RecordStorageLocation *,
107 llvm::SmallDenseMap<const FunctionDecl *, StorageLocation *>>;
108 ConstMethodReturnStorageLocationsType ConstMethodReturnStorageLocations;
109 };
110
111 namespace internal {
112
113 template <typename T>
114 llvm::SmallDenseMap<const RecordStorageLocation *,
115 llvm::SmallDenseMap<const FunctionDecl *, T *>>
joinConstMethodMap(const llvm::SmallDenseMap<const RecordStorageLocation *,llvm::SmallDenseMap<const FunctionDecl *,T * >> & Map1,const llvm::SmallDenseMap<const RecordStorageLocation *,llvm::SmallDenseMap<const FunctionDecl *,T * >> & Map2,LatticeEffect & Effect)116 joinConstMethodMap(
117 const llvm::SmallDenseMap<const RecordStorageLocation *,
118 llvm::SmallDenseMap<const FunctionDecl *, T *>>
119 &Map1,
120 const llvm::SmallDenseMap<const RecordStorageLocation *,
121 llvm::SmallDenseMap<const FunctionDecl *, T *>>
122 &Map2,
123 LatticeEffect &Effect) {
124 llvm::SmallDenseMap<const RecordStorageLocation *,
125 llvm::SmallDenseMap<const FunctionDecl *, T *>>
126 Result;
127 for (auto &[Loc, DeclToT] : Map1) {
128 auto It = Map2.find(Loc);
129 if (It == Map2.end()) {
130 Effect = LatticeJoinEffect::Changed;
131 continue;
132 }
133 const auto &OtherDeclToT = It->second;
134 auto &JoinedDeclToT = Result[Loc];
135 for (auto [Func, Var] : DeclToT) {
136 T *OtherVar = OtherDeclToT.lookup(Func);
137 if (OtherVar == nullptr || OtherVar != Var) {
138 Effect = LatticeJoinEffect::Changed;
139 continue;
140 }
141 JoinedDeclToT.insert({Func, Var});
142 }
143 }
144 return Result;
145 }
146
147 } // namespace internal
148
149 template <typename Base>
join(const CachedConstAccessorsLattice<Base> & Other)150 LatticeEffect CachedConstAccessorsLattice<Base>::join(
151 const CachedConstAccessorsLattice<Base> &Other) {
152
153 LatticeEffect Effect = Base::join(Other);
154
155 // For simplicity, we only retain values that are identical, but not ones that
156 // are non-identical but equivalent. This is likely to be sufficient in
157 // practice, and it reduces implementation complexity considerably.
158
159 ConstMethodReturnValues =
160 clang::dataflow::internal::joinConstMethodMap<dataflow::Value>(
161 ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect);
162
163 ConstMethodReturnStorageLocations =
164 clang::dataflow::internal::joinConstMethodMap<dataflow::StorageLocation>(
165 ConstMethodReturnStorageLocations,
166 Other.ConstMethodReturnStorageLocations, Effect);
167
168 return Effect;
169 }
170
171 template <typename Base>
getOrCreateConstMethodReturnValue(const RecordStorageLocation & RecordLoc,const CallExpr * CE,Environment & Env)172 Value *CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnValue(
173 const RecordStorageLocation &RecordLoc, const CallExpr *CE,
174 Environment &Env) {
175 QualType Type = CE->getType();
176 assert(!Type.isNull());
177 assert(!Type->isReferenceType());
178 assert(!Type->isRecordType());
179
180 auto &ObjMap = ConstMethodReturnValues[&RecordLoc];
181 const FunctionDecl *DirectCallee = CE->getDirectCallee();
182 if (DirectCallee == nullptr)
183 return nullptr;
184 auto it = ObjMap.find(DirectCallee);
185 if (it != ObjMap.end())
186 return it->second;
187
188 Value *Val = Env.createValue(Type);
189 if (Val != nullptr)
190 ObjMap.insert({DirectCallee, Val});
191 return Val;
192 }
193
194 template <typename Base>
195 StorageLocation &
getOrCreateConstMethodReturnStorageLocation(const RecordStorageLocation & RecordLoc,const FunctionDecl * Callee,Environment & Env,llvm::function_ref<void (StorageLocation &)> Initialize)196 CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnStorageLocation(
197 const RecordStorageLocation &RecordLoc, const FunctionDecl *Callee,
198 Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize) {
199 assert(Callee != nullptr);
200 QualType Type = Callee->getReturnType();
201 assert(!Type.isNull());
202 assert(Type->isReferenceType() || Type->isRecordType());
203 auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc];
204 auto it = ObjMap.find(Callee);
205 if (it != ObjMap.end())
206 return *it->second;
207
208 StorageLocation &Loc = Env.createStorageLocation(Type.getNonReferenceType());
209 Initialize(Loc);
210
211 ObjMap.insert({Callee, &Loc});
212 return Loc;
213 }
214
215 } // namespace dataflow
216 } // namespace clang
217
218 #endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
219