Dushistov created this revision.
Dushistov added reviewers: dcoughlin, Ayal, xazax.hun, zaks.anna.
Dushistov added a subscriber: cfe-commits.

In Qt 4/5 it is possible connect classes methods in such way:

connect(ObjectPointer1, SIGNAL(methodOfObject1()), ObjectPointer2, 
SLOT(methodOfObject2());

and when you call  this method -> `ObjectPointer1->methodOfObject1()`  method 
of `ObjectPointer2->methodOfObject2()` will be called automatically.

The only problem that you can check of correctness of  method name and its 
argument only
at runtime (more details can be found here 
http://doc.qt.io/qt-4.8/signalsandslots.html).

So I implement this checker to help re factoring large Qt based projects.

Note: Qt 4 have only such method to connect signals and slots.
 Qt 5 uses two methods, described  above and based on function pointers, but 
because of SIGNAL/SLOT syntax with disadvantages
have also advantages (see http://doc.qt.io/qt-5/signalsandslots-syntaxes.html) 
sometime Qt5 also
have not compile time checked signal/slot connection.

What this checker do exactly:
1)It checks that such methods with such signatures exists in both classes
2)It checks that slot subset of parameters match the first parameters of signal
3)It checks that signature of method written in proper way (normalized), this 
improve performance,
see 
https://marcmutz.wordpress.com/effective-qt/prefer-to-use-normalised-signalslot-signatures/
 and http://doc.qt.io/qt-5/qmetaobject.html#normalizedSignature

http://reviews.llvm.org/D14592

Files:
  lib/StaticAnalyzer/Checkers/CMakeLists.txt
  lib/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/QtSignalSlotChecker.cpp
  test/Analysis/qt_connect.cpp
  unittests/StaticAnalyzer/CMakeLists.txt
  unittests/StaticAnalyzer/QtSignalSlotCheckerTest.cpp

Index: unittests/StaticAnalyzer/QtSignalSlotCheckerTest.cpp
===================================================================
--- /dev/null
+++ unittests/StaticAnalyzer/QtSignalSlotCheckerTest.cpp
@@ -0,0 +1,37 @@
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace ento {
+
+extern std::string qtNormalizeSignature(const llvm::StringRef &Method);
+
+TEST(QtSignalSlotChecker, QMetaObjectNormalizedSignature) {
+  const std::pair<const char *, const char *> NormConv[] = {
+    {
+      "a(const int&, int const&, const int *, int const *, int * const, const int * const, int &, int *)",
+     "a(int,int,const int*,const int*,int*const,int*const,int&,int*)"
+    },
+    {
+      "b(const unsigned&, const unsigned long&, unsigned char, std::vector<int>&, std::vector<int> const&, unsigned short, short)",
+      "b(uint,ulong,unsigned char,std::vector<int>&,std::vector<int>,unsigned short,short)"
+    },
+    {
+      "c(const int * const * const)",
+      "c(int*const*const)"
+    },
+    {
+      "d(class QString&, enum QScriptEngine::QObjectWrapOption, struct QString*)",
+      "d(QString&,QScriptEngine::QObjectWrapOption,QString*)"
+    },
+    {
+	    "a(int const a, const int& b)",
+	    "a(int a,int&b)"
+    }
+  };
+  for (auto&& InOut : NormConv) {
+    EXPECT_EQ(InOut.second, qtNormalizeSignature(InOut.first));
+  }
+}
+} // end namespace ento
+} // end namespace clang
Index: unittests/StaticAnalyzer/CMakeLists.txt
===================================================================
--- unittests/StaticAnalyzer/CMakeLists.txt
+++ unittests/StaticAnalyzer/CMakeLists.txt
@@ -4,10 +4,12 @@
 
 add_clang_unittest(StaticAnalysisTests
   AnalyzerOptionsTest.cpp
+  QtSignalSlotCheckerTest.cpp
   )
 
 target_link_libraries(StaticAnalysisTests
   clangBasic
   clangAnalysis
-  clangStaticAnalyzerCore 
+  clangStaticAnalyzerCore
+  clangStaticAnalyzerCheckers
   )
