fix gradient cache race-condition and heap-use-after-free

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2021-11-29 11:44:23 +02:00
parent 1b601ad247
commit f4ceb2e759
3 changed files with 198 additions and 256 deletions

View file

@ -57,6 +57,198 @@ static inline QRgb qConvertRgb16To32(uint c)
| ((((c) << 8) & 0xf80000) | (((c) << 3) & 0x70000));
}
void QGradientData::generateGradientColorTable(const QGradient& gradient, int opacity)
{
QGradientStops stops = gradient.stops();
int stopCount = stops.count();
Q_ASSERT(stopCount > 0);
bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
if (stopCount == 2) {
uint first_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity);
uint second_color = ARGB_COMBINE_ALPHA(stops[1].second.rgba(), opacity);
qreal first_stop = stops[0].first;
qreal second_stop = stops[1].first;
if (second_stop < first_stop) {
qSwap(first_color, second_color);
qSwap(first_stop, second_stop);
}
if (colorInterpolation) {
first_color = PREMUL(first_color);
second_color = PREMUL(second_color);
}
int first_index = qRound(first_stop * (GRADIENT_STOPTABLE_SIZE-1));
int second_index = qRound(second_stop * (GRADIENT_STOPTABLE_SIZE-1));
uint red_first = qRed(first_color) << 16;
uint green_first = qGreen(first_color) << 16;
uint blue_first = qBlue(first_color) << 16;
uint alpha_first = qAlpha(first_color) << 16;
uint red_second = qRed(second_color) << 16;
uint green_second = qGreen(second_color) << 16;
uint blue_second = qBlue(second_color) << 16;
uint alpha_second = qAlpha(second_color) << 16;
int i = 0;
for (; i <= qMin(GRADIENT_STOPTABLE_SIZE, first_index); ++i) {
if (colorInterpolation)
colorTable[i] = first_color;
else
colorTable[i] = PREMUL(first_color);
}
if (i < second_index) {
qreal reciprocal = qreal(1) / (second_index - first_index);
int red_delta = qRound(int(red_second - red_first) * reciprocal);
int green_delta = qRound(int(green_second - green_first) * reciprocal);
int blue_delta = qRound(int(blue_second - blue_first) * reciprocal);
int alpha_delta = qRound(int(alpha_second - alpha_first) * reciprocal);
// rounding
red_first += 1 << 15;
green_first += 1 << 15;
blue_first += 1 << 15;
alpha_first += 1 << 15;
for (; i < qMin(GRADIENT_STOPTABLE_SIZE, second_index); ++i) {
red_first += red_delta;
green_first += green_delta;
blue_first += blue_delta;
alpha_first += alpha_delta;
const uint color = ((alpha_first << 8) & 0xff000000) | (red_first & 0xff0000)
| ((green_first >> 8) & 0xff00) | (blue_first >> 16);
if (colorInterpolation)
colorTable[i] = color;
else
colorTable[i] = PREMUL(color);
}
}
for (; i < GRADIENT_STOPTABLE_SIZE; ++i) {
if (colorInterpolation)
colorTable[i] = second_color;
else
colorTable[i] = PREMUL(second_color);
}
return;
}
uint current_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity);
if (stopCount == 1) {
current_color = PREMUL(current_color);
for (int i = 0; i < GRADIENT_STOPTABLE_SIZE; ++i)
colorTable[i] = current_color;
return;
}
// The position where the gradient begins and ends
qreal begin_pos = stops[0].first;
qreal end_pos = stops[stopCount-1].first;
int pos = 0; // The position in the color table.
uint next_color;
qreal incr = 1 / qreal(GRADIENT_STOPTABLE_SIZE); // the double increment.
qreal dpos = 1.5 * incr; // current position in gradient stop list (0 to 1)
// Up to first point
colorTable[pos++] = PREMUL(current_color);
while (dpos <= begin_pos) {
colorTable[pos] = colorTable[pos - 1];
++pos;
dpos += incr;
}
int current_stop = 0; // We always interpolate between current and current + 1.
qreal t; // position between current left and right stops
qreal t_delta; // the t increment per entry in the color table
if (dpos < end_pos) {
// Gradient area
while (dpos > stops[current_stop+1].first)
++current_stop;
if (current_stop != 0)
current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity);
next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity);
if (colorInterpolation) {
current_color = PREMUL(current_color);
next_color = PREMUL(next_color);
}
qreal diff = stops[current_stop+1].first - stops[current_stop].first;
qreal c = (diff == 0) ? qreal(0) : 256 / diff;
t = (dpos - stops[current_stop].first) * c;
t_delta = incr * c;
while (true) {
Q_ASSERT(current_stop < stopCount);
int dist = qRound(t);
int idist = 256 - dist;
if (colorInterpolation)
colorTable[pos] = INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist);
else
colorTable[pos] = PREMUL(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
++pos;
dpos += incr;
if (dpos >= end_pos)
break;
t += t_delta;
int skip = 0;
while (dpos > stops[current_stop+skip+1].first)
++skip;
if (skip != 0) {
current_stop += skip;
if (skip == 1)
current_color = next_color;
else
current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity);
next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity);
if (colorInterpolation) {
if (skip != 1)
current_color = PREMUL(current_color);
next_color = PREMUL(next_color);
}
qreal diff = stops[current_stop+1].first - stops[current_stop].first;
qreal c = (diff == 0) ? qreal(0) : 256 / diff;
t = (dpos - stops[current_stop].first) * c;
t_delta = incr * c;
}
}
}
// After last point
current_color = PREMUL(ARGB_COMBINE_ALPHA(stops[stopCount - 1].second.rgba(), opacity));
while (pos < GRADIENT_STOPTABLE_SIZE - 1) {
colorTable[pos] = current_color;
++pos;
}
// Make sure the last color stop is represented at the end of the table
colorTable[GRADIENT_STOPTABLE_SIZE - 1] = current_color;
}
/*
Destination fetch. This is simple as we don't have to do bounds checks or
transformations

View file

@ -182,9 +182,10 @@ struct QGradientData
#define GRADIENT_STOPTABLE_SIZE 1024
#define GRADIENT_STOPTABLE_SIZE_SHIFT 10
uint* colorTable; //[GRADIENT_STOPTABLE_SIZE];
uint colorTable[GRADIENT_STOPTABLE_SIZE];
bool alphaColor;
void generateGradientColorTable(const QGradient& g, int opacity);
};
struct QTextureData

View file

@ -3186,257 +3186,6 @@ QImage QRasterBuffer::bufferImage() const
}
#endif
class QGradientCache
{
public:
QGradientCache();
private:
struct CacheInfo
{
inline CacheInfo(QGradientStops s, int op, QGradient::InterpolationMode mode) :
stops(s), opacity(op), interpolationMode(mode) {}
uint buffer[GRADIENT_STOPTABLE_SIZE];
QGradientStops stops;
int opacity;
QGradient::InterpolationMode interpolationMode;
};
typedef QCache<quint64, CacheInfo> QGradientColorTableHash;
public:
inline const uint *getBuffer(const QGradient &gradient, int opacity) {
quint64 hash_val = opacity + gradient.interpolationMode();
QGradientStops stops = gradient.stops();
for (int i = 0; i < stops.size() && i <= 2; i++)
hash_val += stops[i].second.rgba();
QMutexLocker lock(&mutex);
CacheInfo* match = cache.object(hash_val);
if (!match)
return addCacheElement(hash_val, gradient, opacity);
return match->buffer;
}
inline int paletteSize() const { return GRADIENT_STOPTABLE_SIZE; }
protected:
inline void generateGradientColorTable(const QGradient& g,
uint *colorTable,
int size, int opacity) const;
uint *addCacheElement(quint64 hash_val, const QGradient &gradient, int opacity) {
CacheInfo *cache_entry = new CacheInfo(gradient.stops(), opacity, gradient.interpolationMode());
generateGradientColorTable(gradient, cache_entry->buffer, paletteSize(), opacity);
cache.insert(hash_val, cache_entry);
return cache_entry->buffer;
}
QGradientColorTableHash cache;
QMutex mutex;
};
QGradientCache::QGradientCache()
{
cache.setMaxCost(60);
}
void QGradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, int opacity) const
{
QGradientStops stops = gradient.stops();
int stopCount = stops.count();
Q_ASSERT(stopCount > 0);
bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation);
if (stopCount == 2) {
uint first_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity);
uint second_color = ARGB_COMBINE_ALPHA(stops[1].second.rgba(), opacity);
qreal first_stop = stops[0].first;
qreal second_stop = stops[1].first;
if (second_stop < first_stop) {
qSwap(first_color, second_color);
qSwap(first_stop, second_stop);
}
if (colorInterpolation) {
first_color = PREMUL(first_color);
second_color = PREMUL(second_color);
}
int first_index = qRound(first_stop * (GRADIENT_STOPTABLE_SIZE-1));
int second_index = qRound(second_stop * (GRADIENT_STOPTABLE_SIZE-1));
uint red_first = qRed(first_color) << 16;
uint green_first = qGreen(first_color) << 16;
uint blue_first = qBlue(first_color) << 16;
uint alpha_first = qAlpha(first_color) << 16;
uint red_second = qRed(second_color) << 16;
uint green_second = qGreen(second_color) << 16;
uint blue_second = qBlue(second_color) << 16;
uint alpha_second = qAlpha(second_color) << 16;
int i = 0;
for (; i <= qMin(GRADIENT_STOPTABLE_SIZE, first_index); ++i) {
if (colorInterpolation)
colorTable[i] = first_color;
else
colorTable[i] = PREMUL(first_color);
}
if (i < second_index) {
qreal reciprocal = qreal(1) / (second_index - first_index);
int red_delta = qRound(int(red_second - red_first) * reciprocal);
int green_delta = qRound(int(green_second - green_first) * reciprocal);
int blue_delta = qRound(int(blue_second - blue_first) * reciprocal);
int alpha_delta = qRound(int(alpha_second - alpha_first) * reciprocal);
// rounding
red_first += 1 << 15;
green_first += 1 << 15;
blue_first += 1 << 15;
alpha_first += 1 << 15;
for (; i < qMin(GRADIENT_STOPTABLE_SIZE, second_index); ++i) {
red_first += red_delta;
green_first += green_delta;
blue_first += blue_delta;
alpha_first += alpha_delta;
const uint color = ((alpha_first << 8) & 0xff000000) | (red_first & 0xff0000)
| ((green_first >> 8) & 0xff00) | (blue_first >> 16);
if (colorInterpolation)
colorTable[i] = color;
else
colorTable[i] = PREMUL(color);
}
}
for (; i < GRADIENT_STOPTABLE_SIZE; ++i) {
if (colorInterpolation)
colorTable[i] = second_color;
else
colorTable[i] = PREMUL(second_color);
}
return;
}
uint current_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity);
if (stopCount == 1) {
current_color = PREMUL(current_color);
for (int i = 0; i < size; ++i)
colorTable[i] = current_color;
return;
}
// The position where the gradient begins and ends
qreal begin_pos = stops[0].first;
qreal end_pos = stops[stopCount-1].first;
int pos = 0; // The position in the color table.
uint next_color;
qreal incr = 1 / qreal(size); // the double increment.
qreal dpos = 1.5 * incr; // current position in gradient stop list (0 to 1)
// Up to first point
colorTable[pos++] = PREMUL(current_color);
while (dpos <= begin_pos) {
colorTable[pos] = colorTable[pos - 1];
++pos;
dpos += incr;
}
int current_stop = 0; // We always interpolate between current and current + 1.
qreal t; // position between current left and right stops
qreal t_delta; // the t increment per entry in the color table
if (dpos < end_pos) {
// Gradient area
while (dpos > stops[current_stop+1].first)
++current_stop;
if (current_stop != 0)
current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity);
next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity);
if (colorInterpolation) {
current_color = PREMUL(current_color);
next_color = PREMUL(next_color);
}
qreal diff = stops[current_stop+1].first - stops[current_stop].first;
qreal c = (diff == 0) ? qreal(0) : 256 / diff;
t = (dpos - stops[current_stop].first) * c;
t_delta = incr * c;
while (true) {
Q_ASSERT(current_stop < stopCount);
int dist = qRound(t);
int idist = 256 - dist;
if (colorInterpolation)
colorTable[pos] = INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist);
else
colorTable[pos] = PREMUL(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
++pos;
dpos += incr;
if (dpos >= end_pos)
break;
t += t_delta;
int skip = 0;
while (dpos > stops[current_stop+skip+1].first)
++skip;
if (skip != 0) {
current_stop += skip;
if (skip == 1)
current_color = next_color;
else
current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity);
next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity);
if (colorInterpolation) {
if (skip != 1)
current_color = PREMUL(current_color);
next_color = PREMUL(next_color);
}
qreal diff = stops[current_stop+1].first - stops[current_stop].first;
qreal c = (diff == 0) ? qreal(0) : 256 / diff;
t = (dpos - stops[current_stop].first) * c;
t_delta = incr * c;
}
}
}
// After last point
current_color = PREMUL(ARGB_COMBINE_ALPHA(stops[stopCount - 1].second.rgba(), opacity));
while (pos < size - 1) {
colorTable[pos] = current_color;
++pos;
}
// Make sure the last color stop is represented at the end of the table
colorTable[size - 1] = current_color;
}
Q_GLOBAL_STATIC(QGradientCache, qt_gradient_cache)
void QSpanData::init(QRasterBuffer *rb, const QRasterPaintEngine *pe)
{
rasterBuffer = rb;
@ -3471,7 +3220,7 @@ void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode
type = LinearGradient;
const QLinearGradient *g = static_cast<const QLinearGradient *>(brush.gradient());
gradient.alphaColor = !brush.isOpaque() || alpha != 256;
gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha));
gradient.generateGradientColorTable(*g, alpha);
gradient.spread = g->spread();
QLinearGradientData &linearData = gradient.linear;
@ -3488,7 +3237,7 @@ void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode
type = RadialGradient;
const QRadialGradient *g = static_cast<const QRadialGradient *>(brush.gradient());
gradient.alphaColor = !brush.isOpaque() || alpha != 256;
gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha));
gradient.generateGradientColorTable(*g, alpha);
gradient.spread = g->spread();
QRadialGradientData &radialData = gradient.radial;
@ -3509,7 +3258,7 @@ void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode
type = ConicalGradient;
const QConicalGradient *g = static_cast<const QConicalGradient *>(brush.gradient());
gradient.alphaColor = !brush.isOpaque() || alpha != 256;
gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha));
gradient.generateGradientColorTable(*g, alpha);
gradient.spread = QGradient::RepeatSpread;
QConicalGradientData &conicalData = gradient.conical;