Browse Source

Remove panic handling, use fatal errors instead

Instead of having a separate panic concept which previously differed from
fatal error handling in that there was no context attached to the error,
use fatal errors also for call sites which previously used the panic handler.

Because these call sites are context-free (DUK_ASSERT() failures) simply call
the Duktape-wide default fatal error handler instead of the user fatal error
handler.  For heap creation errors (self test failures) the udata is available;
for assertion it isn't and NULL is used instead.

Add a config option to replace the Duktape-wide fatal error handler; the
default one just segfaults on purpose, to avoid creating postability issues
by depending on e.g. abort().

Remove the error code from the fatal error function signature (it's mostly
pointless) and change the "ctx" argument to "udata" (heap userdata) which is
less confusing than an arbitrary context related to the heap (especially
because it's unsafe to actually use the "ctx" to e.g. call into the Duktape
API).

The fatal error signature change also affects the duk_fatal() API call, which
loses the error code argument.
pull/781/head
Sami Vaarala 9 years ago
parent
commit
407ce44d55
  1. 4
      src/duk_api_public.h.in
  2. 20
      src/duk_api_stack.c
  3. 76
      src/duk_error.h
  4. 7
      src/duk_error_longjmp.c
  5. 67
      src/duk_error_macros.c
  6. 2
      src/duk_heap.h
  7. 8
      src/duk_heap_alloc.c
  8. 4
      src/duk_internal.h
  9. 162
      src/duk_selftest.c
  10. 2
      src/duk_selftest.h

4
src/duk_api_public.h.in

@ -49,7 +49,7 @@ typedef duk_ret_t (*duk_c_function)(duk_context *ctx);
typedef void *(*duk_alloc_function) (void *udata, duk_size_t size); typedef void *(*duk_alloc_function) (void *udata, duk_size_t size);
typedef void *(*duk_realloc_function) (void *udata, void *ptr, duk_size_t size); typedef void *(*duk_realloc_function) (void *udata, void *ptr, duk_size_t size);
typedef void (*duk_free_function) (void *udata, void *ptr); typedef void (*duk_free_function) (void *udata, void *ptr);
typedef void (*duk_fatal_function) (duk_context *ctx, duk_errcode_t code, const char *msg); typedef void (*duk_fatal_function) (void *udata, const char *msg);
typedef void (*duk_decode_char_function) (void *udata, duk_codepoint_t codepoint); typedef void (*duk_decode_char_function) (void *udata, duk_codepoint_t codepoint);
typedef duk_codepoint_t (*duk_map_char_function) (void *udata, duk_codepoint_t codepoint); typedef duk_codepoint_t (*duk_map_char_function) (void *udata, duk_codepoint_t codepoint);
typedef duk_ret_t (*duk_safe_call_function) (duk_context *ctx, void *udata); typedef duk_ret_t (*duk_safe_call_function) (duk_context *ctx, void *udata);
@ -297,7 +297,7 @@ DUK_EXTERNAL_DECL void duk_gc(duk_context *ctx, duk_uint_t flags);
*/ */
DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_throw(duk_context *ctx)); DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_throw(duk_context *ctx));
DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char *err_msg)); DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_fatal(duk_context *ctx, const char *err_msg));
DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...)); DUK_API_NORETURN(DUK_EXTERNAL_DECL void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...));

20
src/duk_api_stack.c

