diff --git a/image-sixel.c b/image-sixel.c index 1d814015c..9b0b412ae 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -823,7 +823,7 @@ static void sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, size_t slen) { - while (*used + slen >= *len + 1) { + while (*used + slen + 1 > *len) { *buf = xreallocarray(*buf, 2, *len); (*len) *= 2; } diff --git a/regress/image-sixel-buffer.c b/regress/image-sixel-buffer.c new file mode 100644 index 000000000..d6869bc55 --- /dev/null +++ b/regress/image-sixel-buffer.c @@ -0,0 +1,72 @@ +/* Exercise SIXEL output lengths at the encoder's allocation boundary. */ + +#undef NDEBUG +#include + +#include "../tmux.h" + +#ifdef ENABLE_IMAGES +#include "../image-sixel.c" +#include "../xmalloc.c" + +/* Abort if an allocation or formatting check fails. */ +void +fatal(__unused const char *fmt, ...) +{ + abort(); +} + +/* Abort if an allocation or formatting check fails. */ +void +fatalx(__unused const char *fmt, ...) +{ + abort(); +} + +/* Encode lengths below, at and above the initial 8192-byte allocation. */ +int +main(void) +{ + struct sixel_image image = { 0 }; + u_int colour, width, x; + size_t size; + char *data; + + colour = (2u << 25) | (100u << 16) | (100u << 8) | 100u; + image.sy = 2; + image.ncolours = 1; + image.used_colours = 1; + image.colours = &colour; + image.lines = xcalloc(2, sizeof *image.lines); + + /* Headers and controls add 26 bytes to these uncompressed rasters. */ + for (width = 8165; width <= 8167; width++) { + image.sx = width; + image.lines[0].sx = width; + image.lines[1].sx = width; + image.lines[0].pixels = xcalloc(width, sizeof(uint16_t)); + image.lines[1].pixels = xcalloc(width, sizeof(uint16_t)); + + /* Alternate row bits so adjacent columns cannot be compressed. */ + for (x = 0; x < width; x++) + image.lines[x % 2].pixels[x] = 1; + data = sixel_print(&image, NULL, &size); + assert(data != NULL); + assert(size == width + 26); + assert(data[size] == '\0'); + assert(strlen(data) == size); + free(data); + free(image.lines[0].pixels); + free(image.lines[1].pixels); + } + free(image.lines); + return (0); +} +#else +/* Skip the encoder check when image support is disabled. */ +int +main(void) +{ + return (0); +} +#endif diff --git a/regress/image-sixel-buffer.mk b/regress/image-sixel-buffer.mk new file mode 100644 index 000000000..e76d1457c --- /dev/null +++ b/regress/image-sixel-buffer.mk @@ -0,0 +1,15 @@ +# Use the configured compiler and flags for the standalone encoder check. + +.PHONY: sixel-buffer-probe sixel-buffer-test + +sixel-buffer-probe: + $(CC) $(CFLAGS) $(LDFLAGS) -fsanitize=address \ + -o "$(SIXEL_TEST_DIR)/probe" "$(SIXEL_TEST_DIR)/probe.c" + +sixel-buffer-test: + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -UNDEBUG \ + -ffunction-sections -fdata-sections -fsanitize=address \ + -fno-omit-frame-pointer $(SIXEL_TEST_LDFLAGS) $(LDFLAGS) \ + -o "$(SIXEL_TEST_DIR)/test" "$(srcdir)/regress/image-sixel-buffer.c" \ + "$(srcdir)/compat/reallocarray.c" diff --git a/regress/image-sixel-buffer.sh b/regress/image-sixel-buffer.sh new file mode 100644 index 000000000..142a5715f --- /dev/null +++ b/regress/image-sixel-buffer.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Catch a trailing NUL written beyond an exactly full SIXEL output buffer. + +PATH=/bin:/usr/bin +export PATH + +SIXEL_TEST_DIR=$(mktemp -d) || exit 1 +trap 'rm -rf "$SIXEL_TEST_DIR"' 0 1 15 +export SIXEL_TEST_DIR +ASAN_OPTIONS=detect_leaks=0 +export ASAN_OPTIONS + +case $(uname -s) in +Darwin) SIXEL_TEST_LDFLAGS=-Wl,-dead_strip ;; +*) SIXEL_TEST_LDFLAGS=-Wl,--gc-sections ;; +esac +export SIXEL_TEST_LDFLAGS + +# Skip when the configured compiler or its runtime cannot use ASan. +printf 'int main(void) { return 0; }\n' >"$SIXEL_TEST_DIR/probe.c" +if ! make -s -C .. -f Makefile -f regress/image-sixel-buffer.mk \ + sixel-buffer-probe >"$SIXEL_TEST_DIR/probe.log" 2>&1; then + exit 0 +fi +if ! "$SIXEL_TEST_DIR/probe" >"$SIXEL_TEST_DIR/probe.log" 2>&1; then + exit 0 +fi + +make -s -C .. -f Makefile -f regress/image-sixel-buffer.mk \ + sixel-buffer-test || exit 1 +"$SIXEL_TEST_DIR/test" || exit 1