1 //===- 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 
38 using namespace clang;
39 using namespace ento;
40 using namespace ast_matchers;
41 
42 namespace {
43 
44 class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
45 public:
46   bool Pedantic;
47 
48   void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
49                         BugReporter &BR) const;
50 };
51 
52 class Callback : public MatchFinder::MatchCallback {
53   const NumberObjectConversionChecker *C;
54   BugReporter &BR;
55   AnalysisDeclContext *ADC;
56 
57 public:
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 
65 void 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 
139 void 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 
259 void 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 }
268