changeset 95:e82eca4f8606

core: simplify error/panic routines
author David Demelier <markand@malikania.fr>
date Mon, 30 Mar 2020 14:07:21 +0200
parents ed72843a7194
children 04011a942e3b
files doxygen/Doxyfile doxygen/page-howto-initialization.c src/adventure/main.c src/adventure/mainmenu_state.c src/adventure/panic_state.c src/adventure/splashscreen_state.c src/core/error.c src/core/error.h src/core/panic.c src/core/panic.h src/core/plat.h src/core/util.c src/core/util.h tests/test-map.c tests/test-panic.c
diffstat 15 files changed, 234 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/doxygen/Doxyfile	Mon Mar 30 13:34:42 2020 +0200
+++ b/doxygen/Doxyfile	Mon Mar 30 14:07:21 2020 +0200
@@ -21,6 +21,8 @@
 PROJECT_NUMBER         = "0.1.0"
 PROJECT_BRIEF          = "2D RPG game in C"
 PROJECT_LOGO           =
+ENABLE_PREPROCESSING   = YES
+PREDEFINED             = DOXYGEN
 OUTPUT_DIRECTORY       = doxygen
 ALLOW_UNICODE_NAMES    = YES
 STRIP_FROM_PATH        = src
--- a/doxygen/page-howto-initialization.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/doxygen/page-howto-initialization.c	Mon Mar 30 14:07:21 2020 +0200
@@ -28,12 +28,12 @@
  * \section example Example
  *
  * Init the core and create a window of Full HD resolution. The function \ref
- * error_fatal will print the error and exit if needed.
+ * panic will all the panic handler.
  *
  * \code
  * if (!sys_init())
- * 	error_fatal();
+ * 	panic();
  * if (!window_init("My Awesome Game", 1920, 1080))
- * 	error_fatal();
+ * 	panic();
  * \endcode
  */
--- a/src/adventure/main.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/adventure/main.c	Mon Mar 30 14:07:21 2020 +0200
@@ -60,9 +60,9 @@
 init(void)
 {
 	if (!sys_init())
-		error_fatal();
+		panic();
 	if (!window_init("Molko's Adventure", WINDOW_WIDTH, WINDOW_HEIGHT))
-		error_fatal();
+		panic();
 
 	/* Init unrecoverable panic state. */
 	panic_state_init();
@@ -82,11 +82,11 @@
 	int panic_trigger = 0;
 
 	if (!(font_open(&font, sys_datapath("fonts/DejaVuSans.ttf"), 15)))
-		error_fatal();
+		panic();
 	if (!(font_open(&debug_font, sys_datapath("fonts/DejaVuSans.ttf"), 10)))
-		error_fatal();
+		panic();
 	if (!(image_open(&frame, sys_datapath("images/message.png"))))
-		error_fatal();
+		panic();
 
 	debug_options.default_font = &debug_font;
 	debug_options.enable = true;
@@ -110,7 +110,7 @@
 			    ev.type == EVENT_KEYDOWN &&
 			    ev.key.key == KEY_F12) {
 				if (++panic_trigger == 3)
-					panic("User panic request.");
+					panicf("User panic request.");
 			}
 
 			game_handle(&ev);
--- a/src/adventure/mainmenu_state.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/adventure/mainmenu_state.c	Mon Mar 30 14:07:21 2020 +0200
@@ -63,7 +63,7 @@
 	struct font font = { 0 };
 
 	if (!font_open(&font, sys_datapath("fonts/pirata-one.ttf"), 30))
-		error_fatal();
+		panic();
 
 	substate = SUBSTATE_MOVING;
 	x = splashscreen_state_data.x;
@@ -91,11 +91,11 @@
 {
 	/* Prepare map. */
 	if (!map_open(&map_state_data.map.map, sys_datapath("maps/test.map")))
-		error_fatal();
+		panic();
 
 	/* Prepare image and sprite. */
 	if (!(image_open(&image, sys_datapath("sprites/test-walk.png"))))
-		error_fatal();
+		panic();
 
 	sprite_init(&map_state_data.player.sprite, &image, 48, 48);
 	game_switch(&map_state, false);
--- a/src/adventure/panic_state.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/adventure/panic_state.c	Mon Mar 30 14:07:21 2020 +0200
@@ -65,6 +65,20 @@
 static struct label lerror;
 
 static void
+die(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "abort: ");
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	exit(1);
+}
+
+static void
 enter(void)
 {
 }
