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