Work around compiler bug in QTransform

gcc version 13.2.0 (Ubuntu 13.2.0-23ubuntu4) seems to remove some
code in release builds that is embedded in a macro in qtransfrom.cpp.
This patch changes the macro into a function such that it is not
removed by gcc.

It seems like the switch statement is (wrongly) optimized away if it is
embedded in the QTransform::map(QPolygonF) overload. nx and ny are
always 0 (as initialized by QPolygonF) after the macro (but only within
the QTransform::map(QPolygonF) function.

The bug happens only in a release build. Added debug messages of nx and
ny created no output, adding qDebug() << t, which made the macro work.

It seems that a complex combination of macro and switch statement can
tigger the bug in gcc. So rewriting the macro as a function works around
the bug.

Fixes: QTBUG-127723
Change-Id: I7e0b02cde276e591cf773f4e18dd505134421957
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit 11b70b66de627d3bd0ef4193d5cfa624c88ac96a)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Matthias Rauter 2024-08-06 13:01:45 +02:00 committed by Qt Cherry-pick Bot
parent 11473410c2
commit 1909435b03
3 changed files with 72 additions and 65 deletions

View File

@ -26,40 +26,38 @@ static void nanWarning(const char *func)
#define Q_NEAR_CLIP (sizeof(qreal) == sizeof(double) ? 0.000001 : 0.0001)
#ifdef MAP
# undef MAP
#endif
#define MAP(x, y, nx, ny) \
do { \
qreal FX_ = x; \
qreal FY_ = y; \
switch(t) { \
case TxNone: \
nx = FX_; \
ny = FY_; \
break; \
case TxTranslate: \
nx = FX_ + m_matrix[2][0]; \
ny = FY_ + m_matrix[2][1]; \
break; \
case TxScale: \
nx = m_matrix[0][0] * FX_ + m_matrix[2][0]; \
ny = m_matrix[1][1] * FY_ + m_matrix[2][1]; \
break; \
case TxRotate: \
case TxShear: \
case TxProject: \
nx = m_matrix[0][0] * FX_ + m_matrix[1][0] * FY_ + m_matrix[2][0]; \
ny = m_matrix[0][1] * FX_ + m_matrix[1][1] * FY_ + m_matrix[2][1]; \
if (t == TxProject) { \
qreal w = (m_matrix[0][2] * FX_ + m_matrix[1][2] * FY_ + m_matrix[2][2]); \
if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP); \
w = qreal(1.)/w; \
nx *= w; \
ny *= w; \
} \
} \
} while (0)
void QTransform::do_map(qreal x, qreal y, qreal &nx, qreal &ny) const
{
const TransformationType t = inline_type();
switch (t) {
case QTransform::TxNone:
nx = x;
ny = y;
return;
case QTransform::TxTranslate:
nx = x + m_matrix[2][0];
ny = y + m_matrix[2][1];
return;
case QTransform::TxScale:
nx = m_matrix[0][0] * x + m_matrix[2][0];
ny = m_matrix[1][1] * y + m_matrix[2][1];
return;
case QTransform::TxRotate:
case QTransform::TxShear:
case QTransform::TxProject:
nx = m_matrix[0][0] * x + m_matrix[1][0] * y + m_matrix[2][0];
ny = m_matrix[0][1] * x + m_matrix[1][1] * y + m_matrix[2][1];
if (t == QTransform::TxProject) {
qreal w = (m_matrix[0][2] * x + m_matrix[1][2] * y + m_matrix[2][2]);
if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP);
w = qreal(1.)/w;
nx *= w;
ny *= w;
}
return;
}
Q_UNREACHABLE_RETURN();
}
/*!
\class QTransform
@ -1144,8 +1142,7 @@ QPoint QTransform::map(const QPoint &p) const
qreal x = 0, y = 0;
TransformationType t = inline_type();
MAP(fx, fy, x, y);
do_map(fx, fy, x, y);
return QPoint(qRound(x), qRound(y));
}
@ -1173,8 +1170,7 @@ QPointF QTransform::map(const QPointF &p) const
qreal x = 0, y = 0;
TransformationType t = inline_type();
MAP(fx, fy, x, y);
do_map(fx, fy, x, y);
return QPointF(x, y);
}
@ -1222,9 +1218,8 @@ QLine QTransform::map(const QLine &l) const
qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0;
TransformationType t = inline_type();
MAP(fx1, fy1, x1, y1);
MAP(fx2, fy2, x2, y2);
do_map(fx1, fy1, x1, y1);
do_map(fx2, fy2, x2, y2);
return QLine(qRound(x1), qRound(y1), qRound(x2), qRound(y2));
}
@ -1249,9 +1244,8 @@ QLineF QTransform::map(const QLineF &l) const
qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0;
TransformationType t = inline_type();
MAP(fx1, fy1, x1, y1);
MAP(fx2, fy2, x2, y2);
do_map(fx1, fy1, x1, y1);
do_map(fx2, fy2, x2, y2);
return QLineF(x1, y1, x2, y2);
}
@ -1296,7 +1290,7 @@ QPolygonF QTransform::map(const QPolygonF &a) const
QPointF *dp = p.data();
for(i = 0; i < size; ++i) {
MAP(da[i].xp, da[i].yp, dp[i].xp, dp[i].yp);
do_map(da[i].xp, da[i].yp, dp[i].xp, dp[i].yp);
}
return p;
}
@ -1324,7 +1318,7 @@ QPolygon QTransform::map(const QPolygon &a) const
for(i = 0; i < size; ++i) {
qreal nx = 0, ny = 0;
MAP(da[i].xp, da[i].yp, nx, ny);
do_map(da[i].xp, da[i].yp, nx, ny);
dp[i].xp = qRound(nx);
dp[i].yp = qRound(ny);
}
@ -1545,7 +1539,7 @@ QPainterPath QTransform::map(const QPainterPath &path) const
// Full xform
for (int i=0; i<path.elementCount(); ++i) {
QPainterPath::Element &e = copy.d_ptr->elements[i];
MAP(e.x, e.y, e.x, e.y);
do_map(e.x, e.y, e.x, e.y);
}
}
@ -1598,12 +1592,12 @@ QPolygon QTransform::mapToPolygon(const QRect &rect) const
y[2] = y[0]+h;
y[3] = y[2];
} else {
qreal right = rect.x() + rect.width();
qreal bottom = rect.y() + rect.height();
MAP(rect.x(), rect.y(), x[0], y[0]);
MAP(right, rect.y(), x[1], y[1]);
MAP(right, bottom, x[2], y[2]);
MAP(rect.x(), bottom, x[3], y[3]);
auto right = rect.x() + rect.width();
auto bottom = rect.y() + rect.height();
do_map(rect.x(), rect.y(), x[0], y[0]);
do_map(right, rect.y(), x[1], y[1]);
do_map(right, bottom, x[2], y[2]);
do_map(rect.x(), bottom, x[3], y[3]);
}
// all coordinates are correctly, transform to a pointarray
@ -1768,22 +1762,22 @@ QRect QTransform::mapRect(const QRect &rect) const
return QRect(x, y, w, h);
} else {
qreal x = 0, y = 0;
MAP(rect.left(), rect.top(), x, y);
do_map(rect.left(), rect.top(), x, y);
qreal xmin = x;
qreal ymin = y;
qreal xmax = x;
qreal ymax = y;
MAP(rect.right() + 1, rect.top(), x, y);
do_map(rect.right() + 1, rect.top(), x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
MAP(rect.right() + 1, rect.bottom() + 1, x, y);
do_map(rect.right() + 1, rect.bottom() + 1, x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
MAP(rect.left(), rect.bottom() + 1, x, y);
do_map(rect.left(), rect.bottom() + 1, x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
@ -1833,22 +1827,22 @@ QRectF QTransform::mapRect(const QRectF &rect) const
return QRectF(x, y, w, h);
} else {
qreal x = 0, y = 0;
MAP(rect.x(), rect.y(), x, y);
do_map(rect.x(), rect.y(), x, y);
qreal xmin = x;
qreal ymin = y;
qreal xmax = x;
qreal ymax = y;
MAP(rect.x() + rect.width(), rect.y(), x, y);
do_map(rect.x() + rect.width(), rect.y(), x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
MAP(rect.x() + rect.width(), rect.y() + rect.height(), x, y);
do_map(rect.x() + rect.width(), rect.y() + rect.height(), x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
MAP(rect.x(), rect.y() + rect.height(), x, y);
do_map(rect.x(), rect.y() + rect.height(), x, y);
xmin = qMin(xmin, x);
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
@ -1883,8 +1877,7 @@ QRectF QTransform::mapRect(const QRectF &rect) const
*/
void QTransform::map(qreal x, qreal y, qreal *tx, qreal *ty) const
{
TransformationType t = inline_type();
MAP(x, y, *tx, *ty);
do_map(x, y, *tx, *ty);
}
/*!
@ -1897,9 +1890,8 @@ void QTransform::map(qreal x, qreal y, qreal *tx, qreal *ty) const
*/
void QTransform::map(int x, int y, int *tx, int *ty) const
{
TransformationType t = inline_type();
qreal fx = 0, fy = 0;
MAP(x, y, fx, fy);
do_map(x, y, fx, fy);
*tx = qRound(fx);
*ty = qRound(fy);
}

View File

@ -145,6 +145,7 @@ public:
private:
inline TransformationType inline_type() const;
void do_map(qreal x, qreal y, qreal &nx, qreal &ny) const;
qreal m_matrix[3][3];
mutable uint m_type : 5;

View File

@ -34,6 +34,7 @@ private slots:
void projectivePathMapping();
void mapInt();
void mapPathWithPoint();
void mapRectToPolygon(); // QTBUG-127723
private:
void mapping_data();
@ -700,6 +701,19 @@ void tst_QTransform::mapPathWithPoint()
QCOMPARE(p.currentPosition(), QPointF(20, 20));
}
void tst_QTransform::mapRectToPolygon()
{
QRectF r(7, 7, 36, 36);
QTransform tx(2, 0, 0, 2, 0, 0);
QPolygonF polygon1 = tx.mapToPolygon(r.toRect()).toPolygonF();
QPolygonF polygon2 = tx.map(QPolygonF(r));
if (polygon1.size() > 4)
polygon1.removeLast();
if (polygon2.size() > 4)
polygon2.removeLast();
QCOMPARE(polygon1, polygon2);
}
QTEST_APPLESS_MAIN(tst_QTransform)