/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- 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 BasicObjCFoundationChecks, a class that encapsulates |
10 | | // a set of simple checks to run on Objective-C code using Apple's Foundation |
11 | | // classes. |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #include "clang/AST/ASTContext.h" |
16 | | #include "clang/AST/DeclObjC.h" |
17 | | #include "clang/AST/Expr.h" |
18 | | #include "clang/AST/ExprObjC.h" |
19 | | #include "clang/AST/StmtObjC.h" |
20 | | #include "clang/Analysis/DomainSpecific/CocoaConventions.h" |
21 | | #include "clang/Analysis/SelectorExtras.h" |
22 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
23 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
24 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
25 | | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
26 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
27 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
28 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
29 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" |
30 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
31 | | #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" |
32 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
33 | | #include "llvm/ADT/STLExtras.h" |
34 | | #include "llvm/ADT/SmallString.h" |
35 | | #include "llvm/ADT/StringMap.h" |
36 | | #include "llvm/Support/raw_ostream.h" |
37 | | #include <optional> |
38 | | |
39 | | using namespace clang; |
40 | | using namespace ento; |
41 | | using namespace llvm; |
42 | | |
43 | | namespace { |
44 | | class APIMisuse : public BugType { |
45 | | public: |
46 | | APIMisuse(const CheckerBase *checker, const char *name) |
47 | 85 | : BugType(checker, name, "API Misuse (Apple)") {} |
48 | | }; |
49 | | } // end anonymous namespace |
50 | | |
51 | | //===----------------------------------------------------------------------===// |
52 | | // Utility functions. |
53 | | //===----------------------------------------------------------------------===// |
54 | | |
55 | 46 | static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) { |
56 | 46 | if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) |
57 | 46 | return ID->getIdentifier()->getName(); |
58 | 0 | return StringRef(); |
59 | 46 | } |
60 | | |
61 | | enum FoundationClass { |
62 | | FC_None, |
63 | | FC_NSArray, |
64 | | FC_NSDictionary, |
65 | | FC_NSEnumerator, |
66 | | FC_NSNull, |
67 | | FC_NSOrderedSet, |
68 | | FC_NSSet, |
69 | | FC_NSString |
70 | | }; |
71 | | |
72 | | static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID, |
73 | 1.50k | bool IncludeSuperclasses = true) { |
74 | 1.50k | static llvm::StringMap<FoundationClass> Classes; |
75 | 1.50k | if (Classes.empty()) { |
76 | 15 | Classes["NSArray"] = FC_NSArray; |
77 | 15 | Classes["NSDictionary"] = FC_NSDictionary; |
78 | 15 | Classes["NSEnumerator"] = FC_NSEnumerator; |
79 | 15 | Classes["NSNull"] = FC_NSNull; |
80 | 15 | Classes["NSOrderedSet"] = FC_NSOrderedSet; |
81 | 15 | Classes["NSSet"] = FC_NSSet; |
82 | 15 | Classes["NSString"] = FC_NSString; |
83 | 15 | } |
84 | | |
85 | | // FIXME: Should we cache this at all? |
86 | 1.50k | FoundationClass result = Classes.lookup(ID->getIdentifier()->getName()); |
87 | 1.50k | if (result == FC_None && IncludeSuperclasses801 ) |
88 | 702 | if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) |
89 | 449 | return findKnownClass(Super); |
90 | | |
91 | 1.05k | return result; |
92 | 1.50k | } |
93 | | |
94 | | //===----------------------------------------------------------------------===// |
95 | | // NilArgChecker - Check for prohibited nil arguments to ObjC method calls. |
96 | | //===----------------------------------------------------------------------===// |
97 | | |
98 | | namespace { |
99 | | class NilArgChecker : public Checker<check::PreObjCMessage, |
100 | | check::PostStmt<ObjCDictionaryLiteral>, |
101 | | check::PostStmt<ObjCArrayLiteral>, |
102 | | EventDispatcher<ImplicitNullDerefEvent>> { |
103 | | mutable std::unique_ptr<APIMisuse> BT; |
104 | | |
105 | | mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors; |
106 | | mutable Selector ArrayWithObjectSel; |
107 | | mutable Selector AddObjectSel; |
108 | | mutable Selector InsertObjectAtIndexSel; |
109 | | mutable Selector ReplaceObjectAtIndexWithObjectSel; |
110 | | mutable Selector SetObjectAtIndexedSubscriptSel; |
111 | | mutable Selector ArrayByAddingObjectSel; |
112 | | mutable Selector DictionaryWithObjectForKeySel; |
113 | | mutable Selector SetObjectForKeySel; |
114 | | mutable Selector SetObjectForKeyedSubscriptSel; |
115 | | mutable Selector RemoveObjectForKeySel; |
116 | | |
117 | | void warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const; |
118 | | |
119 | | void warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned Arg, |
120 | | FoundationClass Class, bool CanBeSubscript = false) const; |
121 | | |
122 | | void generateBugReport(ExplodedNode *N, StringRef Msg, SourceRange Range, |
123 | | const Expr *Expr, CheckerContext &C) const; |
124 | | |
125 | | public: |
126 | | void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; |
127 | | void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; |
128 | | void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; |
129 | | }; |
130 | | } // end anonymous namespace |
131 | | |
132 | | void NilArgChecker::warnIfNilExpr(const Expr *E, |
133 | | const char *Msg, |
134 | 55 | CheckerContext &C) const { |
135 | 55 | auto Location = C.getSVal(E).getAs<Loc>(); |
136 | 55 | if (!Location) |
137 | 0 | return; |
138 | | |
139 | 55 | auto [NonNull, Null] = C.getState()->assume(*Location); |
140 | | |
141 | | // If it's known to be null. |
142 | 55 | if (!NonNull && Null9 ) { |
143 | 9 | if (ExplodedNode *N = C.generateErrorNode()) { |
144 | 8 | generateBugReport(N, Msg, E->getSourceRange(), E, C); |
145 | 8 | return; |
146 | 8 | } |
147 | 9 | } |
148 | | |
149 | | // If it might be null, assume that it cannot after this operation. |
150 | 47 | if (Null) { |
151 | | // One needs to make sure the pointer is non-null to be used here. |
152 | 9 | if (ExplodedNode *N = C.generateSink(Null, C.getPredecessor())) { |
153 | 8 | dispatchEvent({*Location, /*IsLoad=*/false, N, &C.getBugReporter(), |
154 | 8 | /*IsDirectDereference=*/false}); |
155 | 8 | } |
156 | 9 | C.addTransition(NonNull); |
157 | 9 | } |
158 | 47 | } |
159 | | |
160 | | void NilArgChecker::warnIfNilArg(CheckerContext &C, |
161 | | const ObjCMethodCall &msg, |
162 | | unsigned int Arg, |
163 | | FoundationClass Class, |
164 | 65 | bool CanBeSubscript) const { |
165 | | // Check if the argument is nil. |
166 | 65 | ProgramStateRef State = C.getState(); |
167 | 65 | if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) |
168 | 20 | return; |
169 | | |
170 | | // NOTE: We cannot throw non-fatal errors from warnIfNilExpr, |
171 | | // because it's called multiple times from some callers, so it'd cause |
172 | | // an unwanted state split if two or more non-fatal errors are thrown |
173 | | // within the same checker callback. For now we don't want to, but |
174 | | // it'll need to be fixed if we ever want to. |
175 | 45 | if (ExplodedNode *N = C.generateErrorNode()) { |
176 | 44 | SmallString<128> sbuf; |
177 | 44 | llvm::raw_svector_ostream os(sbuf); |
178 | | |
179 | 44 | if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript5 ) { |
180 | | |
181 | 4 | if (Class == FC_NSArray) { |
182 | 1 | os << "Array element cannot be nil"; |
183 | 3 | } else if (Class == FC_NSDictionary) { |
184 | 3 | if (Arg == 0) { |
185 | 0 | os << "Value stored into '"; |
186 | 0 | os << GetReceiverInterfaceName(msg) << "' cannot be nil"; |
187 | 3 | } else { |
188 | 3 | assert(Arg == 1); |
189 | 3 | os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; |
190 | 3 | } |
191 | 3 | } else |
192 | 0 | llvm_unreachable("Missing foundation class for the subscript expr"); |
193 | | |
194 | 40 | } else { |
195 | 40 | if (Class == FC_NSDictionary) { |
196 | 7 | if (Arg == 0) |
197 | 3 | os << "Value argument "; |
198 | 4 | else { |
199 | 4 | assert(Arg == 1); |
200 | 4 | os << "Key argument "; |
201 | 4 | } |
202 | 7 | os << "to '"; |
203 | 7 | msg.getSelector().print(os); |
204 | 7 | os << "' cannot be nil"; |
205 | 33 | } else { |
206 | 33 | os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '"; |
207 | 33 | msg.getSelector().print(os); |
208 | 33 | os << "' cannot be nil"; |
209 | 33 | } |
210 | 40 | } |
211 | | |
212 | 44 | generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), |
213 | 44 | msg.getArgExpr(Arg), C); |
214 | 44 | } |
215 | 45 | } |
216 | | |
217 | | void NilArgChecker::generateBugReport(ExplodedNode *N, |
218 | | StringRef Msg, |
219 | | SourceRange Range, |
220 | | const Expr *E, |
221 | 52 | CheckerContext &C) const { |
222 | 52 | if (!BT) |
223 | 8 | BT.reset(new APIMisuse(this, "nil argument")); |
224 | | |
225 | 52 | auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); |
226 | 52 | R->addRange(Range); |
227 | 52 | bugreporter::trackExpressionValue(N, E, *R); |
228 | 52 | C.emitReport(std::move(R)); |
229 | 52 | } |
230 | | |
231 | | void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, |
232 | 491 | CheckerContext &C) const { |
233 | 491 | const ObjCInterfaceDecl *ID = msg.getReceiverInterface(); |
234 | 491 | if (!ID) |
235 | 8 | return; |
236 | | |
237 | 483 | FoundationClass Class = findKnownClass(ID); |
238 | | |
239 | 483 | static const unsigned InvalidArgIndex = UINT_MAX; |
240 | 483 | unsigned Arg = InvalidArgIndex; |
241 | 483 | bool CanBeSubscript = false; |
242 | | |
243 | 483 | if (Class == FC_NSString) { |
244 | 329 | Selector S = msg.getSelector(); |
245 | | |
246 | 329 | if (S.isUnarySelector()) |
247 | 293 | return; |
248 | | |
249 | 36 | if (StringSelectors.empty()) { |
250 | 4 | ASTContext &Ctx = C.getASTContext(); |
251 | 4 | Selector Sels[] = { |
252 | 4 | getKeywordSelector(Ctx, "caseInsensitiveCompare"), |
253 | 4 | getKeywordSelector(Ctx, "compare"), |
254 | 4 | getKeywordSelector(Ctx, "compare", "options"), |
255 | 4 | getKeywordSelector(Ctx, "compare", "options", "range"), |
256 | 4 | getKeywordSelector(Ctx, "compare", "options", "range", "locale"), |
257 | 4 | getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet"), |
258 | 4 | getKeywordSelector(Ctx, "initWithFormat"), |
259 | 4 | getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare"), |
260 | 4 | getKeywordSelector(Ctx, "localizedCompare"), |
261 | 4 | getKeywordSelector(Ctx, "localizedStandardCompare"), |
262 | 4 | }; |
263 | 4 | for (Selector KnownSel : Sels) |
264 | 40 | StringSelectors[KnownSel] = 0; |
265 | 4 | } |
266 | 36 | auto I = StringSelectors.find(S); |
267 | 36 | if (I == StringSelectors.end()) |
268 | 8 | return; |
269 | 28 | Arg = I->second; |
270 | 154 | } else if (Class == FC_NSArray) { |
271 | 33 | Selector S = msg.getSelector(); |
272 | | |
273 | 33 | if (S.isUnarySelector()) |
274 | 17 | return; |
275 | | |
276 | 16 | if (ArrayWithObjectSel.isNull()) { |
277 | 5 | ASTContext &Ctx = C.getASTContext(); |
278 | 5 | ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject"); |
279 | 5 | AddObjectSel = getKeywordSelector(Ctx, "addObject"); |
280 | 5 | InsertObjectAtIndexSel = |
281 | 5 | getKeywordSelector(Ctx, "insertObject", "atIndex"); |
282 | 5 | ReplaceObjectAtIndexWithObjectSel = |
283 | 5 | getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject"); |
284 | 5 | SetObjectAtIndexedSubscriptSel = |
285 | 5 | getKeywordSelector(Ctx, "setObject", "atIndexedSubscript"); |
286 | 5 | ArrayByAddingObjectSel = getKeywordSelector(Ctx, "arrayByAddingObject"); |
287 | 5 | } |
288 | | |
289 | 16 | if (S == ArrayWithObjectSel || S == AddObjectSel || |
290 | 16 | S == InsertObjectAtIndexSel10 || S == ArrayByAddingObjectSel9 ) { |
291 | 8 | Arg = 0; |
292 | 8 | } else if (S == SetObjectAtIndexedSubscriptSel) { |
293 | 2 | Arg = 0; |
294 | 2 | CanBeSubscript = true; |
295 | 6 | } else if (S == ReplaceObjectAtIndexWithObjectSel) { |
296 | 1 | Arg = 1; |
297 | 1 | } |
298 | 121 | } else if (Class == FC_NSDictionary) { |
299 | 21 | Selector S = msg.getSelector(); |
300 | | |
301 | 21 | if (S.isUnarySelector()) |
302 | 1 | return; |
303 | | |
304 | 20 | if (DictionaryWithObjectForKeySel.isNull()) { |
305 | 2 | ASTContext &Ctx = C.getASTContext(); |
306 | 2 | DictionaryWithObjectForKeySel = |
307 | 2 | getKeywordSelector(Ctx, "dictionaryWithObject", "forKey"); |
308 | 2 | SetObjectForKeySel = getKeywordSelector(Ctx, "setObject", "forKey"); |
309 | 2 | SetObjectForKeyedSubscriptSel = |
310 | 2 | getKeywordSelector(Ctx, "setObject", "forKeyedSubscript"); |
311 | 2 | RemoveObjectForKeySel = getKeywordSelector(Ctx, "removeObjectForKey"); |
312 | 2 | } |
313 | | |
314 | 20 | if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel18 ) { |
315 | 7 | Arg = 0; |
316 | 7 | warnIfNilArg(C, msg, /* Arg */1, Class); |
317 | 13 | } else if (S == SetObjectForKeyedSubscriptSel) { |
318 | 8 | CanBeSubscript = true; |
319 | 8 | Arg = 1; |
320 | 8 | } else if (5 S == RemoveObjectForKeySel5 ) { |
321 | 4 | Arg = 0; |
322 | 4 | } |
323 | 20 | } |
324 | | |
325 | | // If argument is '0', report a warning. |
326 | 164 | if ((Arg != InvalidArgIndex)) |
327 | 58 | warnIfNilArg(C, msg, Arg, Class, CanBeSubscript); |
328 | 164 | } |
329 | | |
330 | | void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL, |
331 | 10 | CheckerContext &C) const { |
332 | 10 | unsigned NumOfElements = AL->getNumElements(); |
333 | 31 | for (unsigned i = 0; i < NumOfElements; ++i21 ) { |
334 | 21 | warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C); |
335 | 21 | } |
336 | 10 | } |
337 | | |
338 | | void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, |
339 | 12 | CheckerContext &C) const { |
340 | 12 | unsigned NumOfElements = DL->getNumElements(); |
341 | 29 | for (unsigned i = 0; i < NumOfElements; ++i17 ) { |
342 | 17 | ObjCDictionaryElement Element = DL->getKeyValueElement(i); |
343 | 17 | warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C); |
344 | 17 | warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C); |
345 | 17 | } |
346 | 12 | } |
347 | | |
348 | | //===----------------------------------------------------------------------===// |
349 | | // Checking for mismatched types passed to CFNumberCreate/CFNumberGetValue. |
350 | | //===----------------------------------------------------------------------===// |
351 | | |
352 | | namespace { |
353 | | class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > { |
354 | | mutable std::unique_ptr<APIMisuse> BT; |
355 | | mutable IdentifierInfo *ICreate = nullptr, *IGetValue = nullptr; |
356 | | public: |
357 | 47 | CFNumberChecker() = default; |
358 | | |
359 | | void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; |
360 | | }; |
361 | | } // end anonymous namespace |
362 | | |
363 | | enum CFNumberType { |
364 | | kCFNumberSInt8Type = 1, |
365 | | kCFNumberSInt16Type = 2, |
366 | | kCFNumberSInt32Type = 3, |
367 | | kCFNumberSInt64Type = 4, |
368 | | kCFNumberFloat32Type = 5, |
369 | | kCFNumberFloat64Type = 6, |
370 | | kCFNumberCharType = 7, |
371 | | kCFNumberShortType = 8, |
372 | | kCFNumberIntType = 9, |
373 | | kCFNumberLongType = 10, |
374 | | kCFNumberLongLongType = 11, |
375 | | kCFNumberFloatType = 12, |
376 | | kCFNumberDoubleType = 13, |
377 | | kCFNumberCFIndexType = 14, |
378 | | kCFNumberNSIntegerType = 15, |
379 | | kCFNumberCGFloatType = 16 |
380 | | }; |
381 | | |
382 | 8 | static std::optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { |
383 | 8 | static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; |
384 | | |
385 | 8 | if (i < kCFNumberCharType) |
386 | 7 | return FixedSize[i-1]; |
387 | | |
388 | 1 | QualType T; |
389 | | |
390 | 1 | switch (i) { |
391 | 0 | case kCFNumberCharType: T = Ctx.CharTy; break; |
392 | 0 | case kCFNumberShortType: T = Ctx.ShortTy; break; |
393 | 0 | case kCFNumberIntType: T = Ctx.IntTy; break; |
394 | 1 | case kCFNumberLongType: T = Ctx.LongTy; break; |
395 | 0 | case kCFNumberLongLongType: T = Ctx.LongLongTy; break; |
396 | 0 | case kCFNumberFloatType: T = Ctx.FloatTy; break; |
397 | 0 | case kCFNumberDoubleType: T = Ctx.DoubleTy; break; |
398 | 0 | case kCFNumberCFIndexType: |
399 | 0 | case kCFNumberNSIntegerType: |
400 | 0 | case kCFNumberCGFloatType: |
401 | | // FIXME: We need a way to map from names to Type*. |
402 | 0 | default: |
403 | 0 | return std::nullopt; |
404 | 1 | } |
405 | | |
406 | 1 | return Ctx.getTypeSize(T); |
407 | 1 | } |
408 | | |
409 | | #if 0 |
410 | | static const char* GetCFNumberTypeStr(uint64_t i) { |
411 | | static const char* Names[] = { |
412 | | "kCFNumberSInt8Type", |
413 | | "kCFNumberSInt16Type", |
414 | | "kCFNumberSInt32Type", |
415 | | "kCFNumberSInt64Type", |
416 | | "kCFNumberFloat32Type", |
417 | | "kCFNumberFloat64Type", |
418 | | "kCFNumberCharType", |
419 | | "kCFNumberShortType", |
420 | | "kCFNumberIntType", |
421 | | "kCFNumberLongType", |
422 | | "kCFNumberLongLongType", |
423 | | "kCFNumberFloatType", |
424 | | "kCFNumberDoubleType", |
425 | | "kCFNumberCFIndexType", |
426 | | "kCFNumberNSIntegerType", |
427 | | "kCFNumberCGFloatType" |
428 | | }; |
429 | | |
430 | | return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType"; |
431 | | } |
432 | | #endif |
433 | | |
434 | | void CFNumberChecker::checkPreStmt(const CallExpr *CE, |
435 | 305 | CheckerContext &C) const { |
436 | 305 | ProgramStateRef state = C.getState(); |
437 | 305 | const FunctionDecl *FD = C.getCalleeDecl(CE); |
438 | 305 | if (!FD) |
439 | 3 | return; |
440 | | |
441 | 302 | ASTContext &Ctx = C.getASTContext(); |
442 | 302 | if (!ICreate) { |
443 | 19 | ICreate = &Ctx.Idents.get("CFNumberCreate"); |
444 | 19 | IGetValue = &Ctx.Idents.get("CFNumberGetValue"); |
445 | 19 | } |
446 | 302 | if (!(FD->getIdentifier() == ICreate || FD->getIdentifier() == IGetValue296 ) || |
447 | 302 | CE->getNumArgs() != 38 ) |
448 | 294 | return; |
449 | | |
450 | | // Get the value of the "theType" argument. |
451 | 8 | SVal TheTypeVal = C.getSVal(CE->getArg(1)); |
452 | | |
453 | | // FIXME: We really should allow ranges of valid theType values, and |
454 | | // bifurcate the state appropriately. |
455 | 8 | std::optional<nonloc::ConcreteInt> V = |
456 | 8 | dyn_cast<nonloc::ConcreteInt>(TheTypeVal); |
457 | 8 | if (!V) |
458 | 0 | return; |
459 | | |
460 | 8 | uint64_t NumberKind = V->getValue().getLimitedValue(); |
461 | 8 | std::optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind); |
462 | | |
463 | | // FIXME: In some cases we can emit an error. |
464 | 8 | if (!OptCFNumberSize) |
465 | 0 | return; |
466 | | |
467 | 8 | uint64_t CFNumberSize = *OptCFNumberSize; |
468 | | |
469 | | // Look at the value of the integer being passed by reference. Essentially |
470 | | // we want to catch cases where the value passed in is not equal to the |
471 | | // size of the type being created. |
472 | 8 | SVal TheValueExpr = C.getSVal(CE->getArg(2)); |
473 | | |
474 | | // FIXME: Eventually we should handle arbitrary locations. We can do this |
475 | | // by having an enhanced memory model that does low-level typing. |
476 | 8 | std::optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>(); |
477 | 8 | if (!LV) |
478 | 0 | return; |
479 | | |
480 | 8 | const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts()); |
481 | 8 | if (!R) |
482 | 0 | return; |
483 | | |
484 | 8 | QualType T = Ctx.getCanonicalType(R->getValueType()); |
485 | | |
486 | | // FIXME: If the pointee isn't an integer type, should we flag a warning? |
487 | | // People can do weird stuff with pointers. |
488 | | |
489 | 8 | if (!T->isIntegralOrEnumerationType()) |
490 | 0 | return; |
491 | | |
492 | 8 | uint64_t PrimitiveTypeSize = Ctx.getTypeSize(T); |
493 | | |
494 | 8 | if (PrimitiveTypeSize == CFNumberSize) |
495 | 3 | return; |
496 | | |
497 | | // FIXME: We can actually create an abstract "CFNumber" object that has |
498 | | // the bits initialized to the provided values. |
499 | 5 | ExplodedNode *N = C.generateNonFatalErrorNode(); |
500 | 5 | if (N) { |
501 | 5 | SmallString<128> sbuf; |
502 | 5 | llvm::raw_svector_ostream os(sbuf); |
503 | 5 | bool isCreate = (FD->getIdentifier() == ICreate); |
504 | | |
505 | 5 | if (isCreate) { |
506 | 3 | os << (PrimitiveTypeSize == 8 ? "An "1 : "A "2 ) |
507 | 3 | << PrimitiveTypeSize << "-bit integer is used to initialize a " |
508 | 3 | << "CFNumber object that represents " |
509 | 3 | << (CFNumberSize == 8 ? "an "1 : "a "2 ) |
510 | 3 | << CFNumberSize << "-bit integer; "; |
511 | 3 | } else { |
512 | 2 | os << "A CFNumber object that represents " |
513 | 2 | << (CFNumberSize == 8 ? "an "1 : "a "1 ) |
514 | 2 | << CFNumberSize << "-bit integer is used to initialize " |
515 | 2 | << (PrimitiveTypeSize == 8 ? "an "1 : "a "1 ) |
516 | 2 | << PrimitiveTypeSize << "-bit integer; "; |
517 | 2 | } |
518 | | |
519 | 5 | if (PrimitiveTypeSize < CFNumberSize) |
520 | 3 | os << (CFNumberSize - PrimitiveTypeSize) |
521 | 3 | << " bits of the CFNumber value will " |
522 | 3 | << (isCreate ? "be garbage."2 : "overwrite adjacent storage."1 ); |
523 | 2 | else |
524 | 2 | os << (PrimitiveTypeSize - CFNumberSize) |
525 | 2 | << " bits of the integer value will be " |
526 | 2 | << (isCreate ? "lost."1 : "garbage."1 ); |
527 | | |
528 | 5 | if (!BT) |
529 | 1 | BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs")); |
530 | | |
531 | 5 | auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); |
532 | 5 | report->addRange(CE->getArg(2)->getSourceRange()); |
533 | 5 | C.emitReport(std::move(report)); |
534 | 5 | } |
535 | 5 | } |
536 | | |
537 | | //===----------------------------------------------------------------------===// |
538 | | // CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments. |
539 | | //===----------------------------------------------------------------------===// |
540 | | |
541 | | namespace { |
542 | | class CFRetainReleaseChecker : public Checker<check::PreCall> { |
543 | | mutable APIMisuse BT{this, "null passed to CF memory management function"}; |
544 | | const CallDescriptionSet ModelledCalls = { |
545 | | {{"CFRetain"}, 1}, |
546 | | {{"CFRelease"}, 1}, |
547 | | {{"CFMakeCollectable"}, 1}, |
548 | | {{"CFAutorelease"}, 1}, |
549 | | }; |
550 | | |
551 | | public: |
552 | | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
553 | | }; |
554 | | } // end anonymous namespace |
555 | | |
556 | | void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, |
557 | 2.71k | CheckerContext &C) const { |
558 | | // TODO: Make this check part of CallDescription. |
559 | 2.71k | if (!Call.isGlobalCFunction()) |
560 | 1.58k | return; |
561 | | |
562 | | // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease. |
563 | 1.13k | if (!ModelledCalls.contains(Call)) |
564 | 893 | return; |
565 | | |
566 | | // Get the argument's value. |
567 | 239 | SVal ArgVal = Call.getArgSVal(0); |
568 | 239 | std::optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); |
569 | 239 | if (!DefArgVal) |
570 | 0 | return; |
571 | | |
572 | | // Is it null? |
573 | 239 | ProgramStateRef state = C.getState(); |
574 | 239 | ProgramStateRef stateNonNull, stateNull; |
575 | 239 | std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal); |
576 | | |
577 | 239 | if (!stateNonNull) { |
578 | 14 | ExplodedNode *N = C.generateErrorNode(stateNull); |
579 | 14 | if (!N) |
580 | 0 | return; |
581 | | |
582 | 14 | SmallString<64> Str; |
583 | 14 | raw_svector_ostream OS(Str); |
584 | 14 | OS << "Null pointer argument in call to " |
585 | 14 | << cast<FunctionDecl>(Call.getDecl())->getName(); |
586 | | |
587 | 14 | auto report = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N); |
588 | 14 | report->addRange(Call.getArgSourceRange(0)); |
589 | 14 | bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report); |
590 | 14 | C.emitReport(std::move(report)); |
591 | 14 | return; |
592 | 14 | } |
593 | | |
594 | | // From here on, we know the argument is non-null. |
595 | 225 | C.addTransition(stateNonNull); |
596 | 225 | } |
597 | | |
598 | | //===----------------------------------------------------------------------===// |
599 | | // Check for sending 'retain', 'release', or 'autorelease' directly to a Class. |
600 | | //===----------------------------------------------------------------------===// |
601 | | |
602 | | namespace { |
603 | | class ClassReleaseChecker : public Checker<check::PreObjCMessage> { |
604 | | mutable Selector releaseS; |
605 | | mutable Selector retainS; |
606 | | mutable Selector autoreleaseS; |
607 | | mutable Selector drainS; |
608 | | mutable std::unique_ptr<BugType> BT; |
609 | | |
610 | | public: |
611 | | void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; |
612 | | }; |
613 | | } // end anonymous namespace |
614 | | |
615 | | void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, |
616 | 1.28k | CheckerContext &C) const { |
617 | 1.28k | if (!BT) { |
618 | 14 | BT.reset(new APIMisuse( |
619 | 14 | this, "message incorrectly sent to class instead of class instance")); |
620 | | |
621 | 14 | ASTContext &Ctx = C.getASTContext(); |
622 | 14 | releaseS = GetNullarySelector("release", Ctx); |
623 | 14 | retainS = GetNullarySelector("retain", Ctx); |
624 | 14 | autoreleaseS = GetNullarySelector("autorelease", Ctx); |
625 | 14 | drainS = GetNullarySelector("drain", Ctx); |
626 | 14 | } |
627 | | |
628 | 1.28k | if (msg.isInstanceMessage()) |
629 | 924 | return; |
630 | 362 | const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); |
631 | 362 | assert(Class); |
632 | | |
633 | 362 | Selector S = msg.getSelector(); |
634 | 362 | if (!(S == releaseS || S == retainS359 || S == autoreleaseS356 || S == drainS353 )) |
635 | 350 | return; |
636 | | |
637 | 12 | if (ExplodedNode *N = C.generateNonFatalErrorNode()) { |
638 | 12 | SmallString<200> buf; |
639 | 12 | llvm::raw_svector_ostream os(buf); |
640 | | |
641 | 12 | os << "The '"; |
642 | 12 | S.print(os); |
643 | 12 | os << "' message should be sent to instances " |
644 | 12 | "of class '" << Class->getName() |
645 | 12 | << "' and not the class directly"; |
646 | | |
647 | 12 | auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); |
648 | 12 | report->addRange(msg.getSourceRange()); |
649 | 12 | C.emitReport(std::move(report)); |
650 | 12 | } |
651 | 12 | } |
652 | | |
653 | | //===----------------------------------------------------------------------===// |
654 | | // Check for passing non-Objective-C types to variadic methods that expect |
655 | | // only Objective-C types. |
656 | | //===----------------------------------------------------------------------===// |
657 | | |
658 | | namespace { |
659 | | class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> { |
660 | | mutable Selector arrayWithObjectsS; |
661 | | mutable Selector dictionaryWithObjectsAndKeysS; |
662 | | mutable Selector setWithObjectsS; |
663 | | mutable Selector orderedSetWithObjectsS; |
664 | | mutable Selector initWithObjectsS; |
665 | | mutable Selector initWithObjectsAndKeysS; |
666 | | mutable std::unique_ptr<BugType> BT; |
667 | | |
668 | | bool isVariadicMessage(const ObjCMethodCall &msg) const; |
669 | | |
670 | | public: |
671 | | void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; |
672 | | }; |
673 | | } // end anonymous namespace |
674 | | |
675 | | /// isVariadicMessage - Returns whether the given message is a variadic message, |
676 | | /// where all arguments must be Objective-C types. |
677 | | bool |
678 | 96 | VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const { |
679 | 96 | const ObjCMethodDecl *MD = msg.getDecl(); |
680 | | |
681 | 96 | if (!MD || !MD->isVariadic()95 || isa<ObjCProtocolDecl>(MD->getDeclContext())15 ) |
682 | 82 | return false; |
683 | | |
684 | 14 | Selector S = msg.getSelector(); |
685 | | |
686 | 14 | if (msg.isInstanceMessage()) { |
687 | | // FIXME: Ideally we'd look at the receiver interface here, but that's not |
688 | | // useful for init, because alloc returns 'id'. In theory, this could lead |
689 | | // to false positives, for example if there existed a class that had an |
690 | | // initWithObjects: implementation that does accept non-Objective-C pointer |
691 | | // types, but the chance of that happening is pretty small compared to the |
692 | | // gains that this analysis gives. |
693 | 8 | const ObjCInterfaceDecl *Class = MD->getClassInterface(); |
694 | | |
695 | 8 | switch (findKnownClass(Class)) { |
696 | 1 | case FC_NSArray: |
697 | 2 | case FC_NSOrderedSet: |
698 | 3 | case FC_NSSet: |
699 | 3 | return S == initWithObjectsS; |
700 | 5 | case FC_NSDictionary: |
701 | 5 | return S == initWithObjectsAndKeysS; |
702 | 0 | default: |
703 | 0 | return false; |
704 | 8 | } |
705 | 8 | } else { |
706 | 6 | const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); |
707 | | |
708 | 6 | switch (findKnownClass(Class)) { |
709 | 3 | case FC_NSArray: |
710 | 3 | return S == arrayWithObjectsS; |
711 | 1 | case FC_NSOrderedSet: |
712 | 1 | return S == orderedSetWithObjectsS; |
713 | 1 | case FC_NSSet: |
714 | 1 | return S == setWithObjectsS; |
715 | 1 | case FC_NSDictionary: |
716 | 1 | return S == dictionaryWithObjectsAndKeysS; |
717 | 0 | default: |
718 | 0 | return false; |
719 | 6 | } |
720 | 6 | } |
721 | 14 | } |
722 | | |
723 | | void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, |
724 | 96 | CheckerContext &C) const { |
725 | 96 | if (!BT) { |
726 | 5 | BT.reset(new APIMisuse(this, |
727 | 5 | "Arguments passed to variadic method aren't all " |
728 | 5 | "Objective-C pointer types")); |
729 | | |
730 | 5 | ASTContext &Ctx = C.getASTContext(); |
731 | 5 | arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx); |
732 | 5 | dictionaryWithObjectsAndKeysS = |
733 | 5 | GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); |
734 | 5 | setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); |
735 | 5 | orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx); |
736 | | |
737 | 5 | initWithObjectsS = GetUnarySelector("initWithObjects", Ctx); |
738 | 5 | initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx); |
739 | 5 | } |
740 | | |
741 | 96 | if (!isVariadicMessage(msg)) |
742 | 82 | return; |
743 | | |
744 | | // We are not interested in the selector arguments since they have |
745 | | // well-defined types, so the compiler will issue a warning for them. |
746 | 14 | unsigned variadicArgsBegin = msg.getSelector().getNumArgs(); |
747 | | |
748 | | // We're not interested in the last argument since it has to be nil or the |
749 | | // compiler would have issued a warning for it elsewhere. |
750 | 14 | unsigned variadicArgsEnd = msg.getNumArgs() - 1; |
751 | | |
752 | 14 | if (variadicArgsEnd <= variadicArgsBegin) |
753 | 0 | return; |
754 | | |
755 | | // Verify that all arguments have Objective-C types. |
756 | 14 | std::optional<ExplodedNode *> errorNode; |
757 | | |
758 | 32 | for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I18 ) { |
759 | 18 | QualType ArgTy = msg.getArgExpr(I)->getType(); |
760 | 18 | if (ArgTy->isObjCObjectPointerType()) |
761 | 4 | continue; |
762 | | |
763 | | // Block pointers are treaded as Objective-C pointers. |
764 | 14 | if (ArgTy->isBlockPointerType()) |
765 | 1 | continue; |
766 | | |
767 | | // Ignore pointer constants. |
768 | 13 | if (isa<loc::ConcreteInt>(msg.getArgSVal(I))) |
769 | 1 | continue; |
770 | | |
771 | | // Ignore pointer types annotated with 'NSObject' attribute. |
772 | 12 | if (C.getASTContext().isObjCNSObjectType(ArgTy)) |
773 | 1 | continue; |
774 | | |
775 | | // Ignore CF references, which can be toll-free bridged. |
776 | 11 | if (coreFoundation::isCFObjectRef(ArgTy)) |
777 | 1 | continue; |
778 | | |
779 | | // Generate only one error node to use for all bug reports. |
780 | 10 | if (!errorNode) |
781 | 9 | errorNode = C.generateNonFatalErrorNode(); |
782 | | |
783 | 10 | if (!*errorNode) |
784 | 0 | continue; |
785 | | |
786 | 10 | SmallString<128> sbuf; |
787 | 10 | llvm::raw_svector_ostream os(sbuf); |
788 | | |
789 | 10 | StringRef TypeName = GetReceiverInterfaceName(msg); |
790 | 10 | if (!TypeName.empty()) |
791 | 10 | os << "Argument to '" << TypeName << "' method '"; |
792 | 0 | else |
793 | 0 | os << "Argument to method '"; |
794 | | |
795 | 10 | msg.getSelector().print(os); |
796 | 10 | os << "' should be an Objective-C pointer type, not '"; |
797 | 10 | ArgTy.print(os, C.getLangOpts()); |
798 | 10 | os << "'"; |
799 | | |
800 | 10 | auto R = |
801 | 10 | std::make_unique<PathSensitiveBugReport>(*BT, os.str(), *errorNode); |
802 | 10 | R->addRange(msg.getArgSourceRange(I)); |
803 | 10 | C.emitReport(std::move(R)); |
804 | 10 | } |
805 | 14 | } |
806 | | |
807 | | //===----------------------------------------------------------------------===// |
808 | | // Improves the modeling of loops over Cocoa collections. |
809 | | //===----------------------------------------------------------------------===// |
810 | | |
811 | | // The map from container symbol to the container count symbol. |
812 | | // We currently will remember the last container count symbol encountered. |
813 | | REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef) |
814 | | REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool) |
815 | | |
816 | | namespace { |
817 | | class ObjCLoopChecker |
818 | | : public Checker<check::PostStmt<ObjCForCollectionStmt>, |
819 | | check::PostObjCMessage, |
820 | | check::DeadSymbols, |
821 | | check::PointerEscape > { |
822 | | mutable IdentifierInfo *CountSelectorII = nullptr; |
823 | | |
824 | | bool isCollectionCountMethod(const ObjCMethodCall &M, |
825 | | CheckerContext &C) const; |
826 | | |
827 | | public: |
828 | 48 | ObjCLoopChecker() = default; |
829 | | void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const; |
830 | | void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; |
831 | | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
832 | | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
833 | | const InvalidatedSymbols &Escaped, |
834 | | const CallEvent *Call, |
835 | | PointerEscapeKind Kind) const; |
836 | | }; |
837 | | } // end anonymous namespace |
838 | | |
839 | 226 | static bool isKnownNonNilCollectionType(QualType T) { |
840 | 226 | const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); |
841 | 226 | if (!PT) |
842 | 0 | return false; |
843 | | |
844 | 226 | const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); |
845 | 226 | if (!ID) |
846 | 2 | return false; |
847 | | |
848 | 224 | switch (findKnownClass(ID)) { |
849 | 29 | case FC_NSArray: |
850 | 99 | case FC_NSDictionary: |
851 | 131 | case FC_NSEnumerator: |
852 | 134 | case FC_NSOrderedSet: |
853 | 154 | case FC_NSSet: |
854 | 154 | return true; |
855 | 70 | default: |
856 | 70 | return false; |
857 | 224 | } |
858 | 224 | } |
859 | | |
860 | | /// Assumes that the collection is non-nil. |
861 | | /// |
862 | | /// If the collection is known to be nil, returns NULL to indicate an infeasible |
863 | | /// path. |
864 | | static ProgramStateRef checkCollectionNonNil(CheckerContext &C, |
865 | | ProgramStateRef State, |
866 | 226 | const ObjCForCollectionStmt *FCS) { |
867 | 226 | if (!State) |
868 | 0 | return nullptr; |
869 | | |
870 | 226 | SVal CollectionVal = C.getSVal(FCS->getCollection()); |
871 | 226 | std::optional<DefinedSVal> KnownCollection = |
872 | 226 | CollectionVal.getAs<DefinedSVal>(); |
873 | 226 | if (!KnownCollection) |
874 | 0 | return State; |
875 | | |
876 | 226 | ProgramStateRef StNonNil, StNil; |
877 | 226 | std::tie(StNonNil, StNil) = State->assume(*KnownCollection); |
878 | 226 | if (StNil && !StNonNil41 ) { |
879 | | // The collection is nil. This path is infeasible. |
880 | 0 | return nullptr; |
881 | 0 | } |
882 | | |
883 | 226 | return StNonNil; |
884 | 226 | } |
885 | | |
886 | | /// Assumes that the collection elements are non-nil. |
887 | | /// |
888 | | /// This only applies if the collection is one of those known not to contain |
889 | | /// nil values. |
890 | | static ProgramStateRef checkElementNonNil(CheckerContext &C, |
891 | | ProgramStateRef State, |
892 | 226 | const ObjCForCollectionStmt *FCS) { |
893 | 226 | if (!State) |
894 | 0 | return nullptr; |
895 | | |
896 | | // See if the collection is one where we /know/ the elements are non-nil. |
897 | 226 | if (!isKnownNonNilCollectionType(FCS->getCollection()->getType())) |
898 | 72 | return State; |
899 | | |
900 | 154 | const LocationContext *LCtx = C.getLocationContext(); |
901 | 154 | const Stmt *Element = FCS->getElement(); |
902 | | |
903 | | // FIXME: Copied from ExprEngineObjC. |
904 | 154 | std::optional<Loc> ElementLoc; |
905 | 154 | if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) { |
906 | 94 | const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl()); |
907 | 94 | assert(ElemDecl->getInit() == nullptr); |
908 | 94 | ElementLoc = State->getLValue(ElemDecl, LCtx); |
909 | 94 | } else { |
910 | 60 | ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>(); |
911 | 60 | } |
912 | | |
913 | 154 | if (!ElementLoc) |
914 | 0 | return State; |
915 | | |
916 | | // Go ahead and assume the value is non-nil. |
917 | 154 | SVal Val = State->getSVal(*ElementLoc); |
918 | 154 | return State->assume(cast<DefinedOrUnknownSVal>(Val), true); |
919 | 154 | } |
920 | | |
921 | | /// Returns NULL state if the collection is known to contain elements |
922 | | /// (or is known not to contain elements if the Assumption parameter is false.) |
923 | | static ProgramStateRef |
924 | | assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, |
925 | 317 | SymbolRef CollectionS, bool Assumption) { |
926 | 317 | if (!State || !CollectionS) |
927 | 3 | return State; |
928 | | |
929 | 314 | const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS); |
930 | 314 | if (!CountS) { |
931 | 224 | const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS); |
932 | 224 | if (!KnownNonEmpty) |
933 | 94 | return State->set<ContainerNonEmptyMap>(CollectionS, Assumption); |
934 | 130 | return (Assumption == *KnownNonEmpty) ? State124 : nullptr6 ; |
935 | 224 | } |
936 | | |
937 | 90 | SValBuilder &SvalBuilder = C.getSValBuilder(); |
938 | 90 | SVal CountGreaterThanZeroVal = |
939 | 90 | SvalBuilder.evalBinOp(State, BO_GT, |
940 | 90 | nonloc::SymbolVal(*CountS), |
941 | 90 | SvalBuilder.makeIntVal(0, (*CountS)->getType()), |
942 | 90 | SvalBuilder.getConditionType()); |
943 | 90 | std::optional<DefinedSVal> CountGreaterThanZero = |
944 | 90 | CountGreaterThanZeroVal.getAs<DefinedSVal>(); |
945 | 90 | if (!CountGreaterThanZero) { |
946 | | // The SValBuilder cannot construct a valid SVal for this condition. |
947 | | // This means we cannot properly reason about it. |
948 | 0 | return State; |
949 | 0 | } |
950 | | |
951 | 90 | return State->assume(*CountGreaterThanZero, Assumption); |
952 | 90 | } |
953 | | |
954 | | static ProgramStateRef |
955 | | assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, |
956 | | const ObjCForCollectionStmt *FCS, |
957 | 309 | bool Assumption) { |
958 | 309 | if (!State) |
959 | 0 | return nullptr; |
960 | | |
961 | 309 | SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol(); |
962 | 309 | return assumeCollectionNonEmpty(C, State, CollectionS, Assumption); |
963 | 309 | } |
964 | | |
965 | | /// If the fist block edge is a back edge, we are reentering the loop. |
966 | | static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N, |
967 | 1.27k | const ObjCForCollectionStmt *FCS) { |
968 | 1.27k | if (!N) |
969 | 0 | return false; |
970 | | |
971 | 1.27k | ProgramPoint P = N->getLocation(); |
972 | 1.27k | if (std::optional<BlockEdge> BE = P.getAs<BlockEdge>()) { |
973 | 197 | return BE->getSrc()->getLoopTarget() == FCS; |
974 | 197 | } |
975 | | |
976 | | // Keep looking for a block edge. |
977 | 1.07k | for (const ExplodedNode *N : N->preds()) { |
978 | 1.07k | if (alreadyExecutedAtLeastOneLoopIteration(N, FCS)) |
979 | 631 | return true; |
980 | 1.07k | } |
981 | | |
982 | 446 | return false; |
983 | 1.07k | } |
984 | | |
985 | | void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS, |
986 | 423 | CheckerContext &C) const { |
987 | 423 | ProgramStateRef State = C.getState(); |
988 | | |
989 | | // Check if this is the branch for the end of the loop. |
990 | 423 | if (!ExprEngine::hasMoreIteration(State, FCS, C.getLocationContext())) { |
991 | 197 | if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS)) |
992 | 83 | State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false); |
993 | | |
994 | | // Otherwise, this is a branch that goes through the loop body. |
995 | 226 | } else { |
996 | 226 | State = checkCollectionNonNil(C, State, FCS); |
997 | 226 | State = checkElementNonNil(C, State, FCS); |
998 | 226 | State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true); |
999 | 226 | } |
1000 | | |
1001 | 423 | if (!State) |
1002 | 31 | C.generateSink(C.getState(), C.getPredecessor()); |
1003 | 392 | else if (State != C.getState()) |
1004 | 208 | C.addTransition(State); |
1005 | 423 | } |
1006 | | |
1007 | | bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M, |
1008 | 60 | CheckerContext &C) const { |
1009 | 60 | Selector S = M.getSelector(); |
1010 | | // Initialize the identifiers on first use. |
1011 | 60 | if (!CountSelectorII) |
1012 | 2 | CountSelectorII = &C.getASTContext().Idents.get("count"); |
1013 | | |
1014 | | // If the method returns collection count, record the value. |
1015 | 60 | return S.isUnarySelector() && |
1016 | 60 | (S.getIdentifierInfoForSlot(0) == CountSelectorII)43 ; |
1017 | 60 | } |
1018 | | |
1019 | | void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M, |
1020 | 145 | CheckerContext &C) const { |
1021 | 145 | if (!M.isInstanceMessage()) |
1022 | 44 | return; |
1023 | | |
1024 | 101 | const ObjCInterfaceDecl *ClassID = M.getReceiverInterface(); |
1025 | 101 | if (!ClassID) |
1026 | 0 | return; |
1027 | | |
1028 | 101 | FoundationClass Class = findKnownClass(ClassID); |
1029 | 101 | if (Class != FC_NSDictionary && |
1030 | 101 | Class != FC_NSArray77 && |
1031 | 101 | Class != FC_NSSet43 && |
1032 | 101 | Class != FC_NSOrderedSet42 ) |
1033 | 41 | return; |
1034 | | |
1035 | 60 | SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol(); |
1036 | 60 | if (!ContainerS) |
1037 | 0 | return; |
1038 | | |
1039 | | // If we are processing a call to "count", get the symbolic value returned by |
1040 | | // a call to "count" and add it to the map. |
1041 | 60 | if (!isCollectionCountMethod(M, C)) |
1042 | 36 | return; |
1043 | | |
1044 | 24 | const Expr *MsgExpr = M.getOriginExpr(); |
1045 | 24 | SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol(); |
1046 | 24 | if (CountS) { |
1047 | 24 | ProgramStateRef State = C.getState(); |
1048 | | |
1049 | 24 | C.getSymbolManager().addSymbolDependency(ContainerS, CountS); |
1050 | 24 | State = State->set<ContainerCountMap>(ContainerS, CountS); |
1051 | | |
1052 | 24 | if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) { |
1053 | 8 | State = State->remove<ContainerNonEmptyMap>(ContainerS); |
1054 | 8 | State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty); |
1055 | 8 | } |
1056 | | |
1057 | 24 | C.addTransition(State); |
1058 | 24 | } |
1059 | 24 | } |
1060 | | |
1061 | 410 | static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) { |
1062 | 410 | const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call); |
1063 | 410 | if (!Message) |
1064 | 255 | return nullptr; |
1065 | | |
1066 | 155 | const ObjCMethodDecl *MD = Message->getDecl(); |
1067 | 155 | if (!MD) |
1068 | 1 | return nullptr; |
1069 | | |
1070 | 154 | const ObjCInterfaceDecl *StaticClass; |
1071 | 154 | if (isa<ObjCProtocolDecl>(MD->getDeclContext())) { |
1072 | | // We can't find out where the method was declared without doing more work. |
1073 | | // Instead, see if the receiver is statically typed as a known immutable |
1074 | | // collection. |
1075 | 4 | StaticClass = Message->getOriginExpr()->getReceiverInterface(); |
1076 | 150 | } else { |
1077 | 150 | StaticClass = MD->getClassInterface(); |
1078 | 150 | } |
1079 | | |
1080 | 154 | if (!StaticClass) |
1081 | 0 | return nullptr; |
1082 | | |
1083 | 154 | switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) { |
1084 | 99 | case FC_None: |
1085 | 99 | return nullptr; |
1086 | 35 | case FC_NSArray: |
1087 | 53 | case FC_NSDictionary: |
1088 | 53 | case FC_NSEnumerator: |
1089 | 53 | case FC_NSNull: |
1090 | 54 | case FC_NSOrderedSet: |
1091 | 55 | case FC_NSSet: |
1092 | 55 | case FC_NSString: |
1093 | 55 | break; |
1094 | 154 | } |
1095 | | |
1096 | 55 | return Message->getReceiverSVal().getAsSymbol(); |
1097 | 154 | } |
1098 | | |
1099 | | ProgramStateRef |
1100 | | ObjCLoopChecker::checkPointerEscape(ProgramStateRef State, |
1101 | | const InvalidatedSymbols &Escaped, |
1102 | | const CallEvent *Call, |
1103 | 410 | PointerEscapeKind Kind) const { |
1104 | 410 | SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call); |
1105 | | |
1106 | | // Remove the invalidated symbols from the collection count map. |
1107 | 599 | for (SymbolRef Sym : Escaped) { |
1108 | | // Don't invalidate this symbol's count if we know the method being called |
1109 | | // is declared on an immutable class. This isn't completely correct if the |
1110 | | // receiver is also passed as an argument, but in most uses of NSArray, |
1111 | | // NSDictionary, etc. this isn't likely to happen in a dangerous way. |
1112 | 599 | if (Sym == ImmutableReceiver) |
1113 | 37 | continue; |
1114 | | |
1115 | | // The symbol escaped. Pessimistically, assume that the count could have |
1116 | | // changed. |
1117 | 562 | State = State->remove<ContainerCountMap>(Sym); |
1118 | 562 | State = State->remove<ContainerNonEmptyMap>(Sym); |
1119 | 562 | } |
1120 | 410 | return State; |
1121 | 410 | } |
1122 | | |
1123 | | void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
1124 | 2.92k | CheckerContext &C) const { |
1125 | 2.92k | ProgramStateRef State = C.getState(); |
1126 | | |
1127 | | // Remove the dead symbols from the collection count map. |
1128 | 2.92k | ContainerCountMapTy Tracked = State->get<ContainerCountMap>(); |
1129 | 2.92k | for (SymbolRef Sym : llvm::make_first_range(Tracked)) { |
1130 | 336 | if (SymReaper.isDead(Sym)) { |
1131 | 43 | State = State->remove<ContainerCountMap>(Sym); |
1132 | 43 | State = State->remove<ContainerNonEmptyMap>(Sym); |
1133 | 43 | } |
1134 | 336 | } |
1135 | | |
1136 | 2.92k | C.addTransition(State); |
1137 | 2.92k | } |
1138 | | |
1139 | | namespace { |
1140 | | /// \class ObjCNonNilReturnValueChecker |
1141 | | /// The checker restricts the return values of APIs known to |
1142 | | /// never (or almost never) return 'nil'. |
1143 | | class ObjCNonNilReturnValueChecker |
1144 | | : public Checker<check::PostObjCMessage, |
1145 | | check::PostStmt<ObjCArrayLiteral>, |
1146 | | check::PostStmt<ObjCDictionaryLiteral>, |
1147 | | check::PostStmt<ObjCBoxedExpr> > { |
1148 | | mutable bool Initialized = false; |
1149 | | mutable Selector ObjectAtIndex; |
1150 | | mutable Selector ObjectAtIndexedSubscript; |
1151 | | mutable Selector NullSelector; |
1152 | | |
1153 | | public: |
1154 | 49 | ObjCNonNilReturnValueChecker() = default; |
1155 | | |
1156 | | ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr, |
1157 | | ProgramStateRef State, |
1158 | | CheckerContext &C) const; |
1159 | 23 | void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const { |
1160 | 23 | C.addTransition(assumeExprIsNonNull(E, C.getState(), C)); |
1161 | 23 | } |
1162 | | |
1163 | 5 | void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const { |
1164 | 5 | assumeExprIsNonNull(E, C); |
1165 | 5 | } |
1166 | 8 | void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const { |
1167 | 8 | assumeExprIsNonNull(E, C); |
1168 | 8 | } |
1169 | 10 | void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const { |
1170 | 10 | assumeExprIsNonNull(E, C); |
1171 | 10 | } |
1172 | | |
1173 | | void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; |
1174 | | }; |
1175 | | } // end anonymous namespace |
1176 | | |
1177 | | ProgramStateRef |
1178 | | ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr, |
1179 | | ProgramStateRef State, |
1180 | 29 | CheckerContext &C) const { |
1181 | 29 | SVal Val = C.getSVal(NonNullExpr); |
1182 | 29 | if (std::optional<DefinedOrUnknownSVal> DV = |
1183 | 29 | Val.getAs<DefinedOrUnknownSVal>()) |
1184 | 29 | return State->assume(*DV, true); |
1185 | 0 | return State; |
1186 | 29 | } |
1187 | | |
1188 | | void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M, |
1189 | | CheckerContext &C) |
1190 | 76 | const { |
1191 | 76 | ProgramStateRef State = C.getState(); |
1192 | | |
1193 | 76 | if (!Initialized) { |
1194 | 76 | ASTContext &Ctx = C.getASTContext(); |
1195 | 76 | ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx); |
1196 | 76 | ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx); |
1197 | 76 | NullSelector = GetNullarySelector("null", Ctx); |
1198 | 76 | } |
1199 | | |
1200 | | // Check the receiver type. |
1201 | 76 | if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) { |
1202 | | |
1203 | | // Assume that object returned from '[self init]' or '[super init]' is not |
1204 | | // 'nil' if we are processing an inlined function/method. |
1205 | | // |
1206 | | // A defensive callee will (and should) check if the object returned by |
1207 | | // '[super init]' is 'nil' before doing it's own initialization. However, |
1208 | | // since 'nil' is rarely returned in practice, we should not warn when the |
1209 | | // caller to the defensive constructor uses the object in contexts where |
1210 | | // 'nil' is not accepted. |
1211 | 76 | if (!C.inTopFrame() && M.getDecl()2 && |
1212 | 76 | M.getDecl()->getMethodFamily() == OMF_init1 && |
1213 | 76 | M.isReceiverSelfOrSuper()1 ) { |
1214 | 1 | State = assumeExprIsNonNull(M.getOriginExpr(), State, C); |
1215 | 1 | } |
1216 | | |
1217 | 76 | FoundationClass Cl = findKnownClass(Interface); |
1218 | | |
1219 | | // Objects returned from |
1220 | | // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript] |
1221 | | // are never 'nil'. |
1222 | 76 | if (Cl == FC_NSArray || Cl == FC_NSOrderedSet58 ) { |
1223 | 20 | Selector Sel = M.getSelector(); |
1224 | 20 | if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript18 ) { |
1225 | | // Go ahead and assume the value is non-nil. |
1226 | 4 | State = assumeExprIsNonNull(M.getOriginExpr(), State, C); |
1227 | 4 | } |
1228 | 20 | } |
1229 | | |
1230 | | // Objects returned from [NSNull null] are not nil. |
1231 | 76 | if (Cl == FC_NSNull) { |
1232 | 1 | if (M.getSelector() == NullSelector) { |
1233 | | // Go ahead and assume the value is non-nil. |
1234 | 1 | State = assumeExprIsNonNull(M.getOriginExpr(), State, C); |
1235 | 1 | } |
1236 | 1 | } |
1237 | 76 | } |
1238 | 76 | C.addTransition(State); |
1239 | 76 | } |
1240 | | |
1241 | | //===----------------------------------------------------------------------===// |
1242 | | // Check registration. |
1243 | | //===----------------------------------------------------------------------===// |
1244 | | |
1245 | 53 | void ento::registerNilArgChecker(CheckerManager &mgr) { |
1246 | 53 | mgr.registerChecker<NilArgChecker>(); |
1247 | 53 | } |
1248 | | |
1249 | 106 | bool ento::shouldRegisterNilArgChecker(const CheckerManager &mgr) { |
1250 | 106 | return true; |
1251 | 106 | } |
1252 | | |
1253 | 47 | void ento::registerCFNumberChecker(CheckerManager &mgr) { |
1254 | 47 | mgr.registerChecker<CFNumberChecker>(); |
1255 | 47 | } |
1256 | | |
1257 | 94 | bool ento::shouldRegisterCFNumberChecker(const CheckerManager &mgr) { |
1258 | 94 | return true; |
1259 | 94 | } |
1260 | | |
1261 | 57 | void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { |
1262 | 57 | mgr.registerChecker<CFRetainReleaseChecker>(); |
1263 | 57 | } |
1264 | | |
1265 | 114 | bool ento::shouldRegisterCFRetainReleaseChecker(const CheckerManager &mgr) { |
1266 | 114 | return true; |
1267 | 114 | } |
1268 | | |
1269 | 56 | void ento::registerClassReleaseChecker(CheckerManager &mgr) { |
1270 | 56 | mgr.registerChecker<ClassReleaseChecker>(); |
1271 | 56 | } |
1272 | | |
1273 | 112 | bool ento::shouldRegisterClassReleaseChecker(const CheckerManager &mgr) { |
1274 | 112 | return true; |
1275 | 112 | } |
1276 | | |
1277 | 48 | void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) { |
1278 | 48 | mgr.registerChecker<VariadicMethodTypeChecker>(); |
1279 | 48 | } |
1280 | | |
1281 | 96 | bool ento::shouldRegisterVariadicMethodTypeChecker(const CheckerManager &mgr) { |
1282 | 96 | return true; |
1283 | 96 | } |
1284 | | |
1285 | 48 | void ento::registerObjCLoopChecker(CheckerManager &mgr) { |
1286 | 48 | mgr.registerChecker<ObjCLoopChecker>(); |
1287 | 48 | } |
1288 | | |
1289 | 96 | bool ento::shouldRegisterObjCLoopChecker(const CheckerManager &mgr) { |
1290 | 96 | return true; |
1291 | 96 | } |
1292 | | |
1293 | 49 | void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) { |
1294 | 49 | mgr.registerChecker<ObjCNonNilReturnValueChecker>(); |
1295 | 49 | } |
1296 | | |
1297 | 98 | bool ento::shouldRegisterObjCNonNilReturnValueChecker(const CheckerManager &mgr) { |
1298 | 98 | return true; |
1299 | 98 | } |