/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- DereferenceChecker.cpp - Null dereference checker -----------------===// |
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 defines NullDerefChecker, a builtin check in ExprEngine that performs |
10 | | // checks for null pointers at loads and stores. |
11 | | // |
12 | | //===----------------------------------------------------------------------===// |
13 | | |
14 | | #include "clang/AST/ExprObjC.h" |
15 | | #include "clang/AST/ExprOpenMP.h" |
16 | | #include "clang/Basic/TargetInfo.h" |
17 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
18 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
19 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
20 | | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
21 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
22 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" |
23 | | #include "llvm/ADT/SmallString.h" |
24 | | #include "llvm/Support/raw_ostream.h" |
25 | | |
26 | | using namespace clang; |
27 | | using namespace ento; |
28 | | |
29 | | namespace { |
30 | | class DereferenceChecker |
31 | | : public Checker< check::Location, |
32 | | check::Bind, |
33 | | EventDispatcher<ImplicitNullDerefEvent> > { |
34 | | enum DerefKind { NullPointer, UndefinedPointerValue }; |
35 | | |
36 | | BugType BT_Null{this, "Dereference of null pointer", categories::LogicError}; |
37 | | BugType BT_Undef{this, "Dereference of undefined pointer value", |
38 | | categories::LogicError}; |
39 | | |
40 | | void reportBug(DerefKind K, ProgramStateRef State, const Stmt *S, |
41 | | CheckerContext &C) const; |
42 | | |
43 | | bool suppressReport(CheckerContext &C, const Expr *E) const; |
44 | | |
45 | | public: |
46 | | void checkLocation(SVal location, bool isLoad, const Stmt* S, |
47 | | CheckerContext &C) const; |
48 | | void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; |
49 | | |
50 | | static void AddDerefSource(raw_ostream &os, |
51 | | SmallVectorImpl<SourceRange> &Ranges, |
52 | | const Expr *Ex, const ProgramState *state, |
53 | | const LocationContext *LCtx, |
54 | | bool loadedFrom = false); |
55 | | |
56 | | bool SuppressAddressSpaces = false; |
57 | | }; |
58 | | } // end anonymous namespace |
59 | | |
60 | | void |
61 | | DereferenceChecker::AddDerefSource(raw_ostream &os, |
62 | | SmallVectorImpl<SourceRange> &Ranges, |
63 | | const Expr *Ex, |
64 | | const ProgramState *state, |
65 | | const LocationContext *LCtx, |
66 | 896 | bool loadedFrom) { |
67 | 896 | Ex = Ex->IgnoreParenLValueCasts(); |
68 | 896 | switch (Ex->getStmtClass()) { |
69 | 115 | default: |
70 | 115 | break; |
71 | 712 | case Stmt::DeclRefExprClass: { |
72 | 712 | const DeclRefExpr *DR = cast<DeclRefExpr>(Ex); |
73 | 712 | if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { |
74 | 711 | os << " (" << (loadedFrom ? "loaded from"697 : "from"14 ) |
75 | 711 | << " variable '" << VD->getName() << "')"; |
76 | 711 | Ranges.push_back(DR->getSourceRange()); |
77 | 711 | } |
78 | 712 | break; |
79 | 0 | } |
80 | 64 | case Stmt::MemberExprClass: { |
81 | 64 | const MemberExpr *ME = cast<MemberExpr>(Ex); |
82 | 64 | os << " (" << (loadedFrom ? "loaded from"37 : "via"27 ) |
83 | 64 | << " field '" << ME->getMemberNameInfo() << "')"; |
84 | 64 | SourceLocation L = ME->getMemberLoc(); |
85 | 64 | Ranges.push_back(SourceRange(L, L)); |
86 | 64 | break; |
87 | 0 | } |
88 | 5 | case Stmt::ObjCIvarRefExprClass: { |
89 | 5 | const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(Ex); |
90 | 5 | os << " (" << (loadedFrom ? "loaded from"3 : "via"2 ) |
91 | 5 | << " ivar '" << IV->getDecl()->getName() << "')"; |
92 | 5 | SourceLocation L = IV->getLocation(); |
93 | 5 | Ranges.push_back(SourceRange(L, L)); |
94 | 5 | break; |
95 | 0 | } |
96 | 896 | } |
97 | 896 | } |
98 | | |
99 | 931 | static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false){ |
100 | 931 | const Expr *E = nullptr; |
101 | | |
102 | | // Walk through lvalue casts to get the original expression |
103 | | // that syntactically caused the load. |
104 | 931 | if (const Expr *expr = dyn_cast<Expr>(S)) |
105 | 913 | E = expr->IgnoreParenLValueCasts(); |
106 | | |
107 | 931 | if (IsBind) { |
108 | 22 | const VarDecl *VD; |
109 | 22 | const Expr *Init; |
110 | 22 | std::tie(VD, Init) = parseAssignment(S); |
111 | 22 | if (VD && Init18 ) |
112 | 18 | E = Init; |
113 | 22 | } |
114 | 931 | return E; |
115 | 931 | } |
116 | | |
117 | | bool DereferenceChecker::suppressReport(CheckerContext &C, |
118 | 931 | const Expr *E) const { |
119 | | // Do not report dereferences on memory that use address space #256, #257, |
120 | | // and #258. Those address spaces are used when dereferencing address spaces |
121 | | // relative to the GS, FS, and SS segments on x86/x86-64 targets. |
122 | | // Dereferencing a null pointer in these address spaces is not defined |
123 | | // as an error. All other null dereferences in other address spaces |
124 | | // are defined as an error unless explicitly defined. |
125 | | // See https://clang.llvm.org/docs/LanguageExtensions.html, the section |
126 | | // "X86/X86-64 Language Extensions" |
127 | | |
128 | 931 | QualType Ty = E->getType(); |
129 | 931 | if (!Ty.hasAddressSpace()) |
130 | 915 | return false; |
131 | 16 | if (SuppressAddressSpaces) |
132 | 14 | return true; |
133 | | |
134 | 2 | const llvm::Triple::ArchType Arch = |
135 | 2 | C.getASTContext().getTargetInfo().getTriple().getArch(); |
136 | | |
137 | 2 | if ((Arch == llvm::Triple::x86) || (Arch == llvm::Triple::x86_64)) { |
138 | 1 | switch (toTargetAddressSpace(E->getType().getAddressSpace())) { |
139 | 0 | case 256: |
140 | 0 | case 257: |
141 | 0 | case 258: |
142 | 0 | return true; |
143 | 1 | } |
144 | 1 | } |
145 | 2 | return false; |
146 | 2 | } |
147 | | |
148 | 10 | static bool isDeclRefExprToReference(const Expr *E) { |
149 | 10 | if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) |
150 | 1 | return DRE->getDecl()->getType()->isReferenceType(); |
151 | 9 | return false; |
152 | 10 | } |
153 | | |
154 | | void DereferenceChecker::reportBug(DerefKind K, ProgramStateRef State, |
155 | 917 | const Stmt *S, CheckerContext &C) const { |
156 | 917 | const BugType *BT = nullptr; |
157 | 917 | llvm::StringRef DerefStr1; |
158 | 917 | llvm::StringRef DerefStr2; |
159 | 917 | switch (K) { |
160 | 905 | case DerefKind::NullPointer: |
161 | 905 | BT = &BT_Null; |
162 | 905 | DerefStr1 = " results in a null pointer dereference"; |
163 | 905 | DerefStr2 = " results in a dereference of a null pointer"; |
164 | 905 | break; |
165 | 12 | case DerefKind::UndefinedPointerValue: |
166 | 12 | BT = &BT_Undef; |
167 | 12 | DerefStr1 = " results in an undefined pointer dereference"; |
168 | 12 | DerefStr2 = " results in a dereference of an undefined pointer value"; |
169 | 12 | break; |
170 | 917 | }; |
171 | | |
172 | | // Generate an error node. |
173 | 917 | ExplodedNode *N = C.generateErrorNode(State); |
174 | 917 | if (!N) |
175 | 0 | return; |
176 | | |
177 | 917 | SmallString<100> buf; |
178 | 917 | llvm::raw_svector_ostream os(buf); |
179 | | |
180 | 917 | SmallVector<SourceRange, 2> Ranges; |
181 | | |
182 | 917 | switch (S->getStmtClass()) { |
183 | 44 | case Stmt::ArraySubscriptExprClass: { |
184 | 44 | os << "Array access"; |
185 | 44 | const ArraySubscriptExpr *AE = cast<ArraySubscriptExpr>(S); |
186 | 44 | AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), |
187 | 44 | State.get(), N->getLocationContext()); |
188 | 44 | os << DerefStr1; |
189 | 44 | break; |
190 | 0 | } |
191 | 0 | case Stmt::OMPArraySectionExprClass: { |
192 | 0 | os << "Array access"; |
193 | 0 | const OMPArraySectionExpr *AE = cast<OMPArraySectionExpr>(S); |
194 | 0 | AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), |
195 | 0 | State.get(), N->getLocationContext()); |
196 | 0 | os << DerefStr1; |
197 | 0 | break; |
198 | 0 | } |
199 | 822 | case Stmt::UnaryOperatorClass: { |
200 | 822 | os << BT->getDescription(); |
201 | 822 | const UnaryOperator *U = cast<UnaryOperator>(S); |
202 | 822 | AddDerefSource(os, Ranges, U->getSubExpr()->IgnoreParens(), |
203 | 822 | State.get(), N->getLocationContext(), true); |
204 | 822 | break; |
205 | 0 | } |
206 | 26 | case Stmt::MemberExprClass: { |
207 | 26 | const MemberExpr *M = cast<MemberExpr>(S); |
208 | 26 | if (M->isArrow() || isDeclRefExprToReference(M->getBase())10 ) { |
209 | 17 | os << "Access to field '" << M->getMemberNameInfo() << "'" << DerefStr2; |
210 | 17 | AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), |
211 | 17 | State.get(), N->getLocationContext(), true); |
212 | 17 | } |
213 | 26 | break; |
214 | 0 | } |
215 | 13 | case Stmt::ObjCIvarRefExprClass: { |
216 | 13 | const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(S); |
217 | 13 | os << "Access to instance variable '" << *IV->getDecl() << "'" << DerefStr2; |
218 | 13 | AddDerefSource(os, Ranges, IV->getBase()->IgnoreParenCasts(), |
219 | 13 | State.get(), N->getLocationContext(), true); |
220 | 13 | break; |
221 | 0 | } |
222 | 12 | default: |
223 | 12 | break; |
224 | 917 | } |
225 | | |
226 | 917 | auto report = std::make_unique<PathSensitiveBugReport>( |
227 | 917 | *BT, buf.empty() ? BT->getDescription()21 : buf.str()896 , N); |
228 | | |
229 | 917 | bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); |
230 | | |
231 | 917 | for (SmallVectorImpl<SourceRange>::iterator |
232 | 1.69k | I = Ranges.begin(), E = Ranges.end(); I!=E; ++I780 ) |
233 | 780 | report->addRange(*I); |
234 | | |
235 | 917 | C.emitReport(std::move(report)); |
236 | 917 | } |
237 | | |
238 | | void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, |
239 | 263k | CheckerContext &C) const { |
240 | | // Check for dereference of an undefined value. |
241 | 263k | if (l.isUndef()) { |
242 | 12 | const Expr *DerefExpr = getDereferenceExpr(S); |
243 | 12 | if (!suppressReport(C, DerefExpr)) |
244 | 12 | reportBug(DerefKind::UndefinedPointerValue, C.getState(), DerefExpr, C); |
245 | 12 | return; |
246 | 12 | } |
247 | | |
248 | 263k | DefinedOrUnknownSVal location = l.castAs<DefinedOrUnknownSVal>(); |
249 | | |
250 | | // Check for null dereferences. |
251 | 263k | if (!isa<Loc>(location)) |
252 | 0 | return; |
253 | | |
254 | 263k | ProgramStateRef state = C.getState(); |
255 | | |
256 | 263k | ProgramStateRef notNullState, nullState; |
257 | 263k | std::tie(notNullState, nullState) = state->assume(location); |
258 | | |
259 | 263k | if (nullState) { |
260 | 4.14k | if (!notNullState) { |
261 | | // We know that 'location' can only be null. This is what |
262 | | // we call an "explicit" null dereference. |
263 | 897 | const Expr *expr = getDereferenceExpr(S); |
264 | 897 | if (!suppressReport(C, expr)) { |
265 | 888 | reportBug(DerefKind::NullPointer, nullState, expr, C); |
266 | 888 | return; |
267 | 888 | } |
268 | 897 | } |
269 | | |
270 | | // Otherwise, we have the case where the location could either be |
271 | | // null or not-null. Record the error node as an "implicit" null |
272 | | // dereference. |
273 | 3.25k | if (ExplodedNode *N = C.generateSink(nullState, C.getPredecessor())) { |
274 | 3.24k | ImplicitNullDerefEvent event = {l, isLoad, N, &C.getBugReporter(), |
275 | 3.24k | /*IsDirectDereference=*/true}; |
276 | 3.24k | dispatchEvent(event); |
277 | 3.24k | } |
278 | 3.25k | } |
279 | | |
280 | | // From this point forward, we know that the location is not null. |
281 | 262k | C.addTransition(notNullState); |
282 | 262k | } |
283 | | |
284 | | void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, |
285 | 82.2k | CheckerContext &C) const { |
286 | | // If we're binding to a reference, check if the value is known to be null. |
287 | 82.2k | if (V.isUndef()) |
288 | 361 | return; |
289 | | |
290 | 81.9k | const MemRegion *MR = L.getAsRegion(); |
291 | 81.9k | const TypedValueRegion *TVR = dyn_cast_or_null<TypedValueRegion>(MR); |
292 | 81.9k | if (!TVR) |
293 | 701 | return; |
294 | | |
295 | 81.2k | if (!TVR->getValueType()->isReferenceType()) |
296 | 80.3k | return; |
297 | | |
298 | 901 | ProgramStateRef State = C.getState(); |
299 | | |
300 | 901 | ProgramStateRef StNonNull, StNull; |
301 | 901 | std::tie(StNonNull, StNull) = State->assume(V.castAs<DefinedOrUnknownSVal>()); |
302 | | |
303 | 901 | if (StNull) { |
304 | 38 | if (!StNonNull) { |
305 | 22 | const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); |
306 | 22 | if (!suppressReport(C, expr)) { |
307 | 17 | reportBug(DerefKind::NullPointer, StNull, expr, C); |
308 | 17 | return; |
309 | 17 | } |
310 | 22 | } |
311 | | |
312 | | // At this point the value could be either null or non-null. |
313 | | // Record this as an "implicit" null dereference. |
314 | 21 | if (ExplodedNode *N = C.generateSink(StNull, C.getPredecessor())) { |
315 | 21 | ImplicitNullDerefEvent event = {V, /*isLoad=*/true, N, |
316 | 21 | &C.getBugReporter(), |
317 | 21 | /*IsDirectDereference=*/true}; |
318 | 21 | dispatchEvent(event); |
319 | 21 | } |
320 | 21 | } |
321 | | |
322 | | // Unlike a regular null dereference, initializing a reference with a |
323 | | // dereferenced null pointer does not actually cause a runtime exception in |
324 | | // Clang's implementation of references. |
325 | | // |
326 | | // int &r = *p; // safe?? |
327 | | // if (p != NULL) return; // uh-oh |
328 | | // r = 5; // trap here |
329 | | // |
330 | | // The standard says this is invalid as soon as we try to create a "null |
331 | | // reference" (there is no such thing), but turning this into an assumption |
332 | | // that 'p' is never null will not match our actual runtime behavior. |
333 | | // So we do not record this assumption, allowing us to warn on the last line |
334 | | // of this example. |
335 | | // |
336 | | // We do need to add a transition because we may have generated a sink for |
337 | | // the "implicit" null dereference. |
338 | 884 | C.addTransition(State, this); |
339 | 884 | } |
340 | | |
341 | 1.28k | void ento::registerDereferenceChecker(CheckerManager &mgr) { |
342 | 1.28k | auto *Chk = mgr.registerChecker<DereferenceChecker>(); |
343 | 1.28k | Chk->SuppressAddressSpaces = mgr.getAnalyzerOptions().getCheckerBooleanOption( |
344 | 1.28k | mgr.getCurrentCheckerName(), "SuppressAddressSpaces"); |
345 | 1.28k | } |
346 | | |
347 | 2.57k | bool ento::shouldRegisterDereferenceChecker(const CheckerManager &mgr) { |
348 | 2.57k | return true; |
349 | 2.57k | } |