@@ -143,7 +157,7 @@
 		font_render(&data.font, &labels[i].texture, labels[i].text);
 
 		if (!texture_ok(&labels[i].texture))
-			error_fatal();
+			die("%s\n", error());
 	}
 }
 
@@ -209,5 +223,5 @@
 	 * on the console.
 	 */
 	if (!(font_open(&data.font, sys_datapath(FONT), FONT_SZ)))
-		error_fatal();
+		die("%s", error());
 }
--- a/src/adventure/splashscreen_state.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/adventure/splashscreen_state.c	Mon Mar 30 14:07:21 2020 +0200
@@ -16,17 +16,17 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include "error.h"
-#include "font.h"
-#include "game.h"
-#include "image.h"
-#include "map.h"
-#include "map_state.h"
-#include "painter.h"
-#include "state.h"
-#include "sys.h"
-#include "texture.h"
-#include "window.h"
+#include <font.h>
+#include <game.h>
+#include <image.h>
+#include <map.h>
+#include <map_state.h>
+#include <painter.h>
+#include <panic.h>
+#include <state.h>
+#include <sys.h>
+#include <texture.h>
+#include <window.h>
 
 #include "splashscreen_state.h"
 #include "mainmenu_state.h"
@@ -43,9 +43,9 @@
 	};
 
 	if (!(font_open(&font, sys_datapath("fonts/teutonic1.ttf"), 130)))
-		error_fatal();
+		panic();
 	if (!(font_render(&font, &splashscreen_state_data.text, "Molko's Adventure")))
-		error_fatal();
+		panic();
 
 	/* Compute position. */
 	const unsigned int w = splashscreen_state_data.text.w;
