ObjCAutoreleaseWriteChecker.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file defines ObjCAutoreleaseWriteChecker which warns against writes
  10. // into autoreleased out parameters which cause crashes.
  11. // An example of a problematic write is a write to @c error in the example
  12. // below:
  13. //
  14. // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list {
  15. // [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  16. // NSString *myString = obj;
  17. // if ([myString isEqualToString:@"error"] && error)
  18. // *error = [NSError errorWithDomain:@"MyDomain" code:-1];
  19. // }];
  20. // return false;
  21. // }
  22. //
  23. // Such code will crash on read from `*error` due to the autorelease pool
  24. // in `enumerateObjectsUsingBlock` implementation freeing the error object
  25. // on exit from the function.
  26. //
  27. //===----------------------------------------------------------------------===//
  28. #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/BugReporter/CommonBugCategories.h"
  33. #include "clang/StaticAnalyzer/Core/Checker.h"
  34. #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
  35. #include "llvm/ADT/Twine.h"
  36. using namespace clang;
  37. using namespace ento;
  38. using namespace ast_matchers;
  39. namespace {
  40. const char *ProblematicWriteBind = "problematicwrite";
  41. const char *CapturedBind = "capturedbind";
  42. const char *ParamBind = "parambind";
  43. const char *IsMethodBind = "ismethodbind";
  44. const char *IsARPBind = "isautoreleasepoolbind";
  45. class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
  46. public:
  47. void checkASTCodeBody(const Decl *D,
  48. AnalysisManager &AM,
  49. BugReporter &BR) const;
  50. private:
  51. std::vector<std::string> SelectorsWithAutoreleasingPool = {
  52. // Common to NSArray, NSSet, NSOrderedSet
  53. "enumerateObjectsUsingBlock:",
  54. "enumerateObjectsWithOptions:usingBlock:",
  55. // Common to NSArray and NSOrderedSet
  56. "enumerateObjectsAtIndexes:options:usingBlock:",
  57. "indexOfObjectAtIndexes:options:passingTest:",
  58. "indexesOfObjectsAtIndexes:options:passingTest:",
  59. "indexOfObjectPassingTest:",
  60. "indexOfObjectWithOptions:passingTest:",
  61. "indexesOfObjectsPassingTest:",
  62. "indexesOfObjectsWithOptions:passingTest:",
  63. // NSDictionary
  64. "enumerateKeysAndObjectsUsingBlock:",
  65. "enumerateKeysAndObjectsWithOptions:usingBlock:",
  66. "keysOfEntriesPassingTest:",
  67. "keysOfEntriesWithOptions:passingTest:",
  68. // NSSet
  69. "objectsPassingTest:",
  70. "objectsWithOptions:passingTest:",
  71. "enumerateIndexPathsWithOptions:usingBlock:",
  72. // NSIndexSet
  73. "enumerateIndexesWithOptions:usingBlock:",
  74. "enumerateIndexesUsingBlock:",
  75. "enumerateIndexesInRange:options:usingBlock:",
  76. "enumerateRangesUsingBlock:",
  77. "enumerateRangesWithOptions:usingBlock:",
  78. "enumerateRangesInRange:options:usingBlock:",
  79. "indexPassingTest:",
  80. "indexesPassingTest:",
  81. "indexWithOptions:passingTest:",
  82. "indexesWithOptions:passingTest:",
  83. "indexInRange:options:passingTest:",
  84. "indexesInRange:options:passingTest:"
  85. };
  86. std::vector<std::string> FunctionsWithAutoreleasingPool = {
  87. "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
  88. };
  89. }
  90. static inline std::vector<llvm::StringRef>
  91. toRefs(const std::vector<std::string> &V) {
  92. return std::vector<llvm::StringRef>(V.begin(), V.end());
  93. }
  94. static decltype(auto)
  95. callsNames(const std::vector<std::string> &FunctionNames) {
  96. return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
  97. }
  98. static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
  99. AnalysisManager &AM,
  100. const ObjCAutoreleaseWriteChecker *Checker) {
  101. AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
  102. const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
  103. QualType Ty = PVD->getType();
  104. if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
  105. return;
  106. const char *ActionMsg = "Write to";
  107. const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
  108. bool IsCapture = false;
  109. // Prefer to warn on write, but if not available, warn on capture.
  110. if (!MarkedStmt) {
  111. MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
  112. assert(MarkedStmt);
  113. ActionMsg = "Capture of";
  114. IsCapture = true;
  115. }
  116. SourceRange Range = MarkedStmt->getSourceRange();
  117. PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
  118. MarkedStmt, BR.getSourceManager(), ADC);
  119. bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
  120. const char *FunctionDescription = IsMethod ? "method" : "function";
  121. bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
  122. llvm::SmallString<128> BugNameBuf;
  123. llvm::raw_svector_ostream BugName(BugNameBuf);
  124. BugName << ActionMsg
  125. << " autoreleasing out parameter inside autorelease pool";
  126. llvm::SmallString<128> BugMessageBuf;
  127. llvm::raw_svector_ostream BugMessage(BugMessageBuf);
  128. BugMessage << ActionMsg << " autoreleasing out parameter ";
  129. if (IsCapture)
  130. BugMessage << "'" + PVD->getName() + "' ";
  131. BugMessage << "inside ";
  132. if (IsARP)
  133. BugMessage << "locally-scoped autorelease pool;";
  134. else
  135. BugMessage << "autorelease pool that may exit before "
  136. << FunctionDescription << " returns;";
  137. BugMessage << " consider writing first to a strong local variable"
  138. " declared outside ";
  139. if (IsARP)
  140. BugMessage << "of the autorelease pool";
  141. else
  142. BugMessage << "of the block";
  143. BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
  144. categories::MemoryRefCount, BugMessage.str(), Location,
  145. Range);
  146. }
  147. void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
  148. AnalysisManager &AM,
  149. BugReporter &BR) const {
  150. auto DoublePointerParamM =
  151. parmVarDecl(hasType(hasCanonicalType(pointerType(
  152. pointee(hasCanonicalType(objcObjectPointerType()))))))
  153. .bind(ParamBind);
  154. auto ReferencedParamM =
  155. declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
  156. // Write into a binded object, e.g. *ParamBind = X.
  157. auto WritesIntoM = binaryOperator(
  158. hasLHS(unaryOperator(
  159. hasOperatorName("*"),
  160. hasUnaryOperand(
  161. ignoringParenImpCasts(ReferencedParamM))
  162. )),
  163. hasOperatorName("=")
  164. ).bind(ProblematicWriteBind);
  165. auto ArgumentCaptureM = hasAnyArgument(
  166. ignoringParenImpCasts(ReferencedParamM));
  167. auto CapturedInParamM = stmt(anyOf(
  168. callExpr(ArgumentCaptureM),
  169. objcMessageExpr(ArgumentCaptureM)));
  170. // WritesIntoM happens inside a block passed as an argument.
  171. auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
  172. hasType(hasCanonicalType(blockPointerType())),
  173. forEachDescendant(
  174. stmt(anyOf(WritesIntoM, CapturedInParamM))
  175. )));
  176. auto BlockPassedToMarkedFuncM = stmt(anyOf(
  177. callExpr(allOf(
  178. callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
  179. objcMessageExpr(allOf(
  180. hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
  181. WritesOrCapturesInBlockM))
  182. ));
  183. // WritesIntoM happens inside an explicit @autoreleasepool.
  184. auto WritesOrCapturesInPoolM =
  185. autoreleasePoolStmt(
  186. forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
  187. .bind(IsARPBind);
  188. auto HasParamAndWritesInMarkedFuncM =
  189. allOf(hasAnyParameter(DoublePointerParamM),
  190. anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
  191. forEachDescendant(WritesOrCapturesInPoolM)));
  192. auto MatcherM = decl(anyOf(
  193. objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
  194. functionDecl(HasParamAndWritesInMarkedFuncM),
  195. blockDecl(HasParamAndWritesInMarkedFuncM)));
  196. auto Matches = match(MatcherM, *D, AM.getASTContext());
  197. for (BoundNodes Match : Matches)
  198. emitDiagnostics(Match, D, BR, AM, this);
  199. }
  200. void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
  201. Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
  202. }
  203. bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
  204. return true;
  205. }