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