Skip to content

Commit ffa7868

Browse files
[analyzer] Fix false positives in inner pointer checker (PR49628)
This patch supports std::data and std::addressof functions. rdar://73463300 Differential Revision: https://reviews.llvm.org/D99260 (cherry picked from commit 663ac91)
1 parent ae78d15 commit ffa7868

File tree

2 files changed

+84
-26
lines changed

2 files changed

+84
-26
lines changed

clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp

+54-26
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ namespace {
3434
class InnerPointerChecker
3535
: public Checker<check::DeadSymbols, check::PostCall> {
3636

37-
CallDescription AppendFn, AssignFn, ClearFn, CStrFn, DataFn, EraseFn,
38-
InsertFn, PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn,
39-
ShrinkToFitFn, SwapFn;
37+
CallDescription AppendFn, AssignFn, AddressofFn, ClearFn, CStrFn, DataFn,
38+
DataMemberFn, EraseFn, InsertFn, PopBackFn, PushBackFn, ReplaceFn,
39+
ReserveFn, ResizeFn, ShrinkToFitFn, SwapFn;
4040

4141
public:
4242
class InnerPointerBRVisitor : public BugReporterVisitor {
@@ -73,9 +73,10 @@ class InnerPointerChecker
7373
InnerPointerChecker()
7474
: AppendFn({"std", "basic_string", "append"}),
7575
AssignFn({"std", "basic_string", "assign"}),
76+
AddressofFn({"std", "addressof"}),
7677
ClearFn({"std", "basic_string", "clear"}),
77-
CStrFn({"std", "basic_string", "c_str"}),
78-
DataFn({"std", "basic_string", "data"}),
78+
CStrFn({"std", "basic_string", "c_str"}), DataFn({"std", "data"}, 1),
79+
DataMemberFn({"std", "basic_string", "data"}),
7980
EraseFn({"std", "basic_string", "erase"}),
8081
InsertFn({"std", "basic_string", "insert"}),
8182
PopBackFn({"std", "basic_string", "pop_back"}),
@@ -90,6 +91,9 @@ class InnerPointerChecker
9091
/// pointers referring to the container object's inner buffer.
9192
bool isInvalidatingMemberFunction(const CallEvent &Call) const;
9293

94+
/// Check whether the called function returns a raw inner pointer.
95+
bool isInnerPointerAccessFunction(const CallEvent &Call) const;
96+
9397
/// Mark pointer symbols associated with the given memory region released
9498
/// in the program state.
9599
void markPtrSymbolsReleased(const CallEvent &Call, ProgramStateRef State,
@@ -130,6 +134,12 @@ bool InnerPointerChecker::isInvalidatingMemberFunction(
130134
Call.isCalled(SwapFn));
131135
}
132136

137+
bool InnerPointerChecker::isInnerPointerAccessFunction(
138+
const CallEvent &Call) const {
139+
return (Call.isCalled(CStrFn) || Call.isCalled(DataFn) ||
140+
Call.isCalled(DataMemberFn));
141+
}
142+
133143
void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent &Call,
134144
ProgramStateRef State,
135145
const MemRegion *MR,
@@ -172,6 +182,11 @@ void InnerPointerChecker::checkFunctionArguments(const CallEvent &Call,
172182
if (!ArgRegion)
173183
continue;
174184

185+
// std::addressof function accepts a non-const reference as an argument,
186+
// but doesn't modify it.
187+
if (Call.isCalled(AddressofFn))
188+
continue;
189+
175190
markPtrSymbolsReleased(Call, State, ArgRegion, C);
176191
}
177192
}
@@ -195,36 +210,49 @@ void InnerPointerChecker::checkPostCall(const CallEvent &Call,
195210
CheckerContext &C) const {
196211
ProgramStateRef State = C.getState();
197212

213+
// TODO: Do we need these to be typed?
214+
const TypedValueRegion *ObjRegion = nullptr;
215+
198216
if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) {
199-
// TODO: Do we need these to be typed?
200-
const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>(
217+
ObjRegion = dyn_cast_or_null<TypedValueRegion>(
201218
ICall->getCXXThisVal().getAsRegion());
202-
if (!ObjRegion)
203-
return;
204219

205-
if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) {
206-
SVal RawPtr = Call.getReturnValue();
207-
if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) {
208-
// Start tracking this raw pointer by adding it to the set of symbols
209-
// associated with this container object in the program state map.
220+
// Check [string.require] / second point.
221+
if (isInvalidatingMemberFunction(Call)) {
222+
markPtrSymbolsReleased(Call, State, ObjRegion, C);
223+
return;
224+
}
225+
}
210226

211-
PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
212-
const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion);
213-
PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet();
214-
assert(C.wasInlined || !Set.contains(Sym));
215-
Set = F.add(Set, Sym);
227+
if (isInnerPointerAccessFunction(Call)) {
216228

217-
State = State->set<RawPtrMap>(ObjRegion, Set);
218-
C.addTransition(State);
219-
}
220-
return;
229+
if (isa<SimpleFunctionCall>(Call)) {
230+
// NOTE: As of now, we only have one free access function: std::data.
231+
// If we add more functions like this in the list, hardcoded
232+
// argument index should be changed.
233+
ObjRegion =
234+
dyn_cast_or_null<TypedValueRegion>(Call.getArgSVal(0).getAsRegion());
221235
}
222236

223-
// Check [string.require] / second point.
224-
if (isInvalidatingMemberFunction(Call)) {
225-
markPtrSymbolsReleased(Call, State, ObjRegion, C);
237+
if (!ObjRegion)
226238
return;
239+
240+
SVal RawPtr = Call.getReturnValue();
241+
if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) {
242+
// Start tracking this raw pointer by adding it to the set of symbols
243+
// associated with this container object in the program state map.
244+
245+
PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
246+
const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion);
247+
PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet();
248+
assert(C.wasInlined || !Set.contains(Sym));
249+
Set = F.add(Set, Sym);
250+
251+
State = State->set<RawPtrMap>(ObjRegion, Set);
252+
C.addTransition(State);
227253
}
254+
255+
return;
228256
}
229257