--- a/src/core/error.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/error.c	Mon Mar 30 14:07:21 2020 +0200
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <assert.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,6 +46,8 @@
 bool
 error_printf(const char *fmt, ...)
 {
+	assert(fmt);
+
 	va_list ap;
 
 	va_start(ap, fmt);
@@ -57,35 +60,13 @@
 bool
 error_vprintf(const char *fmt, va_list ap)
 {
+	assert(fmt);
+
 	vsnprintf(buffer, sizeof (buffer), fmt, ap);
 
 	return false;
 }
 
-noreturn void
-error_fatal(void)
-{
-	fprintf(stderr, "%s\n", buffer);
-	exit(1);
-}
-
-noreturn void
-error_fatalf(const char *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	error_vfatalf(fmt, ap);
-	va_end(ap);
-}
-
-noreturn void
-error_vfatalf(const char *fmt, va_list ap)
-{
-	fprintf(stderr, fmt, ap);
-	exit(1);
-}
-
 /* private: error_p.h */
 
 bool
--- a/src/core/error.h	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/error.h	Mon Mar 30 14:07:21 2020 +0200
@@ -27,12 +27,13 @@
 
 #include <stdarg.h>
 #include <stdbool.h>
-#include <stdnoreturn.h>
+
+#include "plat.h"
 
 /**
  * Get the last error returned.
  *
- * \return the error string
+ * \return The error string.
  */
 const char *
 error(void);
@@ -41,7 +42,7 @@
  * Convenient helper that sets last error from global C errno and then return
  * false.
  *
- * \return false
+ * \return Always false.
  */
 bool
 error_errno(void);
@@ -49,43 +50,22 @@
 /**
  * Set the game error with a printf-like format.
  *
+ * \pre fmt != NULL
  * \param fmt the format string
- * \return false
+ * \return Always false.
  */
 bool
-error_printf(const char *fmt, ...);
+error_printf(const char *fmt, ...) PLAT_PRINTF(1, 2);
 
 /**
  * Similar to \ref error_printf.
  *
+ * \pre fmt != NULL
  * \param fmt the format stinrg
  * \param ap the variadic arguments pointer
- * \return false
+ * \return Always false.
  */
 bool
-error_vprintf(const char *fmt, va_list ap);
-
-/**
- * Print last registered error and exit with code 1.
- */
-noreturn void
-error_fatal(void);
-
-/**
- * Prints an error to stderr and exit with code 1.
- *
- * \param fmt the format string
- */
-noreturn void
-error_fatalf(const char *fmt, ...);
-
-/**
- * Similar to \ref error_fatalf.
- *
- * \param fmt the format string
- * \param ap the variadic arguments pointer
- */
-noreturn void
-error_vfatalf(const char *fmt, va_list ap);
+error_vprintf(const char *fmt, va_list ap) PLAT_PRINTF(1, 0);
 
 #endif /* !MOLKO_ERROR_H */
--- a/src/core/panic.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/panic.c	Mon Mar 30 14:07:21 2020 +0200
@@ -17,30 +17,60 @@
  */
 
 #include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
 
 #include "error.h"
 #include "panic.h"
 
-void (*panic_handler)(void) = error_fatal;
+static noreturn void
+terminate(void)
+{
+	fprintf(stderr, "%s", error());
+	exit(1);
+}
 
-void
-panic(const char *fmt, ...)
+void (*panic_handler)(void) = terminate;
+
+noreturn void
+panicf(const char *fmt, ...)
 {
 	assert(fmt);
 
 	va_list ap;
 
+	/*
+	 * Store the error before calling panic because va_end would not be
+	 * called.
+	 */
 	va_start(ap, fmt);
-	panicv(fmt, ap);
+	error_vprintf(fmt, ap);
 	va_end(ap);
+
+	panic();
 }
 
-void
-panicv(const char *fmt, va_list ap)
+noreturn void
+vpanicf(const char *fmt, va_list ap)
 {
 	assert(fmt);
 	assert(panic_handler);
 
 	error_vprintf(fmt, ap);
+	panic();
+}
+
+noreturn void
+panic(void)
+{
+	assert(panic_handler);
+
 	panic_handler();
+
+	/*
+	 * This should not happen, if it does it means the user did not fully
+	 * satisfied the constraint of panic_handler.
+	 */
+	fprintf(stderr, "abort: panic handler returned");
+	exit(1);
 }
--- a/src/core/panic.h	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/panic.h	Mon Mar 30 14:07:21 2020 +0200
@@ -39,11 +39,19 @@
  */
 
 #include <stdarg.h>
+#include <stdnoreturn.h>
+
+#include "plat.h"
 
 /**
  * \brief Global panic handler.
  *
- * The default implementation shows the last error and exit with code 1.
+ * The default implementation shows the last error and exit with code 1. The
+ * function must not return so you have to implement a setjmp/longjmp or a
+ * exception to be thrown.
+ *
+ * If the user defined function returns, panic routines will finally exit with
+ * code 1.
  */
 extern void (*panic_handler)(void);
 
@@ -56,17 +64,24 @@
  * \pre fmt != NULL
  * \param fmt the printf(3) format string
  */
-void
-panic(const char *fmt, ...);
+noreturn void
+panicf(const char *fmt, ...) PLAT_PRINTF(1, 2);
 
 /**
- * Similar to \ref panic but with a va_list argument instead.
+ * Similar to \ref panicf but with a arguments pointer.
  *
  * \pre fmt != NULL
  * \param fmt the printf(3) format string
  * \param ap the arguments pointer
  */
-void
-panicv(const char *fmt, va_list ap);
+noreturn void
+vpanicf(const char *fmt, va_list ap) PLAT_PRINTF(1, 0);
+
+/**
+ * Similar to \ref panicf but use last error stored using \ref error.h
+ * routines.
+ */
+noreturn void
+panic(void);
 
 #endif /* !MOLKO_PANIC_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/plat.h	Mon Mar 30 14:07:21 2020 +0200
@@ -0,0 +1,49 @@
+/*
+ * plat.h -- non-portable platform specific code
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MOLKO_PLAT_H
+#define MOLKO_PLAT_H
+
+/**
+ * \file plat.h
+ * \brief Non-portable platform specific code.
+ */
+
+/*
+ * This block is used for doxygen documentation, the macros here are never
+ * exposed.
+ */
+#if defined(DOXYGEN)
+
+/**
+ * Printf specifier for function supporting the printf(3) syntax. This is
+ * currently only supported on GCC/Clang
+ */
+#define PLAT_PRINTF(p1, p2)
+
+#else
+
+#if defined(__GNUC__)
+#define PLAT_PRINTF(p1, p2) __attribute__ ((format (printf, p1, p2)))
+#else
+#define PLAT_PRINTF(p1, p2)
+#endif
+
+#endif /* !DOXYGEN  */
+
+#endif /* !MOLKO_PLAT_H */
--- a/src/core/util.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/util.c	Mon Mar 30 14:07:21 2020 +0200
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
@@ -31,7 +32,7 @@
 	void *mem;
 
 	if (!(mem = malloc(size)))
-		panic("%s\n", strerror(errno));
+		panicf("%s", strerror(errno));
 
 	return mem;
 }
