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