diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 815faac..5f2bf8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,19 +4,34 @@ on: - pull_request jobs: build: + name: ${{ matrix.name }} runs-on: ${{ matrix.os }} strategy: matrix: - os: - - ubuntu-22.04 - - macos-12 - - windows-2022 + include: + - name: "Linux x64 (Ubuntu 22.04)" + os: ubuntu-22.04 + config: {} + - name: "Linux 32-bit (Ubuntu 22.04)" + os: ubuntu-22.04 + config: { cflags: "-m32"} + - name: "macOS 12" + os: macos-12 + config: {} + - name: "Windows 2022" + os: windows-2022 + config: {} + env: + CFLAGS: ${{ matrix.config.cflags }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - name: Dependencies (ubuntu) if: contains(matrix.os, 'ubuntu') run: sudo apt update && sudo apt install -y gifsicle valgrind meson + - name: Dependencies (ubuntu 32-bit) + if: contains(matrix.name, '32-bit') + run: sudo apt update && sudo apt install -y gcc-multilib - name: Dependencies (macos) if: contains(matrix.os, 'macos') run: brew install gifsicle meson @@ -33,9 +48,9 @@ jobs: run: for f in build/*.gif; do gifsicle --no-ignore-errors --info $f || exit 1; done shell: bash - name: Valgrind - if: contains(matrix.os, 'ubuntu') + if: contains(matrix.name, 'Linux x64') run: meson test -C build --list | grep -v zip | grep -v checksums | sed 's/^/"/;s/$/"/' | xargs meson test -C build/ --wrap "valgrind --memcheck:leak-check=full --memcheck:show-leak-kinds=definite --memcheck:error-exitcode=1" shell: bash - name: Valgrind output - if: contains(matrix.os, 'ubuntu') + if: contains(matrix.name, 'Linux x64') run: cat build/meson-logs/testlog-valgrind.txt diff --git a/meson.build b/meson.build index 62a6b6d..cbdef5f 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'libcgif', 'c', - version : '0.4.0', + version : '0.4.1', license : 'MIT', default_options : ['c_std=c99'], ) diff --git a/src/cgif_raw.c b/src/cgif_raw.c index 3f9ed95..b059ccc 100644 --- a/src/cgif_raw.c +++ b/src/cgif_raw.c @@ -323,8 +323,8 @@ static int LZW_GenerateStream(LZWResult* pResult, const uint32_t numPixel, const // pack the generated LZW data into blocks of 255 bytes uint8_t *byteList; // lzw-data packed in byte-list uint8_t *byteListBlock; // lzw-data packed in byte-list with 255-block structure - uint64_t MaxByteListLen = MAX_CODE_LEN*lzwPos/8ul +2ul +1ul; // conservative upper bound - uint64_t MaxByteListBlockLen = MAX_CODE_LEN*lzwPos*(BLOCK_SIZE+1ul)/8ul/BLOCK_SIZE +2ul +1ul +1ul; // conservative upper bound + uint64_t MaxByteListLen = MAX_CODE_LEN * lzwPos / 8ull + 2ull + 1ull; // conservative upper bound + uint64_t MaxByteListBlockLen = MAX_CODE_LEN * lzwPos * (BLOCK_SIZE + 1ull) / 8ull / BLOCK_SIZE + 2ull + 1ull +1ull; // conservative upper bound byteList = malloc(MaxByteListLen); // TBD check return value of malloc byteListBlock = malloc(MaxByteListBlockLen); // TBD check return value of malloc bytePos = create_byte_list(byteList,lzwPos, pContext->pLZWData, initDictLen, initCodeLen); diff --git a/tests/meson.build b/tests/meson.build index 4d8ff32..855e3d6 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,6 +30,7 @@ tests = [ { 'name' : 'min_size', 'seed_should_fail' : false}, { 'name' : 'more_than_256_colors', 'seed_should_fail' : false}, { 'name' : 'noise256', 'seed_should_fail' : false}, + { 'name' : 'noise256_large', 'seed_should_fail' : false}, { 'name' : 'noise6', 'seed_should_fail' : false}, { 'name' : 'noise6_interlaced', 'seed_should_fail' : false}, { 'name' : 'noloop', 'seed_should_fail' : false}, diff --git a/tests/noise256_large.c b/tests/noise256_large.c new file mode 100644 index 0000000..e4d9b40 --- /dev/null +++ b/tests/noise256_large.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include "cgif.h" + +#define WIDTH 3000 +#define HEIGHT 3000 + +static uint64_t seed; + +// unsigned integer overflow expected +__attribute__((no_sanitize("integer"))) +int psdrand(void) { + // simple pseudo random function from musl libc + seed = 6364136223846793005ULL * seed + 1; + return seed >> 33; +} + +int main(void) { + CGIF* pGIF; + CGIF_Config gConfig; + CGIF_FrameConfig fConfig; + uint8_t* pImageData; + cgif_result r; + uint8_t aPalette[256 * 3]; + + seed = 22; + for(int i = 0; i < 256; ++i) { + aPalette[i * 3] = psdrand() % 256; + aPalette[i * 3 + 1] = psdrand() % 256; + aPalette[i * 3 + 2] = psdrand() % 256; + } + memset(&gConfig, 0, sizeof(CGIF_Config)); + memset(&fConfig, 0, sizeof(CGIF_FrameConfig)); + gConfig.width = WIDTH; + gConfig.height = HEIGHT; + gConfig.pGlobalPalette = aPalette; + gConfig.numGlobalPaletteEntries = 256; + gConfig.path = "noise256_large.gif"; + // + // create new GIF + pGIF = cgif_newgif(&gConfig); + if(pGIF == NULL) { + fputs("failed to create new GIF via cgif_newgif()\n", stderr); + return 1; + } + // + // add frames to GIF + pImageData = malloc(WIDTH * HEIGHT); + for(int i = 0; i < WIDTH * HEIGHT; ++i) pImageData[i] = psdrand() % 256; + fConfig.pImageData = pImageData; + r = cgif_addframe(pGIF, &fConfig); + free(pImageData); + // + // write GIF to file + r = cgif_close(pGIF); // free allocated space at the end of the session + + // check for errors + if(r != CGIF_OK) { + fprintf(stderr, "failed to create GIF. error code: %d\n", r); + return 2; + } + return 0; +} diff --git a/tests/tests.sha256 b/tests/tests.sha256 index ba90fbf..a206bae 100644 --- a/tests/tests.sha256 +++ b/tests/tests.sha256 @@ -21,6 +21,7 @@ eeb9acd181da401748c9f39c59dbb5ecd71fd6f8f1685002f767de2ec0329bf4 min_color_tabl b263ce6bde416426b6676c320846f73870d40b2125bfc9630087c3c54df21ac8 min_size.gif 5244e4ca77ea3923d27e790b9dddb6032a686e0272ba3d78ff5fa4af0523647b more_than_256_colors.gif 11c8de343ccd528e319afbe3ec0d0c657c1e6292faf6cb318713bcaf2979ab0d noise256.gif +75a2a7b04a21b785977fb2506e6468478418703835876755e78453223820b924 noise256_large.gif ff1da19b0448af07d8561f0ee62b184f99ac7dcb75f1a2215474190ec6648901 noise6.gif 14cd8b2486e725d4136e1af656aa2398a882694fd1774ebfb23c57dae5ae00da noise6_interlaced.gif 98ec5ef9223f2c51bc2f4f94da4468f3b1e3c537a64d8050649787aaf433d13d noloop.gif