aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSergio Martins <smartins@kde.org>2018-03-08 20:15:07 +0000
committerSergio Martins <smartins@kde.org>2018-03-11 21:32:38 +0000
commit34bcfc8677ffe7063b553af1a0a4ab5b94729c70 (patch)
treee65ed0acf4dffc8af7a473eb61d6a6a349df523e /src
parent6e05abc60870316f8e6d00b9e879891518739af5 (diff)
Introduce wrong-qevent-cast
Warns when QEvents are cast to possibly the wrong class
Diffstat (limited to 'src')
-rw-r--r--src/Checks.h2
-rw-r--r--src/HierarchyUtils.h9
-rw-r--r--src/checks/level0/README-wrong-qevent-cast.md11
-rw-r--r--src/checks/level0/wrong-qevent-cast.cpp274
-rw-r--r--src/checks/level0/wrong-qevent-cast.h39
5 files changed, 335 insertions, 0 deletions
diff --git a/src/Checks.h b/src/Checks.h
index b819036c..3ee51d76 100644
--- a/src/Checks.h
+++ b/src/Checks.h
@@ -59,6 +59,7 @@
#include "checks/level0/temporary-iterator.h"
#include "checks/level0/unused-non-trivial-variable.h"
#include "checks/level0/writing-to-temporary.h"
+#include "checks/level0/wrong-qevent-cast.h"
#include "checks/level0/wrong-qglobalstatic.h"
#include "checks/level1/auto-unexpected-qstringbuilder.h"
#include "checks/level1/child-event-qobject-cast.h"
@@ -147,6 +148,7 @@ void CheckManager::registerChecks()
registerCheck(check<TemporaryIterator>("temporary-iterator", CheckLevel0, RegisteredCheck::Option_VisitsStmts));
registerCheck(check<UnusedNonTrivialVariable>("unused-non-trivial-variable", CheckLevel0, RegisteredCheck::Option_VisitsStmts));
registerCheck(check<WritingToTemporary>("writing-to-temporary", CheckLevel0, RegisteredCheck::Option_VisitsStmts));
+ registerCheck(check<WrongQEventCast>("wrong-qevent-cast", CheckLevel0, RegisteredCheck::Option_VisitsStmts));
registerCheck(check<WrongQGlobalStatic>("wrong-qglobalstatic", CheckLevel0, RegisteredCheck::Option_VisitsStmts));
registerCheck(check<AutoUnexpectedQStringBuilder>("auto-unexpected-qstringbuilder", CheckLevel1, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls));
registerFixIt(1, "fix-auto-unexpected-qstringbuilder", "auto-unexpected-qstringbuilder");
diff --git a/src/HierarchyUtils.h b/src/HierarchyUtils.h
index 0db7f548..bef0cbba 100644
--- a/src/HierarchyUtils.h
+++ b/src/HierarchyUtils.h
@@ -118,6 +118,10 @@ T* getFirstChildOfType2(clang::Stmt *stm)
if (clazy::hasChildren(stm)) {
auto child = *(stm->child_begin());
+
+ if (!child) // can happen
+ return nullptr;
+
if (auto s = clang::dyn_cast<T>(child))
return s;
@@ -265,6 +269,11 @@ T* unpeal(clang::Stmt *stmt, IgnoreStmts options = IgnoreNone)
return nullptr;
}
+inline clang::SwitchStmt* getSwitchFromCase(clang::ParentMap *pmap, clang::CaseStmt *caseStm)
+{
+ return getFirstParentOfType<clang::SwitchStmt>(pmap, caseStm);
+}
+
}
#endif
diff --git a/src/checks/level0/README-wrong-qevent-cast.md b/src/checks/level0/README-wrong-qevent-cast.md
new file mode 100644
index 00000000..96244231
--- /dev/null
+++ b/src/checks/level0/README-wrong-qevent-cast.md
@@ -0,0 +1,11 @@
+# wrong-qevent-cast
+
+Warns when a QEvent is possibly cast to the wrong derived class via static_cast.
+
+Example:
+switch (ev->type()) {
+ case QEvent::MouseMove:
+ auto e = static_cast<QKeyEvent*>(ev);
+}
+
+Currently only casts inside switches are verified.
diff --git a/src/checks/level0/wrong-qevent-cast.cpp b/src/checks/level0/wrong-qevent-cast.cpp
new file mode 100644
index 00000000..d61a10d7
--- /dev/null
+++ b/src/checks/level0/wrong-qevent-cast.cpp
@@ -0,0 +1,274 @@
+/*
+ This file is part of the clazy static checker.
+
+ Copyright (C) 2018 Sergio Martins <smartins@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "wrong-qevent-cast.h"
+#include "Utils.h"
+#include "HierarchyUtils.h"
+#include "QtUtils.h"
+#include "TypeUtils.h"
+#include "ClazyContext.h"
+
+#include <clang/AST/AST.h>
+#include <unordered_map>
+
+using namespace clang;
+using namespace std;
+
+typedef vector<StringRef> ClassNameList;
+
+enum QtUnregularlyNamedEventTypes {
+ DragEnter = 60,
+ DragLeave = 62,
+ OrientationChange = 208,
+ ActionAdded = 114,
+ ActionRemoved = 115,
+ ActionChanged = 99,
+ ChildAdded = 68,
+ ChildRemoved = 71,
+ ChildPolished = 69,
+ MouseButtonPress = 2,
+ MouseButtonRelease = 3,
+ MouseButtonDblClick = 4,
+ MouseMove = 5,
+ NonClientAreaMouseMove = 173,
+ NonClientAreaMouseButtonPress = 174,
+ NonClientAreaMouseButtonRelease = 175,
+ NonClientAreaMouseButtonDblClick = 176,
+ FocusIn = 8,
+ FocusOut = 9,
+ FocusAboutToChange = 23,
+ Gesture = 198,
+ GestureOverride = 202,
+ HoverEnter = 127,
+ HoverLeave = 128,
+ HoverMove = 129,
+ TabletEnterProximity = 171,
+ TabletLeaveProximity = 172,
+ TabletPress = 92,
+ TabletMove = 87,
+ TabletRelease = 93,
+ ToolTip = 110,
+ Wheel = 31,
+ KeyPress = 6,
+ KeyRelease = 7,
+ ShortcutOverride = 51,
+ DragMove = 61,
+ GraphicsSceneMouseMove = 155,
+ GraphicsSceneMousePress = 156,
+ GraphicsSceneMouseRelease = 157,
+ GraphicsSceneMouseDoubleClick = 158,
+ GraphicsSceneContextMenu = 159,
+ GraphicsSceneHoverEnter = 160,
+ GraphicsSceneHoverMove = 161,
+ GraphicsSceneHoverLeave = 162,
+ GraphicsSceneHelp = 163,
+ GraphicsSceneDragEnter = 164,
+ GraphicsSceneDragMove = 165,
+ GraphicsSceneDragLeave = 166,
+ GraphicsSceneDrop = 167,
+ GraphicsSceneWheel = 168,
+ GraphicsSceneResize = 181,
+ TouchBegin = 194,
+ TouchEnd = 196,
+ TouchCancel = 209,
+ TouchUpdate = 195,
+ NativeGesture = 197,
+ MetaCall = 43,
+ WhatsThis = 111,
+ ContextMenu = 82,
+ QueryWhatsThis = 123
+ // StatusTip = 112 not irregular, but qtbase casts it to QHelpEvent for some reason, needs investigation
+};
+
+
+WrongQEventCast::WrongQEventCast(const std::string &name, ClazyContext *context)
+ : CheckBase(name, context)
+{
+
+
+}
+
+static bool eventTypeMatchesClass(QtUnregularlyNamedEventTypes eventType, string eventTypeStr, StringRef className)
+{
+ // In the simplest case, the class is "Q" + eventType + "Event"
+ string expectedClassName = string("Q") + eventTypeStr + string("Event");
+ if (expectedClassName == className)
+ return true;
+
+ // Otherwise it's unregular and we need a map:
+
+ static unordered_map<QtUnregularlyNamedEventTypes, ClassNameList> map = {
+ { ActionAdded, {"QActionEvent" } },
+ { ActionRemoved, {"QActionEvent" } },
+ { ActionChanged, {"QActionEvent" } },
+ { ChildAdded, {"QChildEvent" } },
+ { ChildRemoved, {"QChildEvent" } },
+ { ChildPolished, {"QChildEvent" } },
+ { MetaCall, {"QDBusSpyCallEvent", "QDBusCallDeliveryEvent"} },
+ { DragEnter, {"QDragEnterEvent", "QDragMoveEvent", "QDropEvent" } },
+ { DragLeave, {"QDragLeaveEvent", "QDragMoveEvent", "QDropEvent" } },
+ { DragMove, {"QDragMoveEvent", "QDropEvent" } },
+ { FocusIn, {"QFocusEvent" } },
+ { FocusOut, {"QFocusEvent" } },
+ { FocusAboutToChange, {"QFocusEvent" } },
+ { Gesture, {"QGestureEvent" } },
+ { GestureOverride, {"QGestureEvent" } },
+ { GraphicsSceneContextMenu, {"QGraphicsSceneEvent" } },
+ { GraphicsSceneHoverEnter, { "QGraphicsSceneHoverEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneHoverMove, {"QGraphicsSceneHoverEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneHoverLeave, {"QGraphicsSceneHoverEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneHelp, { "QGraphicsSceneEvent" } },
+ { GraphicsSceneDragEnter, {"QGraphicsSceneDragDropEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneDragMove, {"QGraphicsSceneDragDropEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneDragLeave, {"QGraphicsSceneDragDropEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneDrop, {"QGraphicsSceneDragDropEvent", "QGraphicsSceneEvent" } },
+ { GraphicsSceneWheel, {"QGraphicsSceneEvent" } },
+ { GraphicsSceneResize, {"QGraphicsSceneEvent" } },
+ { GraphicsSceneMouseMove, {"QGraphicsSceneMouseEvent" } },
+ { GraphicsSceneMousePress, {"QGraphicsSceneMouseEvent" } },
+ { GraphicsSceneMouseRelease, {"QGraphicsSceneMouseEvent" } },
+ { GraphicsSceneMouseDoubleClick, {"QGraphicsSceneMouseEvent" } },
+ //{ StatusTip, {"QStatusTipEvent" } },
+ { ToolTip, {"QHelpEvent" } },
+ { WhatsThis, {"QHelpEvent" } },
+ { QueryWhatsThis, {"QHelpEvent" } },
+ { HoverEnter, {"QHoverEvent", "QInputEvent" } },
+ { HoverLeave, {"QHoverEvent", "QInputEvent" } },
+ { HoverMove, {"QHoverEvent", "QInputEvent" } },
+ { KeyPress, {"QKeyEvent", "QInputEvent" } },
+ { KeyRelease, {"QKeyEvent", "QInputEvent" } },
+ { ShortcutOverride, {"QKeyEvent", "QInputEvent" } },
+ { MouseButtonPress, {"QMouseEvent" } },
+ { MouseButtonRelease, {"QMouseEvent" } },
+ { MouseButtonDblClick, {"QMouseEvent" } },
+ { MouseMove, {"QMouseEvent" } },
+ { NonClientAreaMouseMove, {"QMouseEvent" } },
+ { NonClientAreaMouseButtonPress, {"QMouseEvent" } },
+ { NonClientAreaMouseButtonRelease, {"QMouseEvent" } },
+ { NonClientAreaMouseButtonRelease, {"QMouseEvent" } },
+ { NonClientAreaMouseButtonDblClick, {"QMouseEvent" } },
+ { NativeGesture, { "QInputEvent" } },
+ { OrientationChange, {"QScreenOrientationChangeEvent" } },
+ { TabletEnterProximity, {"QTabletEvent", "QInputEvent" } },
+ { TabletLeaveProximity, {"QTabletEvent", "QInputEvent" } },
+ { TabletPress, {"QTabletEvent", "QInputEvent" } },
+ { TabletMove, {"QTabletEvent", "QInputEvent" } },
+ { TabletRelease, {"QTabletEvent", "QInputEvent" } },
+ { TouchBegin, {"QTouchEvent", "QInputEvent" } },
+ { TouchCancel, {"QTouchEvent", "QInputEvent" } },
+ { TouchEnd, {"QTouchEvent", "QInputEvent" } },
+ { TouchUpdate, {"QTouchEvent", "QInputEvent" } },
+ { Wheel, {"QInputEvent" } },
+ { ContextMenu, {"QInputEvent" } }
+ };
+
+ auto it = map.find(eventType);
+ if (it == map.cend())
+ return false;
+
+ const ClassNameList &classes = it->second;
+ const bool found = clazy::find(classes, className) != classes.cend();
+
+ return found;
+}
+
+
+// TODO: Use iterators
+CaseStmt* getCaseStatement(clang::ParentMap *pmap, Stmt *stmt, DeclRefExpr *event)
+{
+ Stmt *s = pmap->getParent(stmt);
+
+ while (s) {
+
+ if (auto ifStmt = dyn_cast<IfStmt>(s)) {
+ // if there's we're inside an if statement then skip, to avoid false-positives
+ auto declRef = clazy::getFirstChildOfType2<DeclRefExpr>(ifStmt->getCond());
+ if (declRef && declRef->getDecl() == event->getDecl())
+ return nullptr;
+ }
+
+
+ if (auto caseStmt = dyn_cast<CaseStmt>(s)) {
+ auto switchStmt = clazy::getSwitchFromCase(pmap, caseStmt);
+ if (switchStmt) {
+ auto declRef = clazy::getFirstChildOfType2<DeclRefExpr>(switchStmt->getCond());
+
+ llvm::errs() << "Found a switch statement " << switchStmt << "with declref" << declRef << "; " << event << "\n";
+ switchStmt->getCond()->dump();
+
+ // Does this switch refer to the same QEvent ?
+ if (declRef && declRef->getDecl() == event->getDecl())
+ return caseStmt;
+ }
+ }
+
+ s = pmap->getParent(s);
+ }
+
+ return nullptr;
+}
+
+void WrongQEventCast::VisitStmt(clang::Stmt *stmt)
+{
+ auto cast = dyn_cast<CXXStaticCastExpr>(stmt);
+ if (!cast)
+ return;
+
+ Expr *e = cast->getSubExpr();
+
+ QualType t = e ? e->getType() : QualType();
+ QualType pointeeType = t.isNull() ? QualType() : TypeUtils::pointeeQualType(t);
+ CXXRecordDecl *rec = pointeeType.isNull() ? nullptr : pointeeType->getAsCXXRecordDecl();
+
+ if (!rec || clazy::name(rec) != "QEvent")
+ return;
+
+ CXXRecordDecl *castTo = Utils::namedCastOuterDecl(cast);
+ if (!castTo)
+ return;
+
+ auto declref = clazy::getFirstChildOfType2<DeclRefExpr>(cast->getSubExpr());
+ if (!declref)
+ return;
+
+ auto caseStmt = getCaseStatement(m_context->parentMap, stmt, declref);
+ if (!caseStmt)
+ return;
+
+ auto caseValue = clazy::getFirstChildOfType2<DeclRefExpr>(caseStmt->getLHS());
+ if (!caseValue)
+ return;
+
+
+ auto enumeratorDecl = dyn_cast<EnumConstantDecl>(caseValue->getDecl());
+ if (!enumeratorDecl)
+ return;
+
+ auto enumeratorVal = static_cast<QtUnregularlyNamedEventTypes>(enumeratorDecl->getInitVal().getExtValue());
+
+ string eventTypeStr = enumeratorDecl->getNameAsString();
+ StringRef castToName = clazy::name(castTo);
+
+ if (eventTypeMatchesClass(enumeratorVal, eventTypeStr, castToName))
+ return;
+
+ emitWarning(stmt, string("Cast from a QEvent::") + eventTypeStr + " event to " + string(castToName) + " looks suspicious.");
+}
diff --git a/src/checks/level0/wrong-qevent-cast.h b/src/checks/level0/wrong-qevent-cast.h
new file mode 100644
index 00000000..043cdb82
--- /dev/null
+++ b/src/checks/level0/wrong-qevent-cast.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of the clazy static checker.
+
+ Copyright (C) 2018 Sergio Martins <smartins@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef CLAZY_WRONG_QEVENT_CAST_H
+#define CLAZY_WRONG_QEVENT_CAST_H
+
+#include "checkbase.h"
+
+
+/**
+ * See README-wrong-qevent-cast.md for more info.
+ */
+class WrongQEventCast : public CheckBase
+{
+public:
+ explicit WrongQEventCast(const std::string &name, ClazyContext *context);
+ void VisitStmt(clang::Stmt *) override;
+private:
+};
+
+#endif