blob: e0e892def0a49a3e1acaa93b77889843ab7c83fa [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) {
Artem Dergacheve14d8812016-10-31 03:08:48 +000066 bool IsPedanticMatch =
67 (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
Artem Dergachev940c7702016-10-18 11:06:28 +000068 if (IsPedanticMatch && !C->Pedantic)
69 return;
70
Artem Dergachev940c7702016-10-18 11:06:28 +000071 ASTContext &ACtx = ADC->getASTContext();
72
73 if (const Expr *CheckIfNull =
74 Result.Nodes.getNodeAs<Expr>("check_if_null")) {
Artem Dergacheve14d8812016-10-31 03:08:48 +000075 // Unless the macro indicates that the intended type is clearly not
76 // a pointer type, we should avoid warning on comparing pointers
77 // to zero literals in non-pedantic mode.
78 // FIXME: Introduce an AST matcher to implement the macro-related logic?
79 bool MacroIndicatesWeShouldSkipTheCheck = false;
Artem Dergachev940c7702016-10-18 11:06:28 +000080 SourceLocation Loc = CheckIfNull->getLocStart();
81 if (Loc.isMacroID()) {
82 StringRef MacroName = Lexer::getImmediateMacroName(
83 Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
Artem Dergacheve14d8812016-10-31 03:08:48 +000084 if (MacroName == "NULL" || MacroName == "nil")
Artem Dergachev940c7702016-10-18 11:06:28 +000085 return;
Artem Dergacheve14d8812016-10-31 03:08:48 +000086 if (MacroName == "YES" || MacroName == "NO")
87 MacroIndicatesWeShouldSkipTheCheck = true;
88 }
89 if (!MacroIndicatesWeShouldSkipTheCheck) {
Artem Dergachev940c7702016-10-18 11:06:28 +000090 llvm::APSInt Result;
91 if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
92 Result, ACtx, Expr::SE_AllowSideEffects)) {
93 if (Result == 0) {
94 if (!C->Pedantic)
95 return;
96 IsPedanticMatch = true;
97 }
98 }
99 }
100 }
101
Artem Dergacheve14d8812016-10-31 03:08:48 +0000102 const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
103 assert(Conv);
104
105 const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object");
106 const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object");
107 const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object");
108 bool IsCpp = (ConvertedCppObject != nullptr);
109 bool IsObjC = (ConvertedObjCObject != nullptr);
110 const Expr *Obj = IsObjC ? ConvertedObjCObject
111 : IsCpp ? ConvertedCppObject
112 : ConvertedCObject;
113 assert(Obj);
114
115 bool IsComparison =
116 (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
117
118 bool IsOSNumber =
119 (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
120
121 bool IsInteger =
122 (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr);
123 bool IsObjCBool =
124 (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr);
125 bool IsCppBool =
126 (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr);
127
Artem Dergachev940c7702016-10-18 11:06:28 +0000128 llvm::SmallString<64> Msg;
129 llvm::raw_svector_ostream OS(Msg);
Artem Dergachev940c7702016-10-18 11:06:28 +0000130
Artem Dergacheve14d8812016-10-31 03:08:48 +0000131 // Remove ObjC ARC qualifiers.
132 QualType ObjT = Obj->getType().getUnqualifiedType();
Artem Dergachev940c7702016-10-18 11:06:28 +0000133
Artem Dergacheve14d8812016-10-31 03:08:48 +0000134 // Remove consts from pointers.
135 if (IsCpp) {
136 assert(ObjT.getCanonicalType()->isPointerType());
137 ObjT = ACtx.getPointerType(
138 ObjT->getPointeeType().getCanonicalType().getUnqualifiedType());
Artem Dergachev940c7702016-10-18 11:06:28 +0000139 }
140
Artem Dergacheve14d8812016-10-31 03:08:48 +0000141 if (IsComparison)
142 OS << "Comparing ";
143 else
144 OS << "Converting ";
145
146 OS << "a pointer value of type '" << ObjT.getAsString() << "' to a ";
147
148 std::string EuphemismForPlain = "primitive";
149 std::string SuggestedApi = IsObjC ? (IsInteger ? "" : "-boolValue")
150 : IsCpp ? (IsOSNumber ? "" : "getValue()")
151 : "CFNumberGetValue()";
152 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 SuggestedApi =
156 "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 EuphemismForPlain = "scalar";
162 }
163
164 if (IsInteger)
165 OS << EuphemismForPlain << " integer value";
166 else if (IsObjCBool)
167 OS << EuphemismForPlain << " BOOL value";
168 else if (IsCppBool)
169 OS << EuphemismForPlain << " bool value";
170 else // Branch condition?
171 OS << EuphemismForPlain << " boolean value";
172
173
174 if (IsPedanticMatch)
175 OS << "; instead, either compare the pointer to "
176 << (IsObjC ? "nil" : IsCpp ? "nullptr" : "NULL") << " or ";
177 else
178 OS << "; did you mean to ";
179
180 if (IsComparison)
181 OS << "compare the result of calling " << SuggestedApi;
182 else
183 OS << "call " << SuggestedApi;
184
185 if (!IsPedanticMatch)
186 OS << "?";
187
Artem Dergachev940c7702016-10-18 11:06:28 +0000188 BR.EmitBasicReport(
189 ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
190 OS.str(),
191 PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
192 Conv->getSourceRange());
193}
194
195void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
196 AnalysisManager &AM,
197 BugReporter &BR) const {
Artem Dergacheve14d8812016-10-31 03:08:48 +0000198 // Currently this matches CoreFoundation opaque pointer typedefs.
199 auto CSuspiciousNumberObjectExprM =
200 expr(ignoringParenImpCasts(
201 expr(hasType(
202 typedefType(hasDeclaration(anyOf(
203 typedefDecl(hasName("CFNumberRef")),
204 typedefDecl(hasName("CFBooleanRef")))))))
205 .bind("c_object")));
Artem Dergachev940c7702016-10-18 11:06:28 +0000206
Artem Dergacheve14d8812016-10-31 03:08:48 +0000207 // Currently this matches XNU kernel number-object pointers.
208 auto CppSuspiciousNumberObjectExprM =
Artem Dergachev940c7702016-10-18 11:06:28 +0000209 expr(ignoringParenImpCasts(
210 expr(hasType(hasCanonicalType(
211 pointerType(pointee(hasCanonicalType(
212 recordType(hasDeclaration(
Artem Dergacheve14d8812016-10-31 03:08:48 +0000213 anyOf(
214 cxxRecordDecl(hasName("OSBoolean")),
215 cxxRecordDecl(hasName("OSNumber"))
216 .bind("osnumber"))))))))))
217 .bind("cpp_object")));
Artem Dergachev940c7702016-10-18 11:06:28 +0000218
Artem Dergacheve14d8812016-10-31 03:08:48 +0000219 // Currently this matches NeXTSTEP number objects.
220 auto ObjCSuspiciousNumberObjectExprM =
221 expr(ignoringParenImpCasts(
222 expr(hasType(hasCanonicalType(
223 objcObjectPointerType(pointee(
224 qualType(hasCanonicalType(
225 qualType(hasDeclaration(
226 objcInterfaceDecl(hasName("NSNumber")))))))))))
227 .bind("objc_object")));
Artem Dergachev940c7702016-10-18 11:06:28 +0000228
Artem Dergacheve14d8812016-10-31 03:08:48 +0000229 auto SuspiciousNumberObjectExprM = anyOf(
230 CSuspiciousNumberObjectExprM,
231 CppSuspiciousNumberObjectExprM,
232 ObjCSuspiciousNumberObjectExprM);
Artem Dergachev940c7702016-10-18 11:06:28 +0000233
Artem Dergacheve14d8812016-10-31 03:08:48 +0000234 // Useful for predicates like "Unless we've seen the same object elsewhere".
235 auto AnotherSuspiciousNumberObjectExprM =
236 expr(anyOf(
237 equalsBoundNode("c_object"),
238 equalsBoundNode("objc_object"),
239 equalsBoundNode("cpp_object")));
Artem Dergachev940c7702016-10-18 11:06:28 +0000240
241 // The .bind here is in order to compose the error message more accurately.
Artem Dergacheve14d8812016-10-31 03:08:48 +0000242 auto ObjCSuspiciousScalarBooleanTypeM =
Artem Dergachev940c7702016-10-18 11:06:28 +0000243 qualType(typedefType(hasDeclaration(
244 typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
245
246 // The .bind here is in order to compose the error message more accurately.
Artem Dergacheve14d8812016-10-31 03:08:48 +0000247 auto SuspiciousScalarBooleanTypeM =
Artem Dergachev940c7702016-10-18 11:06:28 +0000248 qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
Artem Dergacheve14d8812016-10-31 03:08:48 +0000249 ObjCSuspiciousScalarBooleanTypeM));
Artem Dergachev940c7702016-10-18 11:06:28 +0000250
251 // The .bind here is in order to compose the error message more accurately.
Artem Dergacheve14d8812016-10-31 03:08:48 +0000252 // Also avoid intptr_t and uintptr_t because they were specifically created
253 // for storing pointers.
254 auto SuspiciousScalarNumberTypeM =
Artem Dergachev940c7702016-10-18 11:06:28 +0000255 qualType(hasCanonicalType(isInteger()),
256 unless(typedefType(hasDeclaration(
257 typedefDecl(matchesName("^::u?intptr_t$"))))))
258 .bind("int_type");
259
Artem Dergacheve14d8812016-10-31 03:08:48 +0000260 auto SuspiciousScalarTypeM =
261 qualType(anyOf(SuspiciousScalarBooleanTypeM,
262 SuspiciousScalarNumberTypeM));
Artem Dergachev940c7702016-10-18 11:06:28 +0000263
Artem Dergacheve14d8812016-10-31 03:08:48 +0000264 auto SuspiciousScalarExprM =
265 expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
Artem Dergachev940c7702016-10-18 11:06:28 +0000266
267 auto ConversionThroughAssignmentM =
268 binaryOperator(hasOperatorName("="),
Artem Dergacheve14d8812016-10-31 03:08:48 +0000269 hasLHS(SuspiciousScalarExprM),
270 hasRHS(SuspiciousNumberObjectExprM));
Artem Dergachev940c7702016-10-18 11:06:28 +0000271
272 auto ConversionThroughBranchingM =
Artem Dergacheve14d8812016-10-31 03:08:48 +0000273 ifStmt(hasCondition(SuspiciousNumberObjectExprM))
Artem Dergachev940c7702016-10-18 11:06:28 +0000274 .bind("pedantic");
275
276 auto ConversionThroughCallM =
Artem Dergacheve14d8812016-10-31 03:08:48 +0000277 callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
278 ignoringParenImpCasts(
279 SuspiciousNumberObjectExprM))));
Artem Dergachev940c7702016-10-18 11:06:28 +0000280
281 // We bind "check_if_null" to modify the warning message
282 // in case it was intended to compare a pointer to 0 with a relatively-ok
283 // construct "x == 0" or "x != 0".
284 auto ConversionThroughEquivalenceM =
285 binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
Artem Dergacheve14d8812016-10-31 03:08:48 +0000286 hasEitherOperand(SuspiciousNumberObjectExprM),
287 hasEitherOperand(SuspiciousScalarExprM
288 .bind("check_if_null")))
289 .bind("comparison");
Artem Dergachev940c7702016-10-18 11:06:28 +0000290
291 auto ConversionThroughComparisonM =
292 binaryOperator(anyOf(hasOperatorName(">="), hasOperatorName(">"),
293 hasOperatorName("<="), hasOperatorName("<")),
Artem Dergacheve14d8812016-10-31 03:08:48 +0000294 hasEitherOperand(SuspiciousNumberObjectExprM),
295 hasEitherOperand(SuspiciousScalarExprM))
296 .bind("comparison");
Artem Dergachev940c7702016-10-18 11:06:28 +0000297
298 auto ConversionThroughConditionalOperatorM =
299 conditionalOperator(
Artem Dergacheve14d8812016-10-31 03:08:48 +0000300 hasCondition(SuspiciousNumberObjectExprM),
301 unless(hasTrueExpression(
302 hasDescendant(AnotherSuspiciousNumberObjectExprM))),
303 unless(hasFalseExpression(
304 hasDescendant(AnotherSuspiciousNumberObjectExprM))))
Artem Dergachev940c7702016-10-18 11:06:28 +0000305 .bind("pedantic");
306
307 auto ConversionThroughExclamationMarkM =
Artem Dergacheve14d8812016-10-31 03:08:48 +0000308 unaryOperator(hasOperatorName("!"),
309 has(expr(SuspiciousNumberObjectExprM)))
Artem Dergachev940c7702016-10-18 11:06:28 +0000310 .bind("pedantic");
311
312 auto ConversionThroughExplicitBooleanCastM =
Artem Dergacheve14d8812016-10-31 03:08:48 +0000313 explicitCastExpr(hasType(SuspiciousScalarBooleanTypeM),
314 has(expr(SuspiciousNumberObjectExprM)));
Artem Dergachev940c7702016-10-18 11:06:28 +0000315
316 auto ConversionThroughExplicitNumberCastM =
Artem Dergacheve14d8812016-10-31 03:08:48 +0000317 explicitCastExpr(hasType(SuspiciousScalarNumberTypeM),
318 has(expr(SuspiciousNumberObjectExprM)));
Artem Dergachev940c7702016-10-18 11:06:28 +0000319
320 auto ConversionThroughInitializerM =
321 declStmt(hasSingleDecl(
Artem Dergacheve14d8812016-10-31 03:08:48 +0000322 varDecl(hasType(SuspiciousScalarTypeM),
323 hasInitializer(SuspiciousNumberObjectExprM))));
Artem Dergachev940c7702016-10-18 11:06:28 +0000324
325 auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
326 ConversionThroughBranchingM,
327 ConversionThroughCallM,
328 ConversionThroughComparisonM,
329 ConversionThroughConditionalOperatorM,
330 ConversionThroughEquivalenceM,
331 ConversionThroughExclamationMarkM,
332 ConversionThroughExplicitBooleanCastM,
333 ConversionThroughExplicitNumberCastM,
334 ConversionThroughInitializerM)).bind("conv");
335
Artem Dergacheve14d8812016-10-31 03:08:48 +0000336 MatchFinder F;
337 Callback CB(this, BR, AM.getAnalysisDeclContext(D));
338
Artem Dergachev940c7702016-10-18 11:06:28 +0000339 F.addMatcher(stmt(forEachDescendant(FinalM)), &CB);
340 F.match(*D->getBody(), AM.getASTContext());
341}
342
343void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
Artem Dergacheve14d8812016-10-31 03:08:48 +0000344 NumberObjectConversionChecker *Chk =
345 Mgr.registerChecker<NumberObjectConversionChecker>();
346 Chk->Pedantic =
347 Mgr.getAnalyzerOptions().getBooleanOption("Pedantic", false, Chk);
Artem Dergachev940c7702016-10-18 11:06:28 +0000348}