NumberObjectConversionChecker.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. #include "ClangSACheckers.h"
  29. #include "clang/ASTMatchers/ASTMatchFinder.h"
  30. #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
  31. #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
  32. #include "clang/StaticAnalyzer/Core/Checker.h"
  33. #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
  34. #include "clang/Lex/Lexer.h"
  35. #include "llvm/ADT/APSInt.h"
  36. using namespace clang;
  37. using namespace ento;
  38. using namespace ast_matchers;
  39. namespace {
  40. class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
  41. public:
  42. bool Pedantic;
  43. void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
  44. BugReporter &BR) const;
  45. };
  46. class Callback : public MatchFinder::MatchCallback {
  47. const NumberObjectConversionChecker *C;
  48. BugReporter &BR;
  49. AnalysisDeclContext *ADC;
  50. public:
  51. Callback(const NumberObjectConversionChecker *C,
  52. BugReporter &BR, AnalysisDeclContext *ADC)
  53. : C(C), BR(BR), ADC(ADC) {}
  54. virtual void run(const MatchFinder::MatchResult &Result);
  55. };
  56. } // end of anonymous namespace
  57. void Callback::run(const MatchFinder::MatchResult &Result) {
  58. bool IsPedanticMatch =
  59. (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
  60. if (IsPedanticMatch && !C->Pedantic)
  61. return;
  62. ASTContext &ACtx = ADC->getASTContext();
  63. if (const Expr *CheckIfNull =
  64. Result.Nodes.getNodeAs<Expr>("check_if_null")) {
  65. // Unless the macro indicates that the intended type is clearly not
  66. // a pointer type, we should avoid warning on comparing pointers
  67. // to zero literals in non-pedantic mode.
  68. // FIXME: Introduce an AST matcher to implement the macro-related logic?
  69. bool MacroIndicatesWeShouldSkipTheCheck = false;
  70. SourceLocation Loc = CheckIfNull->getBeginLoc();
  71. if (Loc.isMacroID()) {
  72. StringRef MacroName = Lexer::getImmediateMacroName(
  73. Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
  74. if (MacroName == "NULL" || MacroName == "nil")
  75. return;
  76. if (MacroName == "YES" || MacroName == "NO")
  77. MacroIndicatesWeShouldSkipTheCheck = true;
  78. }
  79. if (!MacroIndicatesWeShouldSkipTheCheck) {
  80. Expr::EvalResult EVResult;
  81. if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
  82. EVResult, ACtx, Expr::SE_AllowSideEffects)) {
  83. llvm::APSInt Result = EVResult.Val.getInt();
  84. if (Result == 0) {
  85. if (!C->Pedantic)
  86. return;
  87. IsPedanticMatch = true;
  88. }
  89. }
  90. }
  91. }
  92. const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
  93. assert(Conv);
  94. const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object");
  95. const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object");
  96. const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object");
  97. bool IsCpp = (ConvertedCppObject != nullptr);
  98. bool IsObjC = (ConvertedObjCObject != nullptr);
  99. const Expr *Obj = IsObjC ? ConvertedObjCObject
  100. : IsCpp ? ConvertedCppObject
  101. : ConvertedCObject;
  102. assert(Obj);
  103. bool IsComparison =
  104. (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
  105. bool IsOSNumber =
  106. (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
  107. bool IsInteger =
  108. (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr);
  109. bool IsObjCBool =
  110. (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr);
  111. bool IsCppBool =
  112. (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr);
  113. llvm::SmallString<64> Msg;
  114. llvm::raw_svector_ostream OS(Msg);
  115. // Remove ObjC ARC qualifiers.
  116. QualType ObjT = Obj->getType().getUnqualifiedType();
  117. // Remove consts from pointers.
  118. if (IsCpp) {
  119. assert(ObjT.getCanonicalType()->isPointerType());
  120. ObjT = ACtx.getPointerType(
  121. ObjT->getPointeeType().getCanonicalType().getUnqualifiedType());
  122. }
  123. if (IsComparison)
  124. OS << "Comparing ";
  125. else
  126. OS << "Converting ";
  127. OS << "a pointer value of type '" << ObjT.getAsString() << "' to a ";
  128. std::string EuphemismForPlain = "primitive";
  129. std::string SuggestedApi = IsObjC ? (IsInteger ? "" : "-boolValue")
  130. : IsCpp ? (IsOSNumber ? "" : "getValue()")
  131. : "CFNumberGetValue()";
  132. if (SuggestedApi.empty()) {
  133. // A generic message if we're not sure what API should be called.
  134. // FIXME: Pattern-match the integer type to make a better guess?
  135. SuggestedApi =
  136. "a method on '" + ObjT.getAsString() + "' to get the scalar value";
  137. // "scalar" is not quite correct or common, but some documentation uses it
  138. // when describing object methods we suggest. For consistency, we use
  139. // "scalar" in the whole sentence when we need to use this word in at least
  140. // one place, otherwise we use "primitive".
  141. EuphemismForPlain = "scalar";
  142. }
  143. if (IsInteger)
  144. OS << EuphemismForPlain << " integer value";
  145. else if (IsObjCBool)
  146. OS << EuphemismForPlain << " BOOL value";
  147. else if (IsCppBool)
  148. OS << EuphemismForPlain << " bool value";
  149. else // Branch condition?
  150. OS << EuphemismForPlain << " boolean value";
  151. if (IsPedanticMatch)
  152. OS << "; instead, either compare the pointer to "
  153. << (IsObjC ? "nil" : IsCpp ? "nullptr" : "NULL") << " or ";
  154. else
  155. OS << "; did you mean to ";
  156. if (IsComparison)
  157. OS << "compare the result of calling " << SuggestedApi;
  158. else
  159. OS << "call " << SuggestedApi;
  160. if (!IsPedanticMatch)
  161. OS << "?";
  162. BR.EmitBasicReport(
  163. ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
  164. OS.str(),
  165. PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
  166. Conv->getSourceRange());
  167. }
  168. void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
  169. AnalysisManager &AM,
  170. BugReporter &BR) const {
  171. // Currently this matches CoreFoundation opaque pointer typedefs.
  172. auto CSuspiciousNumberObjectExprM =
  173. expr(ignoringParenImpCasts(
  174. expr(hasType(
  175. typedefType(hasDeclaration(anyOf(
  176. typedefDecl(hasName("CFNumberRef")),
  177. typedefDecl(hasName("CFBooleanRef")))))))
  178. .bind("c_object")));
  179. // Currently this matches XNU kernel number-object pointers.
  180. auto CppSuspiciousNumberObjectExprM =
  181. expr(ignoringParenImpCasts(
  182. expr(hasType(hasCanonicalType(
  183. pointerType(pointee(hasCanonicalType(
  184. recordType(hasDeclaration(
  185. anyOf(
  186. cxxRecordDecl(hasName("OSBoolean")),
  187. cxxRecordDecl(hasName("OSNumber"))
  188. .bind("osnumber"))))))))))
  189. .bind("cpp_object")));
  190. // Currently this matches NeXTSTEP number objects.
  191. auto ObjCSuspiciousNumberObjectExprM =
  192. expr(ignoringParenImpCasts(
  193. expr(hasType(hasCanonicalType(
  194. objcObjectPointerType(pointee(
  195. qualType(hasCanonicalType(
  196. qualType(hasDeclaration(
  197. objcInterfaceDecl(hasName("NSNumber")))))))))))
  198. .bind("objc_object")));
  199. auto SuspiciousNumberObjectExprM = anyOf(
  200. CSuspiciousNumberObjectExprM,
  201. CppSuspiciousNumberObjectExprM,
  202. ObjCSuspiciousNumberObjectExprM);
  203. // Useful for predicates like "Unless we've seen the same object elsewhere".
  204. auto AnotherSuspiciousNumberObjectExprM =
  205. expr(anyOf(
  206. equalsBoundNode("c_object"),
  207. equalsBoundNode("objc_object"),
  208. equalsBoundNode("cpp_object")));
  209. // The .bind here is in order to compose the error message more accurately.
  210. auto ObjCSuspiciousScalarBooleanTypeM =
  211. qualType(typedefType(hasDeclaration(
  212. typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
  213. // The .bind here is in order to compose the error message more accurately.
  214. auto SuspiciousScalarBooleanTypeM =
  215. qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
  216. ObjCSuspiciousScalarBooleanTypeM));
  217. // The .bind here is in order to compose the error message more accurately.
  218. // Also avoid intptr_t and uintptr_t because they were specifically created
  219. // for storing pointers.
  220. auto SuspiciousScalarNumberTypeM =
  221. qualType(hasCanonicalType(isInteger()),
  222. unless(typedefType(hasDeclaration(
  223. typedefDecl(matchesName("^::u?intptr_t$"))))))
  224. .bind("int_type");
  225. auto SuspiciousScalarTypeM =
  226. qualType(anyOf(SuspiciousScalarBooleanTypeM,
  227. SuspiciousScalarNumberTypeM));
  228. auto SuspiciousScalarExprM =
  229. expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
  230. auto ConversionThroughAssignmentM =
  231. binaryOperator(allOf(hasOperatorName("="),
  232. hasLHS(SuspiciousScalarExprM),
  233. hasRHS(SuspiciousNumberObjectExprM)));
  234. auto ConversionThroughBranchingM =
  235. ifStmt(allOf(
  236. hasCondition(SuspiciousNumberObjectExprM),
  237. unless(hasConditionVariableStatement(declStmt())
  238. ))).bind("pedantic");
  239. auto ConversionThroughCallM =
  240. callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
  241. ignoringParenImpCasts(
  242. SuspiciousNumberObjectExprM))));
  243. // We bind "check_if_null" to modify the warning message
  244. // in case it was intended to compare a pointer to 0 with a relatively-ok
  245. // construct "x == 0" or "x != 0".
  246. auto ConversionThroughEquivalenceM =
  247. binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
  248. hasEitherOperand(SuspiciousNumberObjectExprM),
  249. hasEitherOperand(SuspiciousScalarExprM
  250. .bind("check_if_null"))))
  251. .bind("comparison");
  252. auto ConversionThroughComparisonM =
  253. binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
  254. hasOperatorName("<="), hasOperatorName("<")),
  255. hasEitherOperand(SuspiciousNumberObjectExprM),
  256. hasEitherOperand(SuspiciousScalarExprM)))
  257. .bind("comparison");
  258. auto ConversionThroughConditionalOperatorM =
  259. conditionalOperator(allOf(
  260. hasCondition(SuspiciousNumberObjectExprM),
  261. unless(hasTrueExpression(
  262. hasDescendant(AnotherSuspiciousNumberObjectExprM))),
  263. unless(hasFalseExpression(
  264. hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
  265. .bind("pedantic");
  266. auto ConversionThroughExclamationMarkM =
  267. unaryOperator(allOf(hasOperatorName("!"),
  268. has(expr(SuspiciousNumberObjectExprM))))
  269. .bind("pedantic");
  270. auto ConversionThroughExplicitBooleanCastM =
  271. explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
  272. has(expr(SuspiciousNumberObjectExprM))));
  273. auto ConversionThroughExplicitNumberCastM =
  274. explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
  275. has(expr(SuspiciousNumberObjectExprM))));
  276. auto ConversionThroughInitializerM =
  277. declStmt(hasSingleDecl(
  278. varDecl(hasType(SuspiciousScalarTypeM),
  279. hasInitializer(SuspiciousNumberObjectExprM))));
  280. auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
  281. ConversionThroughBranchingM,
  282. ConversionThroughCallM,
  283. ConversionThroughComparisonM,
  284. ConversionThroughConditionalOperatorM,
  285. ConversionThroughEquivalenceM,
  286. ConversionThroughExclamationMarkM,
  287. ConversionThroughExplicitBooleanCastM,
  288. ConversionThroughExplicitNumberCastM,
  289. ConversionThroughInitializerM)).bind("conv");
  290. MatchFinder F;
  291. Callback CB(this, BR, AM.getAnalysisDeclContext(D));
  292. F.addMatcher(stmt(forEachDescendant(FinalM)), &CB);
  293. F.match(*D->getBody(), AM.getASTContext());
  294. }
  295. void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
  296. NumberObjectConversionChecker *Chk =
  297. Mgr.registerChecker<NumberObjectConversionChecker>();
  298. Chk->Pedantic =
  299. Mgr.getAnalyzerOptions().getCheckerBooleanOption("Pedantic", false, Chk);
  300. }