Skip to content

Commit c0ee6cd

Browse files
authored
add malloc failure test for cgif API (#94)
* add malloc failure test for cgif API * set aFrames[2] to NULL to prevent double-free on allocation failure * address review feedback * address review feedback
1 parent 946fe13 commit c0ee6cd

4 files changed

Lines changed: 166 additions & 0 deletions

File tree

src/cgif.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
550550
// keep the flushed frame in memory, as we might need it to write the next one.
551551
pGIF->aFrames[0] = pGIF->aFrames[1];
552552
pGIF->aFrames[1] = pGIF->aFrames[2];
553+
pGIF->aFrames[2] = NULL;
553554
}
554555
// create new Frame struct + make a deep copy of pConfig.
555556
pNewFrame = malloc(sizeof(CGIF_Frame));

tests/ealloc.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include <stdlib.h>
2+
#include <stdint.h>
3+
#include <string.h>
4+
#include <stdio.h>
5+
6+
#include "cgif.h"
7+
#include "cgif_raw.h"
8+
9+
#define WIDTH 2
10+
#define HEIGHT 2
11+
12+
/* malloc failure injection */
13+
static int fail_after;
14+
static int malloc_count;
15+
16+
static void* cgif_test_malloc(size_t size) {
17+
if(fail_after > 0) {
18+
++malloc_count;
19+
if(malloc_count == fail_after) {
20+
return NULL;
21+
}
22+
}
23+
return malloc(size);
24+
}
25+
26+
/* redirect malloc calls inside library sources to our wrapper */
27+
#define malloc(s) cgif_test_malloc(s)
28+
#include "../src/cgif_raw.c"
29+
/* avoid duplicate static function name */
30+
#define calcNextPower2Ex cgif_calcNextPower2Ex
31+
#include "../src/cgif.c"
32+
#undef calcNextPower2Ex
33+
#undef malloc
34+
35+
/* no-op write callback */
36+
static int writeFn(void* pContext, const uint8_t* pData, const size_t numBytes) {
37+
(void)pContext;
38+
(void)pData;
39+
(void)numBytes;
40+
return 0;
41+
}
42+
43+
int main(void) {
44+
CGIF* pGIF;
45+
CGIF_Config gConfig;
46+
CGIF_FrameConfig fConfig;
47+
cgif_result r;
48+
uint8_t aPalette[] = {
49+
0x00, 0x00, 0x00, // black
50+
0xFF, 0xFF, 0xFF, // white
51+
};
52+
uint8_t aImageData0[WIDTH * HEIGHT];
53+
uint8_t aImageData1[WIDTH * HEIGHT];
54+
memset(aImageData0, 0, sizeof(aImageData0));
55+
memset(aImageData1, 1, sizeof(aImageData1));
56+
57+
for(fail_after = 1; ; ++fail_after) {
58+
memset(&gConfig, 0, sizeof(gConfig));
59+
gConfig.pWriteFn = writeFn;
60+
gConfig.width = WIDTH;
61+
gConfig.height = HEIGHT;
62+
gConfig.pGlobalPalette = aPalette;
63+
gConfig.numGlobalPaletteEntries = 2;
64+
gConfig.attrFlags = CGIF_ATTR_IS_ANIMATED;
65+
66+
malloc_count = 0;
67+
68+
pGIF = cgif_newgif(&gConfig);
69+
if(malloc_count >= fail_after) {
70+
if(pGIF != NULL) {
71+
fprintf(stderr, "expected NULL from cgif_newgif (fail_after=%d)\n", fail_after);
72+
return 1;
73+
}
74+
continue;
75+
}
76+
if(pGIF == NULL) {
77+
fputs("unexpected NULL from cgif_newgif\n", stderr);
78+
return 1;
79+
}
80+
81+
// frame 1: local color table + interlaced
82+
memset(&fConfig, 0, sizeof(fConfig));
83+
fConfig.pImageData = aImageData0;
84+
fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE | CGIF_FRAME_ATTR_INTERLACED;
85+
fConfig.pLocalPalette = aPalette;
86+
fConfig.numLocalPaletteEntries = 2;
87+
r = cgif_addframe(pGIF, &fConfig);
88+
if(malloc_count >= fail_after) {
89+
if(r != CGIF_EALLOC) {
90+
fprintf(stderr, "expected CGIF_EALLOC from cgif_addframe (frame 1), got: %d\n", r);
91+
return 1;
92+
}
93+
cgif_close(pGIF);
94+
continue;
95+
}
96+
if(r != CGIF_OK) {
97+
fprintf(stderr, "unexpected error from cgif_addframe (frame 1): %d\n", r);
98+
return 1;
99+
}
100+
101+
// frame 2: diff window optimization
102+
memset(&fConfig, 0, sizeof(fConfig));
103+
fConfig.pImageData = aImageData1;
104+
fConfig.genFlags = CGIF_FRAME_GEN_USE_DIFF_WINDOW;
105+
r = cgif_addframe(pGIF, &fConfig);
106+
if(malloc_count >= fail_after) {
107+
if(r != CGIF_EALLOC) {
108+
fprintf(stderr, "expected CGIF_EALLOC from cgif_addframe (frame 2), got: %d\n", r);
109+
return 1;
110+
}
111+
cgif_close(pGIF);
112+
continue;
113+
}
114+
if(r != CGIF_OK) {
115+
fprintf(stderr, "unexpected error from cgif_addframe (frame 2): %d\n", r);
116+
return 1;
117+
}
118+
119+
// frame 3: transparency optimization (triggers queue flush + separate malloc path)
120+
memset(&fConfig, 0, sizeof(fConfig));
121+
fConfig.pImageData = aImageData0;
122+
fConfig.genFlags = CGIF_FRAME_GEN_USE_TRANSPARENCY;
123+
r = cgif_addframe(pGIF, &fConfig);
124+
if(malloc_count >= fail_after) {
125+
if(r != CGIF_EALLOC) {
126+
fprintf(stderr, "expected CGIF_EALLOC from cgif_addframe (frame 3), got: %d\n", r);
127+
return 1;
128+
}
129+
cgif_close(pGIF);
130+
continue;
131+
}
132+
if(r != CGIF_OK) {
133+
fprintf(stderr, "unexpected error from cgif_addframe (frame 3): %d\n", r);
134+
return 1;
135+
}
136+
137+
r = cgif_close(pGIF);
138+
if(malloc_count >= fail_after) {
139+
if(r != CGIF_EALLOC) {
140+
fprintf(stderr, "expected CGIF_EALLOC from cgif_close, got: %d (fail_after=%d)\n", r, fail_after);
141+
return 1;
142+
}
143+
continue;
144+
}
145+
if(r != CGIF_OK) {
146+
fprintf(stderr, "unexpected error from cgif_close: %d\n", r);
147+
return 1;
148+
}
149+
break;
150+
}
151+
152+
return 0;
153+
}

tests/ealloc_raw.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ int main(void) {
9191
}
9292
break;
9393
}
94+
// our injected failure was hit during close (e.g. LZW encoding)
95+
if(r != CGIF_EALLOC) {
96+
fprintf(stderr, "expected CGIF_EALLOC from cgif_raw_close, got: %d (n=%d)\n", r, fail_after);
97+
return 1;
98+
}
9499
}
95100

96101
return 0;

tests/meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ foreach t : tests_index + tests_rgb
7676
endforeach
7777

7878
# malloc failure tests (compile source directly to intercept malloc)
79+
test_ealloc_exe = executable(
80+
'test_ealloc',
81+
'ealloc.c',
82+
include_directories : ['../inc/'],
83+
)
84+
test('ealloc', test_ealloc_exe, priority : 0)
85+
7986
test_ealloc_raw_exe = executable(
8087
'test_ealloc_raw',
8188
'ealloc_raw.c',

0 commit comments

Comments
 (0)