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