blob: 6208490f15af37080911f63cdaf8c0c180fdd9a0 [file] [log] [blame]
/*
* 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 c[8];
} duk__test_double_union;
#define DUK__DBLUNION_CMP_TRUE(a,b) do { \
if (DUK_MEMCMP((const void *) (a), (const void *) (b), sizeof(duk__test_double_union)) != 0) { \
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test 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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double union compares true (expected false)"); \
} \
} while (0)
typedef union {
duk_uint32_t i;
duk_uint8_t c[8];
} duk__test_u32_union;
/*
* Various sanity checks for typing
*/
DUK_LOCAL void duk__selftest_types(void) {
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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test 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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test 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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_size_t is smaller than duk_uint_t");
}
if (!(sizeof(duk_int_t) >= 4)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_int_t is not 32 bits");
}
}
/*
* Packed tval sanity
*/
DUK_LOCAL void duk__selftest_packed_tval(void) {
#if defined(DUK_USE_PACKED_TVAL)
if (sizeof(void *) > 4) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: packed duk_tval in use but sizeof(void *) > 4");
}
#endif
}
/*
* Two's complement arithmetic.
*/
DUK_LOCAL void duk__selftest_twos_complement(void) {
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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: two's complement arithmetic");
}
}
/*
* 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 void duk__selftest_byte_order(void) {
duk__test_u32_union u1;
duk__test_double_union u2;
/*
* >>> struct.pack('>d', 102030405060).encode('hex')
* '4237c17c6dc40000'
*/
#if defined(DUK_USE_INTEGER_LE)
u1.c[0] = 0xef; u1.c[1] = 0xbe; u1.c[2] = 0xad; u1.c[3] = 0xde;
#elif defined(DUK_USE_INTEGER_ME)
#error integer mixed endian not supported now
#elif defined(DUK_USE_INTEGER_BE)
u1.c[0] = 0xde; u1.c[1] = 0xad; u1.c[2] = 0xbe; u1.c[3] = 0xef;
#else
#error unknown integer endianness
#endif
#if defined(DUK_USE_DOUBLE_LE)
u2.c[0] = 0x00; u2.c[1] = 0x00; u2.c[2] = 0xc4; u2.c[3] = 0x6d;
u2.c[4] = 0x7c; u2.c[5] = 0xc1; u2.c[6] = 0x37; u2.c[7] = 0x42;
#elif defined(DUK_USE_DOUBLE_ME)
u2.c[0] = 0x7c; u2.c[1] = 0xc1; u2.c[2] = 0x37; u2.c[3] = 0x42;
u2.c[4] = 0x00; u2.c[5] = 0x00; u2.c[6] = 0xc4; u2.c[7] = 0x6d;
#elif defined(DUK_USE_DOUBLE_BE)
u2.c[0] = 0x42; u2.c[1] = 0x37; u2.c[2] = 0xc1; u2.c[3] = 0x7c;
u2.c[4] = 0x6d; u2.c[5] = 0xc4; u2.c[6] = 0x00; u2.c[7] = 0x00;
#else
#error unknown double endianness
#endif
if (u1.i != (duk_uint32_t) 0xdeadbeefUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_uint32_t byte order");
}
if (u2.d != (double) 102030405060.0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double byte order");
}
}
/*
* DUK_BSWAP macros
*/
DUK_LOCAL void duk__selftest_bswap_macros(void) {
duk_uint32_t x32;
duk_uint16_t x16;
duk_double_union du;
duk_double_t du_diff;
x16 = 0xbeefUL;
x16 = DUK_BSWAP16(x16);
if (x16 != (duk_uint16_t) 0xefbeUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_BSWAP16");
}
x32 = 0xdeadbeefUL;
x32 = DUK_BSWAP32(x32);
if (x32 != (duk_uint32_t) 0xefbeaddeUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_BSWAP32");
}
/* >>> 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_FPRINTF(DUK_STDERR, "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_FPRINTF(DUK_STDERR, "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_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_DBLUNION_DOUBLE_NTOH");
}
}
/*
* Basic double / byte union memory layout.
*/
DUK_LOCAL void duk__selftest_double_union_size(void) {
if (sizeof(duk__test_double_union) != 8) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: invalid union size");
}
}
/*
* Union aliasing, see misc/clang_aliasing.c.
*/
DUK_LOCAL void duk__selftest_double_aliasing(void) {
duk__test_double_union a, b;
/* 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_D(DUK_DPRINT("skip double aliasing self test when duk_tval is not packed"));
return;
#endif
/* Test signaling NaN and alias assignment in all endianness combinations.
*/
/* little endian */
a.c[0] = 0x11; a.c[1] = 0x22; a.c[2] = 0x33; a.c[3] = 0x44;
a.c[4] = 0x00; a.c[5] = 0x00; a.c[6] = 0xf1; a.c[7] = 0xff;
b = a;
DUK__DBLUNION_CMP_TRUE(&a, &b);
/* big endian */
a.c[0] = 0xff; a.c[1] = 0xf1; a.c[2] = 0x00; a.c[3] = 0x00;
a.c[4] = 0x44; a.c[5] = 0x33; a.c[6] = 0x22; a.c[7] = 0x11;
b = a;
DUK__DBLUNION_CMP_TRUE(&a, &b);
/* mixed endian */
a.c[0] = 0x00; a.c[1] = 0x00; a.c[2] = 0xf1; a.c[3] = 0xff;
a.c[4] = 0x11; a.c[5] = 0x22; a.c[6] = 0x33; a.c[7] = 0x44;
b = a;
DUK__DBLUNION_CMP_TRUE(&a, &b);
}
/*
* Zero sign, see misc/tcc_zerosign2.c.
*/
DUK_LOCAL void duk__selftest_double_zero_sign(void) {
duk__test_double_union a, b;
a.d = 0.0;
b.d = -a.d;
DUK__DBLUNION_CMP_FALSE(&a, &b);
}
/*
* 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 void duk__selftest_struct_align(void) {
#if (DUK_USE_ALIGN_BY == 4)
if ((sizeof(duk_hbuffer_fixed) % 4) != 0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: sizeof(duk_hbuffer_fixed) not aligned to 4");
}
#elif (DUK_USE_ALIGN_BY == 8)
if ((sizeof(duk_hbuffer_fixed) % 8) != 0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test 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
}
/*
* 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 void duk__selftest_64bit_arithmetic(void) {
#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 != 0x80000000LL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: casting 2147483648.0 to duk_int64_t failed");
}
#else
/* nop */
#endif
}
/*
* Casting
*/
DUK_LOCAL void duk__selftest_cast_double_to_small_uint(void) {
/*
* https://github.com/svaarala/duktape/issues/127#issuecomment-77863473
*/
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 (!(d1 == 1.0 && u == 1 && d2 == 1.0 && d1 == d2)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test 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 (!(d1v == 1.0 && uv == 1 && d2v == 1.0 && d1v == d2v)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double to duk_small_uint_t cast failed");
}
}
DUK_LOCAL void 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_double_t dv;
duk_uint32_t uv;
dv = 3735928559.0; /* 0xdeadbeef in decimal */
uv = (duk_uint32_t) dv;
if (uv != 0xdeadbeefUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double to duk_uint32_t cast failed");
}
}
/*
* Self test main
*/
DUK_INTERNAL void duk_selftest_run_tests(void) {
duk__selftest_types();
duk__selftest_packed_tval();
duk__selftest_twos_complement();
duk__selftest_byte_order();
duk__selftest_bswap_macros();
duk__selftest_double_union_size();
duk__selftest_double_aliasing();
duk__selftest_double_zero_sign();
duk__selftest_struct_align();
duk__selftest_64bit_arithmetic();
duk__selftest_cast_double_to_small_uint();
duk__selftest_cast_double_to_uint32();
}
#undef DUK__DBLUNION_CMP_TRUE
#undef DUK__DBLUNION_CMP_FALSE
#endif /* DUK_USE_SELF_TESTS */