From d31a12a210bec646eadc23c11ede29f05e72e373 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Jan 2024 15:09:51 +0900 Subject: [PATCH] Optional detail info at assertion failure --- error.c | 15 +++++++ include/ruby/assert.h | 91 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/error.c b/error.c index 16006b483f..385da82f38 100644 --- a/error.c +++ b/error.c @@ -1122,11 +1122,26 @@ rb_report_bug_valist(VALUE file, int line, const char *fmt, va_list args) void rb_assert_failure(const char *file, int line, const char *name, const char *expr) +{ + rb_assert_failure_detail(file, line, name, expr, NULL); +} + +void +rb_assert_failure_detail(const char *file, int line, const char *name, const char *expr, + const char *fmt, ...) { FILE *out = stderr; fprintf(out, "Assertion Failed: %s:%d:", file, line); if (name) fprintf(out, "%s:", name); fprintf(out, "%s\n%s\n\n", expr, rb_dynamic_description); + + if (fmt && *fmt) { + va_list args; + va_start(args, fmt); + vfprintf(out, fmt, args); + va_end(args); + } + preface_dump(out); rb_vm_bugreport(NULL, out); bug_report_end(out, -1); diff --git a/include/ruby/assert.h b/include/ruby/assert.h index 0c052363bc..ebeae3e7be 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -22,6 +22,7 @@ */ #include "ruby/internal/assume.h" #include "ruby/internal/attr/cold.h" +#include "ruby/internal/attr/format.h" #include "ruby/internal/attr/noreturn.h" #include "ruby/internal/cast.h" #include "ruby/internal/dllexport.h" @@ -132,6 +133,11 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NORETURN() RBIMPL_ATTR_COLD() void rb_assert_failure(const char *file, int line, const char *name, const char *expr); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_COLD() +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 5, 6) +void rb_assert_failure_detail(const char *file, int line, const char *name, const char *expr, const char *fmt, ...); RBIMPL_SYMBOL_EXPORT_END() #ifdef RUBY_FUNCTION_NAME_STRING @@ -147,8 +153,22 @@ RBIMPL_SYMBOL_EXPORT_END() * * @param mesg The message to display. */ -#define RUBY_ASSERT_FAIL(mesg) \ +#if defined(HAVE___VA_OPT__) +# if RBIMPL_HAS_WARNING("-Wgnu-zero-variadic-macro-arguments") +/* __VA_OPT__ is to be used for the zero variadic macro arguments + * cases. */ +RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) +# endif +# define RUBY_ASSERT_FAIL(mesg, ...) \ + rb_assert_failure##__VA_OPT__(_detail)( \ + __FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_FAIL(mesg, ...) \ rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) +#else +# define RUBY_ASSERT_FAIL(mesg) \ + rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) +#endif /** * Asserts that the expression is truthy. If not aborts with the message. @@ -156,15 +176,27 @@ RBIMPL_SYMBOL_EXPORT_END() * @param expr What supposedly evaluates to true. * @param mesg The message to display on failure. */ -#define RUBY_ASSERT_MESG(expr, mesg) \ +#if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG(expr, ...) \ + (RB_LIKELY(expr) ? RBIMPL_ASSERT_NOTHING : RUBY_ASSERT_FAIL(__VA_ARGS__)) +#else +# define RUBY_ASSERT_MESG(expr, mesg) \ (RB_LIKELY(expr) ? RBIMPL_ASSERT_NOTHING : RUBY_ASSERT_FAIL(mesg)) +#endif /** * A variant of #RUBY_ASSERT that does not interface with #RUBY_DEBUG. * * @copydetails #RUBY_ASSERT */ -#define RUBY_ASSERT_ALWAYS(expr) RUBY_ASSERT_MESG((expr), #expr) +#if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_ALWAYS(expr, ...) \ + RUBY_ASSERT_MESG(expr, #expr __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_ALWAYS(expr, ...) RUBY_ASSERT_MESG(expr, #expr) +#else +# define RUBY_ASSERT_ALWAYS(expr) RUBY_ASSERT_MESG((expr), #expr) +#endif /** * Asserts that the given expression is truthy if and only if #RUBY_DEBUG is truthy. @@ -172,9 +204,20 @@ RBIMPL_SYMBOL_EXPORT_END() * @param expr What supposedly evaluates to true. */ #if RUBY_DEBUG -# define RUBY_ASSERT(expr) RUBY_ASSERT_MESG((expr), #expr) +# if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT(expr, ...) \ + RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) +# elif !defined(__cplusplus) +# define RUBY_ASSERT(expr, ...) RUBY_ASSERT_MESG((expr), #expr) +# else +# define RUBY_ASSERT(expr) RUBY_ASSERT_MESG((expr), #expr) +# endif #else -# define RUBY_ASSERT(expr) RBIMPL_ASSERT_NOTHING +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT(/* expr, */...) RBIMPL_ASSERT_NOTHING +# else +# define RUBY_ASSERT(expr) RBIMPL_ASSERT_NOTHING +# endif #endif /** @@ -187,9 +230,20 @@ RBIMPL_SYMBOL_EXPORT_END() /* Currently `RUBY_DEBUG == ! defined(NDEBUG)` is always true. There is no * difference any longer between this one and `RUBY_ASSERT`. */ #if defined(NDEBUG) -# define RUBY_ASSERT_NDEBUG(expr) RBIMPL_ASSERT_NOTHING +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_NDEBUG(/* expr, */...) RBIMPL_ASSERT_NOTHING +# else +# define RUBY_ASSERT_NDEBUG(expr) RBIMPL_ASSERT_NOTHING +# endif #else -# define RUBY_ASSERT_NDEBUG(expr) RUBY_ASSERT_MESG((expr), #expr) +# if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_NDEBUG(expr, ...) \ + RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) +# elif !defined(__cplusplus) +# define RUBY_ASSERT_NDEBUG(expr, ...) RUBY_ASSERT_MESG((expr), #expr) +# else +# define RUBY_ASSERT_NDEBUG(expr) RUBY_ASSERT_MESG((expr), #expr) +# endif #endif /** @@ -197,10 +251,20 @@ RBIMPL_SYMBOL_EXPORT_END() * @param mesg The message to display on failure. */ #if RUBY_DEBUG -# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) RUBY_ASSERT_MESG((expr), (mesg)) +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG_WHEN(cond, /* expr, */...) \ + RUBY_ASSERT_MESG(__VA_ARGS__) +# else +# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) RUBY_ASSERT_MESG((expr), (mesg)) +# endif #else -# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) \ +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG_WHEN(cond, expr, ...) \ + ((cond) ? RUBY_ASSERT_MESG((expr), __VA_ARGS__) : RBIMPL_ASSERT_NOTHING) +# else +# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) \ ((cond) ? RUBY_ASSERT_MESG((expr), (mesg)) : RBIMPL_ASSERT_NOTHING) +# endif #endif /** @@ -210,7 +274,14 @@ RBIMPL_SYMBOL_EXPORT_END() * @param cond Extra condition that shall hold for assertion to take effect. * @param expr What supposedly evaluates to true. */ -#define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) +#if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_WHEN(cond, expr, ...) \ + RUBY_ASSERT_MESG_WHEN(cond, expr, #expr __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_WHEN(cond, expr, ...) RUBY_ASSERT_MESG_WHEN(cond, expr, #expr) +#else +# define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) +#endif /** * This is either #RUBY_ASSERT or #RBIMPL_ASSUME, depending on #RUBY_DEBUG.