diff options
| author | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2025-02-02 22:17:42 +0100 |
|---|---|---|
| committer | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2025-02-28 23:40:58 +0100 |
| commit | 1145e1709d1072f7dd45683e9c25a14615603854 (patch) | |
| tree | 8bd7be93b147b54694f20e51103f42af5812dde0 /src/corelib/tools/qrect.cpp | |
| parent | 56de11397559af3b9694ef2b99d93a469889ae5e (diff) | |
Geometry classes: introduce check for overflows
The integer-based geometry classes all risk triggering UB via signed
integer overflow. Their representation and APIs exclusively use `int`.
This means that for instance one can build a QLine spanning from
x1=INT_MIN to x2=INT_MAX, and they try asking its delta-x (dx()),
causing an integer overflow in the computation.
So far, this has always been "undefined behavior that works in
practice", effectively wrapping values around on all of our supported
implementation. This choice however makes it impossible to have hardened
/ ubsan builds of Qt. It also causes some bizarre behaviors: for
instance, a maximal QRect (going from (INT_MIN, INT_MIN) to (INT_MAX,
INT_MAX)) reports "false" when asked if it contains() a 10x10 rectangle.
(That's because QRect::contains() overflows.). Users may not readily
notice what is going wrong here.
After some thoughts, I've decided that the burden here lies on Qt.
While it's true that these geometry classes are nothing but glorified
int holders, their representation and handling is entirely chosen and
controlled by Qt. The user is not supposed to know "in advance" that
QRect holds a couple of points and not, say, a point and a size.
Qt also chose the shape of the APIs, taking and returning ints, even
when some of these APIs cannot return meaningful results due to the
chosen representation.
The best we can do at this point is to trigger assertions if some
calculation overflows. Therefore, deploy the newly introduced private
checked integer class, that will assert if something goes wrong. In
release builds they will keep the pre-existing behavior (wraparound),
but they will remove the associated UB.
The changes are mostly mechanical:
1) change the representation of the geometry classes to use the checked
integer (instead of plain `int`);
2) change getters/setters to deal with conversions from and to the
checked integer;
plus some minimal fallout. Operator overloading on the checked integer
keeps the code 99% similar, and doesn't decrease readability. Some care
is necessary to try and make all internal calculations use the checked
int, instead of falling back to operations on `int`.
I've tried to keep the behavior identical as much as possible (except of
course for detecting overflow). In particular I haven't reengineered the
existing implementations to so something "smarter" or use 64 bit
arithmetic if possible or so.
The only exception is the QRectF constructor from QRect. Since a QRectF
can faithfully represent any QRect, in the conversion I'm taking
special care not to call width() or height() as they may overflow.
Instead, I calculate them using int64. This allows for a maximal QRect
to be converted to a QRectF without loss of information (instead of what
was happening before).
Some other remarks:
* operator/ checks that the divisor is not zero. This was done in
several places already and just missing here and there.
* QRect::isNull had a bizarre behavior (autotested) where a maximal
rectangle was considered null (because of wraparounds). I've decided
to keep this behavior.
* Several test rows in tst_QRect were relying on overflow behaviors.
However, many others were already commented out, with a comment
saying "it would cause an overflow". Therefore I've commented them
out as well.
* The integral classes sometime have functions taking qreal (e.g.
`operator*`). In there, qRound is applied to keep the result integral.
In principle qRound can also overflow and cause UB, but I've decided
to tackle that separately because I'm not sure what should happen
(QTBUG-133261). This also happens for the fp->integral conversion
functions (e.g. QRectF::toRect()).
* The functions are marked constexpr, so add some minimal testing to
make sure I'm not breaking it.
* Some functions are also marked noexcept. We need to have a broader
discussion whether that's actually wrong, and how to recover from
there.
Task-number: QTBUG-98965
Task-number: QTBUG-132947
Task-number: QTBUG-133261
Change-Id: Idde1daa5c6ca3e37ba0e402617576c5d130615f5
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/corelib/tools/qrect.cpp')
| -rw-r--r-- | src/corelib/tools/qrect.cpp | 68 |
1 files changed, 34 insertions, 34 deletions
diff --git a/src/corelib/tools/qrect.cpp b/src/corelib/tools/qrect.cpp index a82183bc804..6ca3caa0f44 100644 --- a/src/corelib/tools/qrect.cpp +++ b/src/corelib/tools/qrect.cpp @@ -788,7 +788,7 @@ QRect QRect::normalized() const noexcept bool QRect::contains(const QPoint &p, bool proper) const noexcept { - int l, r; + Representation l, r; if (x2 < x1 - 1) { l = x2 + 1; r = x1 - 1; @@ -803,7 +803,7 @@ bool QRect::contains(const QPoint &p, bool proper) const noexcept if (p.x() < l || p.x() > r) return false; } - int t, b; + Representation t, b; if (y2 < y1 - 1) { t = y2 + 1; b = y1 - 1; @@ -855,15 +855,15 @@ bool QRect::contains(const QRect &r, bool proper) const noexcept if (isNull() || r.isNull()) return false; - int l1 = x1; - int r1 = x1 - 1; + Representation l1 = x1; + Representation r1 = x1 - 1; if (x2 < x1 - 1) l1 = x2 + 1; else r1 = x2; - int l2 = r.x1; - int r2 = r.x1 - 1; + Representation l2 = r.x1; + Representation r2 = r.x1 - 1; if (r.x2 < r.x1 - 1) l2 = r.x2 + 1; else @@ -877,15 +877,15 @@ bool QRect::contains(const QRect &r, bool proper) const noexcept return false; } - int t1 = y1; - int b1 = y1 - 1; + Representation t1 = y1; + Representation b1 = y1 - 1; if (y2 < y1 - 1) t1 = y2 + 1; else b1 = y2; - int t2 = r.y1; - int b2 = r.y1 - 1; + Representation t2 = r.y1; + Representation b2 = r.y1 - 1; if (r.y2 < r.y1 - 1) t2 = r.y2 + 1; else @@ -935,29 +935,29 @@ QRect QRect::operator|(const QRect &r) const noexcept if (r.isNull()) return *this; - int l1 = x1; - int r1 = x1 - 1; + Representation l1 = x1; + Representation r1 = x1 - 1; if (x2 < x1 - 1) l1 = x2 + 1; else r1 = x2; - int l2 = r.x1; - int r2 = r.x1 - 1; + Representation l2 = r.x1; + Representation r2 = r.x1 - 1; if (r.x2 < r.x1 - 1) l2 = r.x2 + 1; else r2 = r.x2; - int t1 = y1; - int b1 = y1 - 1; + Representation t1 = y1; + Representation b1 = y1 - 1; if (y2 < y1 - 1) t1 = y2 + 1; else b1 = y2; - int t2 = r.y1; - int b2 = r.y1 - 1; + Representation t2 = r.y1; + Representation b2 = r.y1 - 1; if (r.y2 < r.y1 - 1) t2 = r.y2 + 1; else @@ -997,15 +997,15 @@ QRect QRect::operator&(const QRect &r) const noexcept if (isNull() || r.isNull()) return QRect(); - int l1 = x1; - int r1 = x2; + Representation l1 = x1; + Representation r1 = x2; if (x2 < x1 - 1) { l1 = x2 + 1; r1 = x1 - 1; } - int l2 = r.x1; - int r2 = r.x2; + Representation l2 = r.x1; + Representation r2 = r.x2; if (r.x2 < r.x1 - 1) { l2 = r.x2 + 1; r2 = r.x1 - 1; @@ -1014,15 +1014,15 @@ QRect QRect::operator&(const QRect &r) const noexcept if (l1 > r2 || l2 > r1) return QRect(); - int t1 = y1; - int b1 = y2; + Representation t1 = y1; + Representation b1 = y2; if (y2 < y1 - 1) { t1 = y2 + 1; b1 = y1 - 1; } - int t2 = r.y1; - int b2 = r.y2; + Representation t2 = r.y1; + Representation b2 = r.y2; if (r.y2 < r.y1 - 1) { t2 = r.y2 + 1; b2 = r.y1 - 1; @@ -1069,15 +1069,15 @@ bool QRect::intersects(const QRect &r) const noexcept if (isNull() || r.isNull()) return false; - int l1 = x1; - int r1 = x2; + Representation l1 = x1; + Representation r1 = x2; if (x2 < x1 - 1) { l1 = x2 + 1; r1 = x1 - 1; } - int l2 = r.x1; - int r2 = r.x2; + Representation l2 = r.x1; + Representation r2 = r.x2; if (r.x2 < r.x1 - 1) { l2 = r.x2 + 1; r2 = r.x1 - 1; @@ -1086,15 +1086,15 @@ bool QRect::intersects(const QRect &r) const noexcept if (l1 > r2 || l2 > r1) return false; - int t1 = y1; - int b1 = y2; + Representation t1 = y1; + Representation b1 = y2; if (y2 < y1 - 1) { t1 = y2 + 1; b1 = y1 - 1; } - int t2 = r.y1; - int b2 = r.y2; + Representation t2 = r.y1; + Representation b2 = r.y2; if (r.y2 < r.y1 - 1) { t2 = r.y2 + 1; b2 = r.y1 - 1; |
