Artem Dergachev | 940c770 | 2016-10-18 11:06:28 +0000 | [diff] [blame] | 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 | } |