aboutsummaryrefslogtreecommitdiff
path: root/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp')
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp209
1 files changed, 209 insertions, 0 deletions
diff --git a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
new file mode 100644
index 000000000000..81bcda51b8f8
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
@@ -0,0 +1,209 @@
+//===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// 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 {@code 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 "ClangSACheckers.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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";
+
+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 auto callsNames(std::vector<std::string> FunctionNames)
+ -> decltype(callee(functionDecl())) {
+ 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 *Name = IsMethod ? "method" : "function";
+
+ BR.EmitBasicReport(
+ ADC->getDecl(), Checker,
+ /*Name=*/(llvm::Twine(ActionMsg)
+ + " autoreleasing out parameter inside autorelease pool").str(),
+ /*Category=*/"Memory",
+ (llvm::Twine(ActionMsg) + " autoreleasing out parameter " +
+ (IsCapture ? "'" + PVD->getName() + "'" + " " : "") + "inside " +
+ "autorelease pool that may exit before " + Name + " returns; consider "
+ "writing first to a strong local variable declared outside of the block")
+ .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))
+ ));
+
+ auto HasParamAndWritesInMarkedFuncM = allOf(
+ hasAnyParameter(DoublePointerParamM),
+ forEachDescendant(BlockPassedToMarkedFuncM));
+
+ 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>();
+}