From 178573a12863d7d50149f01892193eada528b206 Mon Sep 17 00:00:00 2001 From: acqn Date: Tue, 17 Oct 2023 17:57:19 +0800 Subject: [PATCH] Fixed inlined strlen when it takes a string literal with extra characters after the first '\0'. Added testcases. --- src/cc65/stdfunc.c | 8 +- test/val/bug2207.c | 569 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 575 insertions(+), 2 deletions(-) create mode 100644 test/val/bug2207.c diff --git a/src/cc65/stdfunc.c b/src/cc65/stdfunc.c index 246bce192..6d74dd386 100644 --- a/src/cc65/stdfunc.c +++ b/src/cc65/stdfunc.c @@ -833,7 +833,7 @@ static void StdFunc_strcmp (FuncDesc* F attribute ((unused)), ExprDesc* Expr) */ if (ED_IsLocLiteral (&Arg2.Expr) && IS_Get (&WritableStrings) == 0 && - GetLiteralSize (Arg2.Expr.V.LVal) == 1 && + GetLiteralSize (Arg2.Expr.V.LVal) >= 1 && GetLiteralStr (Arg2.Expr.V.LVal)[0] == '\0') { /* Drop the generated code so we have the first argument in the @@ -1226,9 +1226,13 @@ static void StdFunc_strlen (FuncDesc* F attribute ((unused)), ExprDesc* Expr) ** at runtime. */ if (ED_IsLocLiteral (&Arg) && IS_Get (&WritableStrings) == 0) { + /* Get the length of the C string within the string literal. + ** Note: Keep in mind that the literal could contain '\0' in it. + */ + size_t Len = strnlen (GetLiteralStr (Arg.V.LVal), GetLiteralSize (Arg.V.LVal) - 1); /* Constant string literal */ - ED_MakeConstAbs (Expr, GetLiteralSize (Arg.V.LVal) - 1, type_size_t); + ED_MakeConstAbs (Expr, Len, type_size_t); /* We don't need the literal any longer */ ReleaseLiteral (Arg.V.LVal); diff --git a/test/val/bug2207.c b/test/val/bug2207.c new file mode 100644 index 000000000..df4887fe3 --- /dev/null +++ b/test/val/bug2207.c @@ -0,0 +1,569 @@ +#include +#include +#include +#include + + + +/********* Macros *********/ + +#define DEBUG_DETAIL 0 + +/* TODO: enable these when they can be compiled */ +#define SKIP_VOID_RETURN_VALUE_TESTS 1 +#define SKIP_INLINED_ARG_SIDE_EFFECT_TESTS 1 + +#define CHECK(C) \ + if (!(C)) { \ + ++failures; \ + print_header(); \ + printf(" failed: expected %s\n", #C); \ + } + +#define CHECK_RANGE(L, R, D, N) \ + index = my_memcmp(L, R, D, N); \ + if (index >= 0) { \ + ++failures; \ + print_header(); \ + printf(" failed: %s vs %s dismatch at [%d]\n", #L, #R, index); \ + } + +#define MEM_SIZE 512 +#define STACK_SIZE 160 +#define ZP_SIZE 8 +#define MAGIC_SIZE 129 + +#define BROKEN_STR "hello\0!" +#define BROKEN_STR_SIZE 6 +#define BROKEN_STR_LEN 5 + + + +/********* Data *********/ + +unsigned failures; +int need_header = 1; +const char* test_header; + +static int x; +static int y; +static int z; +static int index; + +static char mem_dst[MEM_SIZE]; +static char mem_src[MEM_SIZE]; +static char mem_ori[MEM_SIZE]; + +#pragma data-name(push, "ZEROPAGE", "zp") +#pragma bss-name(push, "ZEROPAGE", "zp") +static char zp_src[ZP_SIZE]; +static char zp_dst[ZP_SIZE]; +static char zp_ori[ZP_SIZE]; +static char* p_zp_src; +static char* p_zp_dst; +static char* p_zp_ori; +#pragma bss-name(pop) +#pragma data-name(pop) + + + +/********* Helpers *********/ + +void my_memfill(void *dst, int init, size_t size) +{ + unsigned i; + + for (i = 0; i < size; ++i) + { + ((char*)dst)[i] = init; + init += 3; + } +} + +void my_memset(void *dst, int val, size_t size) +{ + unsigned i; + + for (i = 0; i < size; ++i) + { + ((unsigned char *)dst)[i] = val; + } +} + +void my_memcpy(void *dst, const void *src, size_t size) +{ + unsigned i; + + for (i = 0; i < size; ++i) + { + ((char*)dst)[i] = ((char*)src)[i]; + } +} + +int my_memcmp(const void *dst, const void *src, int diff, size_t size) +{ + unsigned i; + + diff %= 256; + for (i = 0; i < size; ++i) + { + if ((unsigned char)(((unsigned char*)dst)[i] - ((unsigned char*)src)[i]) != diff) + { +#if DEBUG_DETAIL + /* DEBUG */ + printf("%d vs %d\n", (unsigned char)(((unsigned char*)dst)[i] - ((unsigned char*)src)[i]), diff); +#endif + return i; + } + } + + return -1; +} + +void* mul_two(void* p, int* px) +{ + *px *= 2; + return p; +} + +void* add_one(void* p, int* px) +{ + *px += 1; + return p; +} + +void* negate(void* p, int* px) +{ + *px = -*px; + return p; +} + +void set_header(const char* name) +{ + if (need_header == 0) + { + printf("\n"); + } + test_header = name; + need_header = 1; +} + +void print_header(void) +{ + if (need_header) + { + need_header = 0; + printf("%s test\n", test_header); + } +} + + + +/********* Tests *********/ + +/* memcpy */ +void test_memcpy(void) +{ + const char *name = 0; + unsigned size = 0; + void *p; + + /* init */ + my_memfill(mem_ori, 1000, sizeof mem_ori); + my_memfill(zp_ori, 1000, sizeof zp_ori); + p_zp_src = zp_src; + p_zp_dst = zp_dst; + p_zp_ori = zp_ori; + +#if !SKIP_INLINED_ARG_SIDE_EFFECT_TESTS + /* arg3 == 0 */ + set_header("p = memcpy(arg1, arg2, 0)"); + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + x = 42; + y = -42; + z = 36; + p = memcpy(mul_two(mem_dst, &x), add_one(mem_src, &y), (negate(0, &z), 0)); + CHECK(p == mem_dst); + CHECK(x == 84); + CHECK(y == -41); + CHECK(z == -36); + CHECK_RANGE(mem_dst, mem_ori, 2000, size); + CHECK_RANGE(mem_src, mem_ori, 1000, size); +#endif + +#if !SKIP_INLINED_ARG_SIDE_EFFECT_TESTS + /* Check if the arguments are still generated if the return value is unused. + ** We have this question since the first argument could be directly used as + ** the return value when this function gets inlined. + */ +#if !SKIP_VOID_RETURN_VALUE_TESTS + set_header("(void)memcpy(arg1, arg2, 0)"); +#else + set_header("memcpy(arg1, arg2, 0)"); +#endif + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + x = 42; + y = -42; + z = 36; +#if !SKIP_VOID_RETURN_VALUE_TESTS + (void) +#endif + memcpy(mul_two(mem_dst, &x), add_one(mem_src, &y), (negate(0, &z), 0)); + CHECK(x == 84); + CHECK(y == -41); + CHECK(z == -36); + CHECK_RANGE(mem_dst, mem_ori, 2000, size); + CHECK_RANGE(mem_src, mem_ori, 1000, size); +#endif + + /* The memcpy inliner will give up with further optimizations if any of + ** the arguments have side effects. + */ + + /* arg1: const addr, arg2: const addr, arg3 <= 129 */ + set_header("memcpy(const_addr_1, const_addr_2, 129) w/ side-effects"); + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + x = 42; + y = -42; + z = 36; + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(mul_two(mem_dst, &x), add_one(mem_src, &y), (negate(0, &z), MAGIC_SIZE)); + size = MAGIC_SIZE; + CHECK(p == mem_dst); + CHECK(x == 84); + CHECK(y == -41); + CHECK(z == -36); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, MEM_SIZE - size); + + /* arg1: (void*)ptr, arg2: const_addr_2 */ + set_header("memcpy((void*)ptr, const_addr_2, 129)"); + { + void *ptr = mem_dst; + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy((void*)ptr, mem_src, MAGIC_SIZE); + size = MAGIC_SIZE; + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, MEM_SIZE - size); + } + + /* arg1: const addr, arg2: const addr, arg3 <= 129 */ + set_header("memcpy(const_addr_1, const_addr_2, 129)"); + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(mem_dst, mem_src, MAGIC_SIZE); + size = MAGIC_SIZE; + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, MEM_SIZE - size); + + /* arg1: const addr, arg2: const addr, arg3 <= 256 */ + set_header("memcpy(const_addr_1, const_addr_2, 256)"); + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + my_memfill(mem_src, 2000, size); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(mem_dst, mem_src, 256); + size = 256; + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, size - size); + + /* arg1: ptr on zp, arg2: ptr on zp, arg3 <= 256 */ + set_header("memcpy(p_on_zp_1, p_on_zp_2, 4)"); + /* We cannot allocate 256 bytes on the zeropage, unfortunately */ + my_memfill(zp_dst, 3000, ZP_SIZE); + my_memfill(zp_src, 2000, ZP_SIZE); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(p_zp_dst, p_zp_src, ZP_SIZE / 2); + size = ZP_SIZE / 2; + CHECK(p == zp_dst); + CHECK_RANGE(zp_dst, zp_ori, 1000, size); + CHECK_RANGE(zp_dst + size, zp_ori + size, 2000, ZP_SIZE - size); + + /* arg1: on stack, arg2: const addr, arg3 <= 129 */ + set_header("memcpy(on_stack_1, const_addr_2, 129)"); + { + char sp_dst[STACK_SIZE]; + my_memfill(sp_dst, 3000, STACK_SIZE); + my_memfill(mem_src, 2000, STACK_SIZE); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(sp_dst, mem_src, MAGIC_SIZE); + size = MAGIC_SIZE; + CHECK(p == sp_dst); + CHECK_RANGE(sp_dst, mem_ori, 1000, size); + CHECK_RANGE(sp_dst + size, mem_ori + size, 2000, STACK_SIZE - size); + } + + /* arg1: on stack, arg2: const addr, arg3 <= 256 */ + set_header("memcpy(on_stack_1, const_addr_2, 144)"); + { + char sp_dst[STACK_SIZE]; + /* We cannot allocate 256 bytes on the stack, unfortunately */ + my_memfill(sp_dst, 3000, STACK_SIZE); + my_memfill(mem_src, 2000, STACK_SIZE); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(sp_dst, mem_src, 144); + size = 144; + CHECK(p == sp_dst); + CHECK_RANGE(sp_dst, mem_ori, 1000, size); + CHECK_RANGE(sp_dst + size, mem_ori + size, 2000, STACK_SIZE - size); + } + + /* arg1: const addr, arg2: on stack, arg3 <= 129 */ + set_header("memcpy(const_addr_1, on_stack_2, 129)"); + { + char sp_src[STACK_SIZE]; + /* We cannot allocate 256 bytes on the stack, unfortunately */ + my_memfill(mem_dst, 3000, STACK_SIZE); + my_memfill(sp_src, 2000, STACK_SIZE); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(mem_dst, sp_src, 129); + size = 129; + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, STACK_SIZE - size); + } + + /* arg1: const addr, arg2: on stack, arg3 <= 256 */ + set_header("memcpy(const_addr_1, on_stack_2, 144)"); + { + char sp_src[STACK_SIZE]; + /* We cannot allocate 256 bytes on the stack, unfortunately */ + my_memfill(mem_dst, 3000, STACK_SIZE); + my_memfill(sp_src, 2000, STACK_SIZE); + /* memcpy size here must be an integer constant to allow the optimization */ + p = memcpy(mem_dst, sp_src, 144); + size = 144; + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_ori, 1000, size); + CHECK_RANGE(mem_dst + size, mem_ori + size, 2000, STACK_SIZE - size); + } +} + +/* memset */ +void test_memset(void) +{ + const char *name = 0; + unsigned size = 0; + void *p; + + /* init */ + my_memfill(mem_ori, 1000, sizeof mem_ori); + my_memfill(zp_ori, 1000, sizeof zp_ori); + p_zp_dst = zp_dst; + p_zp_ori = zp_ori; + +#if !SKIP_INLINED_ARG_SIDE_EFFECT_TESTS + /* arg3 == 0 */ + set_header("p = memset(arg1, arg2, 0)"); + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + x = 42; + y = -42; + z = 36; + p = memset(mul_two(mem_dst, &x), (add_one(0, &y), 42), (negate(0, &z), 0)); + CHECK(p == mem_dst); + CHECK(x == 84); + CHECK(y == -41); + CHECK(z == -36); + CHECK_RANGE(mem_dst, mem_ori, 2000, size); +#endif + +#if !SKIP_INLINED_ARG_SIDE_EFFECT_TESTS + /* Check if the arguments are still generated if the return value is unused. + ** We have this question since the first argument could be directly used as + ** the return value when this function gets inlined. + */ +#if !SKIP_VOID_RETURN_VALUE_TESTS + set_header("(void)memset(arg1, arg2, 0)"); +#else + set_header("memset(arg1, arg2, 0)"); +#endif + size = MEM_SIZE; + my_memfill(mem_dst, 3000, size); + x = 42; + y = -42; + z = 36; +#if !SKIP_VOID_RETURN_VALUE_TESTS + (void) +#endif + memset(mul_two(mem_dst, &x), (add_one(0, &y), 42), (negate(0, &z), 0)); + CHECK(x == 84); + CHECK(y == -41); + CHECK(z == -36); + CHECK_RANGE(mem_dst, mem_ori, 2000, size); +#endif +} + +/* strcmp */ +void test_strcmp(void) +{ + const char *name = 0; + unsigned size = 0; + int res = 0; + + /* init */ + p_zp_dst = zp_dst; + + /* Compared to zero-length C string literal */ + set_header("strcmp(arg1, \"\\0Z\")"); + { + char str[] = "AA"; + size = sizeof str; + my_memcpy(zp_dst, str, size); + x = 42; + y = -42; + + res = strcmp(zp_dst, "\0Z"); + CHECK(res > 0); + +#if !SKIP_INLINED_ARG_SIDE_EFFECT_TESTS + res = strcmp(mul_two(p_zp_dst, &x), (add_one(0, &y), "\0Z")); + CHECK(res > 0); + CHECK(x == 84); + CHECK(y == -41); +#endif + } +} + +/* strcpy */ +void test_strcpy(void) +{ + const char *name = 0; + unsigned size = 0; + void *p; + char stack_dst[ZP_SIZE]; + char stack_src[ZP_SIZE]; + + /* init */ + { + char str[] = BROKEN_STR; + size = BROKEN_STR_LEN + 1; + my_memfill(mem_src, 1000, ZP_SIZE); + my_memfill(stack_src, 1000, ZP_SIZE); + my_memfill(zp_src, 1000, ZP_SIZE); + my_memcpy(mem_src, str, size); + my_memcpy(stack_src, str, size); + my_memcpy(zp_src, str, size); + p_zp_src = zp_src; + p_zp_dst = zp_dst; + p_zp_dst = zp_dst; + } + + /* arg1: const addr, arg2: const addr */ + set_header("strcpy(const_addr_1, const_addr_2)"); + my_memfill(mem_dst, 2000, ZP_SIZE); + size = BROKEN_STR_LEN + 1; + p = strcpy(mem_dst, mem_src); + CHECK(p == mem_dst); + CHECK_RANGE(mem_dst, mem_src, 0, size); + CHECK_RANGE(mem_dst + size, mem_src + size, 1000, ZP_SIZE - size); + + /* arg1: ptr on zp, arg2: ptr on zp */ + set_header("strcpy(p_on_zp_1, p_on_zp_2)"); + my_memfill(zp_dst, 2000, ZP_SIZE); + size = BROKEN_STR_LEN + 1; + p = strcpy(zp_dst, zp_src); + CHECK(p == zp_dst); + CHECK_RANGE(zp_dst, zp_src, 0, size); + CHECK_RANGE(zp_dst + size, zp_src + size, 1000, ZP_SIZE - size); + + /* arg1: on stack, arg2: on stack */ + set_header("strcpy(on_stack_1, on_stack_2)"); + my_memfill(stack_dst, 2000, ZP_SIZE); + size = BROKEN_STR_LEN + 1; + p = strcpy(stack_dst, stack_src); + CHECK(p == stack_dst); + CHECK_RANGE(stack_dst, stack_src, 0, size); + CHECK_RANGE(stack_dst + size, stack_src + size, 1000, ZP_SIZE - size); + + /* TODO: args side-effects tests */ +} + +/* strlen */ +void test_strlen(void) +{ + const char *name = 0; + size_t len; + + /* init */ + { + char str[] = BROKEN_STR; + my_memcpy(mem_ori, str, ZP_SIZE); + //my_memcpy(stack_ori, str, ZP_SIZE); + my_memcpy(zp_ori, str, ZP_SIZE); + p_zp_ori = zp_ori; + } + + /* arg1: string_literal */ + set_header("strlen(\"hello\\0!\")"); + x = 42; + y = -42; + len = strlen((mul_two(0, &x), BROKEN_STR)); + (void)strlen((add_one(0, &y), BROKEN_STR)); + CHECK(len == BROKEN_STR_LEN); + CHECK(x == 84); + CHECK(y == -41); + + /* arg1: array with const addr */ + set_header("strlen(array_const_addr[8])"); + x = 42; + y = -42; + len = strlen((mul_two(0, &x), zp_ori)); + (void)strlen((add_one(0, &y), zp_ori)); + CHECK(len == BROKEN_STR_LEN); + CHECK(x == 84); + CHECK(y == -41); + + /* arg1: array on stack */ + set_header("strlen(array_on_stack[8])"); + { + char p_on_stack[] = BROKEN_STR; + x = 42; + y = -42; + len = strlen((mul_two(0, &x), p_on_stack)); + strlen((add_one(0, &y), p_on_stack)); + CHECK(sizeof p_on_stack == 8); + CHECK(len == BROKEN_STR_LEN); + CHECK(x == 84); + CHECK(y == -41); + } + + /* arg1: ptr on zp */ + set_header("strlen(ptr_on_zp)"); + x = 42; + y = -42; + len = strlen((mul_two(0, &x), p_zp_ori)); + (void)strlen((add_one(0, &y), p_zp_ori)); + CHECK(len == BROKEN_STR_LEN); + CHECK(x == 84); + CHECK(y == -41); +} + +int main(void) +{ + test_memcpy(); + test_memset(); + test_strcmp(); + test_strcpy(); + test_strlen(); + + if (failures > 0) + { + printf("failed items: %u\n", failures); + } + return failures; +}