/* * Self tests to ensure execution environment is sane. Intended to catch * compiler/platform problems which cannot be detected at compile time. */ #include "duk_internal.h" #if defined(DUK_USE_SELF_TESTS) /* * Unions and structs for self tests */ typedef union { double d; duk_uint8_t x[8]; } duk__test_double_union; /* Self test failed. Expects a local variable 'error_count' to exist. */ #define DUK__FAILED(msg) do { \ DUK_D(DUK_DPRINT("self test failed: " #msg " at " DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO))); \ error_count++; \ } while (0) #define DUK__DBLUNION_CMP_TRUE(a,b) do { \ if (duk_memcmp((const void *) (a), (const void *) (b), sizeof(duk__test_double_union)) != 0) { \ DUK__FAILED("double union compares false (expected true)"); \ } \ } while (0) #define DUK__DBLUNION_CMP_FALSE(a,b) do { \ if (duk_memcmp((const void *) (a), (const void *) (b), sizeof(duk__test_double_union)) == 0) { \ DUK__FAILED("double union compares true (expected false)"); \ } \ } while (0) typedef union { duk_uint32_t i; duk_uint8_t x[8]; } duk__test_u32_union; #if defined(DUK_USE_INTEGER_LE) #define DUK__U32_INIT(u, a, b, c, d) do { \ (u)->x[0] = (d); (u)->x[1] = (c); (u)->x[2] = (b); (u)->x[3] = (a); \ } while (0) #elif defined(DUK_USE_INTEGER_ME) #error integer mixed endian not supported now #elif defined(DUK_USE_INTEGER_BE) #define DUK__U32_INIT(u, a, b, c, d) do { \ (u)->x[0] = (a); (u)->x[1] = (b); (u)->x[2] = (c); (u)->x[3] = (d); \ } while (0) #else #error unknown integer endianness #endif #if defined(DUK_USE_DOUBLE_LE) #define DUK__DOUBLE_INIT(u, a, b, c, d, e, f, g, h) do { \ (u)->x[0] = (h); (u)->x[1] = (g); (u)->x[2] = (f); (u)->x[3] = (e); \ (u)->x[4] = (d); (u)->x[5] = (c); (u)->x[6] = (b); (u)->x[7] = (a); \ } while (0) #define DUK__DOUBLE_COMPARE(u, a, b, c, d, e, f, g, h) \ ((u)->x[0] == (h) && (u)->x[1] == (g) && (u)->x[2] == (f) && (u)->x[3] == (e) && \ (u)->x[4] == (d) && (u)->x[5] == (c) && (u)->x[6] == (b) && (u)->x[7] == (a)) #elif defined(DUK_USE_DOUBLE_ME) #define DUK__DOUBLE_INIT(u, a, b, c, d, e, f, g, h) do { \ (u)->x[0] = (d); (u)->x[1] = (c); (u)->x[2] = (b); (u)->x[3] = (a); \ (u)->x[4] = (h); (u)->x[5] = (g); (u)->x[6] = (f); (u)->x[7] = (e); \ } while (0) #define DUK__DOUBLE_COMPARE(u, a, b, c, d, e, f, g, h) \ ((u)->x[0] == (d) && (u)->x[1] == (c) && (u)->x[2] == (b) && (u)->x[3] == (a) && \ (u)->x[4] == (h) && (u)->x[5] == (g) && (u)->x[6] == (f) && (u)->x[7] == (e)) #elif defined(DUK_USE_DOUBLE_BE) #define DUK__DOUBLE_INIT(u, a, b, c, d, e, f, g, h) do { \ (u)->x[0] = (a); (u)->x[1] = (b); (u)->x[2] = (c); (u)->x[3] = (d); \ (u)->x[4] = (e); (u)->x[5] = (f); (u)->x[6] = (g); (u)->x[7] = (h); \ } while (0) #define DUK__DOUBLE_COMPARE(u, a, b, c, d, e, f, g, h) \ ((u)->x[0] == (a) && (u)->x[1] == (b) && (u)->x[2] == (c) && (u)->x[3] == (d) && \ (u)->x[4] == (e) && (u)->x[5] == (f) && (u)->x[6] == (g) && (u)->x[7] == (h)) #else #error unknown double endianness #endif /* * Various sanity checks for typing */ DUK_LOCAL duk_uint_t duk__selftest_types(void) { duk_uint_t error_count = 0; if (!(sizeof(duk_int8_t) == 1 && sizeof(duk_uint8_t) == 1 && sizeof(duk_int16_t) == 2 && sizeof(duk_uint16_t) == 2 && sizeof(duk_int32_t) == 4 && sizeof(duk_uint32_t) == 4)) { DUK__FAILED("duk_(u)int{8,16,32}_t size"); } #if defined(DUK_USE_64BIT_OPS) if (!(sizeof(duk_int64_t) == 8 && sizeof(duk_uint64_t) == 8)) { DUK__FAILED("duk_(u)int64_t size"); } #endif if (!(sizeof(duk_size_t) >= sizeof(duk_uint_t))) { /* Some internal code now assumes that all duk_uint_t values * can be expressed with a duk_size_t. */ DUK__FAILED("duk_size_t is smaller than duk_uint_t"); } if (!(sizeof(duk_int_t) >= 4)) { DUK__FAILED("duk_int_t is not 32 bits"); } return error_count; } /* * Packed tval sanity */ DUK_LOCAL duk_uint_t duk__selftest_packed_tval(void) { duk_uint_t error_count = 0; #if defined(DUK_USE_PACKED_TVAL) if (sizeof(void *) > 4) { DUK__FAILED("packed duk_tval in use but sizeof(void *) > 4"); } #endif return error_count; } /* * Two's complement arithmetic. */ DUK_LOCAL duk_uint_t duk__selftest_twos_complement(void) { duk_uint_t error_count = 0; volatile int test; test = -1; /* Note that byte order doesn't affect this test: all bytes in * 'test' will be 0xFF for two's complement. */ if (((volatile duk_uint8_t *) &test)[0] != (duk_uint8_t) 0xff) { DUK__FAILED("two's complement arithmetic"); } return error_count; } /* * Byte order. Important to self check, because on some exotic platforms * there is no actual detection but rather assumption based on platform * defines. */ DUK_LOCAL duk_uint_t duk__selftest_byte_order(void) { duk_uint_t error_count = 0; duk__test_u32_union u1; duk__test_double_union u2; /* * >>> struct.pack('>d', 102030405060).encode('hex') * '4237c17c6dc40000' */ DUK__U32_INIT(&u1, 0xde, 0xad, 0xbe, 0xef); DUK__DOUBLE_INIT(&u2, 0x42, 0x37, 0xc1, 0x7c, 0x6d, 0xc4, 0x00, 0x00); if (u1.i != (duk_uint32_t) 0xdeadbeefUL) { DUK__FAILED("duk_uint32_t byte order"); } if (!duk_double_equals(u2.d, 102030405060.0)) { DUK__FAILED("double byte order"); } return error_count; } /* * DUK_BSWAP macros */ DUK_LOCAL duk_uint_t duk__selftest_bswap_macros(void) { duk_uint_t error_count = 0; volatile duk_uint32_t x32_input, x32_output; duk_uint32_t x32; volatile duk_uint16_t x16_input, x16_output; duk_uint16_t x16; duk_double_union du; duk_double_t du_diff; #if defined(DUK_BSWAP64) volatile duk_uint64_t x64_input, x64_output; duk_uint64_t x64; #endif /* Cover both compile time and runtime bswap operations, as these * may have different bugs. */ x16_input = 0xbeefUL; x16 = x16_input; x16 = DUK_BSWAP16(x16); x16_output = x16; if (x16_output != (duk_uint16_t) 0xefbeUL) { DUK__FAILED("DUK_BSWAP16"); } x16 = 0xbeefUL; x16 = DUK_BSWAP16(x16); if (x16 != (duk_uint16_t) 0xefbeUL) { DUK__FAILED("DUK_BSWAP16"); } x32_input = 0xdeadbeefUL; x32 = x32_input; x32 = DUK_BSWAP32(x32); x32_output = x32; if (x32_output != (duk_uint32_t) 0xefbeaddeUL) { DUK__FAILED("DUK_BSWAP32"); } x32 = 0xdeadbeefUL; x32 = DUK_BSWAP32(x32); if (x32 != (duk_uint32_t) 0xefbeaddeUL) { DUK__FAILED("DUK_BSWAP32"); } #if defined(DUK_BSWAP64) x64_input = DUK_U64_CONSTANT(0x8899aabbccddeeff); x64 = x64_input; x64 = DUK_BSWAP64(x64); x64_output = x64; if (x64_output != (duk_uint64_t) DUK_U64_CONSTANT(0xffeeddccbbaa9988)) { DUK__FAILED("DUK_BSWAP64"); } x64 = DUK_U64_CONSTANT(0x8899aabbccddeeff); x64 = DUK_BSWAP64(x64); if (x64 != (duk_uint64_t) DUK_U64_CONSTANT(0xffeeddccbbaa9988)) { DUK__FAILED("DUK_BSWAP64"); } #endif /* >>> struct.unpack('>d', '4000112233445566'.decode('hex')) * (2.008366013071895,) */ du.uc[0] = 0x40; du.uc[1] = 0x00; du.uc[2] = 0x11; du.uc[3] = 0x22; du.uc[4] = 0x33; du.uc[5] = 0x44; du.uc[6] = 0x55; du.uc[7] = 0x66; DUK_DBLUNION_DOUBLE_NTOH(&du); du_diff = du.d - 2.008366013071895; #if 0 DUK_D(DUK_DPRINT("du_diff: %lg\n", (double) du_diff)); #endif if (du_diff > 1e-15) { /* Allow very small lenience because some compilers won't parse * exact IEEE double constants (happened in matrix testing with * Linux gcc-4.8 -m32 at least). */ #if 0 DUK_D(DUK_DPRINT("Result of DUK_DBLUNION_DOUBLE_NTOH: %02x %02x %02x %02x %02x %02x %02x %02x\n", (unsigned int) du.uc[0], (unsigned int) du.uc[1], (unsigned int) du.uc[2], (unsigned int) du.uc[3], (unsigned int) du.uc[4], (unsigned int) du.uc[5], (unsigned int) du.uc[6], (unsigned int) du.uc[7])); #endif DUK__FAILED("DUK_DBLUNION_DOUBLE_NTOH"); } return error_count; } /* * Basic double / byte union memory layout. */ DUK_LOCAL duk_uint_t duk__selftest_double_union_size(void) { duk_uint_t error_count = 0; if (sizeof(duk__test_double_union) != 8) { DUK__FAILED("invalid union size"); } return error_count; } /* * Union aliasing, see misc/clang_aliasing.c. */ DUK_LOCAL duk_uint_t duk__selftest_double_aliasing(void) { /* This testcase fails when Emscripten-generated code runs on Firefox. * It's not an issue because the failure should only affect packed * duk_tval representation, which is not used with Emscripten. */ #if defined(DUK_USE_PACKED_TVAL) duk_uint_t error_count = 0; duk__test_double_union a, b; /* Test signaling NaN and alias assignment in all endianness combinations. */ /* little endian */ a.x[0] = 0x11; a.x[1] = 0x22; a.x[2] = 0x33; a.x[3] = 0x44; a.x[4] = 0x00; a.x[5] = 0x00; a.x[6] = 0xf1; a.x[7] = 0xff; b = a; DUK__DBLUNION_CMP_TRUE(&a, &b); /* big endian */ a.x[0] = 0xff; a.x[1] = 0xf1; a.x[2] = 0x00; a.x[3] = 0x00; a.x[4] = 0x44; a.x[5] = 0x33; a.x[6] = 0x22; a.x[7] = 0x11; b = a; DUK__DBLUNION_CMP_TRUE(&a, &b); /* mixed endian */ a.x[0] = 0x00; a.x[1] = 0x00; a.x[2] = 0xf1; a.x[3] = 0xff; a.x[4] = 0x11; a.x[5] = 0x22; a.x[6] = 0x33; a.x[7] = 0x44; b = a; DUK__DBLUNION_CMP_TRUE(&a, &b); return error_count; #else DUK_D(DUK_DPRINT("skip double aliasing self test when duk_tval is not packed")); return 0; #endif } /* * Zero sign, see misc/tcc_zerosign2.c. */ DUK_LOCAL duk_uint_t duk__selftest_double_zero_sign(void) { duk_uint_t error_count = 0; duk__test_double_union a, b; a.d = 0.0; b.d = -a.d; DUK__DBLUNION_CMP_FALSE(&a, &b); return error_count; } /* * Rounding mode: Duktape assumes round-to-nearest, check that this is true. * If we had C99 fenv.h we could check that fegetround() == FE_TONEAREST, * but we don't want to rely on that header; and even if we did, it's good * to ensure the rounding actually works. */ DUK_LOCAL duk_uint_t duk__selftest_double_rounding(void) { duk_uint_t error_count = 0; duk__test_double_union a, b, c; #if 0 /* Include and test manually; these trigger failures: */ fesetround(FE_UPWARD); fesetround(FE_DOWNWARD); fesetround(FE_TOWARDZERO); /* This is the default and passes. */ fesetround(FE_TONEAREST); #endif /* Rounding tests check that none of the other modes (round to * +Inf, round to -Inf, round to zero) can be active: * http://www.gnu.org/software/libc/manual/html_node/Rounding.html */ /* 1.0 + 2^(-53): result is midway between 1.0 and 1.0 + ulp. * Round to nearest: 1.0 * Round to +Inf: 1.0 + ulp * Round to -Inf: 1.0 * Round to zero: 1.0 * => Correct result eliminates round to +Inf. */ DUK__DOUBLE_INIT(&a, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); DUK__DOUBLE_INIT(&b, 0x3c, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); duk_memset((void *) &c, 0, sizeof(c)); c.d = a.d + b.d; if (!DUK__DOUBLE_COMPARE(&c, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) { DUK_D(DUK_DPRINT("broken result (native endiannesss): %02x %02x %02x %02x %02x %02x %02x %02x", (unsigned int) c.x[0], (unsigned int) c.x[1], (unsigned int) c.x[2], (unsigned int) c.x[3], (unsigned int) c.x[4], (unsigned int) c.x[5], (unsigned int) c.x[6], (unsigned int) c.x[7])); DUK__FAILED("invalid result from 1.0 + 0.5ulp"); } /* (1.0 + ulp) + 2^(-53): result is midway between 1.0 + ulp and 1.0 + 2*ulp. * Round to nearest: 1.0 + 2*ulp (round to even mantissa) * Round to +Inf: 1.0 + 2*ulp * Round to -Inf: 1.0 + ulp * Round to zero: 1.0 + ulp * => Correct result eliminates round to -Inf and round to zero. */ DUK__DOUBLE_INIT(&a, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01); DUK__DOUBLE_INIT(&b, 0x3c, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); duk_memset((void *) &c, 0, sizeof(c)); c.d = a.d + b.d; if (!DUK__DOUBLE_COMPARE(&c, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02)) { DUK_D(DUK_DPRINT("broken result (native endiannesss): %02x %02x %02x %02x %02x %02x %02x %02x", (unsigned int) c.x[0], (unsigned int) c.x[1], (unsigned int) c.x[2], (unsigned int) c.x[3], (unsigned int) c.x[4], (unsigned int) c.x[5], (unsigned int) c.x[6], (unsigned int) c.x[7])); DUK__FAILED("invalid result from (1.0 + ulp) + 0.5ulp"); } /* Could do negative number testing too, but the tests above should * differentiate between IEEE 754 rounding modes. */ return error_count; } /* * fmod(): often a portability issue in embedded or bare platform targets. * Check for at least minimally correct behavior. Unlike some other math * functions (like cos()) Duktape relies on fmod() internally too. */ DUK_LOCAL duk_uint_t duk__selftest_fmod(void) { duk_uint_t error_count = 0; duk__test_double_union u1, u2; volatile duk_double_t t1, t2, t3; /* fmod() with integer argument and exponent 2^32 is used by e.g. * ToUint32() and some Duktape internals. */ u1.d = DUK_FMOD(10.0, 4294967296.0); u2.d = 10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); u1.d = DUK_FMOD(4294967306.0, 4294967296.0); u2.d = 10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); u1.d = DUK_FMOD(73014444042.0, 4294967296.0); u2.d = 10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); /* 52-bit integer split into two parts: * >>> 0x1fedcba9876543 * 8987183256397123 * >>> float(0x1fedcba9876543) / float(2**53) * 0.9977777777777778 */ u1.d = DUK_FMOD(8987183256397123.0, 4294967296.0); u2.d = (duk_double_t) 0xa9876543UL; DUK__DBLUNION_CMP_TRUE(&u1, &u2); t1 = 8987183256397123.0; t2 = 4294967296.0; t3 = t1 / t2; u1.d = DUK_FLOOR(t3); u2.d = (duk_double_t) 0x1fedcbUL; DUK__DBLUNION_CMP_TRUE(&u1, &u2); /* C99 behavior is for fmod() result sign to mathc argument sign. */ u1.d = DUK_FMOD(-10.0, 4294967296.0); u2.d = -10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); u1.d = DUK_FMOD(-4294967306.0, 4294967296.0); u2.d = -10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); u1.d = DUK_FMOD(-73014444042.0, 4294967296.0); u2.d = -10.0; DUK__DBLUNION_CMP_TRUE(&u1, &u2); return error_count; } /* * Struct size/alignment if platform requires it * * There are some compiler specific struct padding pragmas etc in use, this * selftest ensures they're correctly detected and used. */ DUK_LOCAL duk_uint_t duk__selftest_struct_align(void) { duk_uint_t error_count = 0; #if (DUK_USE_ALIGN_BY == 4) if ((sizeof(duk_hbuffer_fixed) % 4) != 0) { DUK__FAILED("sizeof(duk_hbuffer_fixed) not aligned to 4"); } #elif (DUK_USE_ALIGN_BY == 8) if ((sizeof(duk_hbuffer_fixed) % 8) != 0) { DUK__FAILED("sizeof(duk_hbuffer_fixed) not aligned to 8"); } #elif (DUK_USE_ALIGN_BY == 1) /* no check */ #else #error invalid DUK_USE_ALIGN_BY #endif return error_count; } /* * 64-bit arithmetic * * There are some platforms/compilers where 64-bit types are available * but don't work correctly. Test for known cases. */ DUK_LOCAL duk_uint_t duk__selftest_64bit_arithmetic(void) { duk_uint_t error_count = 0; #if defined(DUK_USE_64BIT_OPS) volatile duk_int64_t i; volatile duk_double_t d; /* Catch a double-to-int64 cast issue encountered in practice. */ d = 2147483648.0; i = (duk_int64_t) d; if (i != DUK_I64_CONSTANT(0x80000000)) { DUK__FAILED("casting 2147483648.0 to duk_int64_t failed"); } #else /* nop */ #endif return error_count; } /* * Casting */ DUK_LOCAL duk_uint_t duk__selftest_cast_double_to_small_uint(void) { /* * https://github.com/svaarala/duktape/issues/127#issuecomment-77863473 */ duk_uint_t error_count = 0; duk_double_t d1, d2; duk_small_uint_t u; duk_double_t d1v, d2v; duk_small_uint_t uv; /* Test without volatiles */ d1 = 1.0; u = (duk_small_uint_t) d1; d2 = (duk_double_t) u; if (!(duk_double_equals(d1, 1.0) && u == 1 && duk_double_equals(d2, 1.0) && duk_double_equals(d1, d2))) { DUK__FAILED("double to duk_small_uint_t cast failed"); } /* Same test with volatiles */ d1v = 1.0; uv = (duk_small_uint_t) d1v; d2v = (duk_double_t) uv; if (!(duk_double_equals(d1v, 1.0) && uv == 1 && duk_double_equals(d2v, 1.0) && duk_double_equals(d1v, d2v))) { DUK__FAILED("double to duk_small_uint_t cast failed"); } return error_count; } DUK_LOCAL duk_uint_t duk__selftest_cast_double_to_uint32(void) { /* * This test fails on an exotic ARM target; double-to-uint * cast is incorrectly clamped to -signed- int highest value. * * https://github.com/svaarala/duktape/issues/336 */ duk_uint_t error_count = 0; duk_double_t dv; duk_uint32_t uv; dv = 3735928559.0; /* 0xdeadbeef in decimal */ uv = (duk_uint32_t) dv; if (uv != 0xdeadbeefUL) { DUK__FAILED("double to duk_uint32_t cast failed"); } return error_count; } /* * Minimal test of user supplied allocation functions * * - Basic alloc + realloc + free cycle * * - Realloc to significantly larger size to (hopefully) trigger a * relocation and check that relocation copying works */ DUK_LOCAL duk_uint_t duk__selftest_alloc_funcs(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *udata) { duk_uint_t error_count = 0; void *ptr; void *new_ptr; duk_small_int_t i, j; unsigned char x; if (alloc_func == NULL || realloc_func == NULL || free_func == NULL) { return 0; } for (i = 1; i <= 256; i++) { ptr = alloc_func(udata, (duk_size_t) i); if (ptr == NULL) { DUK_D(DUK_DPRINT("alloc failed, ignore")); continue; /* alloc failed, ignore */ } for (j = 0; j < i; j++) { ((unsigned char *) ptr)[j] = (unsigned char) (0x80 + j); } new_ptr = realloc_func(udata, ptr, 1024); if (new_ptr == NULL) { DUK_D(DUK_DPRINT("realloc failed, ignore")); free_func(udata, ptr); continue; /* realloc failed, ignore */ } ptr = new_ptr; for (j = 0; j < i; j++) { x = ((unsigned char *) ptr)[j]; if (x != (unsigned char) (0x80 + j)) { DUK_D(DUK_DPRINT("byte at index %ld doesn't match after realloc: %02lx", (long) j, (unsigned long) x)); DUK__FAILED("byte compare after realloc"); break; } } free_func(udata, ptr); } return error_count; } /* * Self test main */ DUK_INTERNAL duk_uint_t duk_selftest_run_tests(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *udata) { duk_uint_t error_count = 0; DUK_D(DUK_DPRINT("self test starting")); error_count += duk__selftest_types(); error_count += duk__selftest_packed_tval(); error_count += duk__selftest_twos_complement(); error_count += duk__selftest_byte_order(); error_count += duk__selftest_bswap_macros(); error_count += duk__selftest_double_union_size(); error_count += duk__selftest_double_aliasing(); error_count += duk__selftest_double_zero_sign(); error_count += duk__selftest_double_rounding(); error_count += duk__selftest_fmod(); error_count += duk__selftest_struct_align(); error_count += duk__selftest_64bit_arithmetic(); error_count += duk__selftest_cast_double_to_small_uint(); error_count += duk__selftest_cast_double_to_uint32(); error_count += duk__selftest_alloc_funcs(alloc_func, realloc_func, free_func, udata); DUK_D(DUK_DPRINT("self test complete, total error count: %ld", (long) error_count)); return error_count; } #endif /* DUK_USE_SELF_TESTS */