blob: 36c55792e70da0665e5548247da68528ad93d2ce [file] [log] [blame]
Artem Dergachev940c7702016-10-18 11:06:28 +00001//===- NumberObjectConversionChecker.cpp -------------------------*- C++ -*-==//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This file defines NumberObjectConversionChecker, which checks for a
11// particular common mistake when dealing with numbers represented as objects
12// passed around by pointers. Namely, the language allows to reinterpret the
13// pointer as a number directly, often without throwing any warnings,
14// but in most cases the result of such conversion is clearly unexpected,
15// as pointer value, rather than number value represented by the pointee object,
16// becomes the result of such operation.
17//
18// Currently the checker supports the Objective-C NSNumber class,
19// and the OSBoolean class found in macOS low-level code; the latter
20// can only hold boolean values.
21//
22// This checker has an option "Pedantic" (boolean), which enables detection of
23// more conversion patterns (which are most likely more harmless, and therefore
24// are more likely to produce false positives) - disabled by default,
25// enabled with `-analyzer-config osx.NumberObjectConversion:Pedantic=true'.
26//
27//===----------------------------------------------------------------------===//
28
29#include "ClangSACheckers.h"
30#include "clang/ASTMatchers/ASTMatchFinder.h"
31#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
32#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
33#include "clang/StaticAnalyzer/Core/Checker.h"
34#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
35#include "clang/Lex/Lexer.h"
36#include "llvm/ADT/APSInt.h"
37
38using namespace clang;
39using namespace ento;
40using namespace ast_matchers;
41
42namespace {
43
44class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
45public:
46 bool Pedantic;
47
48 void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
49 BugReporter &BR) const;
50};
51
52class Callback : public MatchFinder::MatchCallback {
53 const NumberObjectConversionChecker *C;
54 BugReporter &BR;
55 AnalysisDeclContext *ADC;
56
57public:
58 Callback(const NumberObjectConversionChecker *C,
59 BugReporter &BR, AnalysisDeclContext *ADC)
60 : C(C), BR(BR), ADC(ADC) {}
61 virtual void run(const MatchFinder::MatchResult &Result);
62};
63} // end of anonymous namespace
64
65void Callback::run(const MatchFinder::MatchResult &Result) {
66 bool IsPedanticMatch = (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
67 if (IsPedanticMatch && !C->Pedantic)
68 return;
69
70 const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
71 assert(Conv);
72 const Expr *Osboolean = Result.Nodes.getNodeAs<Expr>("osboolean");
73 const Expr *Nsnumber = Result.Nodes.getNodeAs<Expr>("nsnumber");
74 bool IsObjC = (bool)Nsnumber;
75 const Expr *Obj = IsObjC ? Nsnumber : Osboolean;
76 assert(Obj);
77
78 ASTContext &ACtx = ADC->getASTContext();
79
80 if (const Expr *CheckIfNull =
81 Result.Nodes.getNodeAs<Expr>("check_if_null")) {
82 // We consider NULL to be a pointer, even if it is defined as a plain 0.
83 // FIXME: Introduce a matcher to implement this logic?
84 SourceLocation Loc = CheckIfNull->getLocStart();
85 if (Loc.isMacroID()) {
86 StringRef MacroName = Lexer::getImmediateMacroName(
87 Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
88 if (MacroName != "YES" && MacroName != "NO")
89 return;
90 } else {
91 // Otherwise, comparison of pointers to 0 might still be intentional.
92 // See if this is the case.
93 llvm::APSInt Result;
94 if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
95 Result, ACtx, Expr::SE_AllowSideEffects)) {
96 if (Result == 0) {
97 if (!C->Pedantic)
98 return;
99 IsPedanticMatch = true;
100 }
101 }
102 }
103 }
104
105 llvm::SmallString<64> Msg;
106 llvm::raw_svector_ostream OS(Msg);
107 OS << "Converting '"
108 << Obj->getType().getCanonicalType().getUnqualifiedType().getAsString()
109 << "' to a plain ";
110
111 if (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr)
112 OS << "integer value";
113 else if (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr)
114 OS << "BOOL value";
115 else if (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr)
116 OS << "bool value";
117 else
118 OS << "boolean value for branching";
119
120 if (IsPedanticMatch) {
121 if (IsObjC) {
122 OS << "; please compare the pointer to nil instead "
123 "to suppress this warning";
124 } else {
125 OS << "; please compare the pointer to NULL or nullptr instead "
126 "to suppress this warning";
127 }
128 } else {
129 OS << "; pointer value is being used instead";
130 }
131
132 BR.EmitBasicReport(
133 ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
134 OS.str(),
135 PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
136 Conv->getSourceRange());
137}
138
139void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
140 AnalysisManager &AM,
141 BugReporter &BR) const {
142 MatchFinder F;
143 Callback CB(this, BR, AM.getAnalysisDeclContext(D));
144
145 auto OSBooleanExprM =
146 expr(ignoringParenImpCasts(
147 expr(hasType(hasCanonicalType(
148 pointerType(pointee(hasCanonicalType(
149 recordType(hasDeclaration(
150 cxxRecordDecl(hasName(
151 "OSBoolean")))))))))).bind("osboolean")));
152
153 auto NSNumberExprM =
154 expr(ignoringParenImpCasts(expr(hasType(hasCanonicalType(
155 objcObjectPointerType(pointee(
156 qualType(hasCanonicalType(
157 qualType(hasDeclaration(
158 objcInterfaceDecl(hasName(
159 "NSNumber"))))))))))).bind("nsnumber")));
160
161 auto SuspiciousExprM =
162 anyOf(OSBooleanExprM, NSNumberExprM);
163
164 auto AnotherNSNumberExprM =
165 expr(equalsBoundNode("nsnumber"));
166
167 // The .bind here is in order to compose the error message more accurately.
168 auto ObjCBooleanTypeM =
169 qualType(typedefType(hasDeclaration(
170 typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
171
172 // The .bind here is in order to compose the error message more accurately.
173 auto AnyBooleanTypeM =
174 qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
175 ObjCBooleanTypeM));
176
177
178 // The .bind here is in order to compose the error message more accurately.
179 auto AnyNumberTypeM =
180 qualType(hasCanonicalType(isInteger()),
181 unless(typedefType(hasDeclaration(
182 typedefDecl(matchesName("^::u?intptr_t$"))))))
183 .bind("int_type");
184
185 auto AnyBooleanOrNumberTypeM =
186 qualType(anyOf(AnyBooleanTypeM, AnyNumberTypeM));
187
188 auto AnyBooleanOrNumberExprM =
189 expr(ignoringParenImpCasts(expr(hasType(AnyBooleanOrNumberTypeM))));
190
191 auto ConversionThroughAssignmentM =
192 binaryOperator(hasOperatorName("="),
193 hasLHS(AnyBooleanOrNumberExprM),
194 hasRHS(SuspiciousExprM));
195
196 auto ConversionThroughBranchingM =
197 ifStmt(hasCondition(SuspiciousExprM))
198 .bind("pedantic");
199
200 auto ConversionThroughCallM =
201 callExpr(hasAnyArgument(allOf(hasType(AnyBooleanOrNumberTypeM),
202 ignoringParenImpCasts(SuspiciousExprM))));
203
204 // We bind "check_if_null" to modify the warning message
205 // in case it was intended to compare a pointer to 0 with a relatively-ok
206 // construct "x == 0" or "x != 0".
207 auto ConversionThroughEquivalenceM =
208 binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
209 hasEitherOperand(SuspiciousExprM),
210 hasEitherOperand(AnyBooleanOrNumberExprM
211 .bind("check_if_null")));
212
213 auto ConversionThroughComparisonM =
214 binaryOperator(anyOf(hasOperatorName(">="), hasOperatorName(">"),
215 hasOperatorName("<="), hasOperatorName("<")),
216 hasEitherOperand(SuspiciousExprM),
217 hasEitherOperand(AnyBooleanOrNumberExprM));
218
219 auto ConversionThroughConditionalOperatorM =
220 conditionalOperator(
221 hasCondition(SuspiciousExprM),
222 unless(hasTrueExpression(hasDescendant(AnotherNSNumberExprM))),
223 unless(hasFalseExpression(hasDescendant(AnotherNSNumberExprM))))
224 .bind("pedantic");
225
226 auto ConversionThroughExclamationMarkM =
227 unaryOperator(hasOperatorName("!"), has(expr(SuspiciousExprM)))
228 .bind("pedantic");
229
230 auto ConversionThroughExplicitBooleanCastM =
231 explicitCastExpr(hasType(AnyBooleanTypeM),
232 has(expr(SuspiciousExprM)))
233 .bind("pedantic");
234
235 auto ConversionThroughExplicitNumberCastM =
236 explicitCastExpr(hasType(AnyNumberTypeM),
237 has(expr(SuspiciousExprM)));
238
239 auto ConversionThroughInitializerM =
240 declStmt(hasSingleDecl(
241 varDecl(hasType(AnyBooleanOrNumberTypeM),
242 hasInitializer(SuspiciousExprM))));
243
244 auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
245 ConversionThroughBranchingM,
246 ConversionThroughCallM,
247 ConversionThroughComparisonM,
248 ConversionThroughConditionalOperatorM,
249 ConversionThroughEquivalenceM,
250 ConversionThroughExclamationMarkM,
251 ConversionThroughExplicitBooleanCastM,
252 ConversionThroughExplicitNumberCastM,
253 ConversionThroughInitializerM)).bind("conv");
254
255 F.addMatcher(stmt(forEachDescendant(FinalM)), &CB);
256 F.match(*D->getBody(), AM.getASTContext());
257}
258
259void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
260 const LangOptions &LO = Mgr.getLangOpts();
261 if (LO.CPlusPlus || LO.ObjC2) {
262 NumberObjectConversionChecker *Chk =
263 Mgr.registerChecker<NumberObjectConversionChecker>();
264 Chk->Pedantic =
265 Mgr.getAnalyzerOptions().getBooleanOption("Pedantic", false, Chk);
266 }
267}