123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- //
- // This file defines ObjCAutoreleaseWriteChecker which warns against writes
- // into autoreleased out parameters which cause crashes.
- // An example of a problematic write is a write to @c error in the example
- // below:
- //
- // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list {
- // [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
- // NSString *myString = obj;
- // if ([myString isEqualToString:@"error"] && error)
- // *error = [NSError errorWithDomain:@"MyDomain" code:-1];
- // }];
- // return false;
- // }
- //
- // Such code will crash on read from `*error` due to the autorelease pool
- // in `enumerateObjectsUsingBlock` implementation freeing the error object
- // on exit from the function.
- //
- //===----------------------------------------------------------------------===//
- #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
- #include "clang/StaticAnalyzer/Core/Checker.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
- #include "llvm/ADT/Twine.h"
- using namespace clang;
- using namespace ento;
- using namespace ast_matchers;
- namespace {
- const char *ProblematicWriteBind = "problematicwrite";
- const char *CapturedBind = "capturedbind";
- const char *ParamBind = "parambind";
- const char *IsMethodBind = "ismethodbind";
- const char *IsARPBind = "isautoreleasepoolbind";
- class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
- public:
- void checkASTCodeBody(const Decl *D,
- AnalysisManager &AM,
- BugReporter &BR) const;
- private:
- std::vector<std::string> SelectorsWithAutoreleasingPool = {
- // Common to NSArray, NSSet, NSOrderedSet
- "enumerateObjectsUsingBlock:",
- "enumerateObjectsWithOptions:usingBlock:",
- // Common to NSArray and NSOrderedSet
- "enumerateObjectsAtIndexes:options:usingBlock:",
- "indexOfObjectAtIndexes:options:passingTest:",
- "indexesOfObjectsAtIndexes:options:passingTest:",
- "indexOfObjectPassingTest:",
- "indexOfObjectWithOptions:passingTest:",
- "indexesOfObjectsPassingTest:",
- "indexesOfObjectsWithOptions:passingTest:",
- // NSDictionary
- "enumerateKeysAndObjectsUsingBlock:",
- "enumerateKeysAndObjectsWithOptions:usingBlock:",
- "keysOfEntriesPassingTest:",
- "keysOfEntriesWithOptions:passingTest:",
- // NSSet
- "objectsPassingTest:",
- "objectsWithOptions:passingTest:",
- "enumerateIndexPathsWithOptions:usingBlock:",
- // NSIndexSet
- "enumerateIndexesWithOptions:usingBlock:",
- "enumerateIndexesUsingBlock:",
- "enumerateIndexesInRange:options:usingBlock:",
- "enumerateRangesUsingBlock:",
- "enumerateRangesWithOptions:usingBlock:",
- "enumerateRangesInRange:options:usingBlock:",
- "indexPassingTest:",
- "indexesPassingTest:",
- "indexWithOptions:passingTest:",
- "indexesWithOptions:passingTest:",
- "indexInRange:options:passingTest:",
- "indexesInRange:options:passingTest:"
- };
- std::vector<std::string> FunctionsWithAutoreleasingPool = {
- "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
- };
- }
- static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
- return std::vector<llvm::StringRef>(V.begin(), V.end());
- }
- static decltype(auto) callsNames(std::vector<std::string> FunctionNames) {
- return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
- }
- static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
- AnalysisManager &AM,
- const ObjCAutoreleaseWriteChecker *Checker) {
- AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
- const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
- QualType Ty = PVD->getType();
- if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
- return;
- const char *ActionMsg = "Write to";
- const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
- bool IsCapture = false;
- // Prefer to warn on write, but if not available, warn on capture.
- if (!MarkedStmt) {
- MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
- assert(MarkedStmt);
- ActionMsg = "Capture of";
- IsCapture = true;
- }
- SourceRange Range = MarkedStmt->getSourceRange();
- PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
- MarkedStmt, BR.getSourceManager(), ADC);
- bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
- const char *FunctionDescription = IsMethod ? "method" : "function";
- bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
- llvm::SmallString<128> BugNameBuf;
- llvm::raw_svector_ostream BugName(BugNameBuf);
- BugName << ActionMsg
- << " autoreleasing out parameter inside autorelease pool";
- llvm::SmallString<128> BugMessageBuf;
- llvm::raw_svector_ostream BugMessage(BugMessageBuf);
- BugMessage << ActionMsg << " autoreleasing out parameter ";
- if (IsCapture)
- BugMessage << "'" + PVD->getName() + "' ";
- BugMessage << "inside ";
- if (IsARP)
- BugMessage << "locally-scoped autorelease pool;";
- else
- BugMessage << "autorelease pool that may exit before "
- << FunctionDescription << " returns;";
- BugMessage << " consider writing first to a strong local variable"
- " declared outside ";
- if (IsARP)
- BugMessage << "of the autorelease pool";
- else
- BugMessage << "of the block";
- BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
- categories::MemoryRefCount, BugMessage.str(), Location,
- Range);
- }
- void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
- AnalysisManager &AM,
- BugReporter &BR) const {
- auto DoublePointerParamM =
- parmVarDecl(hasType(hasCanonicalType(pointerType(
- pointee(hasCanonicalType(objcObjectPointerType()))))))
- .bind(ParamBind);
- auto ReferencedParamM =
- declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
- // Write into a binded object, e.g. *ParamBind = X.
- auto WritesIntoM = binaryOperator(
- hasLHS(unaryOperator(
- hasOperatorName("*"),
- hasUnaryOperand(
- ignoringParenImpCasts(ReferencedParamM))
- )),
- hasOperatorName("=")
- ).bind(ProblematicWriteBind);
- auto ArgumentCaptureM = hasAnyArgument(
- ignoringParenImpCasts(ReferencedParamM));
- auto CapturedInParamM = stmt(anyOf(
- callExpr(ArgumentCaptureM),
- objcMessageExpr(ArgumentCaptureM)));
- // WritesIntoM happens inside a block passed as an argument.
- auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
- hasType(hasCanonicalType(blockPointerType())),
- forEachDescendant(
- stmt(anyOf(WritesIntoM, CapturedInParamM))
- )));
- auto BlockPassedToMarkedFuncM = stmt(anyOf(
- callExpr(allOf(
- callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
- objcMessageExpr(allOf(
- hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
- WritesOrCapturesInBlockM))
- ));
- // WritesIntoM happens inside an explicit @autoreleasepool.
- auto WritesOrCapturesInPoolM =
- autoreleasePoolStmt(
- forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
- .bind(IsARPBind);
- auto HasParamAndWritesInMarkedFuncM =
- allOf(hasAnyParameter(DoublePointerParamM),
- anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
- forEachDescendant(WritesOrCapturesInPoolM)));
- auto MatcherM = decl(anyOf(
- objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
- functionDecl(HasParamAndWritesInMarkedFuncM),
- blockDecl(HasParamAndWritesInMarkedFuncM)));
- auto Matches = match(MatcherM, *D, AM.getASTContext());
- for (BoundNodes Match : Matches)
- emitDiagnostics(Match, D, BR, AM, this);
- }
- void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
- }
- bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
- return true;
- }
|