Skip to content

Commit f82e63e

Browse files
committed
[clang][dataflow] Add a lattice to help represent cache const accessor methods
By caching const accessor methods we can sometimes treat method call results as stable (e.g., for issue #58510). Users can clear the cache when a non-const method is called that may modify the state of an object. This is represented as mixin. It will be used in a follow on patch to change bugprone-unchecked-optional-access's lattice from NoopLattice to CachedConstAccessorsLattice<NoopLattice>, along with some additional transfer functions.
1 parent ee4dd14 commit f82e63e

File tree

3 files changed

+439
-0
lines changed

3 files changed

+439
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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/Expr.h"
17+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
18+
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
19+
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
20+
#include "clang/Analysis/FlowSensitive/Value.h"
21+
#include "llvm/ADT/DenseMap.h"
22+
#include "llvm/ADT/STLFunctionalExtras.h"
23+
24+
namespace clang {
25+
namespace dataflow {
26+
27+
/// A mixin for a lattice that additionally maintains a cache of stable method
28+
/// call return values to model const accessors methods. When a non-const method
29+
/// is called, the cache should be cleared causing the next call to a const
30+
/// method to be considered a different value. NOTE: The user is responsible for
31+
/// clearing the cache.
32+
///
33+
/// For example:
34+
///
35+
/// class Bar {
36+
/// public:
37+
/// const std::optional<Foo>& getFoo() const;
38+
/// void clear();
39+
/// };
40+
//
41+
/// void func(Bar& s) {
42+
/// if (s.getFoo().has_value()) {
43+
/// use(s.getFoo().value()); // safe (checked earlier getFoo())
44+
/// s.clear();
45+
/// use(s.getFoo().value()); // unsafe (invalidate cache for s)
46+
/// }
47+
/// }
48+
template <typename Base>
49+
class CachedConstAccessorsLattice : public Base {
50+
public:
51+
using Base::Base; // inherit all constructors
52+
53+
/// Creates or returns a previously created `Value` associated with a const
54+
/// method call `obj.getFoo()` where `RecordLoc` is the
55+
/// `RecordStorageLocation` of `obj`.
56+
/// Returns nullptr if unable to find or create a value.
57+
///
58+
/// Requirements:
59+
///
60+
/// - `CE` should return a value (not a reference or record type)
61+
Value *
62+
getOrCreateConstMethodReturnValue(const RecordStorageLocation &RecordLoc,
63+
const CallExpr *CE, Environment &Env);
64+
65+
/// Creates or returns a previously created `StorageLocation` associated a
66+
/// const method call `obj.getFoo()` where `RecordLoc` is the
67+
/// `RecordStorageLocation` of `obj`.
68+
///
69+
/// The callback `Initialize` runs on the storage location if newly created.
70+
/// Returns nullptr if unable to find or create a value.
71+
///
72+
/// Requirements:
73+
///
74+
/// - `CE` should return a location (GLValue or a record type).
75+
StorageLocation *getOrCreateConstMethodReturnStorageLocation(
76+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
77+
Environment &Env,
78+
llvm::function_ref<void(StorageLocation &)> Initialize);
79+
80+
void clearConstMethodReturnValues(const RecordStorageLocation &RecordLoc) {
81+
ConstMethodReturnValues.erase(&RecordLoc);
82+
}
83+
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 = llvm::SmallDenseMap<
99+
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 *>>
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>
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 = internal::joinConstMethodMap<Value>(
160+
ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect);
161+
162+
ConstMethodReturnStorageLocations =
163+
internal::joinConstMethodMap<StorageLocation>(
164+
ConstMethodReturnStorageLocations,
165+
Other.ConstMethodReturnStorageLocations, Effect);
166+
167+
return Effect;
168+
}
169+
170+
template <typename Base>
171+
Value *CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnValue(
172+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
173+
Environment &Env) {
174+
QualType Type = CE->getType();
175+
assert(!Type.isNull());
176+
assert(!Type->isReferenceType());
177+
assert(!Type->isRecordType());
178+
179+
auto &ObjMap = ConstMethodReturnValues[&RecordLoc];
180+
const FunctionDecl *DirectCallee = CE->getDirectCallee();
181+
if (DirectCallee == nullptr)
182+
return nullptr;
183+
auto it = ObjMap.find(DirectCallee);
184+
if (it != ObjMap.end())
185+
return it->second;
186+
187+
Value *Val = Env.createValue(Type);
188+
if (Val != nullptr)
189+
ObjMap.insert({DirectCallee, Val});
190+
return Val;
191+
}
192+
193+
template <typename Base>
194+
StorageLocation *
195+
CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnStorageLocation(
196+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
197+
Environment &Env,
198+
llvm::function_ref<void(StorageLocation &)> Initialize) {
199+
QualType Type = CE->getType();
200+
assert(!Type.isNull());
201+
assert(CE->isGLValue() || Type->isRecordType());
202+
auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc];
203+
const FunctionDecl *DirectCallee = CE->getDirectCallee();
204+
if (DirectCallee == nullptr)
205+
return nullptr;
206+
auto it = ObjMap.find(DirectCallee);
207+
if (it != ObjMap.end())
208+
return it->second;
209+
210+
StorageLocation &Loc =
211+
Env.createStorageLocation(CE->getType().getNonReferenceType());
212+
Initialize(Loc);
213+
214+
ObjMap.insert({DirectCallee, &Loc});
215+
return &Loc;
216+
}
217+
218+
} // namespace dataflow
219+
} // namespace clang
220+
221+
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H

clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
77
ArenaTest.cpp
88
ASTOpsTest.cpp
99
CFGMatchSwitchTest.cpp
10+
CachedConstAccessorsLatticeTest.cpp
1011
ChromiumCheckModelTest.cpp
1112
DataflowAnalysisContextTest.cpp
1213
DataflowEnvironmentTest.cpp

0 commit comments

Comments
 (0)