Fix on systems with only 64 kb and compiled in CC_BUILD_LOWMEM, that trying to decode PNG would cause some systems to die or become corrupted from stack overflow

It was using ~65,984 bytes of memory on the stack, which was just slightly bigger than the total stack size of 65,536 bytes

By reusing buffer variable and therefore shrinking tmp variable, now 704 less bytes are allocated on the stack
This commit is contained in:
UnknownShadow200 2023-08-20 13:00:18 +10:00
parent ee12cd8ede
commit 4e418f1860

View File

@ -332,7 +332,7 @@ static void ComputeTransparency(struct Bitmap* bmp, BitmapCol col) {
/* TODO: Test a lot of .png files and ensure output is right */ /* TODO: Test a lot of .png files and ensure output is right */
cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) { cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) {
cc_uint8 tmp[PNG_PALETTE * 3]; cc_uint8 tmp[64];
cc_uint32 dataSize, fourCC; cc_uint32 dataSize, fourCC;
cc_result res; cc_result res;
@ -352,6 +352,7 @@ cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) {
cc_uint8 buffer[PNG_BUFFER_SIZE]; cc_uint8 buffer[PNG_BUFFER_SIZE];
cc_uint32 bufferRows, bufferLen; cc_uint32 bufferRows, bufferLen;
cc_uint32 bufferIdx, read, left; cc_uint32 bufferIdx, read, left;
cc_bool initedBuffer = false;
/* idat decompressor */ /* idat decompressor */
struct InflateState inflate; struct InflateState inflate;
@ -373,7 +374,7 @@ cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) {
ZLibHeader_Init(&zlibHeader); ZLibHeader_Init(&zlibHeader);
for (;;) { for (;;) {
res = Stream_Read(stream, tmp, 8); res = Stream_Read(stream, tmp, 8);
if (res) return res; if (res) return res;
dataSize = Stream_GetU32_BE(tmp + 0); dataSize = Stream_GetU32_BE(tmp + 0);
fourCC = Stream_GetU32_BE(tmp + 4); fourCC = Stream_GetU32_BE(tmp + 4);
@ -403,54 +404,53 @@ cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) {
bytesPerPixel = ((samplesPerPixel[col] * bitsPerSample) + 7) >> 3; bytesPerPixel = ((samplesPerPixel[col] * bitsPerSample) + 7) >> 3;
scanlineSize = ((samplesPerPixel[col] * bitsPerSample * bmp->width) + 7) >> 3; scanlineSize = ((samplesPerPixel[col] * bitsPerSample * bmp->width) + 7) >> 3;
scanlineBytes = scanlineSize + 1; /* Add 1 byte for filter byte of each scanline */ scanlineBytes = scanlineSize + 1; /* Add 1 byte for filter byte of each scanline */
Mem_Set(buffer, 0, scanlineBytes); /* Prior row should be 0 per PNG spec */
bufferIdx = scanlineBytes;
bufferRows = PNG_BUFFER_SIZE / scanlineBytes;
bufferLen = bufferRows * scanlineBytes;
} break; } break;
case PNG_FourCC('P','L','T','E'): { case PNG_FourCC('P','L','T','E'): {
if (dataSize > PNG_PALETTE * 3) return PNG_ERR_PAL_SIZE; if (dataSize > PNG_PALETTE * 3) return PNG_ERR_PAL_SIZE;
if ((dataSize % 3) != 0) return PNG_ERR_PAL_SIZE; if ((dataSize % 3) != 0) return PNG_ERR_PAL_SIZE;
res = Stream_Read(stream, tmp, dataSize);
res = Stream_Read(stream, buffer, dataSize);
if (res) return res; if (res) return res;
for (i = 0; i < dataSize; i += 3) { for (i = 0; i < dataSize; i += 3) {
palette[i / 3] &= BITMAPCOLOR_A_MASK; /* set RGB to 0 */ palette[i / 3] &= BITMAPCOLOR_A_MASK; /* set RGB to 0 */
palette[i / 3] |= tmp[i ] << BITMAPCOLOR_R_SHIFT; palette[i / 3] |= buffer[i ] << BITMAPCOLOR_R_SHIFT;
palette[i / 3] |= tmp[i + 1] << BITMAPCOLOR_G_SHIFT; palette[i / 3] |= buffer[i + 1] << BITMAPCOLOR_G_SHIFT;
palette[i / 3] |= tmp[i + 2] << BITMAPCOLOR_B_SHIFT; palette[i / 3] |= buffer[i + 2] << BITMAPCOLOR_B_SHIFT;
} }
} break; } break;
case PNG_FourCC('t','R','N','S'): { case PNG_FourCC('t','R','N','S'): {
if (col == PNG_COLOR_GRAYSCALE) { if (col == PNG_COLOR_GRAYSCALE) {
if (dataSize != 2) return PNG_ERR_TRANS_COUNT; if (dataSize != 2) return PNG_ERR_TRANS_COUNT;
res = Stream_Read(stream, tmp, dataSize);
res = Stream_Read(stream, buffer, dataSize);
if (res) return res; if (res) return res;
/* RGB is always two bytes */ /* RGB is always two bytes */
/* TODO is this right for 16 bits per channel images? */ /* TODO is this right for 16 bits per channel images? */
trnsCol = BitmapCol_Make(tmp[1], tmp[1], tmp[1], 0); trnsCol = BitmapCol_Make(buffer[1], buffer[1], buffer[1], 0);
} else if (col == PNG_COLOR_INDEXED) { } else if (col == PNG_COLOR_INDEXED) {
if (dataSize > PNG_PALETTE) return PNG_ERR_TRANS_COUNT; if (dataSize > PNG_PALETTE) return PNG_ERR_TRANS_COUNT;
res = Stream_Read(stream, tmp, dataSize);
res = Stream_Read(stream, buffer, dataSize);
if (res) return res; if (res) return res;
/* set alpha component of palette */ /* set alpha component of palette */
for (i = 0; i < dataSize; i++) { for (i = 0; i < dataSize; i++) {
palette[i] &= BITMAPCOLOR_RGB_MASK; /* set A to 0 */ palette[i] &= BITMAPCOLOR_RGB_MASK; /* set A to 0 */
palette[i] |= tmp[i] << BITMAPCOLOR_A_SHIFT; palette[i] |= buffer[i] << BITMAPCOLOR_A_SHIFT;
} }
} else if (col == PNG_COLOR_RGB) { } else if (col == PNG_COLOR_RGB) {
if (dataSize != 6) return PNG_ERR_TRANS_COUNT; if (dataSize != 6) return PNG_ERR_TRANS_COUNT;
res = Stream_Read(stream, tmp, dataSize);
res = Stream_Read(stream, buffer, dataSize);
if (res) return res; if (res) return res;
/* R,G,B are always two bytes */ /* R,G,B are always two bytes */
/* TODO is this right for 16 bits per channel images? */ /* TODO is this right for 16 bits per channel images? */
trnsCol = BitmapCol_Make(tmp[1], tmp[3], tmp[5], 0); trnsCol = BitmapCol_Make(buffer[1], buffer[3], buffer[5], 0);
} else { } else {
return PNG_ERR_TRANS_INVALID; return PNG_ERR_TRANS_INVALID;
} }
@ -466,6 +466,15 @@ cc_result Png_Decode(struct Bitmap* bmp, struct Stream* stream) {
} }
if (!bmp->scan0) return PNG_ERR_NO_DATA; if (!bmp->scan0) return PNG_ERR_NO_DATA;
/* Initialise buffer for decoding */
if (!initedBuffer) {
Mem_Set(buffer, 0, scanlineBytes); /* Prior row should be 0 per PNG spec */
bufferIdx = scanlineBytes;
bufferRows = PNG_BUFFER_SIZE / scanlineBytes;
bufferLen = bufferRows * scanlineBytes;
initedBuffer = true;
}
while (curY < bmp->height) { while (curY < bmp->height) {
/* Need to leave one row in buffer untouched for storing prior scanline. Illustrated example of process: /* Need to leave one row in buffer untouched for storing prior scanline. Illustrated example of process:
* |=====| #-----| |-----| #-----| |-----| * |=====| #-----| |-----| #-----| |-----|