xref: /freebsd/contrib/llvm-project/clang/include/clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h (revision 700637cbb5e582861067a11aaca4d053546871d2)
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