r328827 - [analyzer] Path-insensitive checker for writes into an auto-releasing pointer

George Karpenkov via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 29 13:55:34 PDT 2018


Author: george.karpenkov
Date: Thu Mar 29 13:55:34 2018
New Revision: 328827

URL: http://llvm.org/viewvc/llvm-project?rev=328827&view=rev
Log:
[analyzer] Path-insensitive checker for writes into an auto-releasing pointer

from the wrong auto-releasing pool, as such writes may crash.

rdar://25301111

Differential Revision: https://reviews.llvm.org/D44722

Added:
    cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
    cfe/trunk/test/Analysis/autoreleasewritechecker_test.m
Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
    cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Modified: cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td?rev=328827&r1=328826&r2=328827&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td Thu Mar 29 13:55:34 2018
@@ -616,6 +616,9 @@ def ObjCSuperDeallocChecker : Checker<"S
   HelpText<"Warn about improper use of '[super dealloc]' in Objective-C">,
   DescFile<"ObjCSuperDeallocChecker.cpp">;
 
+def AutoreleaseWriteChecker : Checker<"AutoreleaseWrite">,
+  HelpText<"Warn about potentially crashing writes to autoreleasing objects from different autoreleasing pools in Objective-C">,
+  DescFile<"ObjCAutoreleaseWriteChecker.cpp">;
 } // end "osx.cocoa"
 
 let ParentPackage = Performance in {

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=328827&r1=328826&r2=328827&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Thu Mar 29 13:55:34 2018
@@ -63,6 +63,7 @@ add_clang_library(clangStaticAnalyzerChe
   NullabilityChecker.cpp
   NumberObjectConversionChecker.cpp
   ObjCAtSyncChecker.cpp
+  ObjCAutoreleaseWriteChecker.cpp
   ObjCContainersASTChecker.cpp
   ObjCContainersChecker.cpp
   ObjCMissingSuperCallChecker.cpp

Added: cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp?rev=328827&view=auto
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp (added)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp Thu Mar 29 13:55:34 2018
@@ -0,0 +1,157 @@
+//===- 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 are likely to 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 is very likely to crash due to the other queue autorelease pool
+// begin able to free the error object.
+//
+//===----------------------------------------------------------------------===//
+
+#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 *ParamBind = "parambind";
+const char *MethodBind = "methodbind";
+
+class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
+public:
+  void checkASTCodeBody(const Decl *D,
+                        AnalysisManager &AM,
+                        BugReporter &BR) const;
+private:
+  std::vector<std::string> SelectorsWithAutoreleasingPool = {
+      "enumerateObjectsUsingBlock:",
+      "enumerateKeysAndObjectsUsingBlock:",
+      "enumerateKeysAndObjectsWithOptions:usingBlock:",
+      "enumerateObjectsWithOptions:usingBlock:",
+      "enumerateObjectsAtIndexes:options:usingBlock:",
+      "enumerateIndexesWithOptions:usingBlock:",
+      "enumerateIndexesUsingBlock:",
+      "enumerateIndexesInRange:options:usingBlock:",
+      "enumerateRangesUsingBlock:",
+      "enumerateRangesWithOptions:usingBlock:",
+      "enumerateRangesInRange:options:usingBlock:"
+      "objectWithOptions: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);
+  assert(PVD);
+  QualType Ty = PVD->getType();
+  if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
+    return;
+  const auto *SW = Match.getNodeAs<Expr>(ProblematicWriteBind);
+  bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(MethodBind) != nullptr;
+  const char *Name = IsMethod ? "method" : "function";
+  assert(SW);
+  BR.EmitBasicReport(
+      ADC->getDecl(), Checker,
+      /*Name=*/"Writing into auto-releasing variable from a different queue",
+      /*Category=*/"Memory",
+      (llvm::Twine("Writing into an auto-releasing out parameter inside ") +
+       "autorelease pool that may exit before " + Name + " returns; consider "
+       "writing first to a strong local variable declared outside of the block")
+          .str(),
+      PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
+      SW->getSourceRange());
+}
+
+void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
+                                                  AnalysisManager &AM,
+                                                  BugReporter &BR) const {
+
+  // Write into a binded object, e.g. *ParamBind = X.
+  auto WritesIntoM = binaryOperator(
+    hasLHS(unaryOperator(
+        hasOperatorName("*"),
+        hasUnaryOperand(
+          ignoringParenImpCasts(
+            declRefExpr(to(parmVarDecl(equalsBoundNode(ParamBind))))))
+    )),
+    hasOperatorName("=")
+  ).bind(ProblematicWriteBind);
+
+  // WritesIntoM happens inside a block passed as an argument.
+  auto WritesInBlockM = hasAnyArgument(allOf(
+      hasType(hasCanonicalType(blockPointerType())),
+      forEachDescendant(WritesIntoM)
+      ));
+
+  auto CallsAsyncM = stmt(anyOf(
+    callExpr(allOf(
+      callsNames(FunctionsWithAutoreleasingPool), WritesInBlockM)),
+    objcMessageExpr(allOf(
+       hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
+       WritesInBlockM))
+  ));
+
+  auto DoublePointerParamM =
+      parmVarDecl(hasType(pointerType(
+                      pointee(hasCanonicalType(objcObjectPointerType())))))
+          .bind(ParamBind);
+
+  auto HasParamAndWritesAsyncM = allOf(
+      hasAnyParameter(DoublePointerParamM),
+      forEachDescendant(CallsAsyncM));
+
+  auto MatcherM = decl(anyOf(
+      objcMethodDecl(HasParamAndWritesAsyncM).bind(MethodBind),
+      functionDecl(HasParamAndWritesAsyncM)));
+
+  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>();
+}

Added: cfe/trunk/test/Analysis/autoreleasewritechecker_test.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/autoreleasewritechecker_test.m?rev=328827&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/autoreleasewritechecker_test.m (added)
+++ cfe/trunk/test/Analysis/autoreleasewritechecker_test.m Thu Mar 29 13:55:34 2018
@@ -0,0 +1,171 @@
+// RUN: %clang_analyze_cc1 -DARC -fobjc-arc -analyzer-checker=core,osx.cocoa.AutoreleaseWrite %s -fblocks -verify
+
+
+typedef signed char BOOL;
+ at protocol NSObject  - (BOOL)isEqual:(id)object; @end
+ at interface NSObject <NSObject> {}
++(id)alloc;
+-(id)init;
+-(id)autorelease;
+-(id)copy;
+-(id)retain;
+ at end
+typedef int NSZone;
+typedef int NSCoder;
+ at protocol NSCopying  - (id)copyWithZone:(NSZone *)zone; @end
+ at protocol NSCoding  - (void)encodeWithCoder:(NSCoder *)aCoder; @end
+ at interface NSError : NSObject <NSCopying, NSCoding> {}
++ (id)errorWithDomain:(int)domain;
+ at end
+
+typedef int dispatch_semaphore_t;
+typedef void (^block_t)();
+
+ at interface NSArray
+- (void) enumerateObjectsUsingBlock:(block_t)block;
+ at end
+
+typedef int group_t;
+typedef struct dispatch_queue_s *dispatch_queue_t;
+typedef void (^dispatch_block_t)(void);
+extern dispatch_queue_t queue;
+
+void dispatch_group_async(dispatch_queue_t queue,
+                          group_t group,
+                          dispatch_block_t block);
+void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
+dispatch_semaphore_t dispatch_semaphore_create(int);
+
+void dispatch_semaphore_wait(dispatch_semaphore_t, int);
+void dispatch_semaphore_signal(dispatch_semaphore_t);
+
+// No warnings without ARC.
+#ifdef NOARC
+
+// expected-no-diagnostics
+BOOL writeToErrorWithIterator(NSError ** error, NSArray *a) {
+  [a enumerateObjectsUsingBlock:^{
+    *error = [NSError errorWithDomain:1]; // no-warning
+    }];
+  return 0;
+}
+#endif
+
+#ifdef ARC
+ at interface I : NSObject
+- (BOOL) writeToStrongErrorInBlock:(NSError *__strong *)error;
+- (BOOL) writeToErrorInBlock:(NSError *__autoreleasing *)error;
+- (BOOL) writeToLocalErrorInBlock:(NSError **)error;
+- (BOOL) writeToErrorInBlockMultipleTimes:(NSError *__autoreleasing *)error;
+- (BOOL) writeToError:(NSError *__autoreleasing *)error;
+- (BOOL) writeToErrorWithDispatchGroup:(NSError *__autoreleasing *)error;
+ at end
+
+ at implementation I
+
+- (BOOL) writeToErrorInBlock:(NSError *__autoreleasing *)error {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_async(queue, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out parameter inside autorelease pool that may exit before method returns}}
+        }
+        dispatch_semaphore_signal(sem);
+    });
+
+    dispatch_semaphore_wait(sem, 100);
+    return 0;
+}
+
+- (BOOL) writeToErrorWithDispatchGroup:(NSError *__autoreleasing *)error {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_group_async(queue, 0, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out}}
+        }
+        dispatch_semaphore_signal(sem);
+    });
+
+    dispatch_semaphore_wait(sem, 100);
+    return 0;
+}
+
+- (BOOL) writeToLocalErrorInBlock:(NSError *__autoreleasing *)error {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_async(queue, ^{
+        NSError* error2;
+        NSError*__strong* error3 = &error2;
+        if (error) {
+            *error3 = [NSError errorWithDomain:1]; // no-warning
+        }
+        dispatch_semaphore_signal(sem);
+    });
+
+    dispatch_semaphore_wait(sem, 100);
+    return 0;
+}
+
+- (BOOL) writeToStrongErrorInBlock:(NSError *__strong *)error {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_async(queue, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:2]; // no-warning
+        }
+        dispatch_semaphore_signal(sem);
+    });
+
+    dispatch_semaphore_wait(sem, 100);
+    return 0;
+}
+
+- (BOOL) writeToErrorInBlockMultipleTimes:(NSError *__autoreleasing *)error {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_async(queue, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out}}
+        }
+        dispatch_semaphore_signal(sem);
+    });
+    dispatch_async(queue, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out}}
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out}}
+        }
+        dispatch_semaphore_signal(sem);
+    });
+    *error = [NSError errorWithDomain:1]; // no-warning
+
+    dispatch_semaphore_wait(sem, 100);
+    return 0;
+}
+
+- (BOOL) writeToError:(NSError *__autoreleasing *)error {
+    *error = [NSError errorWithDomain:1]; // no-warning
+    return 0;
+}
+ at end
+
+BOOL writeToErrorInBlockFromCFunc(NSError *__autoreleasing* error) {
+    dispatch_semaphore_t sem = dispatch_semaphore_create(0l);
+    dispatch_async(queue, ^{
+        if (error) {
+            *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out}}
+        }
+        dispatch_semaphore_signal(sem);
+    });
+
+    dispatch_semaphore_wait(sem, 100);
+  return 0;
+}
+
+BOOL writeToErrorNoWarning(NSError *__autoreleasing* error) {
+  *error = [NSError errorWithDomain:1]; // no-warning
+  return 0;
+}
+
+BOOL writeToErrorWithIterator(NSError *__autoreleasing* error, NSArray *a) {
+  [a enumerateObjectsUsingBlock:^{
+    *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing out parameter inside autorelease pool that may exit before function returns; consider writing first to a strong local variable declared outside of the block}}
+    }];
+  return 0;
+}
+#endif




More information about the cfe-commits mailing list