From e8fe775b03a9abde11f7ce7a9ebf8e002111e923 Mon Sep 17 00:00:00 2001 From: David Gibson Date: Mon, 2 Jun 2014 22:37:28 +1000 Subject: [PATCH] minmax: New module, safe min and max macros Add a 'minmax' module with typesafe macros to compute minimum, maximum and clamping. Inspired by the versions used in the Linux kernel, but using a different implementation based on __builtin_types_compatible_p() and the build_assert module. Signed-off-by: David Gibson Signed-off-by: Rusty Russell --- Makefile-ccan | 1 + ccan/minmax/LICENSE | 1 + ccan/minmax/_info | 45 ++++++++++++++++ ccan/minmax/minmax.h | 65 +++++++++++++++++++++++ ccan/minmax/test/compile_fail-wrongsign.c | 19 +++++++ ccan/minmax/test/compile_fail-wrongsize.c | 19 +++++++ ccan/minmax/test/run.c | 46 ++++++++++++++++ 7 files changed, 196 insertions(+) create mode 120000 ccan/minmax/LICENSE create mode 100644 ccan/minmax/_info create mode 100644 ccan/minmax/minmax.h create mode 100644 ccan/minmax/test/compile_fail-wrongsign.c create mode 100644 ccan/minmax/test/compile_fail-wrongsize.c create mode 100644 ccan/minmax/test/run.c diff --git a/Makefile-ccan b/Makefile-ccan index cbf1c2fe..1d17e26f 100644 --- a/Makefile-ccan +++ b/Makefile-ccan @@ -20,6 +20,7 @@ MODS_NO_SRC := alignof \ container_of \ darray \ endian \ + minmax \ objset \ short_types \ tcon \ diff --git a/ccan/minmax/LICENSE b/ccan/minmax/LICENSE new file mode 120000 index 00000000..b7951dab --- /dev/null +++ b/ccan/minmax/LICENSE @@ -0,0 +1 @@ +../../licenses/CC0 \ No newline at end of file diff --git a/ccan/minmax/_info b/ccan/minmax/_info new file mode 100644 index 00000000..1490ba43 --- /dev/null +++ b/ccan/minmax/_info @@ -0,0 +1,45 @@ +#include +#include "config.h" + +/** + * minmax - typesafe minimum and maximum functions + * + * The classic implementation of minimum / maximum macros in C can be + * very dangerous. If the two arguments have different sizes, or + * different signedness, type promotion rules can lead to very + * surprising results. + * + * This module implements typesafe versions, which will generate a + * compile time error, if the arguments have different types. + * + * Example: + * #include + * #include + * + * int main(int argc, char *argv[]) + * { + * printf("Signed max: %d\n", max(1, -1)); + * printf("Unsigned max: %u\n", max(1U, -1U)); + * return 0; + * } + * + * Author: David Gibson + * License: CC0 (Public domain) + * + * Ccanlint: + * // We need several gcc extensions + * tests_compile_without_features FAIL + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/build_assert\n"); + return 0; + } + + return 1; +} diff --git a/ccan/minmax/minmax.h b/ccan/minmax/minmax.h new file mode 100644 index 00000000..d111d1bc --- /dev/null +++ b/ccan/minmax/minmax.h @@ -0,0 +1,65 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#ifndef CCAN_MINMAX_H +#define CCAN_MINMAX_H + +#include "config.h" + +#include + +#if !HAVE_STATEMENT_EXPR || !HAVE_TYPEOF +/* + * Without these, there's no way to avoid unsafe double evaluation of + * the arguments + */ +#error Sorry, minmax module requires statement expressions and typeof +#endif + +#if HAVE_BUILTIN_TYPES_COMPATIBLE_P +#define MINMAX_ASSERT_COMPATIBLE(a, b) \ + BUILD_ASSERT(__builtin_types_compatible_p(a, b)) +#else +#define MINMAX_ASSERT_COMPATIBLE(a, b) \ + do { } while (0) +#endif + +#define min(a, b) \ + ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + MINMAX_ASSERT_COMPATIBLE(typeof(_a), typeof(_b)); \ + _a < _b ? _a : _b; \ + }) + +#define max(a, b) \ + ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + MINMAX_ASSERT_COMPATIBLE(typeof(_a), typeof(_b)); \ + _a > _b ? _a : _b; \ + }) + +#define clamp(v, f, c) (max(min((v), (c)), (f))) + + +#define min_t(t, a, b) \ + ({ \ + t _ta = (a); \ + t _tb = (b); \ + min(_ta, _tb); \ + }) +#define max_t(t, a, b) \ + ({ \ + t _ta = (a); \ + t _tb = (b); \ + max(_ta, _tb); \ + }) + +#define clamp_t(t, v, f, c) \ + ({ \ + t _tv = (v); \ + t _tf = (f); \ + t _tc = (c); \ + clamp(_tv, _tf, _tc); \ + }) + +#endif /* CCAN_MINMAX_H */ diff --git a/ccan/minmax/test/compile_fail-wrongsign.c b/ccan/minmax/test/compile_fail-wrongsign.c new file mode 100644 index 00000000..6aeaf1c7 --- /dev/null +++ b/ccan/minmax/test/compile_fail-wrongsign.c @@ -0,0 +1,19 @@ +#include + +static int function(void) +{ +#ifdef FAIL + return min(1, 1U); +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if the typechecks are noops." +#endif +#else + return 0; +#endif +} + +int main(int argc, char *argv[]) +{ + function(); + return 0; +} diff --git a/ccan/minmax/test/compile_fail-wrongsize.c b/ccan/minmax/test/compile_fail-wrongsize.c new file mode 100644 index 00000000..ef57784f --- /dev/null +++ b/ccan/minmax/test/compile_fail-wrongsize.c @@ -0,0 +1,19 @@ +#include + +static int function(void) +{ +#ifdef FAIL + return min(1, 1L); +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if the typechecks are noops." +#endif +#else + return 0; +#endif +} + +int main(int argc, char *argv[]) +{ + function(); + return 0; +} diff --git a/ccan/minmax/test/run.c b/ccan/minmax/test/run.c new file mode 100644 index 00000000..e954efdd --- /dev/null +++ b/ccan/minmax/test/run.c @@ -0,0 +1,46 @@ +#include +#include + +int main(void) +{ + int a, b; + + /* This is how many tests you plan to run */ + plan_tests(23); + + ok1(min(1, 2) == 1); + ok1(max(1, 2) == 2); + ok1(min(-1, 1) == -1); + ok1(max(-1, 1) == 1); + + ok1(min(-1U, 1U) == 1U); + ok1(max(-1U, 1U) == -1U); + + ok1(max_t(signed int, -1, 1U) == 1); + ok1(max_t(unsigned int, -1, 1) == -1U); + + ok1(min_t(signed int, -1, 1U) == -1); + ok1(min_t(unsigned int, -1, 1) == 1U); + + ok1(clamp(1, 2, 5) == 2); + ok1(clamp(2, 2, 5) == 2); + ok1(clamp(3, 2, 5) == 3); + ok1(clamp(5, 2, 5) == 5); + ok1(clamp(6, 2, 5) == 5); + + ok1(clamp(-1, 2, 5) == 2); + ok1(clamp(-1U, 2U, 5U) == 5U); + + ok1(clamp_t(signed int, -1, 2, 5) == 2); + ok1(clamp_t(unsigned int, -1, 2, 5) == 5); + + /* test for double evaluation */ + a = b = 0; + ok1(min(a++, b++) == 0); + ok1((a == 1) && (b == 1)); + ok1(max(++a, ++b) == 2); + ok1((a == 2) && (b == 2)); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} -- 2.39.2