clang 19.0.0git
NumberObjectConversionChecker.cpp
Go to the documentation of this file.
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
34#include "clang/Lex/Lexer.h"
35#include "llvm/ADT/APSInt.h"
36
37using namespace clang;
38using namespace ento;
39using namespace ast_matchers;
40
41namespace {
42
43class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
44public:
45 bool Pedantic;
46
47 void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
48 BugReporter &BR) const;
49};
50
51class Callback : public MatchFinder::MatchCallback {
52 const NumberObjectConversionChecker *C;
53 BugReporter &BR;
55
56public:
57 Callback(const NumberObjectConversionChecker *C,
59 : C(C), BR(BR), ADC(ADC) {}
60 void run(const MatchFinder::MatchResult &Result) override;
61};
62} // end of anonymous namespace
63
64void Callback::run(const MatchFinder::MatchResult &Result) {
65 bool IsPedanticMatch =
66 (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
67 if (IsPedanticMatch && !C->Pedantic)
68 return;
69
70 ASTContext &ACtx = ADC->getASTContext();
71
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")
84 return;
85 if (MacroName == "YES" || MacroName == "NO")
86 MacroIndicatesWeShouldSkipTheCheck = true;
87 }
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();
93 if (Result == 0) {
94 if (!C->Pedantic)
95 return;
96 IsPedanticMatch = true;
97 }
98 }
99 }
100 }
101
102 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
129 llvm::raw_svector_ostream OS(Msg);
130
131 // Remove ObjC ARC qualifiers.
132 QualType ObjT = Obj->getType().getUnqualifiedType();
133
134 // Remove consts from pointers.
135 if (IsCpp) {
136 assert(ObjT.getCanonicalType()->isPointerType());
137 ObjT = ACtx.getPointerType(
139 }
140
141 if (IsComparison)
142 OS << "Comparing ";
143 else
144 OS << "Converting ";
145
146 OS << "a pointer value of type '" << ObjT << "' 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
188 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 {
198 // Currently this matches CoreFoundation opaque pointer typedefs.
199 auto CSuspiciousNumberObjectExprM = expr(ignoringParenImpCasts(
200 expr(hasType(elaboratedType(namesType(typedefType(
201 hasDeclaration(anyOf(typedefDecl(hasName("CFNumberRef")),
202 typedefDecl(hasName("CFBooleanRef")))))))))
203 .bind("c_object")));
204
205 // Currently this matches XNU kernel number-object pointers.
206 auto CppSuspiciousNumberObjectExprM =
207 expr(ignoringParenImpCasts(
208 expr(hasType(hasCanonicalType(
209 pointerType(pointee(hasCanonicalType(
211 anyOf(
212 cxxRecordDecl(hasName("OSBoolean")),
213 cxxRecordDecl(hasName("OSNumber"))
214 .bind("osnumber"))))))))))
215 .bind("cpp_object")));
216
217 // Currently this matches NeXTSTEP number objects.
218 auto ObjCSuspiciousNumberObjectExprM =
219 expr(ignoringParenImpCasts(
220 expr(hasType(hasCanonicalType(
221 objcObjectPointerType(pointee(
222 qualType(hasCanonicalType(
224 objcInterfaceDecl(hasName("NSNumber")))))))))))
225 .bind("objc_object")));
226
227 auto SuspiciousNumberObjectExprM = anyOf(
228 CSuspiciousNumberObjectExprM,
229 CppSuspiciousNumberObjectExprM,
230 ObjCSuspiciousNumberObjectExprM);
231
232 // Useful for predicates like "Unless we've seen the same object elsewhere".
233 auto AnotherSuspiciousNumberObjectExprM =
234 expr(anyOf(
235 equalsBoundNode("c_object"),
236 equalsBoundNode("objc_object"),
237 equalsBoundNode("cpp_object")));
238
239 // The .bind here is in order to compose the error message more accurately.
240 auto ObjCSuspiciousScalarBooleanTypeM =
241 qualType(elaboratedType(namesType(
243 .bind("objc_bool_type");
244
245 // The .bind here is in order to compose the error message more accurately.
246 auto SuspiciousScalarBooleanTypeM =
247 qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
248 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 auto SuspiciousScalarNumberTypeM =
254 qualType(hasCanonicalType(isInteger()),
256 typedefDecl(matchesName("^::u?intptr_t$"))))))))
257 .bind("int_type");
258
259 auto SuspiciousScalarTypeM =
260 qualType(anyOf(SuspiciousScalarBooleanTypeM,
261 SuspiciousScalarNumberTypeM));
262
263 auto SuspiciousScalarExprM =
264 expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
265
266 auto ConversionThroughAssignmentM =
267 binaryOperator(allOf(hasOperatorName("="),
268 hasLHS(SuspiciousScalarExprM),
269 hasRHS(SuspiciousNumberObjectExprM)));
270
271 auto ConversionThroughBranchingM =
273 hasCondition(SuspiciousNumberObjectExprM),
274 unless(hasConditionVariableStatement(declStmt())
275 ))).bind("pedantic");
276
277 auto ConversionThroughCallM =
278 callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
279 ignoringParenImpCasts(
280 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 auto ConversionThroughEquivalenceM =
286 binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
287 hasEitherOperand(SuspiciousNumberObjectExprM),
288 hasEitherOperand(SuspiciousScalarExprM
289 .bind("check_if_null"))))
290 .bind("comparison");
291
292 auto ConversionThroughComparisonM =
293 binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
294 hasOperatorName("<="), hasOperatorName("<")),
295 hasEitherOperand(SuspiciousNumberObjectExprM),
296 hasEitherOperand(SuspiciousScalarExprM)))
297 .bind("comparison");
298
299 auto ConversionThroughConditionalOperatorM =
301 hasCondition(SuspiciousNumberObjectExprM),
302 unless(hasTrueExpression(
303 hasDescendant(AnotherSuspiciousNumberObjectExprM))),
304 unless(hasFalseExpression(
305 hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
306 .bind("pedantic");
307
308 auto ConversionThroughExclamationMarkM =
309 unaryOperator(allOf(hasOperatorName("!"),
310 has(expr(SuspiciousNumberObjectExprM))))
311 .bind("pedantic");
312
313 auto ConversionThroughExplicitBooleanCastM =
314 explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
315 has(expr(SuspiciousNumberObjectExprM))));
316
317 auto ConversionThroughExplicitNumberCastM =
318 explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
319 has(expr(SuspiciousNumberObjectExprM))));
320
321 auto ConversionThroughInitializerM =
322 declStmt(hasSingleDecl(
323 varDecl(hasType(SuspiciousScalarTypeM),
324 hasInitializer(SuspiciousNumberObjectExprM))));
325
326 auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
327 ConversionThroughBranchingM,
328 ConversionThroughCallM,
329 ConversionThroughComparisonM,
330 ConversionThroughConditionalOperatorM,
331 ConversionThroughEquivalenceM,
332 ConversionThroughExclamationMarkM,
333 ConversionThroughExplicitBooleanCastM,
334 ConversionThroughExplicitNumberCastM,
335 ConversionThroughInitializerM)).bind("conv");
336
337 MatchFinder F;
338 Callback CB(this, BR, AM.getAnalysisDeclContext(D));
339
341 F.match(*D->getBody(), AM.getASTContext());
342}
343
344void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
345 NumberObjectConversionChecker *Chk =
346 Mgr.registerChecker<NumberObjectConversionChecker>();
347 Chk->Pedantic =
348 Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
349}
350
351bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) {
352 return true;
353}
APSInt & getInt()
Definition: APValue.h:423
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:182
SourceManager & getSourceManager()
Definition: ASTContext.h:705
QualType getPointerType(QualType T) const
Return the uniqued reference to the type for a pointer to the specified type.
const LangOptions & getLangOpts() const
Definition: ASTContext.h:775
AnalysisDeclContext contains the context data for the function, method or block under analysis.
bool getCheckerBooleanOption(StringRef CheckerName, StringRef OptionName, bool SearchInParents=false) const
Interprets an option's string value as a boolean.
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:86
virtual Stmt * getBody() const
getBody - If this Decl represents a declaration for a body of code, such as a function or method defi...
Definition: DeclBase.h:1077
This represents one expression.
Definition: Expr.h:110
@ SE_AllowSideEffects
Allow any unmodeled side effect.
Definition: Expr.h:671
QualType getType() const
Definition: Expr.h:142
static StringRef getImmediateMacroName(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Retrieve the name of the immediate macro expansion.
Definition: Lexer.cpp:1060
A (possibly-)qualified type.
Definition: Type.h:940
QualType getCanonicalType() const
Definition: Type.h:7411
QualType getUnqualifiedType() const
Retrieve the unqualified variant of the given type, removing as little sugar as possible.
Definition: Type.h:7453
Encodes a location in the source.
Stmt - This represents one statement.
Definition: Stmt.h:84
SourceRange getSourceRange() const LLVM_READONLY
SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...
Definition: Stmt.cpp:326
bool isPointerType() const
Definition: Type.h:7612
QualType getPointeeType() const
If this is a pointer, ObjC object pointer, or block pointer, this returns the respective pointee.
Definition: Type.cpp:694
Called when the Match registered for it was successfully found in the AST.
virtual void run(const MatchResult &Result)=0
Called on every match by the MatchFinder.
A class to allow finding matches over the Clang AST.
void addMatcher(const DeclarationMatcher &NodeMatch, MatchCallback *Action)
Adds a matcher to execute when running over the AST.
void match(const T &Node, ASTContext &Context)
Calls the registered callbacks on all matches on the given Node.
ASTContext & getASTContext() override
AnalysisDeclContext * getAnalysisDeclContext(const Decl *D)
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:585
const AnalyzerOptions & getAnalyzerOptions() const
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
const internal::VariadicDynCastAllOfMatcher< Decl, VarDecl > varDecl
Matches variable declarations.
const AstTypeMatcher< ElaboratedType > elaboratedType
Matches types specified with an elaborated type keyword or with a qualified name.
const AstTypeMatcher< ObjCObjectPointerType > objcObjectPointerType
Matches an Objective-C object pointer type, which is different from a pointer type,...
const internal::VariadicOperatorMatcherFunc< 1, 1 > unless
Matches if the provided matcher does not match.
const internal::ArgumentAdaptingMatcherFunc< internal::HasDescendantMatcher > hasDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
const AstTypeMatcher< PointerType > pointerType
Matches pointer types, but does not match Objective-C object pointer types.
const internal::VariadicDynCastAllOfMatcher< Decl, TypedefDecl > typedefDecl
Matches typedef declarations.
internal::Matcher< NamedDecl > hasName(StringRef Name)
Matches NamedDecl nodes that have the specified name.
Definition: ASTMatchers.h:3078
const internal::VariadicDynCastAllOfMatcher< Stmt, CallExpr > callExpr
Matches call expressions.
const internal::ArgumentAdaptingMatcherFunc< internal::ForEachDescendantMatcher > forEachDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
const internal::VariadicDynCastAllOfMatcher< Stmt, UnaryOperator > unaryOperator
Matches unary operator expressions.
const internal::VariadicDynCastAllOfMatcher< Stmt, BinaryOperator > binaryOperator
Matches binary operator expressions.
const internal::ArgumentAdaptingMatcherFunc< internal::HasMatcher > has
Matches AST nodes that have child AST nodes that match the provided matcher.
const AstTypeMatcher< TypedefType > typedefType
Matches typedef types.
const internal::VariadicDynCastAllOfMatcher< Stmt, ExplicitCastExpr > explicitCastExpr
Matches explicit cast expressions.
const internal::VariadicDynCastAllOfMatcher< Decl, ObjCInterfaceDecl > objcInterfaceDecl
Matches Objective-C interface declarations.
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> allOf
Matches if all given matchers match.
const AstTypeMatcher< RecordType > recordType
Matches record types (e.g.
const internal::VariadicDynCastAllOfMatcher< Decl, CXXRecordDecl > cxxRecordDecl
Matches C++ class declarations.
internal::Matcher< T > traverse(TraversalKind TK, const internal::Matcher< T > &InnerMatcher)
Causes all nested matchers to be matched with the specified traversal kind.
Definition: ASTMatchers.h:817
internal::PolymorphicMatcher< internal::HasDeclarationMatcher, void(internal::HasDeclarationSupportedTypes), internal::Matcher< Decl > > hasDeclaration(const internal::Matcher< Decl > &InnerMatcher)
Matches a node if the declaration associated with that node matches the given matcher.
Definition: ASTMatchers.h:3652
const internal::VariadicDynCastAllOfMatcher< Stmt, DeclStmt > declStmt
Matches declaration statements.
const internal::VariadicAllOfMatcher< Stmt > stmt
Matches statements.
const internal::VariadicDynCastAllOfMatcher< Stmt, Expr > expr
Matches expressions.
const internal::VariadicDynCastAllOfMatcher< Stmt, ConditionalOperator > conditionalOperator
Matches conditional operator expressions.
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> anyOf
Matches if any of the given matchers matches.
const internal::VariadicAllOfMatcher< QualType > qualType
Matches QualTypes in the clang AST.
const internal::VariadicDynCastAllOfMatcher< Stmt, IfStmt > ifStmt
Matches if statements.
The JSON file list parser is used to communicate input to InstallAPI.
@ TK_AsIs
Will traverse all child nodes.
Definition: ASTTypeTraits.h:40
EvalResult is a struct with detailed info about an evaluated expression.
Definition: Expr.h:642
APValue Val
Val - This is the value the expression can be folded to.
Definition: Expr.h:644
Contains all information for a given match.