From 34bcfc8677ffe7063b553af1a0a4ab5b94729c70 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Thu, 8 Mar 2018 20:15:07 +0000 Subject: Introduce wrong-qevent-cast Warns when QEvents are cast to possibly the wrong class --- src/Checks.h | 2 + src/HierarchyUtils.h | 9 + src/checks/level0/README-wrong-qevent-cast.md | 11 ++ src/checks/level0/wrong-qevent-cast.cpp | 274 ++++++++++++++++++++++++++ src/checks/level0/wrong-qevent-cast.h | 39 ++++ 5 files changed, 335 insertions(+) create mode 100644 src/checks/level0/README-wrong-qevent-cast.md create mode 100644 src/checks/level0/wrong-qevent-cast.cpp create mode 100644 src/checks/level0/wrong-qevent-cast.h (limited to 'src') 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("temporary-iterator", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("unused-non-trivial-variable", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("writing-to-temporary", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); + registerCheck(check("wrong-qevent-cast", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("wrong-qglobalstatic", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("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(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(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(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 + + 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 +#include + +using namespace clang; +using namespace std; + +typedef vector 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 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(s)) { + // if there's we're inside an if statement then skip, to avoid false-positives + auto declRef = clazy::getFirstChildOfType2(ifStmt->getCond()); + if (declRef && declRef->getDecl() == event->getDecl()) + return nullptr; + } + + + if (auto caseStmt = dyn_cast(s)) { + auto switchStmt = clazy::getSwitchFromCase(pmap, caseStmt); + if (switchStmt) { + auto declRef = clazy::getFirstChildOfType2(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(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(cast->getSubExpr()); + if (!declref) + return; + + auto caseStmt = getCaseStatement(m_context->parentMap, stmt, declref); + if (!caseStmt) + return; + + auto caseValue = clazy::getFirstChildOfType2(caseStmt->getLHS()); + if (!caseValue) + return; + + + auto enumeratorDecl = dyn_cast(caseValue->getDecl()); + if (!enumeratorDecl) + return; + + auto enumeratorVal = static_cast(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 + + 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 -- cgit v1.2.3