1e5dd7070Spatrick //===- NumberObjectConversionChecker.cpp -------------------------*- C++ -*-==// 2e5dd7070Spatrick // 3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information. 5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6e5dd7070Spatrick // 7e5dd7070Spatrick //===----------------------------------------------------------------------===// 8e5dd7070Spatrick // 9e5dd7070Spatrick // This file defines NumberObjectConversionChecker, which checks for a 10e5dd7070Spatrick // particular common mistake when dealing with numbers represented as objects 11e5dd7070Spatrick // passed around by pointers. Namely, the language allows to reinterpret the 12e5dd7070Spatrick // pointer as a number directly, often without throwing any warnings, 13e5dd7070Spatrick // but in most cases the result of such conversion is clearly unexpected, 14e5dd7070Spatrick // as pointer value, rather than number value represented by the pointee object, 15e5dd7070Spatrick // becomes the result of such operation. 16e5dd7070Spatrick // 17e5dd7070Spatrick // Currently the checker supports the Objective-C NSNumber class, 18e5dd7070Spatrick // and the OSBoolean class found in macOS low-level code; the latter 19e5dd7070Spatrick // can only hold boolean values. 20e5dd7070Spatrick // 21e5dd7070Spatrick // This checker has an option "Pedantic" (boolean), which enables detection of 22e5dd7070Spatrick // more conversion patterns (which are most likely more harmless, and therefore 23e5dd7070Spatrick // are more likely to produce false positives) - disabled by default, 24e5dd7070Spatrick // enabled with `-analyzer-config osx.NumberObjectConversion:Pedantic=true'. 25e5dd7070Spatrick // 26e5dd7070Spatrick //===----------------------------------------------------------------------===// 27e5dd7070Spatrick 28e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 29e5dd7070Spatrick #include "clang/ASTMatchers/ASTMatchFinder.h" 30e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 31e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 32e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h" 33e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 34e5dd7070Spatrick #include "clang/Lex/Lexer.h" 35e5dd7070Spatrick #include "llvm/ADT/APSInt.h" 36e5dd7070Spatrick 37e5dd7070Spatrick using namespace clang; 38e5dd7070Spatrick using namespace ento; 39e5dd7070Spatrick using namespace ast_matchers; 40e5dd7070Spatrick 41e5dd7070Spatrick namespace { 42e5dd7070Spatrick 43e5dd7070Spatrick class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> { 44e5dd7070Spatrick public: 45e5dd7070Spatrick bool Pedantic; 46e5dd7070Spatrick 47e5dd7070Spatrick void checkASTCodeBody(const Decl *D, AnalysisManager &AM, 48e5dd7070Spatrick BugReporter &BR) const; 49e5dd7070Spatrick }; 50e5dd7070Spatrick 51e5dd7070Spatrick class Callback : public MatchFinder::MatchCallback { 52e5dd7070Spatrick const NumberObjectConversionChecker *C; 53e5dd7070Spatrick BugReporter &BR; 54e5dd7070Spatrick AnalysisDeclContext *ADC; 55e5dd7070Spatrick 56e5dd7070Spatrick public: 57e5dd7070Spatrick Callback(const NumberObjectConversionChecker *C, 58e5dd7070Spatrick BugReporter &BR, AnalysisDeclContext *ADC) 59e5dd7070Spatrick : C(C), BR(BR), ADC(ADC) {} 60*ec727ea7Spatrick void run(const MatchFinder::MatchResult &Result) override; 61e5dd7070Spatrick }; 62e5dd7070Spatrick } // end of anonymous namespace 63e5dd7070Spatrick 64e5dd7070Spatrick void Callback::run(const MatchFinder::MatchResult &Result) { 65e5dd7070Spatrick bool IsPedanticMatch = 66e5dd7070Spatrick (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr); 67e5dd7070Spatrick if (IsPedanticMatch && !C->Pedantic) 68e5dd7070Spatrick return; 69e5dd7070Spatrick 70e5dd7070Spatrick ASTContext &ACtx = ADC->getASTContext(); 71e5dd7070Spatrick 72e5dd7070Spatrick if (const Expr *CheckIfNull = 73e5dd7070Spatrick Result.Nodes.getNodeAs<Expr>("check_if_null")) { 74e5dd7070Spatrick // Unless the macro indicates that the intended type is clearly not 75e5dd7070Spatrick // a pointer type, we should avoid warning on comparing pointers 76e5dd7070Spatrick // to zero literals in non-pedantic mode. 77e5dd7070Spatrick // FIXME: Introduce an AST matcher to implement the macro-related logic? 78e5dd7070Spatrick bool MacroIndicatesWeShouldSkipTheCheck = false; 79e5dd7070Spatrick SourceLocation Loc = CheckIfNull->getBeginLoc(); 80e5dd7070Spatrick if (Loc.isMacroID()) { 81e5dd7070Spatrick StringRef MacroName = Lexer::getImmediateMacroName( 82e5dd7070Spatrick Loc, ACtx.getSourceManager(), ACtx.getLangOpts()); 83e5dd7070Spatrick if (MacroName == "NULL" || MacroName == "nil") 84e5dd7070Spatrick return; 85e5dd7070Spatrick if (MacroName == "YES" || MacroName == "NO") 86e5dd7070Spatrick MacroIndicatesWeShouldSkipTheCheck = true; 87e5dd7070Spatrick } 88e5dd7070Spatrick if (!MacroIndicatesWeShouldSkipTheCheck) { 89e5dd7070Spatrick Expr::EvalResult EVResult; 90e5dd7070Spatrick if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt( 91e5dd7070Spatrick EVResult, ACtx, Expr::SE_AllowSideEffects)) { 92e5dd7070Spatrick llvm::APSInt Result = EVResult.Val.getInt(); 93e5dd7070Spatrick if (Result == 0) { 94e5dd7070Spatrick if (!C->Pedantic) 95e5dd7070Spatrick return; 96e5dd7070Spatrick IsPedanticMatch = true; 97e5dd7070Spatrick } 98e5dd7070Spatrick } 99e5dd7070Spatrick } 100e5dd7070Spatrick } 101e5dd7070Spatrick 102e5dd7070Spatrick const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv"); 103e5dd7070Spatrick assert(Conv); 104e5dd7070Spatrick 105e5dd7070Spatrick const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object"); 106e5dd7070Spatrick const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object"); 107e5dd7070Spatrick const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object"); 108e5dd7070Spatrick bool IsCpp = (ConvertedCppObject != nullptr); 109e5dd7070Spatrick bool IsObjC = (ConvertedObjCObject != nullptr); 110e5dd7070Spatrick const Expr *Obj = IsObjC ? ConvertedObjCObject 111e5dd7070Spatrick : IsCpp ? ConvertedCppObject 112e5dd7070Spatrick : ConvertedCObject; 113e5dd7070Spatrick assert(Obj); 114e5dd7070Spatrick 115e5dd7070Spatrick bool IsComparison = 116e5dd7070Spatrick (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr); 117e5dd7070Spatrick 118e5dd7070Spatrick bool IsOSNumber = 119e5dd7070Spatrick (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr); 120e5dd7070Spatrick 121e5dd7070Spatrick bool IsInteger = 122e5dd7070Spatrick (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr); 123e5dd7070Spatrick bool IsObjCBool = 124e5dd7070Spatrick (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr); 125e5dd7070Spatrick bool IsCppBool = 126e5dd7070Spatrick (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr); 127e5dd7070Spatrick 128e5dd7070Spatrick llvm::SmallString<64> Msg; 129e5dd7070Spatrick llvm::raw_svector_ostream OS(Msg); 130e5dd7070Spatrick 131e5dd7070Spatrick // Remove ObjC ARC qualifiers. 132e5dd7070Spatrick QualType ObjT = Obj->getType().getUnqualifiedType(); 133e5dd7070Spatrick 134e5dd7070Spatrick // Remove consts from pointers. 135e5dd7070Spatrick if (IsCpp) { 136e5dd7070Spatrick assert(ObjT.getCanonicalType()->isPointerType()); 137e5dd7070Spatrick ObjT = ACtx.getPointerType( 138e5dd7070Spatrick ObjT->getPointeeType().getCanonicalType().getUnqualifiedType()); 139e5dd7070Spatrick } 140e5dd7070Spatrick 141e5dd7070Spatrick if (IsComparison) 142e5dd7070Spatrick OS << "Comparing "; 143e5dd7070Spatrick else 144e5dd7070Spatrick OS << "Converting "; 145e5dd7070Spatrick 146e5dd7070Spatrick OS << "a pointer value of type '" << ObjT.getAsString() << "' to a "; 147e5dd7070Spatrick 148e5dd7070Spatrick std::string EuphemismForPlain = "primitive"; 149e5dd7070Spatrick std::string SuggestedApi = IsObjC ? (IsInteger ? "" : "-boolValue") 150e5dd7070Spatrick : IsCpp ? (IsOSNumber ? "" : "getValue()") 151e5dd7070Spatrick : "CFNumberGetValue()"; 152e5dd7070Spatrick if (SuggestedApi.empty()) { 153e5dd7070Spatrick // A generic message if we're not sure what API should be called. 154e5dd7070Spatrick // FIXME: Pattern-match the integer type to make a better guess? 155e5dd7070Spatrick SuggestedApi = 156e5dd7070Spatrick "a method on '" + ObjT.getAsString() + "' to get the scalar value"; 157e5dd7070Spatrick // "scalar" is not quite correct or common, but some documentation uses it 158e5dd7070Spatrick // when describing object methods we suggest. For consistency, we use 159e5dd7070Spatrick // "scalar" in the whole sentence when we need to use this word in at least 160e5dd7070Spatrick // one place, otherwise we use "primitive". 161e5dd7070Spatrick EuphemismForPlain = "scalar"; 162e5dd7070Spatrick } 163e5dd7070Spatrick 164e5dd7070Spatrick if (IsInteger) 165e5dd7070Spatrick OS << EuphemismForPlain << " integer value"; 166e5dd7070Spatrick else if (IsObjCBool) 167e5dd7070Spatrick OS << EuphemismForPlain << " BOOL value"; 168e5dd7070Spatrick else if (IsCppBool) 169e5dd7070Spatrick OS << EuphemismForPlain << " bool value"; 170e5dd7070Spatrick else // Branch condition? 171e5dd7070Spatrick OS << EuphemismForPlain << " boolean value"; 172e5dd7070Spatrick 173e5dd7070Spatrick 174e5dd7070Spatrick if (IsPedanticMatch) 175e5dd7070Spatrick OS << "; instead, either compare the pointer to " 176e5dd7070Spatrick << (IsObjC ? "nil" : IsCpp ? "nullptr" : "NULL") << " or "; 177e5dd7070Spatrick else 178e5dd7070Spatrick OS << "; did you mean to "; 179e5dd7070Spatrick 180e5dd7070Spatrick if (IsComparison) 181e5dd7070Spatrick OS << "compare the result of calling " << SuggestedApi; 182e5dd7070Spatrick else 183e5dd7070Spatrick OS << "call " << SuggestedApi; 184e5dd7070Spatrick 185e5dd7070Spatrick if (!IsPedanticMatch) 186e5dd7070Spatrick OS << "?"; 187e5dd7070Spatrick 188e5dd7070Spatrick BR.EmitBasicReport( 189e5dd7070Spatrick ADC->getDecl(), C, "Suspicious number object conversion", "Logic error", 190e5dd7070Spatrick OS.str(), 191e5dd7070Spatrick PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC), 192e5dd7070Spatrick Conv->getSourceRange()); 193e5dd7070Spatrick } 194e5dd7070Spatrick 195e5dd7070Spatrick void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, 196e5dd7070Spatrick AnalysisManager &AM, 197e5dd7070Spatrick BugReporter &BR) const { 198e5dd7070Spatrick // Currently this matches CoreFoundation opaque pointer typedefs. 199e5dd7070Spatrick auto CSuspiciousNumberObjectExprM = 200e5dd7070Spatrick expr(ignoringParenImpCasts( 201e5dd7070Spatrick expr(hasType( 202e5dd7070Spatrick typedefType(hasDeclaration(anyOf( 203e5dd7070Spatrick typedefDecl(hasName("CFNumberRef")), 204e5dd7070Spatrick typedefDecl(hasName("CFBooleanRef"))))))) 205e5dd7070Spatrick .bind("c_object"))); 206e5dd7070Spatrick 207e5dd7070Spatrick // Currently this matches XNU kernel number-object pointers. 208e5dd7070Spatrick auto CppSuspiciousNumberObjectExprM = 209e5dd7070Spatrick expr(ignoringParenImpCasts( 210e5dd7070Spatrick expr(hasType(hasCanonicalType( 211e5dd7070Spatrick pointerType(pointee(hasCanonicalType( 212e5dd7070Spatrick recordType(hasDeclaration( 213e5dd7070Spatrick anyOf( 214e5dd7070Spatrick cxxRecordDecl(hasName("OSBoolean")), 215e5dd7070Spatrick cxxRecordDecl(hasName("OSNumber")) 216e5dd7070Spatrick .bind("osnumber")))))))))) 217e5dd7070Spatrick .bind("cpp_object"))); 218e5dd7070Spatrick 219e5dd7070Spatrick // Currently this matches NeXTSTEP number objects. 220e5dd7070Spatrick auto ObjCSuspiciousNumberObjectExprM = 221e5dd7070Spatrick expr(ignoringParenImpCasts( 222e5dd7070Spatrick expr(hasType(hasCanonicalType( 223e5dd7070Spatrick objcObjectPointerType(pointee( 224e5dd7070Spatrick qualType(hasCanonicalType( 225e5dd7070Spatrick qualType(hasDeclaration( 226e5dd7070Spatrick objcInterfaceDecl(hasName("NSNumber"))))))))))) 227e5dd7070Spatrick .bind("objc_object"))); 228e5dd7070Spatrick 229e5dd7070Spatrick auto SuspiciousNumberObjectExprM = anyOf( 230e5dd7070Spatrick CSuspiciousNumberObjectExprM, 231e5dd7070Spatrick CppSuspiciousNumberObjectExprM, 232e5dd7070Spatrick ObjCSuspiciousNumberObjectExprM); 233e5dd7070Spatrick 234e5dd7070Spatrick // Useful for predicates like "Unless we've seen the same object elsewhere". 235e5dd7070Spatrick auto AnotherSuspiciousNumberObjectExprM = 236e5dd7070Spatrick expr(anyOf( 237e5dd7070Spatrick equalsBoundNode("c_object"), 238e5dd7070Spatrick equalsBoundNode("objc_object"), 239e5dd7070Spatrick equalsBoundNode("cpp_object"))); 240e5dd7070Spatrick 241e5dd7070Spatrick // The .bind here is in order to compose the error message more accurately. 242e5dd7070Spatrick auto ObjCSuspiciousScalarBooleanTypeM = 243e5dd7070Spatrick qualType(typedefType(hasDeclaration( 244e5dd7070Spatrick typedefDecl(hasName("BOOL"))))).bind("objc_bool_type"); 245e5dd7070Spatrick 246e5dd7070Spatrick // The .bind here is in order to compose the error message more accurately. 247e5dd7070Spatrick auto SuspiciousScalarBooleanTypeM = 248e5dd7070Spatrick qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"), 249e5dd7070Spatrick ObjCSuspiciousScalarBooleanTypeM)); 250e5dd7070Spatrick 251e5dd7070Spatrick // The .bind here is in order to compose the error message more accurately. 252e5dd7070Spatrick // Also avoid intptr_t and uintptr_t because they were specifically created 253e5dd7070Spatrick // for storing pointers. 254e5dd7070Spatrick auto SuspiciousScalarNumberTypeM = 255e5dd7070Spatrick qualType(hasCanonicalType(isInteger()), 256e5dd7070Spatrick unless(typedefType(hasDeclaration( 257e5dd7070Spatrick typedefDecl(matchesName("^::u?intptr_t$")))))) 258e5dd7070Spatrick .bind("int_type"); 259e5dd7070Spatrick 260e5dd7070Spatrick auto SuspiciousScalarTypeM = 261e5dd7070Spatrick qualType(anyOf(SuspiciousScalarBooleanTypeM, 262e5dd7070Spatrick SuspiciousScalarNumberTypeM)); 263e5dd7070Spatrick 264e5dd7070Spatrick auto SuspiciousScalarExprM = 265e5dd7070Spatrick expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM)))); 266e5dd7070Spatrick 267e5dd7070Spatrick auto ConversionThroughAssignmentM = 268e5dd7070Spatrick binaryOperator(allOf(hasOperatorName("="), 269e5dd7070Spatrick hasLHS(SuspiciousScalarExprM), 270e5dd7070Spatrick hasRHS(SuspiciousNumberObjectExprM))); 271e5dd7070Spatrick 272e5dd7070Spatrick auto ConversionThroughBranchingM = 273e5dd7070Spatrick ifStmt(allOf( 274e5dd7070Spatrick hasCondition(SuspiciousNumberObjectExprM), 275e5dd7070Spatrick unless(hasConditionVariableStatement(declStmt()) 276e5dd7070Spatrick ))).bind("pedantic"); 277e5dd7070Spatrick 278e5dd7070Spatrick auto ConversionThroughCallM = 279e5dd7070Spatrick callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM), 280e5dd7070Spatrick ignoringParenImpCasts( 281e5dd7070Spatrick SuspiciousNumberObjectExprM)))); 282e5dd7070Spatrick 283e5dd7070Spatrick // We bind "check_if_null" to modify the warning message 284e5dd7070Spatrick // in case it was intended to compare a pointer to 0 with a relatively-ok 285e5dd7070Spatrick // construct "x == 0" or "x != 0". 286e5dd7070Spatrick auto ConversionThroughEquivalenceM = 287e5dd7070Spatrick binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")), 288e5dd7070Spatrick hasEitherOperand(SuspiciousNumberObjectExprM), 289e5dd7070Spatrick hasEitherOperand(SuspiciousScalarExprM 290e5dd7070Spatrick .bind("check_if_null")))) 291e5dd7070Spatrick .bind("comparison"); 292e5dd7070Spatrick 293e5dd7070Spatrick auto ConversionThroughComparisonM = 294e5dd7070Spatrick binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"), 295e5dd7070Spatrick hasOperatorName("<="), hasOperatorName("<")), 296e5dd7070Spatrick hasEitherOperand(SuspiciousNumberObjectExprM), 297e5dd7070Spatrick hasEitherOperand(SuspiciousScalarExprM))) 298e5dd7070Spatrick .bind("comparison"); 299e5dd7070Spatrick 300e5dd7070Spatrick auto ConversionThroughConditionalOperatorM = 301e5dd7070Spatrick conditionalOperator(allOf( 302e5dd7070Spatrick hasCondition(SuspiciousNumberObjectExprM), 303e5dd7070Spatrick unless(hasTrueExpression( 304e5dd7070Spatrick hasDescendant(AnotherSuspiciousNumberObjectExprM))), 305e5dd7070Spatrick unless(hasFalseExpression( 306e5dd7070Spatrick hasDescendant(AnotherSuspiciousNumberObjectExprM))))) 307e5dd7070Spatrick .bind("pedantic"); 308e5dd7070Spatrick 309e5dd7070Spatrick auto ConversionThroughExclamationMarkM = 310e5dd7070Spatrick unaryOperator(allOf(hasOperatorName("!"), 311e5dd7070Spatrick has(expr(SuspiciousNumberObjectExprM)))) 312e5dd7070Spatrick .bind("pedantic"); 313e5dd7070Spatrick 314e5dd7070Spatrick auto ConversionThroughExplicitBooleanCastM = 315e5dd7070Spatrick explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM), 316e5dd7070Spatrick has(expr(SuspiciousNumberObjectExprM)))); 317e5dd7070Spatrick 318e5dd7070Spatrick auto ConversionThroughExplicitNumberCastM = 319e5dd7070Spatrick explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM), 320e5dd7070Spatrick has(expr(SuspiciousNumberObjectExprM)))); 321e5dd7070Spatrick 322e5dd7070Spatrick auto ConversionThroughInitializerM = 323e5dd7070Spatrick declStmt(hasSingleDecl( 324e5dd7070Spatrick varDecl(hasType(SuspiciousScalarTypeM), 325e5dd7070Spatrick hasInitializer(SuspiciousNumberObjectExprM)))); 326e5dd7070Spatrick 327e5dd7070Spatrick auto FinalM = stmt(anyOf(ConversionThroughAssignmentM, 328e5dd7070Spatrick ConversionThroughBranchingM, 329e5dd7070Spatrick ConversionThroughCallM, 330e5dd7070Spatrick ConversionThroughComparisonM, 331e5dd7070Spatrick ConversionThroughConditionalOperatorM, 332e5dd7070Spatrick ConversionThroughEquivalenceM, 333e5dd7070Spatrick ConversionThroughExclamationMarkM, 334e5dd7070Spatrick ConversionThroughExplicitBooleanCastM, 335e5dd7070Spatrick ConversionThroughExplicitNumberCastM, 336e5dd7070Spatrick ConversionThroughInitializerM)).bind("conv"); 337e5dd7070Spatrick 338e5dd7070Spatrick MatchFinder F; 339e5dd7070Spatrick Callback CB(this, BR, AM.getAnalysisDeclContext(D)); 340e5dd7070Spatrick 341*ec727ea7Spatrick F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB); 342e5dd7070Spatrick F.match(*D->getBody(), AM.getASTContext()); 343e5dd7070Spatrick } 344e5dd7070Spatrick 345e5dd7070Spatrick void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) { 346e5dd7070Spatrick NumberObjectConversionChecker *Chk = 347e5dd7070Spatrick Mgr.registerChecker<NumberObjectConversionChecker>(); 348e5dd7070Spatrick Chk->Pedantic = 349e5dd7070Spatrick Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic"); 350e5dd7070Spatrick } 351e5dd7070Spatrick 352*ec727ea7Spatrick bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) { 353e5dd7070Spatrick return true; 354e5dd7070Spatrick } 355