TrustNonnullChecker.cpp
9.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//== TrustNonnullChecker.cpp --------- API nullability modeling -*- C++ -*--==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This checker adds nullability-related assumptions:
//
// 1. Methods annotated with _Nonnull
// which come from system headers actually return a non-null pointer.
//
// 2. NSDictionary key is non-null after the keyword subscript operation
// on read if and only if the resulting expression is non-null.
//
// 3. NSMutableDictionary index is non-null after a write operation.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Analysis/SelectorExtras.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
using namespace clang;
using namespace ento;
/// Records implications between symbols.
/// The semantics is:
/// (antecedent != 0) => (consequent != 0)
/// These implications are then read during the evaluation of the assumption,
/// and the appropriate antecedents are applied.
REGISTER_MAP_WITH_PROGRAMSTATE(NonNullImplicationMap, SymbolRef, SymbolRef)
/// The semantics is:
/// (antecedent == 0) => (consequent == 0)
REGISTER_MAP_WITH_PROGRAMSTATE(NullImplicationMap, SymbolRef, SymbolRef)
namespace {
class TrustNonnullChecker : public Checker<check::PostCall,
check::PostObjCMessage,
check::DeadSymbols,
eval::Assume> {
// Do not try to iterate over symbols with higher complexity.
static unsigned constexpr ComplexityThreshold = 10;
Selector ObjectForKeyedSubscriptSel;
Selector ObjectForKeySel;
Selector SetObjectForKeyedSubscriptSel;
Selector SetObjectForKeySel;
public:
TrustNonnullChecker(ASTContext &Ctx)
: ObjectForKeyedSubscriptSel(
getKeywordSelector(Ctx, "objectForKeyedSubscript")),
ObjectForKeySel(getKeywordSelector(Ctx, "objectForKey")),
SetObjectForKeyedSubscriptSel(
getKeywordSelector(Ctx, "setObject", "forKeyedSubscript")),
SetObjectForKeySel(getKeywordSelector(Ctx, "setObject", "forKey")) {}
ProgramStateRef evalAssume(ProgramStateRef State,
SVal Cond,
bool Assumption) const {
const SymbolRef CondS = Cond.getAsSymbol();
if (!CondS || CondS->computeComplexity() > ComplexityThreshold)
return State;
for (auto B=CondS->symbol_begin(), E=CondS->symbol_end(); B != E; ++B) {
const SymbolRef Antecedent = *B;
State = addImplication(Antecedent, State, true);
State = addImplication(Antecedent, State, false);
}
return State;
}
void checkPostCall(const CallEvent &Call, CheckerContext &C) const {
// Only trust annotations for system headers for non-protocols.
if (!Call.isInSystemHeader())
return;
ProgramStateRef State = C.getState();
if (isNonNullPtr(Call, C))
if (auto L = Call.getReturnValue().getAs<Loc>())
State = State->assume(*L, /*assumption=*/true);
C.addTransition(State);
}
void checkPostObjCMessage(const ObjCMethodCall &Msg,
CheckerContext &C) const {
const ObjCInterfaceDecl *ID = Msg.getReceiverInterface();
if (!ID)
return;
ProgramStateRef State = C.getState();
// Index to setter for NSMutableDictionary is assumed to be non-null,
// as an exception is thrown otherwise.
if (interfaceHasSuperclass(ID, "NSMutableDictionary") &&
(Msg.getSelector() == SetObjectForKeyedSubscriptSel ||
Msg.getSelector() == SetObjectForKeySel)) {
if (auto L = Msg.getArgSVal(1).getAs<Loc>())
State = State->assume(*L, /*assumption=*/true);
}
// Record an implication: index is non-null if the output is non-null.
if (interfaceHasSuperclass(ID, "NSDictionary") &&
(Msg.getSelector() == ObjectForKeyedSubscriptSel ||
Msg.getSelector() == ObjectForKeySel)) {
SymbolRef ArgS = Msg.getArgSVal(0).getAsSymbol();
SymbolRef RetS = Msg.getReturnValue().getAsSymbol();
if (ArgS && RetS) {
// Emulate an implication: the argument is non-null if
// the return value is non-null.
State = State->set<NonNullImplicationMap>(RetS, ArgS);
// Conversely, when the argument is null, the return value
// is definitely null.
State = State->set<NullImplicationMap>(ArgS, RetS);
}
}
C.addTransition(State);
}
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
ProgramStateRef State = C.getState();
State = dropDeadFromGDM<NullImplicationMap>(SymReaper, State);
State = dropDeadFromGDM<NonNullImplicationMap>(SymReaper, State);
C.addTransition(State);
}
private:
/// \returns State with GDM \p MapName where all dead symbols were
// removed.
template <typename MapName>
ProgramStateRef dropDeadFromGDM(SymbolReaper &SymReaper,
ProgramStateRef State) const {
for (const std::pair<SymbolRef, SymbolRef> &P : State->get<MapName>())
if (!SymReaper.isLive(P.first) || !SymReaper.isLive(P.second))
State = State->remove<MapName>(P.first);
return State;
}
/// \returns Whether we trust the result of the method call to be
/// a non-null pointer.
bool isNonNullPtr(const CallEvent &Call, CheckerContext &C) const {
QualType ExprRetType = Call.getResultType();
if (!ExprRetType->isAnyPointerType())
return false;
if (getNullabilityAnnotation(ExprRetType) == Nullability::Nonnull)
return true;
// The logic for ObjC instance method calls is more complicated,
// as the return value is nil when the receiver is nil.
if (!isa<ObjCMethodCall>(&Call))
return false;
const auto *MCall = cast<ObjCMethodCall>(&Call);
const ObjCMethodDecl *MD = MCall->getDecl();
// Distrust protocols.
if (isa<ObjCProtocolDecl>(MD->getDeclContext()))
return false;
QualType DeclRetType = MD->getReturnType();
if (getNullabilityAnnotation(DeclRetType) != Nullability::Nonnull)
return false;
// For class messages it is sufficient for the declaration to be
// annotated _Nonnull.
if (!MCall->isInstanceMessage())
return true;
// Alternatively, the analyzer could know that the receiver is not null.
SVal Receiver = MCall->getReceiverSVal();
ConditionTruthVal TV = C.getState()->isNonNull(Receiver);
if (TV.isConstrainedTrue())
return true;
return false;
}
/// \return Whether \p ID has a superclass by the name \p ClassName.
bool interfaceHasSuperclass(const ObjCInterfaceDecl *ID,
StringRef ClassName) const {
if (ID->getIdentifier()->getName() == ClassName)
return true;
if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
return interfaceHasSuperclass(Super, ClassName);
return false;
}
/// \return a state with an optional implication added (if exists)
/// from a map of recorded implications.
/// If \p Negated is true, checks NullImplicationMap, and assumes
/// the negation of \p Antecedent.
/// Checks NonNullImplicationMap and assumes \p Antecedent otherwise.
ProgramStateRef addImplication(SymbolRef Antecedent,
ProgramStateRef InputState,
bool Negated) const {
if (!InputState)
return nullptr;
SValBuilder &SVB = InputState->getStateManager().getSValBuilder();
const SymbolRef *Consequent =
Negated ? InputState->get<NonNullImplicationMap>(Antecedent)
: InputState->get<NullImplicationMap>(Antecedent);
if (!Consequent)
return InputState;
SVal AntecedentV = SVB.makeSymbolVal(Antecedent);
ProgramStateRef State = InputState;
if ((Negated && InputState->isNonNull(AntecedentV).isConstrainedTrue())
|| (!Negated && InputState->isNull(AntecedentV).isConstrainedTrue())) {
SVal ConsequentS = SVB.makeSymbolVal(*Consequent);
State = InputState->assume(ConsequentS.castAs<DefinedSVal>(), Negated);
if (!State)
return nullptr;
// Drop implications from the map.
if (Negated) {
State = State->remove<NonNullImplicationMap>(Antecedent);
State = State->remove<NullImplicationMap>(*Consequent);
} else {
State = State->remove<NullImplicationMap>(Antecedent);
State = State->remove<NonNullImplicationMap>(*Consequent);
}
}
return State;
}
};
} // end empty namespace
void ento::registerTrustNonnullChecker(CheckerManager &Mgr) {
Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext());
}
bool ento::shouldRegisterTrustNonnullChecker(const CheckerManager &mgr) {
return true;
}