fix: FontDecompressor OOM aborts on the render path (-fno-exceptions makes vector resize fatal) (#2526)

This commit is contained in:
Nick
2026-07-07 09:06:18 +03:00
committed by GitHub
parent 3c5c5e8aa7
commit 5776911af0
2 changed files with 38 additions and 25 deletions
+26 -20
View File
@@ -33,12 +33,24 @@ void FontDecompressor::freePageBuffer() {
}
void FontDecompressor::freeHotGroup() {
hotGroup.clear();
hotGroup.shrink_to_fit();
free(hotGroup);
hotGroup = nullptr;
hotGroupCapacity = 0;
hotGroupFont = nullptr;
hotGroupIndex = UINT16_MAX;
hotGlyphBuf.clear();
hotGlyphBuf.shrink_to_fit();
free(hotGlyphBuf);
hotGlyphBuf = nullptr;
hotGlyphBufCapacity = 0;
}
bool FontDecompressor::ensureCapacity(uint8_t*& buf, uint32_t& capacity, uint32_t needed) {
if (capacity >= needed) return true;
// Grow-only, free-then-malloc: every caller fully rewrites the buffer after a grow, so the
// old contents are dead -- freeing first gives the allocator its best shot on a tight heap.
free(buf);
buf = static_cast<uint8_t*>(malloc(needed)); // owned by FontDecompressor, freed in freeHotGroup()
capacity = buf ? needed : 0;
return buf != nullptr;
}
uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex) {
@@ -170,24 +182,20 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
}
// Check if hot group already has this group decompressed — if not, decompress it
if (!(!hotGroup.empty() && hotGroupFont == fontData && hotGroupIndex == groupIndex)) {
if (!(hotGroup != nullptr && hotGroupFont == fontData && hotGroupIndex == groupIndex)) {
stats.cacheMisses++;
const EpdFontGroup& group = fontData->groups[groupIndex];
hotGroup.resize(group.uncompressedSize);
if (hotGroup.empty()) {
// ensureCapacity may free the buffer, so the cached-group identity dies with it either way.
hotGroupFont = nullptr;
hotGroupIndex = UINT16_MAX;
if (!ensureCapacity(hotGroup, hotGroupCapacity, group.uncompressedSize)) {
LOG_ERR("FDC", "Failed to allocate %u bytes for hot group %u", group.uncompressedSize, groupIndex);
hotGroupFont = nullptr;
hotGroupIndex = UINT16_MAX;
stats.getBitmapTimeUs += micros() - tStart;
return nullptr;
}
if (!decompressGroup(fontData, groupIndex, hotGroup.data(), group.uncompressedSize)) {
hotGroup.clear();
hotGroup.shrink_to_fit();
hotGroupFont = nullptr;
hotGroupIndex = UINT16_MAX;
if (!decompressGroup(fontData, groupIndex, hotGroup, group.uncompressedSize)) {
stats.getBitmapTimeUs += micros() - tStart;
return nullptr;
}
@@ -200,18 +208,16 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
}
// Compact just the requested glyph from byte-aligned data into scratch buffer
if (glyph->dataLength > hotGlyphBuf.size()) {
hotGlyphBuf.resize(glyph->dataLength);
}
if (hotGlyphBuf.empty()) {
if (!ensureCapacity(hotGlyphBuf, hotGlyphBufCapacity, glyph->dataLength)) {
LOG_ERR("FDC", "Failed to allocate %u bytes for glyph scratch", (unsigned)glyph->dataLength);
stats.getBitmapTimeUs += micros() - tStart;
return nullptr;
}
uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex);
compactSingleGlyph(&hotGroup[alignedOff], hotGlyphBuf.data(), glyph->width, glyph->height);
compactSingleGlyph(&hotGroup[alignedOff], hotGlyphBuf, glyph->width, glyph->height);
stats.getBitmapTimeUs += micros() - tStart;
return hotGlyphBuf.data();
return hotGlyphBuf;
}
// --- Prewarm: pre-decompress glyph bitmaps for a page of text ---
+12 -5
View File
@@ -2,8 +2,6 @@
#include <InflateReader.h>
#include <vector>
#include "EpdFontData.h"
class FontDecompressor {
@@ -67,13 +65,22 @@ class FontDecompressor {
// Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path.
// Kept in byte-aligned format; individual glyphs are compacted on demand into hotGlyphBuf.
// Nothrow high-water malloc buffers, NOT std::vector: getBitmap() runs on the render path,
// and under -fno-exceptions a vector resize that hits OOM abort()s the firmware instead of
// failing (field crash: hotGroup.resize() -> std::bad_alloc -> abort with ~11 KB free).
// ensureCapacity() returns false on OOM so the caller can skip the glyph gracefully.
const EpdFontData* hotGroupFont = nullptr;
uint16_t hotGroupIndex = UINT16_MAX;
std::vector<uint8_t> hotGroup;
uint8_t* hotGroup = nullptr; // owned; freed in freeHotGroup()/dtor
uint32_t hotGroupCapacity = 0;
// Scratch buffer for compacting a single glyph from the hot group.
// Valid until the next getBitmap() call.
std::vector<uint8_t> hotGlyphBuf;
// Valid until the next getBitmap() call. Same ownership/OOM contract as hotGroup.
uint8_t* hotGlyphBuf = nullptr;
uint32_t hotGlyphBufCapacity = 0;
// Grow (never shrink) an owned buffer to at least `needed` bytes; false on OOM, buffer freed.
static bool ensureCapacity(uint8_t*& buf, uint32_t& capacity, uint32_t needed);
void freePageBuffer();
void freeHotGroup();