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