ObjCAutoreleaseWriteChecker.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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> toRefs(std::vector<std::string> V) {
  91. return std::vector<llvm::StringRef>(V.begin(), V.end());
  92. }
  93. static decltype(auto) callsNames(std::vector<std::string> FunctionNames) {
  94. return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
  95. }
  96. static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
  97. AnalysisManager &AM,
  98. const ObjCAutoreleaseWriteChecker *Checker) {
  99. AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
  100. const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
  101. QualType Ty = PVD->getType();
  102. if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
  103. return;
  104. const char *ActionMsg = "Write to";
  105. const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
  106. bool IsCapture = false;
  107. // Prefer to warn on write, but if not available, warn on capture.
  108. if (!MarkedStmt) {
  109. MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
  110. assert(MarkedStmt);
  111. ActionMsg = "Capture of";
  112. IsCapture = true;
  113. }
  114. SourceRange Range = MarkedStmt->getSourceRange();
  115. PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
  116. MarkedStmt, BR.getSourceManager(), ADC);
  117. bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
  118. const char *FunctionDescription = IsMethod ? "method" : "function";
  119. bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
  120. llvm::SmallString<128> BugNameBuf;
  121. llvm::raw_svector_ostream BugName(BugNameBuf);
  122. BugName << ActionMsg
  123. << " autoreleasing out parameter inside autorelease pool";
  124. llvm::SmallString<128> BugMessageBuf;
  125. llvm::raw_svector_ostream BugMessage(BugMessageBuf);
  126. BugMessage << ActionMsg << " autoreleasing out parameter ";
  127. if (IsCapture)
  128. BugMessage << "'" + PVD->getName() + "' ";
  129. BugMessage << "inside ";
  130. if (IsARP)
  131. BugMessage << "locally-scoped autorelease pool;";
  132. else
  133. BugMessage << "autorelease pool that may exit before "
  134. << FunctionDescription << " returns;";
  135. BugMessage << " consider writing first to a strong local variable"
  136. " declared outside ";
  137. if (IsARP)
  138. BugMessage << "of the autorelease pool";
  139. else
  140. BugMessage << "of the block";
  141. BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
  142. categories::MemoryRefCount, BugMessage.str(), Location,
  143. Range);
  144. }
  145. void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
  146. AnalysisManager &AM,
  147. BugReporter &BR) const {
  148. auto DoublePointerParamM =
  149. parmVarDecl(hasType(hasCanonicalType(pointerType(
  150. pointee(hasCanonicalType(objcObjectPointerType()))))))
  151. .bind(ParamBind);
  152. auto ReferencedParamM =
  153. declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
  154. // Write into a binded object, e.g. *ParamBind = X.
  155. auto WritesIntoM = binaryOperator(
  156. hasLHS(unaryOperator(
  157. hasOperatorName("*"),
  158. hasUnaryOperand(
  159. ignoringParenImpCasts(ReferencedParamM))
  160. )),
  161. hasOperatorName("=")
  162. ).bind(ProblematicWriteBind);
  163. auto ArgumentCaptureM = hasAnyArgument(
  164. ignoringParenImpCasts(ReferencedParamM));
  165. auto CapturedInParamM = stmt(anyOf(
  166. callExpr(ArgumentCaptureM),
  167. objcMessageExpr(ArgumentCaptureM)));
  168. // WritesIntoM happens inside a block passed as an argument.
  169. auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
  170. hasType(hasCanonicalType(blockPointerType())),
  171. forEachDescendant(
  172. stmt(anyOf(WritesIntoM, CapturedInParamM))
  173. )));
  174. auto BlockPassedToMarkedFuncM = stmt(anyOf(
  175. callExpr(allOf(
  176. callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
  177. objcMessageExpr(allOf(
  178. hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
  179. WritesOrCapturesInBlockM))
  180. ));
  181. // WritesIntoM happens inside an explicit @autoreleasepool.
  182. auto WritesOrCapturesInPoolM =
  183. autoreleasePoolStmt(
  184. forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
  185. .bind(IsARPBind);
  186. auto HasParamAndWritesInMarkedFuncM =
  187. allOf(hasAnyParameter(DoublePointerParamM),
  188. anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
  189. forEachDescendant(WritesOrCapturesInPoolM)));
  190. auto MatcherM = decl(anyOf(
  191. objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
  192. functionDecl(HasParamAndWritesInMarkedFuncM),
  193. blockDecl(HasParamAndWritesInMarkedFuncM)));
  194. auto Matches = match(MatcherM, *D, AM.getASTContext());
  195. for (BoundNodes Match : Matches)
  196. emitDiagnostics(Match, D, BR, AM, this);
  197. }
  198. void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
  199. Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
  200. }
  201. bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
  202. return true;
  203. }