]> granicus.if.org Git - clang/commitdiff
[analyzer] Detect ObjC properties that are both (copy) and Mutable.
authorArtem Dergachev <artem.dergachev@gmail.com>
Tue, 13 Dec 2016 17:19:18 +0000 (17:19 +0000)
committerArtem Dergachev <artem.dergachev@gmail.com>
Tue, 13 Dec 2016 17:19:18 +0000 (17:19 +0000)
When an Objective-C property has a (copy) attribute, the default setter
for this property performs a -copy on the object assigned.

Calling -copy on a mutable NS object such as NSMutableString etc.
produces an immutable object, NSString in our example.
Hence the getter becomes type-incorrect.

rdar://problem/21022397

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

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@289554 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/StaticAnalyzer/Checkers/Checkers.td
lib/StaticAnalyzer/Checkers/CMakeLists.txt
lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp [new file with mode: 0644]
test/Analysis/ObjCPropertiesSyntaxChecks.m [new file with mode: 0644]

index 1b2fd405c88469bd3e36ae22d1400ccc8f4c3059..06514ebca3596dd021e46ac963a3148c2783d00e 100644 (file)
@@ -480,20 +480,21 @@ def CStringNotNullTerm : Checker<"NotNullTerminated">,
 let ParentPackage = OSX in {
 
 def NumberObjectConversionChecker : Checker<"NumberObjectConversion">,
-  InPackage<OSX>,
   HelpText<"Check for erroneous conversions of objects representing numbers into numbers">,
   DescFile<"NumberObjectConversionChecker.cpp">;
 
 def MacOSXAPIChecker : Checker<"API">,
-  InPackage<OSX>,
   HelpText<"Check for proper uses of various Apple APIs">,
   DescFile<"MacOSXAPIChecker.cpp">;
 
 def MacOSKeychainAPIChecker : Checker<"SecKeychainAPI">,
-  InPackage<OSX>,
   HelpText<"Check for proper uses of Secure Keychain APIs">,
   DescFile<"MacOSKeychainAPIChecker.cpp">;
 
+def ObjCPropertyChecker : Checker<"ObjCProperty">,
+  HelpText<"Check for proper uses of Objective-C properties">,
+  DescFile<"ObjCPropertyChecker.cpp">;
+
 } // end "osx"
 
 let ParentPackage = Cocoa in {
index 6996cdb17de6a93ca35775a2a3b6251ed8acf9c4..1038a9685dbdb2dab3682bac9622fa52499a2c99 100644 (file)
@@ -59,6 +59,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   ObjCContainersASTChecker.cpp
   ObjCContainersChecker.cpp
   ObjCMissingSuperCallChecker.cpp
+  ObjCPropertyChecker.cpp
   ObjCSelfInitChecker.cpp
   ObjCSuperDeallocChecker.cpp
   ObjCUnusedIVarsChecker.cpp
diff --git a/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp
new file mode 100644 (file)
index 0000000..b9857e5
--- /dev/null
@@ -0,0 +1,82 @@
+//==- ObjCPropertyChecker.cpp - Check ObjC properties ------------*- C++ -*-==//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This checker finds issues with Objective-C properties.
+//  Currently finds only one kind of issue:
+//  - Find synthesized properties with copy attribute of mutable NS collection
+//    types. Calling -copy on such collections produces an immutable copy,
+//    which contradicts the type of the property.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class ObjCPropertyChecker
+    : public Checker<check::ASTDecl<ObjCPropertyDecl>> {
+  void checkCopyMutable(const ObjCPropertyDecl *D, BugReporter &BR) const;
+
+public:
+  void checkASTDecl(const ObjCPropertyDecl *D, AnalysisManager &Mgr,
+                    BugReporter &BR) const;
+};
+} // end anonymous namespace.
+
+void ObjCPropertyChecker::checkASTDecl(const ObjCPropertyDecl *D,
+                                       AnalysisManager &Mgr,
+                                       BugReporter &BR) const {
+  checkCopyMutable(D, BR);
+}
+
+void ObjCPropertyChecker::checkCopyMutable(const ObjCPropertyDecl *D,
+                                           BugReporter &BR) const {
+  if (D->isReadOnly() || D->getSetterKind() != ObjCPropertyDecl::Copy)
+    return;
+
+  QualType T = D->getType();
+  if (!T->isObjCObjectPointerType())
+    return;
+
+  const std::string &PropTypeName(T->getPointeeType().getCanonicalType()
+                                                     .getUnqualifiedType()
+                                                     .getAsString());
+  if (!StringRef(PropTypeName).startswith("NSMutable"))
+    return;
+
+  const ObjCImplDecl *ImplD = nullptr;
+  if (const ObjCInterfaceDecl *IntD =
+          dyn_cast<ObjCInterfaceDecl>(D->getDeclContext())) {
+    ImplD = IntD->getImplementation();
+  } else {
+    const ObjCCategoryDecl *CatD = cast<ObjCCategoryDecl>(D->getDeclContext());
+    ImplD = CatD->getClassInterface()->getImplementation();
+  }
+
+  if (!ImplD || ImplD->HasUserDeclaredSetterMethod(D))
+    return;
+
+  SmallString<128> Str;
+  llvm::raw_svector_ostream OS(Str);
+  OS << "Property of mutable type '" << PropTypeName
+     << "' has 'copy' attribute; an immutable object will be stored instead";
+
+  BR.EmitBasicReport(
+      D, this, "Objective-C property misuse", "Logic error", OS.str(),
+      PathDiagnosticLocation::createBegin(D, BR.getSourceManager()),
+      D->getSourceRange());
+}
+
+void ento::registerObjCPropertyChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<ObjCPropertyChecker>();
+}
diff --git a/test/Analysis/ObjCPropertiesSyntaxChecks.m b/test/Analysis/ObjCPropertiesSyntaxChecks.m
new file mode 100644 (file)
index 0000000..5c642c5
--- /dev/null
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -w -fblocks -analyze -analyzer-checker=osx.ObjCProperty %s -verify
+
+#include "Inputs/system-header-simulator-objc.h"
+
+@interface I : NSObject {
+  NSMutableString *_mutableExplicitStr;
+  NSMutableString *_trulyMutableStr;
+  NSMutableString *_trulyMutableExplicitStr;
+}
+@property(copy) NSString *str; // no-warning
+@property(copy) NSMutableString *mutableStr; // expected-warning{{Property of mutable type 'NSMutableString' has 'copy' attribute; an immutable object will be stored instead}}
+@property(copy) NSMutableString *mutableExplicitStr; // expected-warning{{Property of mutable type 'NSMutableString' has 'copy' attribute; an immutable object will be stored instead}}
+@property(copy, readonly) NSMutableString *mutableReadonlyStr; // no-warning
+@property(copy, readonly) NSMutableString *mutableReadonlyStrOverriddenInChild; // no-warning
+@property(copy, readonly) NSMutableString *mutableReadonlyStrOverriddenInCategory; // no-warning
+@property(copy) NSMutableString *trulyMutableStr; // no-warning
+@property(copy) NSMutableString *trulyMutableExplicitStr; // no-warning
+@property(copy) NSMutableString *trulyMutableStrWithSynthesizedStorage; // no-warning
+@end
+
+@interface I () {}
+@property(copy) NSMutableString *mutableStrInCategory; // expected-warning{{Property of mutable type 'NSMutableString' has 'copy' attribute; an immutable object will be stored instead}}
+@property (copy, readwrite) NSMutableString *mutableReadonlyStrOverriddenInCategory; // expected-warning{{Property of mutable type 'NSMutableString' has 'copy' attribute; an immutable object will be stored instead}}
+@end
+
+@implementation I
+@synthesize mutableExplicitStr = _mutableExplicitStr;
+- (NSMutableString *)trulyMutableStr {
+  return _trulyMutableStr;
+}
+- (void)setTrulyMutableStr: (NSMutableString *) S {
+  _trulyMutableStr = [S mutableCopy];
+}
+@dynamic trulyMutableExplicitStr;
+- (NSMutableString *)trulyMutableExplicitStr {
+  return _trulyMutableExplicitStr;
+}
+- (void)setTrulyMutableExplicitStr: (NSMutableString *) S {
+  _trulyMutableExplicitStr = [S mutableCopy];
+}
+@synthesize trulyMutableStrWithSynthesizedStorage;
+- (NSMutableString *)trulyMutableStrWithSynthesizedStorage {
+  return trulyMutableStrWithSynthesizedStorage;
+}
+- (void)setTrulyMutableStrWithSynthesizedStorage: (NSMutableString *) S {
+  trulyMutableStrWithSynthesizedStorage = [S mutableCopy];
+}
+@end
+
+@interface J : I {}
+@property (copy, readwrite) NSMutableString *mutableReadonlyStrOverriddenInChild; // expected-warning{{Property of mutable type 'NSMutableString' has 'copy' attribute; an immutable object will be stored instead}}
+@end
+
+@implementation J
+@end
+
+// If we do not see the implementation then we do not want to warn,
+// because we may miss a user-defined setter that works correctly.
+@interface IWithoutImpl : NSObject {}
+@property(copy) NSMutableString *mutableStr; // no-warning
+@end