1//==- ObjCPropertyChecker.cpp - Check ObjC properties ------------*- 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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9//  This checker finds issues with Objective-C properties.
10//  Currently finds only one kind of issue:
11//  - Find synthesized properties with copy attribute of mutable NS collection
12//    types. Calling -copy on such collections produces an immutable copy,
13//    which contradicts the type of the property.
14//
15//===----------------------------------------------------------------------===//
16
17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
19#include "clang/StaticAnalyzer/Core/Checker.h"
20
21using namespace clang;
22using namespace ento;
23
24namespace {
25class ObjCPropertyChecker
26    : public Checker<check::ASTDecl<ObjCPropertyDecl>> {
27  void checkCopyMutable(const ObjCPropertyDecl *D, BugReporter &BR) const;
28
29public:
30  void checkASTDecl(const ObjCPropertyDecl *D, AnalysisManager &Mgr,
31                    BugReporter &BR) const;
32};
33} // end anonymous namespace.
34
35void ObjCPropertyChecker::checkASTDecl(const ObjCPropertyDecl *D,
36                                       AnalysisManager &Mgr,
37                                       BugReporter &BR) const {
38  checkCopyMutable(D, BR);
39}
40
41void ObjCPropertyChecker::checkCopyMutable(const ObjCPropertyDecl *D,
42                                           BugReporter &BR) const {
43  if (D->isReadOnly() || D->getSetterKind() != ObjCPropertyDecl::Copy)
44    return;
45
46  QualType T = D->getType();
47  if (!T->isObjCObjectPointerType())
48    return;
49
50  const std::string &PropTypeName(T->getPointeeType().getCanonicalType()
51                                                     .getUnqualifiedType()
52                                                     .getAsString());
53  if (!StringRef(PropTypeName).startswith("NSMutable"))
54    return;
55
56  const ObjCImplDecl *ImplD = nullptr;
57  if (const ObjCInterfaceDecl *IntD =
58          dyn_cast<ObjCInterfaceDecl>(D->getDeclContext())) {
59    ImplD = IntD->getImplementation();
60  } else if (auto *CatD = dyn_cast<ObjCCategoryDecl>(D->getDeclContext())) {
61    ImplD = CatD->getClassInterface()->getImplementation();
62  }
63
64  if (!ImplD || ImplD->HasUserDeclaredSetterMethod(D))
65    return;
66
67  SmallString<128> Str;
68  llvm::raw_svector_ostream OS(Str);
69  OS << "Property of mutable type '" << PropTypeName
70     << "' has 'copy' attribute; an immutable object will be stored instead";
71
72  BR.EmitBasicReport(
73      D, this, "Objective-C property misuse", "Logic error", OS.str(),
74      PathDiagnosticLocation::createBegin(D, BR.getSourceManager()),
75      D->getSourceRange());
76}
77
78void ento::registerObjCPropertyChecker(CheckerManager &Mgr) {
79  Mgr.registerChecker<ObjCPropertyChecker>();
80}
81
82bool ento::shouldRegisterObjCPropertyChecker(const CheckerManager &mgr) {
83  return true;
84}
85