summaryrefslogtreecommitdiffstats
path: root/src/corelib/itemmodels/qabstractitemmodel.cpp
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2017-09-01 18:27:15 +0200
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2018-01-08 09:41:53 +0000
commit69496d4e22fb6a8805fbcfa798d9aca53621a2ac (patch)
tree788bb62a9981188fb82ad16ef61b1e9e8fdeb35d /src/corelib/itemmodels/qabstractitemmodel.cpp
parent9c53f4d33a1953bc84e5b5e8bbef14db375bb180 (diff)
Introduce QAbstractItemModel::checkIndex()
When implementing a custom model there's the habit, in each and every function that takes a QModelIndex, to carefully checking the index passed by the caller. This index is checked for "legality" (*): does the index belong to this model, is the index pointing to an existing row and column, and so on. These checks are hand-rolled and, as such, slightly different and possibly incomplete (i.e. wrong) every time. What's worse, these checks are implemented via "ordinary" code (if statements). However, passing an illegal index to a QAIM function is a precondition violation, and as such does not (and must not) be checked in ordinary conditions, as it triggers undefined behavior. On the other hand, while debugging a custom model or a custom hierarchy of (proxy) models, having such checks in place can be a significant aid. Enter checkIndex(): a debugging helper for QAbstractItemModel and its subclasses. checkIndex() centralizes the checks for legality of a given index. User code is free to assert on it, or have some other fallback mechanism in case a check fails. (*) Using "legality" here instead of "validity" in order to avoid confusion between QModelIndex::isValid() and what checkIndex() really does. [ChangeLog][QtCore][QAbstractItemModel] Added QAbstractItemModel::checkIndex(), a debugging function for QAbstractItemModel subclasses. Change-Id: I1eea0586b1ac3ededdbfbf46759145022dc5ad86 Reviewed-by: Thorbjørn Lund Martsum <tmartsum@gmail.com> Reviewed-by: David Faure <david.faure@kdab.com>
Diffstat (limited to 'src/corelib/itemmodels/qabstractitemmodel.cpp')
-rw-r--r--src/corelib/itemmodels/qabstractitemmodel.cpp137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp
index 7e1ab851d7..af87f56255 100644
--- a/src/corelib/itemmodels/qabstractitemmodel.cpp
+++ b/src/corelib/itemmodels/qabstractitemmodel.cpp
@@ -48,11 +48,14 @@
#include <qstack.h>
#include <qbitarray.h>
#include <qdatetime.h>
+#include <qloggingcategory.h>
#include <limits.h>
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcCheckIndex, "qt.core.qabstractitemmodel.checkindex")
+
QPersistentModelIndexData *QPersistentModelIndexData::create(const QModelIndex &index)
{
Q_ASSERT(index.isValid()); // we will _never_ insert an invalid index in the list
@@ -3320,6 +3323,140 @@ QModelIndexList QAbstractItemModel::persistentIndexList() const
return result;
}
+/*!
+ \enum QAbstractItemModel::CheckIndexOption
+ \since 5.11
+
+ This enum can be used to control the checks performed by
+ QAbstractItemModel::checkIndex().
+
+ \value CheckIndexOption::NoOption No check options are specified.
+
+ \value CheckIndexOption::IndexIsValid The model index passed to
+ QAbstractItemModel::checkIndex() is checked to be a valid model index.
+
+ \value CheckIndexOption::DoNotUseParent Does not perform any check
+ involving the usage of the parent of the index passed to
+ QAbstractItemModel::checkIndex().
+
+ \value CheckIndexOption::ParentIsInvalid The parent of the model index
+ passed to QAbstractItemModel::checkIndex() is checked to be an invalid
+ model index. If both this option and CheckIndexOption::DoNotUseParent
+ are specified, then this option is ignored.
+*/
+
+/*!
+ \since 5.11
+
+ This function checks whether \a index is a legal model index for
+ this model. A legal model index is either an invalid model index, or a
+ valid model index for which all the following holds:
+
+ \list
+
+ \li the index' model is \c{this};
+ \li the index' row is greater or equal than zero;
+ \li the index' row is less than the row count for the index' parent;
+ \li the index' column is greater or equal than zero;
+ \li the index' column is less than the column count for the index' parent.
+
+ \endlist
+
+ The \a options argument may change some of these checks. If \a options
+ contains \c{CheckIndexOption::IndexIsValid}, then \a index must be a valid
+ index; this is useful when reimplementing functions such as \l{data()} or
+ \l{setData()}, which expect valid indexes.
+
+ If \a options contains \c{CheckIndexOption::DoNotUseParent}, then the
+ checks that would call \l{parent()} are omitted; this allows calling this
+ function from a \l{parent()} reimplementation (otherwise, this would result
+ in endless recursion and a crash).
+
+ If \a options does not contain \c{CheckIndexOption::DoNotUseParent}, and it
+ contains \c{CheckIndexOption::ParentIsInvalid}, then an additional check is
+ performed: the parent index is checked for not being valid. This is useful
+ when implementing flat models such as lists or tables, where no model index
+ should have a valid parent index.
+
+ This function returns true if all the checks succeeded, and false otherwise.
+ This allows to use the function in \l{Q_ASSERT} and similar other debugging
+ mechanisms. If some check failed, a warning message will be printed in the
+ \c{qt.core.qabstractitemmodel.checkindex} logging category, containing
+ some information that may be useful for debugging the failure.
+
+ \note This function is a debugging helper for implementing your own item
+ models. When developing complex models, as well as when building
+ complicated model hierarchies (e.g. using proxy models), it is useful to
+ call this function in order to catch bugs relative to illegal model indices
+ (as defined above) accidentally passed to some QAbstractItemModel API.
+
+ \warning Note that it's undefined behavior to pass illegal indices to item
+ models, so applications must refrain from doing so, and not rely on any
+ "defensive" programming that item models could employ to handle illegal
+ indexes gracefully.
+
+ \sa QModelIndex
+*/
+bool QAbstractItemModel::checkIndex(const QModelIndex &index, CheckIndexOptions options) const
+{
+ if (!index.isValid()) {
+ if (options & CheckIndexOption::IndexIsValid) {
+ qCWarning(lcCheckIndex) << "Index" << index << "is not valid (expected valid)";
+ return false;
+ }
+ return true;
+ }
+
+ if (index.model() != this) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "is for model" << index.model()
+ << "which is different from this model" << this;
+ return false;
+ }
+
+ if (index.row() < 0) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "has negative row" << index.row();
+ return false;
+ }
+
+ if (index.column() < 0) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "has negative column" << index.column();
+ return false;
+ }
+
+ if (!(options & CheckIndexOption::DoNotUseParent)) {
+ const QModelIndex parentIndex = index.parent();
+ if (options & CheckIndexOption::ParentIsInvalid) {
+ if (parentIndex.isValid()) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "has valid parent" << parentIndex
+ << "(expected an invalid parent)";
+ return false;
+ }
+ }
+
+ const int rc = rowCount(parentIndex);
+ if (index.row() >= rc) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "has out of range row" << index.row()
+ << "rowCount() is" << rc;
+ return false;
+ }
+
+ const int cc = columnCount(parentIndex);
+ if (index.column() >= cc) {
+ qCWarning(lcCheckIndex) << "Index" << index
+ << "has out of range column" << index.column()
+ << "columnCount() is" << cc;
+ return false;
+
+ }
+ }
+
+ return true;
+}
/*!
\class QAbstractTableModel