@@ -42,7 +43,7 @@
 	void *mem;
 
 	if (!(mem = calloc(n, size)))
-		panic("%s\n", strerror(errno));
+		panicf("%s", strerror(errno));
 
 	return mem;
 }
@@ -53,11 +54,53 @@
 	void *mem;
 
 	if (!(mem = malloc(size)))
-		panic("%s\n", strerror(errno));
+		panicf("%s", strerror(errno));
 
 	return memcpy(mem, ptr, size);
 }
 
+char *
+eprintf(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+	char *ret;
+
+	va_start(ap, fmt);
+	ret = evprintf(fmt, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+char *
+evprintf(const char *fmt, va_list args)
+{
+	assert(fmt);
+
+	va_list ap;
+	int size;
+	char *ret;
+
+	/* Count number of bytes required. */
+	va_copy(ap, args);
+
+	if ((size = vsnprintf(NULL, 0, fmt, ap)) < 0)
+		panicf("%s", strerror(errno));
+
+	/* Do actual copy. */
+	ret = emalloc(size + 1);
+	va_copy(ap, args);
+
+	if (vsnprintf(ret, size, fmt, ap) != size) {
+		free(ret);
+		panicf("%s", strerror(errno));
+	}
+
+	return ret;
+}
+
 void
 delay(unsigned int ms)
 {
--- a/src/core/util.h	Mon Mar 30 13:34:42 2020 +0200
+++ b/src/core/util.h	Mon Mar 30 14:07:21 2020 +0200
@@ -30,8 +30,11 @@
  *       for convenience.
  */
 
+#include <stdarg.h>
 #include <stddef.h>
 
+#include "plat.h"
+
 /**
  * Get the number of elements in a static array.
  *
@@ -74,6 +77,24 @@
 ememdup(const void *ptr, size_t size);
 
 /**
+ * Create a dynamically allocated string in the printf(3) format string.
+ *
+ * \pre fmt != NULL
+ * \return The heap allocated string.
+ * \post Returned string will never be NULL.
+ */
+char *
+eprintf(const char *fmt, ...) PLAT_PRINTF(1, 2);
+
+/**
+ * Similar to \ref eprintf with arguments pointer.
+ *
+ * \copydoc eprintf
+ */
+char *
+evprintf(const char *fmt, va_list ap) PLAT_PRINTF(1, 0);
+
+/**
  * Put the thread to sleep for a given amount of milliseconds.
  *
  * \param ms the number of milliseconds to wait
--- a/tests/test-map.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/tests/test-map.c	Mon Mar 30 14:07:21 2020 +0200
@@ -29,9 +29,9 @@
 	(void)data;
 
 	if (!sys_init())
-		error_fatal();
+		panic();
 	if (!window_init("Test map", 100, 100))
-		error_fatal();
+		panic();
 }
 
 static void
--- a/tests/test-panic.c	Mon Mar 30 13:34:42 2020 +0200
+++ b/tests/test-panic.c	Mon Mar 30 14:07:21 2020 +0200
@@ -38,7 +38,7 @@
 	panic_handler = handler;
 	handler_called = false;
 
-	panic("this is an error");
+	panicf("this is an error");
 	GREATEST_ASSERT(handler_called);
 	GREATEST_ASSERT_STR_EQ(error(), "this is an error");
 	GREATEST_PASS();