Skip to content

Commit 87dd5dc

Browse files
authored
[clang][dataflow] Add a lattice to help cache const accessor methods (#111006)
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 1c154a2 commit 87dd5dc

File tree

3 files changed

+524
-0
lines changed

3 files changed

+524
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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> class CachedConstAccessorsLattice : public Base {
49+
public:
50+
using Base::Base; // inherit all constructors
51+
52+
/// Creates or returns a previously created `Value` associated with a const
53+
/// method call `obj.getFoo()` where `RecordLoc` is the
54+
/// `RecordStorageLocation` of `obj`.
55+
/// Returns nullptr if unable to find or create a value.
56+
///
57+
/// Requirements:
58+
///
59+
/// - `CE` should return a value (not a reference or record type)
60+
Value *
61+
getOrCreateConstMethodReturnValue(const RecordStorageLocation &RecordLoc,
62+
const CallExpr *CE, Environment &Env);
63+
64+
/// Creates or returns a previously created `StorageLocation` associated with
65+
/// a const method call `obj.getFoo()` where `RecordLoc` is the
66+
/// `RecordStorageLocation` of `obj`.
67+
///
68+
/// The callback `Initialize` runs on the storage location if newly created.
69+
/// Returns nullptr if unable to find or create a value.
70+
///
71+
/// Requirements:
72+
///
73+
/// - `CE` should return a location (GLValue or a record type).
74+
StorageLocation *getOrCreateConstMethodReturnStorageLocation(
75+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
76+
Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize);
77+
78+
void clearConstMethodReturnValues(const RecordStorageLocation &RecordLoc) {
79+
ConstMethodReturnValues.erase(&RecordLoc);
80+
}
81+
82+
void clearConstMethodReturnStorageLocations(
83+
const RecordStorageLocation &RecordLoc) {
84+
ConstMethodReturnStorageLocations.erase(&RecordLoc);
85+
}
86+
87+
bool operator==(const CachedConstAccessorsLattice &Other) const {
88+
return Base::operator==(Other);
89+
}
90+
91+
LatticeJoinEffect join(const CachedConstAccessorsLattice &Other);
92+
93+
private:
94+
// Maps a record storage location and const method to the value to return
95+
// from that const method.
96+
using ConstMethodReturnValuesType =
97+
llvm::SmallDenseMap<const RecordStorageLocation *,
98+
llvm::SmallDenseMap<const FunctionDecl *, Value *>>;
99+
ConstMethodReturnValuesType ConstMethodReturnValues;
100+
101+
// Maps a record storage location and const method to the record storage
102+
// location to return from that const method.
103+
using ConstMethodReturnStorageLocationsType = llvm::SmallDenseMap<
104+
const RecordStorageLocation *,
105+
llvm::SmallDenseMap<const FunctionDecl *, StorageLocation *>>;
106+
ConstMethodReturnStorageLocationsType ConstMethodReturnStorageLocations;
107+
};
108+
109+
namespace internal {
110+
111+
template <typename T>
112+
llvm::SmallDenseMap<const RecordStorageLocation *,
113+
llvm::SmallDenseMap<const FunctionDecl *, T *>>
114+
joinConstMethodMap(
115+
const llvm::SmallDenseMap<const RecordStorageLocation *,
116+
llvm::SmallDenseMap<const FunctionDecl *, T *>>
117+
&Map1,
118+
const llvm::SmallDenseMap<const RecordStorageLocation *,
119+
llvm::SmallDenseMap<const FunctionDecl *, T *>>
120+
&Map2,
121+
LatticeEffect &Effect) {
122+
llvm::SmallDenseMap<const RecordStorageLocation *,
123+
llvm::SmallDenseMap<const FunctionDecl *, T *>>
124+
Result;
125+
for (auto &[Loc, DeclToT] : Map1) {
126+
auto It = Map2.find(Loc);
127+
if (It == Map2.end()) {
128+
Effect = LatticeJoinEffect::Changed;
129+
continue;
130+
}
131+
const auto &OtherDeclToT = It->second;
132+
auto &JoinedDeclToT = Result[Loc];
133+
for (auto [Func, Var] : DeclToT) {
134+
T *OtherVar = OtherDeclToT.lookup(Func);
135+
if (OtherVar == nullptr || OtherVar != Var) {
136+
Effect = LatticeJoinEffect::Changed;
137+
continue;
138+
}
139+
JoinedDeclToT.insert({Func, Var});
140+
}
141+
}
142+
return Result;
143+
}
144+
145+
} // namespace internal
146+
147+
template <typename Base>
148+
LatticeEffect CachedConstAccessorsLattice<Base>::join(
149+
const CachedConstAccessorsLattice<Base> &Other) {
150+
151+
LatticeEffect Effect = Base::join(Other);
152+
153+
// For simplicity, we only retain values that are identical, but not ones that
154+
// are non-identical but equivalent. This is likely to be sufficient in
155+
// practice, and it reduces implementation complexity considerably.
156+
157+
ConstMethodReturnValues = internal::joinConstMethodMap<Value>(
158+
ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect);
159+
160+
ConstMethodReturnStorageLocations =
161+
internal::joinConstMethodMap<StorageLocation>(
162+
ConstMethodReturnStorageLocations,
163+
Other.ConstMethodReturnStorageLocations, Effect);
164+
165+
return Effect;
166+
}
167+
168+
template <typename Base>
169+
Value *CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnValue(
170+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
171+
Environment &Env) {
172+
QualType Type = CE->getType();
173+
assert(!Type.isNull());
174+
assert(!Type->isReferenceType());
175+
assert(!Type->isRecordType());
176+
177+
auto &ObjMap = ConstMethodReturnValues[&RecordLoc];
178+
const FunctionDecl *DirectCallee = CE->getDirectCallee();
179+
if (DirectCallee == nullptr)
180+
return nullptr;
181+
auto it = ObjMap.find(DirectCallee);
182+
if (it != ObjMap.end())
183+
return it->second;
184+
185+
Value *Val = Env.createValue(Type);
186+
if (Val != nullptr)
187+
ObjMap.insert({DirectCallee, Val});
188+
return Val;
189+
}
190+
191+
template <typename Base>
192+
StorageLocation *
193+
CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnStorageLocation(
194+
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
195+
Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize) {
196+
QualType Type = CE->getType();
197+
assert(!Type.isNull());
198+
assert(CE->isGLValue() || Type->isRecordType());
199+
auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc];
200+
const FunctionDecl *DirectCallee = CE->getDirectCallee();
201+
if (DirectCallee == nullptr)
202+
return nullptr;
203+
auto it = ObjMap.find(DirectCallee);
204+
if (it != ObjMap.end())
205+
return it->second;
206+
207+
StorageLocation &Loc =
208+
Env.createStorageLocation(CE->getType().getNonReferenceType());
209+
Initialize(Loc);
210+
211+
ObjMap.insert({DirectCallee, &Loc});
212+
return &Loc;
213+
}
214+
215+
} // namespace dataflow
216+
} // namespace clang
217+
218+
#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)