@ -4337,15 +4337,15 @@ DUK_EXTERNAL void duk_throw(duk_context *ctx) {
duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
/* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't /* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't
* need to check that here. If the value is NULL, a panic occurs because * need to check that here. If the value is NULL, a fatal error occurs
* we can't return. * because we can't return.
*/ */
duk_err_longjmp(thr); duk_err_longjmp(thr);
DUK_UNREACHABLE(); DUK_UNREACHABLE();
} }
DUK_EXTERNAL void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char *err_msg) { DUK_EXTERNAL void duk_fatal(duk_context *ctx, const char *err_msg) {
duk_hthread *thr = (duk_hthread *) ctx; duk_hthread *thr = (duk_hthread *) ctx;
DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT_CTX_VALID(ctx);
@ -4353,16 +4353,22 @@ DUK_EXTERNAL void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char
DUK_ASSERT(thr->heap != NULL); DUK_ASSERT(thr->heap != NULL);
DUK_ASSERT(thr->heap->fatal_func != NULL); DUK_ASSERT(thr->heap->fatal_func != NULL);
DUK_D(DUK_DPRINT("fatal error occurred, code %ld, message %s", DUK_D(DUK_DPRINT("fatal error occurred: %s", err_msg ? err_msg : "NULL"));
(long) err_code, (const char *) err_msg));
/* fatal_func should be noreturn, but noreturn declarations on function /* fatal_func should be noreturn, but noreturn declarations on function
* pointers has a very spotty support apparently so it's not currently * pointers has a very spotty support apparently so it's not currently
* done. * done.
*/ */
thr->heap->fatal_func(ctx, err_code, err_msg); thr->heap->fatal_func(thr->heap->heap_udata, err_msg);
DUK_PANIC(DUK_ERR_API_ERROR, "fatal handler returned"); /* If the fatal handler returns, all bets are off. It'd be nice to
* print something here but since we don't want to depend on stdio,
* there's no way to do so portably.
*/
DUK_D(DUK_DPRINT("fatal error handler returned, all bets are off!"));
for (;;) {
/* loop forever, don't return (function marked noreturn) */
}
} }
DUK_EXTERNAL void duk_error_va_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) { DUK_EXTERNAL void duk_error_va_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) {

76
src/duk_error.h

@ -1,15 +1,16 @@
/* /*
* Error handling macros, assertion macro, error codes. * Error handling macros, assertion macro, error codes.
* *
* There are three level of 'errors': * There are three types of 'errors':
* *
* 1. Ordinary errors, relative to a thread, cause a longjmp, catchable. * 1. Ordinary errors relative to a thread, cause a longjmp, catchable.
* 2. Fatal errors, relative to a heap, cause fatal handler to be called. * 2. Fatal errors relative to a heap, cause fatal handler to be called.
* 3. Panic errors, unrelated to a heap and cause a process exit. * 3. Fatal errors without context, cause the default (not heap specific)
* fatal handler to be called.
* *
* Panics are used by the default fatal error handler and by debug code * Fatal errors without context are used by debug code such as assertions.
* such as assertions. By providing a proper fatal error handler, user * By providing a fatal error handler for a Duktape heap, user code can
* code can avoid panics in non-debug builds. * avoid fatal errors without context in non-debug builds.
*/ */
#ifndef DUK_ERROR_H_INCLUDED #ifndef DUK_ERROR_H_INCLUDED
@ -127,66 +128,41 @@
#endif /* DUK_USE_VERBOSE_ERRORS */ #endif /* DUK_USE_VERBOSE_ERRORS */
/* /*
* Fatal error * Fatal error without context
* *
* There are no fatal error macros at the moment. There are so few call * The macro is an expression to make it compatible with DUK_ASSERT_EXPR().
* sites that the fatal error handler is called directly.
*/ */
/* #define DUK_FATAL_WITHOUT_CONTEXT(msg) \
* Panic error duk_default_fatal_handler(NULL, (msg))
*
* Panic errors are not relative to either a heap or a thread, and cause
* DUK_PANIC() macro to be invoked. Unless a user provides DUK_USE_PANIC_HANDLER,
* DUK_PANIC() calls a helper which prints out the error and causes a process
* exit.
*
* The user can override the macro to provide custom handling. A macro is
* used to allow the user to have inline panic handling if desired (without
* causing a potentially risky function call).
*
* Panics are only used in debug code such as assertions, and by the default
* fatal error handler.
*/
#if defined(DUK_USE_PANIC_HANDLER)
/* already defined, good */
#define DUK_PANIC(code,msg) DUK_USE_PANIC_HANDLER((code),(msg))
#else
#define DUK_PANIC(code,msg) duk_default_panic_handler((code),(msg))
#endif /* DUK_USE_PANIC_HANDLER */
/* /*
* Assert macro: failure causes panic. * Assert macro: failure causes a fatal error.
*
* NOTE: since the assert macro doesn't take a heap/context argument, there's
* no way to look up a heap/context specific fatal error handler which may have
* been given by the application. Instead, assertion failures always use the
* internal default fatal error handler; it can be replaced via duk_config.h
* and then applies to all Duktape heaps.
*/ */
#if defined(DUK_USE_ASSERTIONS) #if defined(DUK_USE_ASSERTIONS)
/* the message should be a compile time constant without formatting (less risk); /* The message should be a compile time constant without formatting (less risk);
* we don't care about assertion text size because they're not used in production * we don't care about assertion text size because they're not used in production
* builds. * builds.
*/ */
#define DUK_ASSERT(x) do { \ #define DUK_ASSERT(x) do { \
if (!(x)) { \ if (!(x)) { \
DUK_PANIC(DUK_ERR_ASSERTION_ERROR, \ DUK_FATAL_WITHOUT_CONTEXT("assertion failed: " #x \
"assertion failed: " #x \
" (" DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"); \ " (" DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"); \
} \ } \
} while (0) } while (0)
/* Assertion compatible inside a comma expression, evaluates to void. /* Assertion compatible inside a comma expression, evaluates to void. */
* Currently not compatible with DUK_USE_PANIC_HANDLER() which may have
* a statement block.
*/
#if defined(DUK_USE_PANIC_HANDLER)
/* XXX: resolve macro definition issue or call through a helper function? */
#define DUK_ASSERT_EXPR(x) ((void) 0)
#else
#define DUK_ASSERT_EXPR(x) \ #define DUK_ASSERT_EXPR(x) \
((void) ((x) ? 0 : (DUK_PANIC(DUK_ERR_ASSERTION_ERROR, \ ((void) ((x) ? 0 : (DUK_FATAL_WITHOUT_CONTEXT("assertion failed: " #x \
"assertion failed: " #x \
" (" DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"), 0))) " (" DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"), 0)))
#endif
#else /* DUK_USE_ASSERTIONS */ #else /* DUK_USE_ASSERTIONS */
@ -422,11 +398,7 @@ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_alloc(duk_hthread *thr));
DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_longjmp(duk_hthread *thr)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_longjmp(duk_hthread *thr));
DUK_NORETURN(DUK_INTERNAL_DECL void duk_default_fatal_handler(duk_context *ctx, duk_errcode_t code, const char *msg)); DUK_NORETURN(DUK_INTERNAL_DECL void duk_default_fatal_handler(void *udata, const char *msg));
#if !defined(DUK_USE_PANIC_HANDLER)
DUK_NORETURN(DUK_INTERNAL_DECL void duk_default_panic_handler(duk_errcode_t code, const char *msg));
#endif
DUK_INTERNAL_DECL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type); DUK_INTERNAL_DECL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type);

7
src/duk_error_longjmp.c

@ -13,8 +13,8 @@ DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) {
&thr->heap->lj.value1, &thr->heap->lj.value2)); &thr->heap->lj.value1, &thr->heap->lj.value2));
#if !defined(DUK_USE_CPP_EXCEPTIONS) #if !defined(DUK_USE_CPP_EXCEPTIONS)
/* If we don't have a jmpbuf_ptr, there is little we can do /* If we don't have a jmpbuf_ptr, there is little we can do except
* except panic. The caller's expectation is that we never * cause a fatal error. The caller's expectation is that we never
* return. * return.
* *
* With C++ exceptions we now just propagate an uncaught error * With C++ exceptions we now just propagate an uncaught error
@ -22,12 +22,11 @@ DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) {
* a dummy jmpbuf for C++ exceptions now, this could be changed. * a dummy jmpbuf for C++ exceptions now, this could be changed.
*/ */
if (!thr->heap->lj.jmpbuf_ptr) { if (!thr->heap->lj.jmpbuf_ptr) {
DUK_D(DUK_DPRINT("uncaught error: type=%d iserror=%d value1=%!T value2=%!T", DUK_D(DUK_DPRINT("uncaught error: type=%d iserror=%d value1=%!T value2=%!T",
(int) thr->heap->lj.type, (int) thr->heap->lj.iserror, (int) thr->heap->lj.type, (int) thr->heap->lj.iserror,
&thr->heap->lj.value1, &thr->heap->lj.value2)); &thr->heap->lj.value1, &thr->heap->lj.value2));
duk_fatal((duk_context *) thr, DUK_ERR_UNCAUGHT_ERROR, "uncaught error"); duk_fatal((duk_context *) thr, "uncaught error");
DUK_UNREACHABLE(); DUK_UNREACHABLE();
} }
#endif /* DUK_USE_CPP_EXCEPTIONS */ #endif /* DUK_USE_CPP_EXCEPTIONS */