Index: test/Analysis/qt_connect.cpp
===================================================================
--- /dev/null
+++ test/Analysis/qt_connect.cpp
@@ -0,0 +1,82 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,cplusplus -analyzer-store=region -verify %s
+
+const char *qFlagLocation(const char *method);
+#define Q_OBJECT
+#define QTOSTRING_HELPER(s) #s
+#define QTOSTRING(s) QTOSTRING_HELPER(s)
+# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
+# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
+# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
+
+#   define slots
+#   define signals protected
+
+namespace Qt {
+    enum ConnectionType {
+        AutoConnection,
+        DirectConnection,
+        QueuedConnection,
+        AutoCompatConnection,
+        BlockingQueuedConnection,
+        UniqueConnection =  0x80
+    };
+}
+struct QString {};
+class QObject {
+public:
+  QObject() {}
+  static bool connect(const QObject *sender, const char *signal,
+                      const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
+};
+
+class QWidget : public QObject {
+public:
+  QWidget() {}
+};
+
+class Sender : public QWidget {
+	Q_OBJECT
+signals:
+	void empty();
+  void looksEmpty(bool = false);
+	void stringRef(const QString&);
+	void stringMutRef(QString&);
+	void f2(int, double*);
+	void f3(int, double*,char);
+	void f4(int, double*,QString);
+public:
+	Sender() {}
+};
+
+class Recv : public QWidget {
+	Q_OBJECT
+public slots:
+	void onEmpty() {}
+	void onStringRef(const QString&) {}
+	void onStringMutRef(QString&) {}
+	void onf2(int, double*) {}
+	void onf3(int, double*,char) {}
+public:
+	Recv() {}
+};
+
+
+class TestCase : public QWidget {
+public:
+	TestCase() {
+		connect(&send, SIGNAL(empty()), &recv, SLOT(onEmpty()));
+		connect(&send, SIGNAL(stringRef(const QString &)), &recv, SLOT(onStringRef(const QString &))); // expected-warning{{consider use normalized variant}}
+		connect(&send, SIGNAL(stringMutRef(QString &)), &recv, SLOT(onStringRef(const QString &))); // expected-warning{{consider use normalized variant}}
+		connect(&send, SIGNAL(stringMutRef(QString &)), &recv, SLOT(onStringMutRef(QString&))); // expected-warning{{consider use normalized variant}}
+		connect(&send, SIGNAL(f2(int, double*)), &recv, SLOT(onf2(int, double*))); // expected-warning{{consider use normalized variant}}
+		connect(&send, SIGNAL(f2(int, double*)), &recv, SLOT(bugaga(int, double*))); // expected-warning{{Can not find such signal}}
+		connect(&send, SIGNAL(f2(int, double*)), &recv, SLOT(onf3(int, double*,char))); // expected-warning{{signal/slot signature mismatch}}
+		connect(&send, SIGNAL(f3(int,double*,char)), &recv, SLOT(onf2(int,double*))); // no-warning
+		connect(&send, SIGNAL(bugaga()), &recv, SLOT(onEmpty()));// expected-warning{{Can not find such signal 'bugaga()'}}
+    connect(&send, SIGNAL(looksEmpty()), &recv, SLOT(onEmpty())); // no-warning
+    connect(&send, SIGNAL(looksEmpty(bool)), &recv, SLOT(onEmpty())); // no-warning
+	}
+private:
+	Sender send;
+	Recv recv;
+};
Index: lib/StaticAnalyzer/Checkers/QtSignalSlotChecker.cpp
===================================================================
--- /dev/null
+++ lib/StaticAnalyzer/Checkers/QtSignalSlotChecker.cpp
@@ -0,0 +1,545 @@
+//=== QtSignalSlotChecker.cpp - A Qt signal/slots usage checker ----*- 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 Qt signal/slot checker, which check that such call
+// QObject::connect(obj1, SIGNAL(singnal(T1, T2)), obj2, SLOT(slot(U1, U2)));
+// used in right way, signal,slots exists, they signatures matches etc.
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace clang {
+namespace ento {
+//make it extern for unit testing
+extern std::string qtNormalizeSignature(const StringRef &);
+} // end namespace ento
+} // end namespace clang
+
+namespace {
+class QtSignalSlotChecker final : public Checker<check::PostCall> {
+public:
+  using DetailsReporterT = std::function<void(llvm::raw_svector_ostream &)>;
+
+  QtSignalSlotChecker() {}
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void reportBug(CheckerContext &C, const char *Title,
+                 const DetailsReporterT &DetailsReporter) const;
+
+private:
+  mutable std::unique_ptr<BuiltinBug> BT;
+};
+} // end anonymous namespace
+
+static bool findSuitableMethod(
+    const CXXRecordDecl *Class,
+    const std::function<bool(const CXXMethodDecl &)> &MethodHandler) {
+  assert(!!MethodHandler);
+
+  if (Class == nullptr)
+    return false;
+
+  for (auto &&M : Class->methods())
+    if (M != nullptr && MethodHandler(*M))
+      return true;
+
+  for (const CXXBaseSpecifier &BC : Class->bases()) {
+    QualType T = BC.getType();
+    const CXXRecordDecl *CT = T.getTypePtr()->getAsCXXRecordDecl();
+    if (CT != nullptr) {
+      if (findSuitableMethod(CT, MethodHandler))
+        return true;
+    }
+  }
+  return false;
+}
+
+static void printMethodNameWithPramaTypes(llvm::raw_svector_ostream &Out,
+                                          const CheckerContext &C,
+                                          const StringRef &MName,
+                                          const CXXMethodDecl &M,
+                                          bool SkipDefArgs) {
+  Out << MName << "(";
+  bool F = false;
+  for (auto It = M.param_begin(), End = M.param_end(); It != End; ++It) {
+    if (SkipDefArgs && (*It)->hasDefaultArg())
+      continue;
+    QualType ParamType = (*It)->getType();
+    if (F)
+      Out << ",";
+    F = true;
+    Out << ParamType.stream(C.getLangOpts());
+  }
+  Out << ")";
+}
+
+static bool
+isMethodWithSignatureExists(CheckerContext &C, const CXXRecordDecl *Class,
+                            const StringRef &MethodName,
+                            const std::string &MethodWithParamsNorm) {
+  return (Class == nullptr)
+             ? false
+             : findSuitableMethod(
+                   Class, [&MethodName, &C,
+                           &MethodWithParamsNorm](const CXXMethodDecl &M) {
+                     const IdentifierInfo *II = M.getIdentifier();
+                     if (II == nullptr)
+                       return false;
+                     StringRef FName = II->getName();
+                     if (FName == MethodName) {
+                       SmallString<128> buf;
+                       {
+                         llvm::raw_svector_ostream Out(buf);
+                         printMethodNameWithPramaTypes(Out, C, FName, M, false);
+                         const std::string NS = qtNormalizeSignature(Out.str());
+                         if (NS == MethodWithParamsNorm)
+                           return true;
+                       }
+                       buf.clear();
+                       {
+                         llvm::raw_svector_ostream Out(buf);
+                         printMethodNameWithPramaTypes(Out, C, FName, M, true);
+                         const std::string NS = qtNormalizeSignature(Out.str());
+                         if (NS == MethodWithParamsNorm)
+                           return true;
+                       }
+                     }
+                     return false;
+                   });
+}
+
+static StringRef getStringLiteral(const Stmt *St) {
+  StringRef Res;
+
+  if (St == nullptr)
+    return Res;
+
+  if (const StringLiteral *S = dyn_cast<StringLiteral>(St)) {
+    Res = S->getString();
+    return Res;
+  }
+  for (const Stmt *SubStmt : St->children()) {
+    if (const StringLiteral *S = dyn_cast<StringLiteral>(SubStmt)) {
+      Res = S->getString();
+      break;
+    } else if (SubStmt != nullptr) {
+      Res = getStringLiteral(SubStmt);
+      if (!Res.empty())
+        break;
+    }
+  }
+  return Res;
+}
+
+void QtSignalSlotChecker::reportBug(
+    CheckerContext &C, const char *Title,
+    const DetailsReporterT &DetailsReporter) const {
+  assert(Title != nullptr);
+  assert(!!DetailsReporter);
+  if (ExplodedNode *N = C.generateNonFatalErrorNode(C.getState())) {
+    if (!BT)
+      BT.reset(new BuiltinBug(this, Title));
+
+    SmallString<256> buf;
+    llvm::raw_svector_ostream os(buf);
+    DetailsReporter(os);
+    auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N);
+    C.emitReport(std::move(Report));
+  }
+}
+
+void QtSignalSlotChecker::checkPostCall(const CallEvent &Call,
+                                        CheckerContext &C) const {
+  const SimpleFunctionCall *SC = dyn_cast<SimpleFunctionCall>(&Call);
+  if (SC == nullptr)
+    return;
+
+  const FunctionDecl *FD = SC->getDecl();
+  if (FD == nullptr)
+    return;
+
+  const IdentifierInfo *II = FD->getIdentifier();
+  if (II == nullptr)
+    return;
+
+  StringRef FName = II->getName();
+
+  if (!FName.endswith("connect") ||
+      FD->getQualifiedNameAsString() != "QObject::connect")
+    return;
+
+  const unsigned nargs = SC->getNumArgs();
+  if (nargs != 5)
+    return;
+
+  const Expr *Sender = SC->getArgExpr(0);
+  const Expr *Receiver = SC->getArgExpr(2);
+
+  const Expr *Signal = SC->getArgExpr(1);
+  assert(Signal != nullptr);
+  const Expr *Func = SC->getArgExpr(3);
+  assert(Func != nullptr);
+
+  auto isConstCharPtr = [](const Expr &Expr) {
+    QualType T = Expr.getType();
+    if (!T.getTypePtr()->isPointerType())
+      return false;
+    T = T.getTypePtr()->getPointeeType();
+    return T.getTypePtr()->isCharType() && T.isConstQualified();
+  };
+
+  if (!isConstCharPtr(*Signal) || !isConstCharPtr(*Func))
+    return;
+
+  auto ExtractSig = [](StringRef FM) { return FM.substr(0, FM.find('\0')); };
+
+  const StringRef SignalSign = ExtractSig(getStringLiteral(Signal));
+  const StringRef FuncSign = ExtractSig(getStringLiteral(Func));
+
+  if (SignalSign.empty() || FuncSign.empty()) {
+    // some of connect argument are variables, not supported yet
+    return;
+  }
+
+  const std::string NormSignalSig = qtNormalizeSignature(SignalSign.substr(1));
+  const std::string NormFuncSig = qtNormalizeSignature(FuncSign.substr(1));
+
+  auto getFuncName = [](const StringRef &Str) {
+    StringRef Res;
+    size_t pos = Str.find('(', 1);
+    if (pos == StringRef::npos)
+      return Res;
+    Res = Str.substr(1, pos - 1);
+    return Res;
+  };
+
+  const StringRef SignalName = getFuncName(SignalSign);
+  const StringRef FuncName = getFuncName(FuncSign);
+  assert(!SignalName.empty());
+  assert(!FuncName.empty());
+
+  auto SenderClass = Sender->getBestDynamicClassType();
+  auto RecvClass = Receiver->getBestDynamicClassType();
+
+  const bool FindResSig =
+      isMethodWithSignatureExists(C, SenderClass, SignalName, NormSignalSig);
+  const bool FindResRecv =
+      isMethodWithSignatureExists(C, RecvClass, FuncName, NormFuncSig);
+
+  const bool SignalSigWarn = NormSignalSig != SignalSign.substr(1);
+  const bool FuncSigWarn = NormFuncSig != FuncSign.substr(1);
+
+  auto getParamPack = [](const std::string &NormSig) {
+    StringRef Res;
+    const size_t B = NormSig.find('(', 1);
+    assert(B != StringRef::npos);
+    const size_t E = NormSig.find(')', B);
+    assert(E != StringRef::npos);
+    if ((E - B) == 1)
+      return Res;
+    Res = NormSig;
+    Res = Res.substr(B + 1, E - B - 1);
+    return Res;
+  };
+
+  const StringRef SigParamPack = getParamPack(NormSignalSig);
+  const StringRef FuncParamPack = getParamPack(NormFuncSig);
+
+  bool Match = false;
+  if (FuncParamPack.empty() || SigParamPack == FuncParamPack) {
+    Match = true;
+  } else if (SigParamPack.startswith(FuncParamPack) &&
+             SigParamPack[FuncParamPack.size()] == ',') {
+    Match = true;
+  }
+
+  const char *Title = "QObject::connect warnings";
+
+  // move more important warning to title
+  if (!Match)
+    Title = "QObject::connect signautre mismatch";
+  else if (!FindResSig || !FindResRecv)
+    Title = "QObject::connect can not find signal or method";
+
+  if (SignalSigWarn || FuncSigWarn || !FindResSig || !FindResRecv || !Match) {
+    reportBug(C, Title, [SignalSigWarn, FuncSigWarn, FindResSig, FindResRecv,
+                         Match, SenderClass, RecvClass, &NormSignalSig,
+                         &SignalSign, &NormFuncSig,
+                         &FuncSign](llvm::raw_svector_ostream &os) {
+      bool NeedSemi = false;
+      if (!FindResSig) {
+        NeedSemi = true;
+        os << "Can not find such signal '" << SignalSign.substr(1)
+           << "' in class " << SenderClass->getQualifiedNameAsString();
+      }
+      if (!FindResRecv) {
+        os << (NeedSemi ? "; " : "") << "Can not find such signal '"
+           << FuncSign.substr(1) << "' in class "
+           << RecvClass->getQualifiedNameAsString();
+        NeedSemi = true;
+      }
+      if (!Match) {
+        os << (NeedSemi ? "; " : "") << "signal/slot signature mismatch";
+        NeedSemi = true;
+      }
+      if (SignalSigWarn) {
+        os << (NeedSemi ? "; " : "") << "You use for as signal signature: '"
+           << SignalSign.substr(1) << "', consider use normalized variant: '"
+           << NormSignalSig << "', this improve performance";
+        NeedSemi = true;
+      }
+      if (FuncSigWarn) {
+        os << (NeedSemi ? "; " : "") << "You use for as signal signature: '"
+           << FuncSign.substr(1) << "', consider use normalized variant: '"
+           << NormFuncSig << "', this improve performance";
+      }
+    });
+  }
+}
+
+static inline bool qtSigIsIdentChar(char C) {
+  return (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
+         (C >= '0' && C <= '9') || C == '_';
+}
+
+static inline bool qtSigIsSpace(char C) { return C == ' ' || C == '\t'; }
+
+static inline bool isRestOfArrayContains(const char *s1, const char *s2,
+                                         size_t n) {
+  return std::strncmp(s1, s2, n) == 0;
+}
+
+static std::string doQtNormalizeType(const char *t, const char *e,
+                                     bool adjustConst = true) {
+  static const char CONST[] = "const ";
+  int len = e - t;
+  /*
+    Convert 'char const *' into 'const char *'. Start at index 1,
+    not 0, because 'const char *' is already OK.
+  */
+  SmallVector<char, 256> constbuf;
+  for (int i = 1; i < len; i++) {
+    if (t[i] == 'c' && isRestOfArrayContains("onst", t + i + 1, 4) &&
+        (i + 5 >= len || !qtSigIsIdentChar(t[i + 5])) &&
+        !qtSigIsIdentChar(t[i - 1])) {
+      constbuf.append(t, t + len);
+      if (qtSigIsSpace(t[i - 1]))
+        constbuf.erase(constbuf.begin() + i - 1, constbuf.begin() + i - 1 + 6);
+      else
+        constbuf.erase(constbuf.begin() + i, constbuf.begin() + i + 5);
+      constbuf.insert(constbuf.begin(), CONST, CONST + sizeof(CONST) - 1);
+      t = constbuf.data();
+      e = constbuf.data() + constbuf.size();
+      break;
+    }
+    /*
+      We musn't convert 'char * const *' into 'const char **'
+      and we must beware of 'Bar<const Bla>'.
+    */
+    if (t[i] == '&' || t[i] == '*' || t[i] == '<')
+      break;
+  }
+  if (adjustConst && e > t + 6 && isRestOfArrayContains("const ", t, 6)) {
+    if (*(e - 1) == '&') { // treat const reference as value
+      t += 6;
+      --e;
+    } else if (qtSigIsIdentChar(*(e - 1)) ||
+               *(e - 1) == '>') { // treat const value as value
+      t += 6;
+    }
+  }
+  std::string Result;
+  Result.reserve(len);
+
+  // consume initial 'const '
+  if (isRestOfArrayContains("const ", t, 6)) {
+    t += 6;
+    Result += "const ";
+  }
+
+  // some type substitutions for 'unsigned x'
+  if (isRestOfArrayContains("unsigned", t, 8)) {
+    // make sure "unsigned" is an isolated word before making substitutions
+    if (!t[8] || !qtSigIsIdentChar(t[8])) {
+      if (isRestOfArrayContains(" int", t + 8, 4)) {
+        t += 8 + 4;
+        Result += "uint";
+      } else if (isRestOfArrayContains(" long", t + 8, 5)) {
+        if ((strlen(t + 8 + 5) < 4 ||
+             !isRestOfArrayContains(t + 8 + 5, " int",
+                                    4)) // preserve '[unsigned] long int'
+            && (strlen(t + 8 + 5) < 5 ||
+                !isRestOfArrayContains(t + 8 + 5, " long",
+                                       5)) // preserve '[unsigned] long long'
+            ) {
+          t += 8 + 5;
+          Result += "ulong";
+        }
+      } else if (!isRestOfArrayContains(" short", t + 8,
+                                        6) // preserve unsigned short
+                 &&
+                 !isRestOfArrayContains(" char", t + 8,
+                                        5)) { // preserve unsigned char
+        //  treat rest (unsigned) as uint
+        t += 8;
+        Result += "uint";
+      }
+    }
+  } else {
+    // discard 'struct', 'class', and 'enum'; they are optional
+    // and we don't want them in the normalized signature
+    struct {
+      const char *keyword;
+      int len;
+    } optional[] = {{"struct ", 7}, {"class ", 6}, {"enum ", 5}, {0, 0}};
+    int i = 0;
+    do {
+      if (isRestOfArrayContains(optional[i].keyword, t, optional[i].len)) {
+        t += optional[i].len;
+        break;
+      }
+    } while (optional[++i].keyword != 0);
+  }
+
+  bool star = false;
+  while (t != e) {
+    char c = *t++;
+    star = star || c == '*';
+    Result += c;
+    if (c == '<') {
+      // template recursion
+      const char *tt = t;
+      int templdepth = 1;
+      while (t != e) {
+        c = *t++;
+        if (c == '<')
+          ++templdepth;
+        if (c == '>')
+          --templdepth;
+        if (templdepth == 0 || (templdepth == 1 && c == ',')) {
+          Result += doQtNormalizeType(tt, t - 1, false);
+          Result += c;
+          if (templdepth == 0) {
+            if (*t == '>')
+              Result += ' '; // avoid >>
+            break;
+          }
+          tt = t;
+        }
+      }
+    }
+
+    // cv qualifers can appear after the type as well
+    if (!qtSigIsIdentChar(c) && t != e &&
+        (e - t >= 5 && isRestOfArrayContains("const", t, 5)) &&
+        (e - t == 5 || !qtSigIsIdentChar(t[5]))) {
+      t += 5;
+      while (t != e && qtSigIsSpace(*t))
+        ++t;
+      if (adjustConst && t != e && *t == '&') {
+        // treat const ref as value
+        ++t;
+      } else if (adjustConst && !star) {
+        // treat const as value
+      } else if (!star) {
+        // move const to the front (but not if const comes after a *)
+        Result.insert(0, "const ");
+      } else {
+        // keep const after a *
+        Result += "const";
+      }
+    }
+  }
+
+  return Result;
+}
+
+template <typename Container>
+static void qtTrimSignature(const StringRef &M, Container &Res) {
+  auto It = M.begin();
+  auto E = M.end();
+  char last = 0;
+  while (It != E && qtSigIsSpace(*It))
+    ++It;
+  while (It != E) {
+    while (It != E && !qtSigIsSpace(*It)) {
+      Res.push_back(last = *It);
+      ++It;
+    }
+    while (It != E && qtSigIsSpace(*It))
+      ++It;
+    if (It != E && ((qtSigIsIdentChar(*It) && qtSigIsIdentChar(last)) ||
+                    ((*It == ':') && (last == '<')))) {
+      Res.push_back(last = ' ');
+    }
+  }
+  Res.push_back('\0');
+}
+
+static char *qtNormalizeType(char *d, int &TemplateDepth, std::string &result) {
+  const char *t = d;
+  while (*d && (TemplateDepth != 0 || (*d != ',' && *d != ')'))) {
+    if (*d == '<')
+      ++TemplateDepth;
+    else if (*d == '>')
+      --TemplateDepth;
+    ++d;
+  }
+  if (!isRestOfArrayContains("void", t, d - t))
+    result += doQtNormalizeType(t, d);
+
+  return d;
+}
+
+std::string ento::qtNormalizeSignature(const StringRef &Method) {
+  std::string Res;
+  if (Method.empty())
+    return Res;
+
+  SmallVector<char, 256> TrimmedM;
+  qtTrimSignature(Method, TrimmedM);
+
+  Res.reserve(Method.size());
+
+  int BraceDepth = 0;
+  int TemplateDepth = 0;
+  char *d = &TrimmedM[0];
+  while (*d) {
+    if (BraceDepth == 1) {
+      d = qtNormalizeType(d, TemplateDepth, Res);
+      if (!*d) // most likely an invalid signature.
+        break;
+    }
+    if (*d == '(')
+      ++BraceDepth;
+    if (*d == ')')
+      --BraceDepth;
+    Res += *d++;
+  }
+
+  return Res;
+}
+
+void ento::registerQtSignalSlotChecker(CheckerManager &mgr) {
+  mgr.registerChecker<QtSignalSlotChecker>();
+}
Index: lib/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- lib/StaticAnalyzer/Checkers/Checkers.td
+++ lib/StaticAnalyzer/Checkers/Checkers.td
@@ -243,6 +243,10 @@
   HelpText<"Check for memory leaks. Traces memory managed by new/delete.">,
   DescFile<"MallocChecker.cpp">;
 
+def QtSignalSlotChecker : Checker<"QtSignalSlotChecker">,
+  HelpText<"Check for proper usage of Qt connect">,
+  DescFile<"QtSignalSlotChecker.cpp">;
+
 } // end: "cplusplus"
 
 let ParentPackage = CplusplusAlpha in {
Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -79,6 +79,7 @@
   VforkChecker.cpp
   VLASizeChecker.cpp
   VirtualCallChecker.cpp
+  QtSignalSlotChecker.cpp
 
   DEPENDS
   ClangSACheckers
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to