/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp
Line | Count | Source |
1 | | //===- NumberObjectConversionChecker.cpp -------------------------*- 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 NumberObjectConversionChecker, which checks for a |
10 | | // particular common mistake when dealing with numbers represented as objects |
11 | | // passed around by pointers. Namely, the language allows to reinterpret the |
12 | | // pointer as a number directly, often without throwing any warnings, |
13 | | // but in most cases the result of such conversion is clearly unexpected, |
14 | | // as pointer value, rather than number value represented by the pointee object, |
15 | | // becomes the result of such operation. |
16 | | // |
17 | | // Currently the checker supports the Objective-C NSNumber class, |
18 | | // and the OSBoolean class found in macOS low-level code; the latter |
19 | | // can only hold boolean values. |
20 | | // |
21 | | // This checker has an option "Pedantic" (boolean), which enables detection of |
22 | | // more conversion patterns (which are most likely more harmless, and therefore |
23 | | // are more likely to produce false positives) - disabled by default, |
24 | | // enabled with `-analyzer-config osx.NumberObjectConversion:Pedantic=true'. |
25 | | // |
26 | | //===----------------------------------------------------------------------===// |
27 | | |
28 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
29 | | #include "clang/ASTMatchers/ASTMatchFinder.h" |
30 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
31 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
32 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
33 | | #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
34 | | #include "clang/Lex/Lexer.h" |
35 | | #include "llvm/ADT/APSInt.h" |
36 | | |
37 | | using namespace clang; |
38 | | using namespace ento; |
39 | | using namespace ast_matchers; |
40 | | |
41 | | namespace { |
42 | | |
43 | | class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> { |
44 | | public: |
45 | | bool Pedantic; |
46 | | |
47 | | void checkASTCodeBody(const Decl *D, AnalysisManager &AM, |
48 | | BugReporter &BR) const; |
49 | | }; |
50 | | |
51 | | class Callback : public MatchFinder::MatchCallback { |
52 | | const NumberObjectConversionChecker *C; |
53 | | BugReporter &BR; |
54 | | AnalysisDeclContext *ADC; |
55 | | |
56 | | public: |
57 | | Callback(const NumberObjectConversionChecker *C, |
58 | | BugReporter &BR, AnalysisDeclContext *ADC) |
59 | 276 | : C(C), BR(BR), ADC(ADC) {} |
60 | | void run(const MatchFinder::MatchResult &Result) override; |
61 | | }; |
62 | | } // end of anonymous namespace |
63 | | |
64 | 162 | void Callback::run(const MatchFinder::MatchResult &Result) { |
65 | 162 | bool IsPedanticMatch = |
66 | 162 | (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr); |
67 | 162 | if (IsPedanticMatch && !C->Pedantic30 ) |
68 | 15 | return; |
69 | | |
70 | 147 | ASTContext &ACtx = ADC->getASTContext(); |
71 | | |
72 | 147 | if (const Expr *CheckIfNull = |
73 | 147 | Result.Nodes.getNodeAs<Expr>("check_if_null")) { |
74 | | // Unless the macro indicates that the intended type is clearly not |
75 | | // a pointer type, we should avoid warning on comparing pointers |
76 | | // to zero literals in non-pedantic mode. |
77 | | // FIXME: Introduce an AST matcher to implement the macro-related logic? |
78 | 54 | bool MacroIndicatesWeShouldSkipTheCheck = false; |
79 | 54 | SourceLocation Loc = CheckIfNull->getBeginLoc(); |
80 | 54 | if (Loc.isMacroID()) { |
81 | 42 | StringRef MacroName = Lexer::getImmediateMacroName( |
82 | 42 | Loc, ACtx.getSourceManager(), ACtx.getLangOpts()); |
83 | 42 | if (MacroName == "NULL" || MacroName == "nil"36 ) |
84 | 6 | return; |
85 | 36 | if (MacroName == "YES" || MacroName == "NO"12 ) |
86 | 28 | MacroIndicatesWeShouldSkipTheCheck = true; |
87 | 36 | } |
88 | 48 | if (!MacroIndicatesWeShouldSkipTheCheck) { |
89 | 20 | Expr::EvalResult EVResult; |
90 | 20 | if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt( |
91 | 20 | EVResult, ACtx, Expr::SE_AllowSideEffects)) { |
92 | 20 | llvm::APSInt Result = EVResult.Val.getInt(); |
93 | 20 | if (Result == 0) { |
94 | 12 | if (!C->Pedantic) |
95 | 6 | return; |
96 | 6 | IsPedanticMatch = true; |
97 | 6 | } |
98 | 20 | } |
99 | 20 | } |
100 | 48 | } |
101 | | |
102 | 135 | const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv"); |
103 | 135 | assert(Conv); |
104 | | |
105 | 135 | const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object"); |
106 | 135 | const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object"); |
107 | 135 | const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object"); |
108 | 135 | bool IsCpp = (ConvertedCppObject != nullptr); |
109 | 135 | bool IsObjC = (ConvertedObjCObject != nullptr); |
110 | 135 | const Expr *Obj = IsObjC ? ConvertedObjCObject90 |
111 | 135 | : IsCpp45 ? ConvertedCppObject27 |
112 | 45 | : ConvertedCObject18 ; |
113 | 135 | assert(Obj); |
114 | | |
115 | 135 | bool IsComparison = |
116 | 135 | (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr); |
117 | | |
118 | 135 | bool IsOSNumber = |
119 | 135 | (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr); |
120 | | |
121 | 135 | bool IsInteger = |
122 | 135 | (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr); |
123 | 135 | bool IsObjCBool = |
124 | 135 | (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr); |
125 | 135 | bool IsCppBool = |
126 | 135 | (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr); |
127 | | |
128 | 135 | llvm::SmallString<64> Msg; |
129 | 135 | llvm::raw_svector_ostream OS(Msg); |
130 | | |
131 | | // Remove ObjC ARC qualifiers. |
132 | 135 | QualType ObjT = Obj->getType().getUnqualifiedType(); |
133 | | |
134 | | // Remove consts from pointers. |
135 | 135 | if (IsCpp) { |
136 | 27 | assert(ObjT.getCanonicalType()->isPointerType()); |
137 | 27 | ObjT = ACtx.getPointerType( |
138 | 27 | ObjT->getPointeeType().getCanonicalType().getUnqualifiedType()); |
139 | 27 | } |
140 | | |
141 | 135 | if (IsComparison) |
142 | 48 | OS << "Comparing "; |
143 | 87 | else |
144 | 87 | OS << "Converting "; |
145 | | |
146 | 135 | OS << "a pointer value of type '" << ObjT << "' to a "; |
147 | | |
148 | 135 | std::string EuphemismForPlain = "primitive"; |
149 | 135 | std::string SuggestedApi = IsObjC ? (90 IsInteger90 ? ""40 : "-boolValue"50 ) |
150 | 135 | : IsCpp45 ? (27 IsOSNumber27 ? ""8 : "getValue()"19 ) |
151 | 45 | : "CFNumberGetValue()"18 ; |
152 | 135 | if (SuggestedApi.empty()) { |
153 | | // A generic message if we're not sure what API should be called. |
154 | | // FIXME: Pattern-match the integer type to make a better guess? |
155 | 48 | SuggestedApi = |
156 | 48 | "a method on '" + ObjT.getAsString() + "' to get the scalar value"; |
157 | | // "scalar" is not quite correct or common, but some documentation uses it |
158 | | // when describing object methods we suggest. For consistency, we use |
159 | | // "scalar" in the whole sentence when we need to use this word in at least |
160 | | // one place, otherwise we use "primitive". |
161 | 48 | EuphemismForPlain = "scalar"; |
162 | 48 | } |
163 | | |
164 | 135 | if (IsInteger) |
165 | 64 | OS << EuphemismForPlain << " integer value"; |
166 | 71 | else if (IsObjCBool) |
167 | 44 | OS << EuphemismForPlain << " BOOL value"; |
168 | 27 | else if (IsCppBool) |
169 | 12 | OS << EuphemismForPlain << " bool value"; |
170 | 15 | else // Branch condition? |
171 | 15 | OS << EuphemismForPlain << " boolean value"; |
172 | | |
173 | | |
174 | 135 | if (IsPedanticMatch) |
175 | 21 | OS << "; instead, either compare the pointer to " |
176 | 21 | << (IsObjC ? "nil"10 : IsCpp11 ? "nullptr"7 : "NULL"4 ) << " or "; |
177 | 114 | else |
178 | 114 | OS << "; did you mean to "; |
179 | | |
180 | 135 | if (IsComparison) |
181 | 48 | OS << "compare the result of calling " << SuggestedApi; |
182 | 87 | else |
183 | 87 | OS << "call " << SuggestedApi; |
184 | | |
185 | 135 | if (!IsPedanticMatch) |
186 | 114 | OS << "?"; |
187 | | |
188 | 135 | BR.EmitBasicReport( |
189 | 135 | ADC->getDecl(), C, "Suspicious number object conversion", "Logic error", |
190 | 135 | OS.str(), |
191 | 135 | PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC), |
192 | 135 | Conv->getSourceRange()); |
193 | 135 | } |
194 | | |
195 | | void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, |
196 | | AnalysisManager &AM, |
197 | 276 | BugReporter &BR) const { |
198 | | // Currently this matches CoreFoundation opaque pointer typedefs. |
199 | 276 | auto CSuspiciousNumberObjectExprM = expr(ignoringParenImpCasts( |
200 | 276 | expr(hasType(elaboratedType(namesType(typedefType( |
201 | 276 | hasDeclaration(anyOf(typedefDecl(hasName("CFNumberRef")), |
202 | 276 | typedefDecl(hasName("CFBooleanRef"))))))))) |
203 | 276 | .bind("c_object"))); |
204 | | |
205 | | // Currently this matches XNU kernel number-object pointers. |
206 | 276 | auto CppSuspiciousNumberObjectExprM = |
207 | 276 | expr(ignoringParenImpCasts( |
208 | 276 | expr(hasType(hasCanonicalType( |
209 | 276 | pointerType(pointee(hasCanonicalType( |
210 | 276 | recordType(hasDeclaration( |
211 | 276 | anyOf( |
212 | 276 | cxxRecordDecl(hasName("OSBoolean")), |
213 | 276 | cxxRecordDecl(hasName("OSNumber")) |
214 | 276 | .bind("osnumber")))))))))) |
215 | 276 | .bind("cpp_object"))); |
216 | | |
217 | | // Currently this matches NeXTSTEP number objects. |
218 | 276 | auto ObjCSuspiciousNumberObjectExprM = |
219 | 276 | expr(ignoringParenImpCasts( |
220 | 276 | expr(hasType(hasCanonicalType( |
221 | 276 | objcObjectPointerType(pointee( |
222 | 276 | qualType(hasCanonicalType( |
223 | 276 | qualType(hasDeclaration( |
224 | 276 | objcInterfaceDecl(hasName("NSNumber"))))))))))) |
225 | 276 | .bind("objc_object"))); |
226 | | |
227 | 276 | auto SuspiciousNumberObjectExprM = anyOf( |
228 | 276 | CSuspiciousNumberObjectExprM, |
229 | 276 | CppSuspiciousNumberObjectExprM, |
230 | 276 | ObjCSuspiciousNumberObjectExprM); |
231 | | |
232 | | // Useful for predicates like "Unless we've seen the same object elsewhere". |
233 | 276 | auto AnotherSuspiciousNumberObjectExprM = |
234 | 276 | expr(anyOf( |
235 | 276 | equalsBoundNode("c_object"), |
236 | 276 | equalsBoundNode("objc_object"), |
237 | 276 | equalsBoundNode("cpp_object"))); |
238 | | |
239 | | // The .bind here is in order to compose the error message more accurately. |
240 | 276 | auto ObjCSuspiciousScalarBooleanTypeM = |
241 | 276 | qualType(elaboratedType(namesType( |
242 | 276 | typedefType(hasDeclaration(typedefDecl(hasName("BOOL"))))))) |
243 | 276 | .bind("objc_bool_type"); |
244 | | |
245 | | // The .bind here is in order to compose the error message more accurately. |
246 | 276 | auto SuspiciousScalarBooleanTypeM = |
247 | 276 | qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"), |
248 | 276 | ObjCSuspiciousScalarBooleanTypeM)); |
249 | | |
250 | | // The .bind here is in order to compose the error message more accurately. |
251 | | // Also avoid intptr_t and uintptr_t because they were specifically created |
252 | | // for storing pointers. |
253 | 276 | auto SuspiciousScalarNumberTypeM = |
254 | 276 | qualType(hasCanonicalType(isInteger()), |
255 | 276 | unless(elaboratedType(namesType(typedefType(hasDeclaration( |
256 | 276 | typedefDecl(matchesName("^::u?intptr_t$")))))))) |
257 | 276 | .bind("int_type"); |
258 | | |
259 | 276 | auto SuspiciousScalarTypeM = |
260 | 276 | qualType(anyOf(SuspiciousScalarBooleanTypeM, |
261 | 276 | SuspiciousScalarNumberTypeM)); |
262 | | |
263 | 276 | auto SuspiciousScalarExprM = |
264 | 276 | expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM)))); |
265 | | |
266 | 276 | auto ConversionThroughAssignmentM = |
267 | 276 | binaryOperator(allOf(hasOperatorName("="), |
268 | 276 | hasLHS(SuspiciousScalarExprM), |
269 | 276 | hasRHS(SuspiciousNumberObjectExprM))); |
270 | | |
271 | 276 | auto ConversionThroughBranchingM = |
272 | 276 | ifStmt(allOf( |
273 | 276 | hasCondition(SuspiciousNumberObjectExprM), |
274 | 276 | unless(hasConditionVariableStatement(declStmt()) |
275 | 276 | ))).bind("pedantic"); |
276 | | |
277 | 276 | auto ConversionThroughCallM = |
278 | 276 | callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM), |
279 | 276 | ignoringParenImpCasts( |
280 | 276 | SuspiciousNumberObjectExprM)))); |
281 | | |
282 | | // We bind "check_if_null" to modify the warning message |
283 | | // in case it was intended to compare a pointer to 0 with a relatively-ok |
284 | | // construct "x == 0" or "x != 0". |
285 | 276 | auto ConversionThroughEquivalenceM = |
286 | 276 | binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")), |
287 | 276 | hasEitherOperand(SuspiciousNumberObjectExprM), |
288 | 276 | hasEitherOperand(SuspiciousScalarExprM |
289 | 276 | .bind("check_if_null")))) |
290 | 276 | .bind("comparison"); |
291 | | |
292 | 276 | auto ConversionThroughComparisonM = |
293 | 276 | binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"), |
294 | 276 | hasOperatorName("<="), hasOperatorName("<")), |
295 | 276 | hasEitherOperand(SuspiciousNumberObjectExprM), |
296 | 276 | hasEitherOperand(SuspiciousScalarExprM))) |
297 | 276 | .bind("comparison"); |
298 | | |
299 | 276 | auto ConversionThroughConditionalOperatorM = |
300 | 276 | conditionalOperator(allOf( |
301 | 276 | hasCondition(SuspiciousNumberObjectExprM), |
302 | 276 | unless(hasTrueExpression( |
303 | 276 | hasDescendant(AnotherSuspiciousNumberObjectExprM))), |
304 | 276 | unless(hasFalseExpression( |
305 | 276 | hasDescendant(AnotherSuspiciousNumberObjectExprM))))) |
306 | 276 | .bind("pedantic"); |
307 | | |
308 | 276 | auto ConversionThroughExclamationMarkM = |
309 | 276 | unaryOperator(allOf(hasOperatorName("!"), |
310 | 276 | has(expr(SuspiciousNumberObjectExprM)))) |
311 | 276 | .bind("pedantic"); |
312 | | |
313 | 276 | auto ConversionThroughExplicitBooleanCastM = |
314 | 276 | explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM), |
315 | 276 | has(expr(SuspiciousNumberObjectExprM)))); |
316 | | |
317 | 276 | auto ConversionThroughExplicitNumberCastM = |
318 | 276 | explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM), |
319 | 276 | has(expr(SuspiciousNumberObjectExprM)))); |
320 | | |
321 | 276 | auto ConversionThroughInitializerM = |
322 | 276 | declStmt(hasSingleDecl( |
323 | 276 | varDecl(hasType(SuspiciousScalarTypeM), |
324 | 276 | hasInitializer(SuspiciousNumberObjectExprM)))); |
325 | | |
326 | 276 | auto FinalM = stmt(anyOf(ConversionThroughAssignmentM, |
327 | 276 | ConversionThroughBranchingM, |
328 | 276 | ConversionThroughCallM, |
329 | 276 | ConversionThroughComparisonM, |
330 | 276 | ConversionThroughConditionalOperatorM, |
331 | 276 | ConversionThroughEquivalenceM, |
332 | 276 | ConversionThroughExclamationMarkM, |
333 | 276 | ConversionThroughExplicitBooleanCastM, |
334 | 276 | ConversionThroughExplicitNumberCastM, |
335 | 276 | ConversionThroughInitializerM)).bind("conv"); |
336 | | |
337 | 276 | MatchFinder F; |
338 | 276 | Callback CB(this, BR, AM.getAnalysisDeclContext(D)); |
339 | | |
340 | 276 | F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB); |
341 | 276 | F.match(*D->getBody(), AM.getASTContext()); |
342 | 276 | } |
343 | | |
344 | 55 | void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) { |
345 | 55 | NumberObjectConversionChecker *Chk = |
346 | 55 | Mgr.registerChecker<NumberObjectConversionChecker>(); |
347 | 55 | Chk->Pedantic = |
348 | 55 | Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic"); |
349 | 55 | } |
350 | | |
351 | 110 | bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) { |
352 | 110 | return true; |
353 | 110 | } |