67
src/duk_error_macros.c

@ -1,5 +1,5 @@
/* /*
* Error, fatal, and panic handling. * Error and fatal handling.
*/ */
#include "duk_internal.h" #include "duk_internal.h"
@ -104,58 +104,29 @@ DUK_INTERNAL void duk_err_alloc(duk_hthread *thr) {
* Default fatal error handler * Default fatal error handler
*/ */
DUK_INTERNAL void duk_default_fatal_handler(duk_context *ctx, duk_errcode_t code, const char *msg) { DUK_INTERNAL void duk_default_fatal_handler(void *udata, const char *msg) {
DUK_UNREF(ctx); DUK_UNREF(udata);
#if defined(DUK_USE_FILE_IO)
DUK_FPRINTF(DUK_STDERR, "FATAL %ld: %s\n", (long) code, (const char *) (msg ? msg : "null"));
DUK_FFLUSH(DUK_STDERR);
#else
/* omit print */
#endif
DUK_D(DUK_DPRINT("default fatal handler called, code %ld -> calling DUK_PANIC()", (long) code));
DUK_PANIC(code, msg);
DUK_UNREACHABLE();
}
/*
* Default panic handler
*/
#if !defined(DUK_USE_PANIC_HANDLER)
DUK_INTERNAL void duk_default_panic_handler(duk_errcode_t code, const char *msg) {
#if defined(DUK_USE_FILE_IO)
DUK_FPRINTF(DUK_STDERR, "PANIC %ld: %s ("
#if defined(DUK_USE_PANIC_ABORT)
"calling abort"
#elif defined(DUK_USE_PANIC_EXIT)
"calling exit"
#elif defined(DUK_USE_PANIC_SEGFAULT)
"segfaulting on purpose"
#else
#error no DUK_USE_PANIC_xxx macro defined
#endif
")\n", (long) code, (const char *) (msg ? msg : "null"));
DUK_FFLUSH(DUK_STDERR);
#else
/* omit print */
DUK_UNREF(code);
DUK_UNREF(msg); DUK_UNREF(msg);
#endif
#if defined(DUK_USE_PANIC_ABORT) #if defined(DUK_USE_FATAL_HANDLER)
DUK_ABORT(); /* duk_config.h provided a custom default fatal handler. */
#elif defined(DUK_USE_PANIC_EXIT) DUK_D(DUK_DPRINT("custom default fatal error handler called: %s", msg ? msg : "NULL"));
DUK_EXIT(-1); DUK_USE_FATAL_HANDLER(udata, msg);
#elif defined(DUK_USE_PANIC_SEGFAULT)
/* exit() afterwards to satisfy "noreturn" */
DUK_CAUSE_SEGFAULT(); /* SCANBUILD: "Dereference of null pointer", normal */
DUK_EXIT(-1);
#else #else
#error no DUK_USE_PANIC_xxx macro defined /* Since we don't want to rely on stdio being available, we'll just
* cause a segfault on purpose: it's a portable way to usually cause
* a process to finish, and when used with valgrind it also gives us
* a nice stack trace. For better behavior application code should
* provide a fatal error handler.
*/
DUK_D(DUK_DPRINT("built-in default fatal error handler called: %s", msg ? msg : "NULL"));
DUK_CAUSE_SEGFAULT(); /* SCANBUILD: "Dereference of null pointer", normal */
#endif #endif
DUK_UNREACHABLE(); DUK_D(DUK_DPRINT("fatal error handler returned or segfault didn't succeed, enter forever loop"));
for (;;) {
/* Loop forever to ensure we don't return. */
}
} }
#endif /* !DUK_USE_PANIC_HANDLER */
#undef DUK__ERRFMT_BUFSIZE #undef DUK__ERRFMT_BUFSIZE

2
src/duk_heap.h

@ -351,7 +351,7 @@ struct duk_heap {
duk_free_function free_func; duk_free_function free_func;
/* Heap udata, used for allocator functions but also for other heap /* Heap udata, used for allocator functions but also for other heap
* level callbacks like pointer compression, etc. * level callbacks like fatal function, pointer compression, etc.
*/ */
void *heap_udata; void *heap_udata;

8
src/duk_heap_alloc.c

@ -748,12 +748,12 @@ duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
#endif #endif
/* /*
* If selftests enabled, run them as early as possible * If selftests enabled, run them as early as possible.
*/ */
#if defined(DUK_USE_SELF_TESTS) #if defined(DUK_USE_SELF_TESTS)
DUK_D(DUK_DPRINT("running self tests")); if (duk_selftest_run_tests() > 0) {
duk_selftest_run_tests(); fatal_func(heap_udata, "self test(s) failed");
DUK_D(DUK_DPRINT("self tests passed")); }
#endif #endif
/* /*

4
src/duk_internal.h

@ -27,9 +27,7 @@
/* /*
* User declarations, e.g. prototypes for user functions used by Duktape * User declarations, e.g. prototypes for user functions used by Duktape
* macros. Concretely, if DUK_USE_PANIC_HANDLER is used and the macro * macros.
* value calls a user function, it needs to be declared for Duktape
* compilation to avoid warnings.
*/ */
DUK_USE_USER_DECLARE() DUK_USE_USER_DECLARE()

162
src/duk_selftest.c

@ -16,15 +16,21 @@ typedef union {
duk_uint8_t c[8]; duk_uint8_t c[8];
} duk__test_double_union; } 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)); \
error_count++; \
} while (0)
#define DUK__DBLUNION_CMP_TRUE(a,b) do { \ #define DUK__DBLUNION_CMP_TRUE(a,b) do { \
if (DUK_MEMCMP((const void *) (a), (const void *) (b), sizeof(duk__test_double_union)) != 0) { \ 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)"); \ DUK__FAILED("double union compares false (expected true)"); \
} \ } \
} while (0) } while (0)
#define DUK__DBLUNION_CMP_FALSE(a,b) do { \ #define DUK__DBLUNION_CMP_FALSE(a,b) do { \
if (DUK_MEMCMP((const void *) (a), (const void *) (b), sizeof(duk__test_double_union)) == 0) { \ 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)"); \ DUK__FAILED("double union compares true (expected false)"); \
} \ } \
} while (0) } while (0)
@ -37,19 +43,21 @@ typedef union {
* Various sanity checks for typing * Various sanity checks for typing
*/ */
DUK_LOCAL void duk__selftest_types(void) { DUK_LOCAL duk_uint_t duk__selftest_types(void) {
duk_uint_t error_count = 0;
if (!(sizeof(duk_int8_t) == 1 && if (!(sizeof(duk_int8_t) == 1 &&
sizeof(duk_uint8_t) == 1 && sizeof(duk_uint8_t) == 1 &&
sizeof(duk_int16_t) == 2 && sizeof(duk_int16_t) == 2 &&
sizeof(duk_uint16_t) == 2 && sizeof(duk_uint16_t) == 2 &&
sizeof(duk_int32_t) == 4 && sizeof(duk_int32_t) == 4 &&
sizeof(duk_uint32_t) == 4)) { sizeof(duk_uint32_t) == 4)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_(u)int{8,16,32}_t size"); DUK__FAILED("duk_(u)int{8,16,32}_t size");
} }
#if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_64BIT_OPS)
if (!(sizeof(duk_int64_t) == 8 && if (!(sizeof(duk_int64_t) == 8 &&
sizeof(duk_uint64_t) == 8)) { sizeof(duk_uint64_t) == 8)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_(u)int64_t size"); DUK__FAILED("duk_(u)int64_t size");
} }
#endif #endif
@ -57,30 +65,37 @@ DUK_LOCAL void duk__selftest_types(void) {
/* Some internal code now assumes that all duk_uint_t values /* Some internal code now assumes that all duk_uint_t values
* can be expressed with a duk_size_t. * 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"); DUK__FAILED("duk_size_t is smaller than duk_uint_t");
} }
if (!(sizeof(duk_int_t) >= 4)) { if (!(sizeof(duk_int_t) >= 4)) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_int_t is not 32 bits"); DUK__FAILED("duk_int_t is not 32 bits");
} }
return error_count;
} }
/* /*
* Packed tval sanity * Packed tval sanity
*/ */
DUK_LOCAL void duk__selftest_packed_tval(void) { DUK_LOCAL duk_uint_t duk__selftest_packed_tval(void) {
duk_uint_t error_count = 0;
#if defined(DUK_USE_PACKED_TVAL) #if defined(DUK_USE_PACKED_TVAL)
if (sizeof(void *) > 4) { if (sizeof(void *) > 4) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: packed duk_tval in use but sizeof(void *) > 4"); DUK__FAILED("packed duk_tval in use but sizeof(void *) > 4");
} }
#endif #endif
return error_count;
} }
/* /*
* Two's complement arithmetic. * Two's complement arithmetic.
*/ */
DUK_LOCAL void duk__selftest_twos_complement(void) { DUK_LOCAL duk_uint_t duk__selftest_twos_complement(void) {
duk_uint_t error_count = 0;
volatile int test; volatile int test;
test = -1; test = -1;
@ -88,8 +103,10 @@ DUK_LOCAL void duk__selftest_twos_complement(void) {
* 'test' will be 0xFF for two's complement. * 'test' will be 0xFF for two's complement.
*/ */
if (((volatile duk_uint8_t *) &test)[0] != (duk_uint8_t) 0xff) { if (((volatile duk_uint8_t *) &test)[0] != (duk_uint8_t) 0xff) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: two's complement arithmetic"); DUK__FAILED("two's complement arithmetic");
} }
return error_count;
} }
/* /*
@ -98,7 +115,8 @@ DUK_LOCAL void duk__selftest_twos_complement(void) {
* defines. * defines.
*/ */
DUK_LOCAL void duk__selftest_byte_order(void) { DUK_LOCAL duk_uint_t duk__selftest_byte_order(void) {
duk_uint_t error_count = 0;
duk__test_u32_union u1; duk__test_u32_union u1;
duk__test_double_union u2; duk__test_double_union u2;
@ -130,19 +148,22 @@ DUK_LOCAL void duk__selftest_byte_order(void) {
#endif #endif
if (u1.i != (duk_uint32_t) 0xdeadbeefUL) { if (u1.i != (duk_uint32_t) 0xdeadbeefUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_uint32_t byte order"); DUK__FAILED("duk_uint32_t byte order");
} }
if (u2.d != (double) 102030405060.0) { if (u2.d != (double) 102030405060.0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double byte order"); DUK__FAILED("double byte order");
} }
return error_count;
} }
/* /*
* DUK_BSWAP macros * DUK_BSWAP macros
*/ */
DUK_LOCAL void duk__selftest_bswap_macros(void) { DUK_LOCAL duk_uint_t duk__selftest_bswap_macros(void) {
duk_uint_t error_count = 0;
duk_uint32_t x32; duk_uint32_t x32;
duk_uint16_t x16; duk_uint16_t x16;
duk_double_union du; duk_double_union du;
@ -151,13 +172,13 @@ DUK_LOCAL void duk__selftest_bswap_macros(void) {
x16 = 0xbeefUL; x16 = 0xbeefUL;
x16 = DUK_BSWAP16(x16); x16 = DUK_BSWAP16(x16);
if (x16 != (duk_uint16_t) 0xefbeUL) { if (x16 != (duk_uint16_t) 0xefbeUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_BSWAP16"); DUK__FAILED("DUK_BSWAP16");
} }
x32 = 0xdeadbeefUL; x32 = 0xdeadbeefUL;
x32 = DUK_BSWAP32(x32); x32 = DUK_BSWAP32(x32);
if (x32 != (duk_uint32_t) 0xefbeaddeUL) { if (x32 != (duk_uint32_t) 0xefbeaddeUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_BSWAP32"); DUK__FAILED("DUK_BSWAP32");
} }
/* >>> struct.unpack('>d', '4000112233445566'.decode('hex')) /* >>> struct.unpack('>d', '4000112233445566'.decode('hex'))
@ -169,7 +190,7 @@ DUK_LOCAL void duk__selftest_bswap_macros(void) {
DUK_DBLUNION_DOUBLE_NTOH(&du); DUK_DBLUNION_DOUBLE_NTOH(&du);
du_diff = du.d - 2.008366013071895; du_diff = du.d - 2.008366013071895;
#if 0 #if 0
DUK_FPRINTF(DUK_STDERR, "du_diff: %lg\n", (double) du_diff); DUK_D(DUK_DPRINT("du_diff: %lg\n", (double) du_diff));
#endif #endif
if (du_diff > 1e-15) { if (du_diff > 1e-15) {
/* Allow very small lenience because some compilers won't parse /* Allow very small lenience because some compilers won't parse
@ -177,41 +198,44 @@ DUK_LOCAL void duk__selftest_bswap_macros(void) {
* Linux gcc-4.8 -m32 at least). * Linux gcc-4.8 -m32 at least).
*/ */
#if 0 #if 0
DUK_FPRINTF(DUK_STDERR, "Result of DUK_DBLUNION_DOUBLE_NTOH: %02x %02x %02x %02x %02x %02x %02x %02x\n", 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[0], (unsigned int) du.uc[1],
(unsigned int) du.uc[2], (unsigned int) du.uc[3], (unsigned int) du.uc[2], (unsigned int) du.uc[3],
(unsigned int) du.uc[4], (unsigned int) du.uc[5], (unsigned int) du.uc[4], (unsigned int) du.uc[5],
(unsigned int) du.uc[6], (unsigned int) du.uc[7]); (unsigned int) du.uc[6], (unsigned int) du.uc[7]));
#endif #endif
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: DUK_DBLUNION_DOUBLE_NTOH"); DUK__FAILED("DUK_DBLUNION_DOUBLE_NTOH");
} }
return error_count;
} }
/* /*
* Basic double / byte union memory layout. * Basic double / byte union memory layout.
*/ */
DUK_LOCAL void duk__selftest_double_union_size(void) { DUK_LOCAL duk_uint_t duk__selftest_double_union_size(void) {
duk_uint_t error_count = 0;
if (sizeof(duk__test_double_union) != 8) { if (sizeof(duk__test_double_union) != 8) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: invalid union size"); DUK__FAILED("invalid union size");
} }
return error_count;
} }
/* /*
* Union aliasing, see misc/clang_aliasing.c. * Union aliasing, see misc/clang_aliasing.c.
*/ */
DUK_LOCAL void duk__selftest_double_aliasing(void) { DUK_LOCAL duk_uint_t duk__selftest_double_aliasing(void) {
duk__test_double_union a, b;
/* This testcase fails when Emscripten-generated code runs on Firefox. /* This testcase fails when Emscripten-generated code runs on Firefox.
* It's not an issue because the failure should only affect packed * It's not an issue because the failure should only affect packed
* duk_tval representation, which is not used with Emscripten. * duk_tval representation, which is not used with Emscripten.
*/ */
#if !defined(DUK_USE_PACKED_TVAL) #if defined(DUK_USE_PACKED_TVAL)
DUK_D(DUK_DPRINT("skip double aliasing self test when duk_tval is not packed")); duk_uint_t error_count = 0;
return; duk__test_double_union a, b;
#endif
/* Test signaling NaN and alias assignment in all endianness combinations. /* Test signaling NaN and alias assignment in all endianness combinations.
*/ */
@ -233,18 +257,27 @@ DUK_LOCAL void duk__selftest_double_aliasing(void) {
a.c[4] = 0x11; a.c[5] = 0x22; a.c[6] = 0x33; a.c[7] = 0x44; a.c[4] = 0x11; a.c[5] = 0x22; a.c[6] = 0x33; a.c[7] = 0x44;
b = a; b = a;
DUK__DBLUNION_CMP_TRUE(&a, &b); 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. * Zero sign, see misc/tcc_zerosign2.c.
*/ */
DUK_LOCAL void duk__selftest_double_zero_sign(void) { DUK_LOCAL duk_uint_t duk__selftest_double_zero_sign(void) {
duk_uint_t error_count = 0;
duk__test_double_union a, b; duk__test_double_union a, b;
a.d = 0.0; a.d = 0.0;
b.d = -a.d; b.d = -a.d;
DUK__DBLUNION_CMP_FALSE(&a, &b); DUK__DBLUNION_CMP_FALSE(&a, &b);
return error_count;
} }
/* /*
@ -254,20 +287,23 @@ DUK_LOCAL void duk__selftest_double_zero_sign(void) {
* selftest ensures they're correctly detected and used. * selftest ensures they're correctly detected and used.
*/ */
DUK_LOCAL void duk__selftest_struct_align(void) { DUK_LOCAL duk_uint_t duk__selftest_struct_align(void) {
duk_uint_t error_count = 0;
#if (DUK_USE_ALIGN_BY == 4) #if (DUK_USE_ALIGN_BY == 4)
if ((sizeof(duk_hbuffer_fixed) % 4) != 0) { if ((sizeof(duk_hbuffer_fixed) % 4) != 0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: sizeof(duk_hbuffer_fixed) not aligned to 4"); DUK__FAILED("sizeof(duk_hbuffer_fixed) not aligned to 4");
} }
#elif (DUK_USE_ALIGN_BY == 8) #elif (DUK_USE_ALIGN_BY == 8)
if ((sizeof(duk_hbuffer_fixed) % 8) != 0) { if ((sizeof(duk_hbuffer_fixed) % 8) != 0) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: sizeof(duk_hbuffer_fixed) not aligned to 8"); DUK__FAILED("sizeof(duk_hbuffer_fixed) not aligned to 8");
} }
#elif (DUK_USE_ALIGN_BY == 1) #elif (DUK_USE_ALIGN_BY == 1)
/* no check */ /* no check */
#else #else
#error invalid DUK_USE_ALIGN_BY #error invalid DUK_USE_ALIGN_BY
#endif #endif
return error_count;
} }
/* /*
@ -277,7 +313,8 @@ DUK_LOCAL void duk__selftest_struct_align(void) {
* but don't work correctly. Test for known cases. * but don't work correctly. Test for known cases.
*/ */
DUK_LOCAL void duk__selftest_64bit_arithmetic(void) { DUK_LOCAL duk_uint_t duk__selftest_64bit_arithmetic(void) {
duk_uint_t error_count = 0;
#if defined(DUK_USE_64BIT_OPS) #if defined(DUK_USE_64BIT_OPS)
volatile duk_int64_t i; volatile duk_int64_t i;
volatile duk_double_t d; volatile duk_double_t d;
@ -286,22 +323,25 @@ DUK_LOCAL void duk__selftest_64bit_arithmetic(void) {
d = 2147483648.0; d = 2147483648.0;
i = (duk_int64_t) d; i = (duk_int64_t) d;
if (i != 0x80000000LL) { if (i != 0x80000000LL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: casting 2147483648.0 to duk_int64_t failed"); DUK__FAILED("casting 2147483648.0 to duk_int64_t failed");
} }
#else #else
/* nop */ /* nop */
#endif #endif
return error_count;
} }
/* /*
* Casting * Casting
*/ */
DUK_LOCAL void duk__selftest_cast_double_to_small_uint(void) { DUK_LOCAL duk_uint_t duk__selftest_cast_double_to_small_uint(void) {
/* /*
* https://github.com/svaarala/duktape/issues/127#issuecomment-77863473 * https://github.com/svaarala/duktape/issues/127#issuecomment-77863473
*/ */
duk_uint_t error_count = 0;
duk_double_t d1, d2; duk_double_t d1, d2;
duk_small_uint_t u; duk_small_uint_t u;
@ -315,7 +355,7 @@ DUK_LOCAL void duk__selftest_cast_double_to_small_uint(void) {
d2 = (duk_double_t) u; d2 = (duk_double_t) u;
if (!(d1 == 1.0 && u == 1 && d2 == 1.0 && d1 == d2)) { 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"); DUK__FAILED("double to duk_small_uint_t cast failed");
} }
/* Same test with volatiles */ /* Same test with volatiles */
@ -325,11 +365,13 @@ DUK_LOCAL void duk__selftest_cast_double_to_small_uint(void) {
d2v = (duk_double_t) uv; d2v = (duk_double_t) uv;
if (!(d1v == 1.0 && uv == 1 && d2v == 1.0 && d1v == d2v)) { 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__FAILED("double to duk_small_uint_t cast failed");
} }
return error_count;
} }
DUK_LOCAL void duk__selftest_cast_double_to_uint32(void) { DUK_LOCAL duk_uint_t duk__selftest_cast_double_to_uint32(void) {
/* /*
* This test fails on an exotic ARM target; double-to-uint * This test fails on an exotic ARM target; double-to-uint
* cast is incorrectly clamped to -signed- int highest value. * cast is incorrectly clamped to -signed- int highest value.
@ -337,6 +379,7 @@ DUK_LOCAL void duk__selftest_cast_double_to_uint32(void) {
* https://github.com/svaarala/duktape/issues/336 * https://github.com/svaarala/duktape/issues/336
*/ */
duk_uint_t error_count = 0;
duk_double_t dv; duk_double_t dv;
duk_uint32_t uv; duk_uint32_t uv;
@ -344,29 +387,40 @@ DUK_LOCAL void duk__selftest_cast_double_to_uint32(void) {
uv = (duk_uint32_t) dv; uv = (duk_uint32_t) dv;
if (uv != 0xdeadbeefUL) { if (uv != 0xdeadbeefUL) {
DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double to duk_uint32_t cast failed"); DUK__FAILED("double to duk_uint32_t cast failed");
} }
return error_count;
} }
/* /*
* Self test main * Self test main
*/ */
DUK_INTERNAL void duk_selftest_run_tests(void) { DUK_INTERNAL duk_uint_t duk_selftest_run_tests(void) {
duk__selftest_types(); duk_uint_t error_count = 0;
duk__selftest_packed_tval();
duk__selftest_twos_complement(); DUK_D(DUK_DPRINT("self test starting"));
duk__selftest_byte_order();
duk__selftest_bswap_macros(); error_count += duk__selftest_types();
duk__selftest_double_union_size(); error_count += duk__selftest_packed_tval();
duk__selftest_double_aliasing(); error_count += duk__selftest_twos_complement();
duk__selftest_double_zero_sign(); error_count += duk__selftest_byte_order();
duk__selftest_struct_align(); error_count += duk__selftest_bswap_macros();
duk__selftest_64bit_arithmetic(); error_count += duk__selftest_double_union_size();
duk__selftest_cast_double_to_small_uint(); error_count += duk__selftest_double_aliasing();
duk__selftest_cast_double_to_uint32(); error_count += duk__selftest_double_zero_sign();
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();
DUK_D(DUK_DPRINT("self test complete, total error count: %ld", (long) error_count));
return error_count;
} }
#undef DUK__FAILED
#undef DUK__DBLUNION_CMP_TRUE #undef DUK__DBLUNION_CMP_TRUE
#undef DUK__DBLUNION_CMP_FALSE #undef DUK__DBLUNION_CMP_FALSE

2
src/duk_selftest.h

@ -6,7 +6,7 @@
#define DUK_SELFTEST_H_INCLUDED #define DUK_SELFTEST_H_INCLUDED
#if defined(DUK_USE_SELF_TESTS) #if defined(DUK_USE_SELF_TESTS)
DUK_INTERNAL_DECL void duk_selftest_run_tests(void); DUK_INTERNAL_DECL duk_uint_t duk_selftest_run_tests(void);
#endif #endif
#endif /* DUK_SELFTEST_H_INCLUDED */ #endif /* DUK_SELFTEST_H_INCLUDED */

Loading…
Cancel
Save