230258
// Check [string.require] / first point.

clang/test/Analysis/inner-pointer.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ void func_value(T a);
1717
string my_string = "default";
1818
void default_arg(int a = 42, string &b = my_string);
1919

20+
template <class T>
21+
T *addressof(T &arg);
22+
23+
char *data(std::string &c);
24+
2025
} // end namespace std
2126

2227
void consume(const char *) {}
@@ -273,6 +278,15 @@ void deref_after_swap() {
273278
// expected-note@-1 {{Inner pointer of container used after re/deallocation}}
274279
}
275280

281+
void deref_after_std_data() {
282+
const char *c;
283+
std::string s;
284+
c = std::data(s); // expected-note {{Pointer to inner buffer of 'std::string' obtained here}}
285+
s.push_back('c'); // expected-note {{Inner buffer of 'std::string' reallocated by call to 'push_back'}}
286+
consume(c); // expected-warning {{Inner pointer of container used after re/deallocation}}
287+
// expected-note@-1 {{Inner pointer of container used after re/deallocation}}
288+
}
289+
276290
struct S {
277291
std::string s;
278292
const char *name() {
@@ -361,8 +375,24 @@ void func_default_arg() {
361375
// expected-note@-1 {{Inner pointer of container used after re/deallocation}}
362376
}
363377

378+
void func_addressof() {
379+
const char *c;
380+
std::string s;
381+
c = s.c_str();
382+
addressof(s);
383+
consume(c); // no-warning
384+
}
385+
386+
void func_std_data() {
387+
const char *c;
388+
std::string s;
389+
c = std::data(s);
390+
consume(c); // no-warning
391+
}
392+
364393
struct T {
365394
std::string to_string() { return s; }
395+
366396
private:
367397
std::string s;
368398
};

0 commit comments

Comments
 (0)