/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //== ObjCContainersChecker.cpp - Path sensitive checker for CFArray *- 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 | | // Performs path sensitive checks of Core Foundation static containers like |
10 | | // CFArray. |
11 | | // 1) Check for buffer overflows: |
12 | | // In CFArrayGetArrayAtIndex( myArray, index), if the index is outside the |
13 | | // index space of theArray (0 to N-1 inclusive (where N is the count of |
14 | | // theArray), the behavior is undefined. |
15 | | // |
16 | | //===----------------------------------------------------------------------===// |
17 | | |
18 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
19 | | #include "clang/AST/ParentMap.h" |
20 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
21 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
22 | | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
23 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
24 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" |
25 | | |
26 | | using namespace clang; |
27 | | using namespace ento; |
28 | | |
29 | | namespace { |
30 | | class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, |
31 | | check::PostStmt<CallExpr>, |
32 | | check::PointerEscape> { |
33 | | mutable std::unique_ptr<BugType> BT; |
34 | 6 | inline void initBugType() const { |
35 | 6 | if (!BT) |
36 | 1 | BT.reset(new BugType(this, "CFArray API", |
37 | 1 | categories::CoreFoundationObjectiveC)); |
38 | 6 | } |
39 | | |
40 | 16 | inline SymbolRef getArraySym(const Expr *E, CheckerContext &C) const { |
41 | 16 | SVal ArrayRef = C.getSVal(E); |
42 | 16 | SymbolRef ArraySym = ArrayRef.getAsSymbol(); |
43 | 16 | return ArraySym; |
44 | 16 | } |
45 | | |
46 | | void addSizeInfo(const Expr *Array, const Expr *Size, |
47 | | CheckerContext &C) const; |
48 | | |
49 | | public: |
50 | | void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; |
51 | | void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; |
52 | | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
53 | | const InvalidatedSymbols &Escaped, |
54 | | const CallEvent *Call, |
55 | | PointerEscapeKind Kind) const; |
56 | | |
57 | | void printState(raw_ostream &OS, ProgramStateRef State, |
58 | | const char *NL, const char *Sep) const override; |
59 | | }; |
60 | | } // end anonymous namespace |
61 | | |
62 | | // ProgramState trait - a map from array symbol to its state. |
63 | | REGISTER_MAP_WITH_PROGRAMSTATE(ArraySizeMap, SymbolRef, DefinedSVal) |
64 | | |
65 | | void ObjCContainersChecker::addSizeInfo(const Expr *Array, const Expr *Size, |
66 | 20 | CheckerContext &C) const { |
67 | 20 | ProgramStateRef State = C.getState(); |
68 | 20 | SVal SizeV = C.getSVal(Size); |
69 | | // Undefined is reported by another checker. |
70 | 20 | if (SizeV.isUnknownOrUndef()) |
71 | 1 | return; |
72 | | |
73 | | // Get the ArrayRef symbol. |
74 | 19 | SVal ArrayRef = C.getSVal(Array); |
75 | 19 | SymbolRef ArraySym = ArrayRef.getAsSymbol(); |
76 | 19 | if (!ArraySym) |
77 | 0 | return; |
78 | | |
79 | 19 | C.addTransition( |
80 | 19 | State->set<ArraySizeMap>(ArraySym, SizeV.castAs<DefinedSVal>())); |
81 | 19 | } |
82 | | |
83 | | void ObjCContainersChecker::checkPostStmt(const CallExpr *CE, |
84 | 351 | CheckerContext &C) const { |
85 | 351 | StringRef Name = C.getCalleeName(CE); |
86 | 351 | if (Name.empty() || CE->getNumArgs() < 1348 ) |
87 | 133 | return; |
88 | | |
89 | | // Add array size information to the state. |
90 | 218 | if (Name.equals("CFArrayCreate")) { |
91 | 17 | if (CE->getNumArgs() < 3) |
92 | 0 | return; |
93 | | // Note, we can visit the Create method in the post-visit because |
94 | | // the CFIndex parameter is passed in by value and will not be invalidated |
95 | | // by the call. |
96 | 17 | addSizeInfo(CE, CE->getArg(2), C); |
97 | 17 | return; |
98 | 17 | } |
99 | | |
100 | 201 | if (Name.equals("CFArrayGetCount")) { |
101 | 3 | addSizeInfo(CE->getArg(0), CE, C); |
102 | 3 | return; |
103 | 3 | } |
104 | 201 | } |
105 | | |
106 | | void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, |
107 | 354 | CheckerContext &C) const { |
108 | 354 | StringRef Name = C.getCalleeName(CE); |
109 | 354 | if (Name.empty() || CE->getNumArgs() < 2351 ) |
110 | 278 | return; |
111 | | |
112 | | // Check the array access. |
113 | 76 | if (Name.equals("CFArrayGetValueAtIndex")) { |
114 | 16 | ProgramStateRef State = C.getState(); |
115 | | // Retrieve the size. |
116 | | // Find out if we saw this array symbol before and have information about |
117 | | // it. |
118 | 16 | const Expr *ArrayExpr = CE->getArg(0); |
119 | 16 | SymbolRef ArraySym = getArraySym(ArrayExpr, C); |
120 | 16 | if (!ArraySym) |
121 | 1 | return; |
122 | | |
123 | 15 | const DefinedSVal *Size = State->get<ArraySizeMap>(ArraySym); |
124 | | |
125 | 15 | if (!Size) |
126 | 3 | return; |
127 | | |
128 | | // Get the index. |
129 | 12 | const Expr *IdxExpr = CE->getArg(1); |
130 | 12 | SVal IdxVal = C.getSVal(IdxExpr); |
131 | 12 | if (IdxVal.isUnknownOrUndef()) |
132 | 0 | return; |
133 | 12 | DefinedSVal Idx = IdxVal.castAs<DefinedSVal>(); |
134 | | |
135 | | // Now, check if 'Idx in [0, Size-1]'. |
136 | 12 | const QualType T = IdxExpr->getType(); |
137 | 12 | ProgramStateRef StInBound, StOutBound; |
138 | 12 | std::tie(StInBound, StOutBound) = State->assumeInBoundDual(Idx, *Size, T); |
139 | 12 | if (StOutBound && !StInBound9 ) { |
140 | 6 | ExplodedNode *N = C.generateErrorNode(StOutBound); |
141 | 6 | if (!N) |
142 | 0 | return; |
143 | 6 | initBugType(); |
144 | 6 | auto R = std::make_unique<PathSensitiveBugReport>( |
145 | 6 | *BT, "Index is out of bounds", N); |
146 | 6 | R->addRange(IdxExpr->getSourceRange()); |
147 | 6 | bugreporter::trackExpressionValue(N, IdxExpr, *R, |
148 | 6 | {bugreporter::TrackingKind::Thorough, |
149 | 6 | /*EnableNullFPSuppression=*/false}); |
150 | 6 | C.emitReport(std::move(R)); |
151 | 6 | return; |
152 | 6 | } |
153 | 12 | } |
154 | 76 | } |
155 | | |
156 | | ProgramStateRef |
157 | | ObjCContainersChecker::checkPointerEscape(ProgramStateRef State, |
158 | | const InvalidatedSymbols &Escaped, |
159 | | const CallEvent *Call, |
160 | 280 | PointerEscapeKind Kind) const { |
161 | 450 | for (const auto &Sym : Escaped) { |
162 | | // When a symbol for a mutable array escapes, we can't reason precisely |
163 | | // about its size any more -- so remove it from the map. |
164 | | // Note that we aren't notified here when a CFMutableArrayRef escapes as a |
165 | | // CFArrayRef. This is because CFArrayRef is typedef'd as a pointer to a |
166 | | // const-qualified type. |
167 | 450 | State = State->remove<ArraySizeMap>(Sym); |
168 | 450 | } |
169 | 280 | return State; |
170 | 280 | } |
171 | | |
172 | | void ObjCContainersChecker::printState(raw_ostream &OS, ProgramStateRef State, |
173 | 0 | const char *NL, const char *Sep) const { |
174 | 0 | ArraySizeMapTy Map = State->get<ArraySizeMap>(); |
175 | 0 | if (Map.isEmpty()) |
176 | 0 | return; |
177 | | |
178 | 0 | OS << Sep << "ObjC container sizes :" << NL; |
179 | 0 | for (auto I : Map) { |
180 | 0 | OS << I.first << " : " << I.second << NL; |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | /// Register checker. |
185 | 47 | void ento::registerObjCContainersChecker(CheckerManager &mgr) { |
186 | 47 | mgr.registerChecker<ObjCContainersChecker>(); |
187 | 47 | } |
188 | | |
189 | 94 | bool ento::shouldRegisterObjCContainersChecker(const CheckerManager &mgr) { |
190 | 94 | return true; |
191 | 94 | } |