1 //===- NumberObjectConversionChecker.cpp -------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
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.
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.
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'.
26 //===----------------------------------------------------------------------===//
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"
37 using namespace clang;
39 using namespace ast_matchers;
43 class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
47 void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
48 BugReporter &BR) const;
51 class Callback : public MatchFinder::MatchCallback {
52 const NumberObjectConversionChecker *C;
54 AnalysisDeclContext *ADC;
57 Callback(const NumberObjectConversionChecker *C,
58 BugReporter &BR, AnalysisDeclContext *ADC)
59 : C(C), BR(BR), ADC(ADC) {}
60 virtual void run(const MatchFinder::MatchResult &Result);
62 } // end of anonymous namespace
64 void Callback::run(const MatchFinder::MatchResult &Result) {
65 bool IsPedanticMatch =
66 (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
67 if (IsPedanticMatch && !C->Pedantic)
70 ASTContext &ACtx = ADC->getASTContext();
72 if (const Expr *CheckIfNull =
73 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 bool MacroIndicatesWeShouldSkipTheCheck = false;
79 SourceLocation Loc = CheckIfNull->getBeginLoc();
80 if (Loc.isMacroID()) {
81 StringRef MacroName = Lexer::getImmediateMacroName(
82 Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
83 if (MacroName == "NULL" || MacroName == "nil")
85 if (MacroName == "YES" || MacroName == "NO")
86 MacroIndicatesWeShouldSkipTheCheck = true;
88 if (!MacroIndicatesWeShouldSkipTheCheck) {
89 Expr::EvalResult EVResult;
90 if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
91 EVResult, ACtx, Expr::SE_AllowSideEffects)) {
92 llvm::APSInt Result = EVResult.Val.getInt();
96 IsPedanticMatch = true;
102 const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
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
116 (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
119 (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
122 (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr);
124 (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr);
126 (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr);
128 llvm::SmallString<64> Msg;
129 llvm::raw_svector_ostream OS(Msg);
131 // Remove ObjC ARC qualifiers.
132 QualType ObjT = Obj->getType().getUnqualifiedType();
134 // Remove consts from pointers.
136 assert(ObjT.getCanonicalType()->isPointerType());
137 ObjT = ACtx.getPointerType(
138 ObjT->getPointeeType().getCanonicalType().getUnqualifiedType());
146 OS << "a pointer value of type '" << ObjT.getAsString() << "' to a ";
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?
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";
165 OS << EuphemismForPlain << " integer value";
167 OS << EuphemismForPlain << " BOOL value";
169 OS << EuphemismForPlain << " bool value";
170 else // Branch condition?
171 OS << EuphemismForPlain << " boolean value";
175 OS << "; instead, either compare the pointer to "
176 << (IsObjC ? "nil" : IsCpp ? "nullptr" : "NULL") << " or ";
178 OS << "; did you mean to ";
181 OS << "compare the result of calling " << SuggestedApi;
183 OS << "call " << SuggestedApi;
185 if (!IsPedanticMatch)
189 ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
191 PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
192 Conv->getSourceRange());
195 void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
197 BugReporter &BR) const {
198 // Currently this matches CoreFoundation opaque pointer typedefs.
199 auto CSuspiciousNumberObjectExprM =
200 expr(ignoringParenImpCasts(
202 typedefType(hasDeclaration(anyOf(
203 typedefDecl(hasName("CFNumberRef")),
204 typedefDecl(hasName("CFBooleanRef")))))))
207 // Currently this matches XNU kernel number-object pointers.
208 auto CppSuspiciousNumberObjectExprM =
209 expr(ignoringParenImpCasts(
210 expr(hasType(hasCanonicalType(
211 pointerType(pointee(hasCanonicalType(
212 recordType(hasDeclaration(
214 cxxRecordDecl(hasName("OSBoolean")),
215 cxxRecordDecl(hasName("OSNumber"))
216 .bind("osnumber"))))))))))
217 .bind("cpp_object")));
219 // 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")));
229 auto SuspiciousNumberObjectExprM = anyOf(
230 CSuspiciousNumberObjectExprM,
231 CppSuspiciousNumberObjectExprM,
232 ObjCSuspiciousNumberObjectExprM);
234 // Useful for predicates like "Unless we've seen the same object elsewhere".
235 auto AnotherSuspiciousNumberObjectExprM =
237 equalsBoundNode("c_object"),
238 equalsBoundNode("objc_object"),
239 equalsBoundNode("cpp_object")));
241 // The .bind here is in order to compose the error message more accurately.
242 auto ObjCSuspiciousScalarBooleanTypeM =
243 qualType(typedefType(hasDeclaration(
244 typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
246 // The .bind here is in order to compose the error message more accurately.
247 auto SuspiciousScalarBooleanTypeM =
248 qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
249 ObjCSuspiciousScalarBooleanTypeM));
251 // The .bind here is in order to compose the error message more accurately.
252 // Also avoid intptr_t and uintptr_t because they were specifically created
253 // for storing pointers.
254 auto SuspiciousScalarNumberTypeM =
255 qualType(hasCanonicalType(isInteger()),
256 unless(typedefType(hasDeclaration(
257 typedefDecl(matchesName("^::u?intptr_t$"))))))
260 auto SuspiciousScalarTypeM =
261 qualType(anyOf(SuspiciousScalarBooleanTypeM,
262 SuspiciousScalarNumberTypeM));
264 auto SuspiciousScalarExprM =
265 expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
267 auto ConversionThroughAssignmentM =
268 binaryOperator(allOf(hasOperatorName("="),
269 hasLHS(SuspiciousScalarExprM),
270 hasRHS(SuspiciousNumberObjectExprM)));
272 auto ConversionThroughBranchingM =
274 hasCondition(SuspiciousNumberObjectExprM),
275 unless(hasConditionVariableStatement(declStmt())
276 ))).bind("pedantic");
278 auto ConversionThroughCallM =
279 callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
280 ignoringParenImpCasts(
281 SuspiciousNumberObjectExprM))));
283 // We bind "check_if_null" to modify the warning message
284 // in case it was intended to compare a pointer to 0 with a relatively-ok
285 // construct "x == 0" or "x != 0".
286 auto ConversionThroughEquivalenceM =
287 binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
288 hasEitherOperand(SuspiciousNumberObjectExprM),
289 hasEitherOperand(SuspiciousScalarExprM
290 .bind("check_if_null"))))
293 auto ConversionThroughComparisonM =
294 binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
295 hasOperatorName("<="), hasOperatorName("<")),
296 hasEitherOperand(SuspiciousNumberObjectExprM),
297 hasEitherOperand(SuspiciousScalarExprM)))
300 auto ConversionThroughConditionalOperatorM =
301 conditionalOperator(allOf(
302 hasCondition(SuspiciousNumberObjectExprM),
303 unless(hasTrueExpression(
304 hasDescendant(AnotherSuspiciousNumberObjectExprM))),
305 unless(hasFalseExpression(
306 hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
309 auto ConversionThroughExclamationMarkM =
310 unaryOperator(allOf(hasOperatorName("!"),
311 has(expr(SuspiciousNumberObjectExprM))))
314 auto ConversionThroughExplicitBooleanCastM =
315 explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
316 has(expr(SuspiciousNumberObjectExprM))));
318 auto ConversionThroughExplicitNumberCastM =
319 explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
320 has(expr(SuspiciousNumberObjectExprM))));
322 auto ConversionThroughInitializerM =
323 declStmt(hasSingleDecl(
324 varDecl(hasType(SuspiciousScalarTypeM),
325 hasInitializer(SuspiciousNumberObjectExprM))));
327 auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
328 ConversionThroughBranchingM,
329 ConversionThroughCallM,
330 ConversionThroughComparisonM,
331 ConversionThroughConditionalOperatorM,
332 ConversionThroughEquivalenceM,
333 ConversionThroughExclamationMarkM,
334 ConversionThroughExplicitBooleanCastM,
335 ConversionThroughExplicitNumberCastM,
336 ConversionThroughInitializerM)).bind("conv");
339 Callback CB(this, BR, AM.getAnalysisDeclContext(D));
341 F.addMatcher(stmt(forEachDescendant(FinalM)), &CB);
342 F.match(*D->getBody(), AM.getASTContext());
345 void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
346 NumberObjectConversionChecker *Chk =
347 Mgr.registerChecker<NumberObjectConversionChecker>();
349 Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
352 bool ento::shouldRegisterNumberObjectConversionChecker(const LangOptions &LO) {