brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.6 KiB · e7fd14d Raw
241 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines ObjCAutoreleaseWriteChecker which warns against writes10// into autoreleased out parameters which cause crashes.11// An example of a problematic write is a write to @c error in the example12// 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 pool24// in `enumerateObjectsUsingBlock` implementation freeing the error object25// on exit from the function.26//27//===----------------------------------------------------------------------===//28 29#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/BugReporter/CommonBugCategories.h"34#include "clang/StaticAnalyzer/Core/Checker.h"35#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"36#include "llvm/ADT/Twine.h"37 38using namespace clang;39using namespace ento;40using namespace ast_matchers;41 42namespace {43 44const char *ProblematicWriteBind = "problematicwrite";45const char *CapturedBind = "capturedbind";46const char *ParamBind = "parambind";47const char *IsMethodBind = "ismethodbind";48const char *IsARPBind = "isautoreleasepoolbind";49 50class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {51public:52  void checkASTCodeBody(const Decl *D,53                        AnalysisManager &AM,54                        BugReporter &BR) const;55private:56  std::vector<std::string> SelectorsWithAutoreleasingPool = {57      // Common to NSArray,  NSSet, NSOrderedSet58      "enumerateObjectsUsingBlock:",59      "enumerateObjectsWithOptions:usingBlock:",60 61      // Common to NSArray and NSOrderedSet62      "enumerateObjectsAtIndexes:options:usingBlock:",63      "indexOfObjectAtIndexes:options:passingTest:",64      "indexesOfObjectsAtIndexes:options:passingTest:",65      "indexOfObjectPassingTest:",66      "indexOfObjectWithOptions:passingTest:",67      "indexesOfObjectsPassingTest:",68      "indexesOfObjectsWithOptions:passingTest:",69 70      // NSDictionary71      "enumerateKeysAndObjectsUsingBlock:",72      "enumerateKeysAndObjectsWithOptions:usingBlock:",73      "keysOfEntriesPassingTest:",74      "keysOfEntriesWithOptions:passingTest:",75 76      // NSSet77      "objectsPassingTest:",78      "objectsWithOptions:passingTest:",79      "enumerateIndexPathsWithOptions:usingBlock:",80 81      // NSIndexSet82      "enumerateIndexesWithOptions:usingBlock:",83      "enumerateIndexesUsingBlock:",84      "enumerateIndexesInRange:options:usingBlock:",85      "enumerateRangesUsingBlock:",86      "enumerateRangesWithOptions:usingBlock:",87      "enumerateRangesInRange:options:usingBlock:",88      "indexPassingTest:",89      "indexesPassingTest:",90      "indexWithOptions:passingTest:",91      "indexesWithOptions:passingTest:",92      "indexInRange:options:passingTest:",93      "indexesInRange:options:passingTest:"94  };95 96  std::vector<std::string> FunctionsWithAutoreleasingPool = {97      "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};98};99}100 101static inline std::vector<llvm::StringRef>102toRefs(const std::vector<std::string> &V) {103  return std::vector<llvm::StringRef>(V.begin(), V.end());104}105 106static decltype(auto)107callsNames(const std::vector<std::string> &FunctionNames) {108  return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));109}110 111static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,112                            AnalysisManager &AM,113                            const ObjCAutoreleaseWriteChecker *Checker) {114  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);115 116  const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);117  QualType Ty = PVD->getType();118  if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)119    return;120  const char *ActionMsg = "Write to";121  const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);122  bool IsCapture = false;123 124  // Prefer to warn on write, but if not available, warn on capture.125  if (!MarkedStmt) {126    MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);127    assert(MarkedStmt);128    ActionMsg = "Capture of";129    IsCapture = true;130  }131 132  SourceRange Range = MarkedStmt->getSourceRange();133  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(134      MarkedStmt, BR.getSourceManager(), ADC);135 136  bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;137  const char *FunctionDescription = IsMethod ? "method" : "function";138  bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;139 140  llvm::SmallString<128> BugNameBuf;141  llvm::raw_svector_ostream BugName(BugNameBuf);142  BugName << ActionMsg143          << " autoreleasing out parameter inside autorelease pool";144 145  llvm::SmallString<128> BugMessageBuf;146  llvm::raw_svector_ostream BugMessage(BugMessageBuf);147  BugMessage << ActionMsg << " autoreleasing out parameter ";148  if (IsCapture)149    BugMessage << "'" + PVD->getName() + "' ";150 151  BugMessage << "inside ";152  if (IsARP)153    BugMessage << "locally-scoped autorelease pool;";154  else155    BugMessage << "autorelease pool that may exit before "156               << FunctionDescription << " returns;";157 158  BugMessage << " consider writing first to a strong local variable"159                " declared outside ";160  if (IsARP)161    BugMessage << "of the autorelease pool";162  else163    BugMessage << "of the block";164 165  BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),166                     categories::MemoryRefCount, BugMessage.str(), Location,167                     Range);168}169 170void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,171                                                  AnalysisManager &AM,172                                                  BugReporter &BR) const {173 174  auto DoublePointerParamM =175      parmVarDecl(hasType(hasCanonicalType(pointerType(176                      pointee(hasCanonicalType(objcObjectPointerType()))))))177          .bind(ParamBind);178 179  auto ReferencedParamM =180      declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);181 182  // Write into a binded object, e.g. *ParamBind = X.183  auto WritesIntoM = binaryOperator(184    hasLHS(unaryOperator(185        hasOperatorName("*"),186        hasUnaryOperand(187          ignoringParenImpCasts(ReferencedParamM))188    )),189    hasOperatorName("=")190  ).bind(ProblematicWriteBind);191 192  auto ArgumentCaptureM = hasAnyArgument(193    ignoringParenImpCasts(ReferencedParamM));194  auto CapturedInParamM = stmt(anyOf(195      callExpr(ArgumentCaptureM),196      objcMessageExpr(ArgumentCaptureM)));197 198  // WritesIntoM happens inside a block passed as an argument.199  auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(200      hasType(hasCanonicalType(blockPointerType())),201      forEachDescendant(202        stmt(anyOf(WritesIntoM, CapturedInParamM))203      )));204 205  auto BlockPassedToMarkedFuncM = stmt(anyOf(206    callExpr(allOf(207      callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),208    objcMessageExpr(allOf(209       hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),210       WritesOrCapturesInBlockM))211  ));212 213  // WritesIntoM happens inside an explicit @autoreleasepool.214  auto WritesOrCapturesInPoolM =215      autoreleasePoolStmt(216          forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))217          .bind(IsARPBind);218 219  auto HasParamAndWritesInMarkedFuncM =220      allOf(hasAnyParameter(DoublePointerParamM),221            anyOf(forEachDescendant(BlockPassedToMarkedFuncM),222                  forEachDescendant(WritesOrCapturesInPoolM)));223 224  auto MatcherM = decl(anyOf(225      objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),226      functionDecl(HasParamAndWritesInMarkedFuncM),227      blockDecl(HasParamAndWritesInMarkedFuncM)));228 229  auto Matches = match(MatcherM, *D, AM.getASTContext());230  for (BoundNodes Match : Matches)231    emitDiagnostics(Match, D, BR, AM, this);232}233 234void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {235  Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();236}237 238bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {239  return true;240}241