We already have two use cases for static assertions (and soon we'll have
yet another). Namely:
- STATIC_ASSERT_UNSIGNED_INT() in "checked-overflow.h". Here, we use our
own trick, based on a negative-sized array typedef that's named with
NBDKIT_UNIQUE_NAME.
- static_assert() in "test-array-size.c". This uses the C11 macro called
static_assert() from <assert.h>, which wraps the C11 _Static_assert().
This is not really great: our baseline is C99, not C11 (per commit
762f7c9e5166, "tests: Set minimum compiler to ISO C99.", 2021-04-08) --
which is why the same assertions are repeated in the code as normal
runtime assert() calls, in case static_assert() is not defined.
Factor out our own STATIC_ASSERT(), from STATIC_ASSERT_UNSIGNED_INT().
Put it to use in "test-array-size.c", replacing both the runtime assert()s
and the compile-time static_assert()s. Note that for the latter, in order
to remain consistent with STATIC_ASSERT_UNSIGNED_INT(), we no longer
provide the *complaint* that we want the compiler to emit upon assertion
failure, but an identifier that stands for the predicate that we *expect*.
When uncommenting the negative test case in "test-array-size.c", the
resultant wall of compiler diagnostics includes the following entry:
test-array-size.c:83:39: error: size of array
‘_array_size_macro_is_applied_to_array13’ is negative
This patch will have to be ported to nbdkit. (IMO we can implement the
change in libnbd first -- the "common" subdir is meant to be common, so no
particular precedence should be assumed.)
Signed-off-by: Laszlo Ersek <lersek(a)redhat.com>
(cherry picked from libnbd commit c593baab3c9c8b17317daece0694ec8b5fc6fb46)
---
common/include/Makefile.am | 1 +
common/include/checked-overflow.h | 8 ++--
common/include/static-assert.h | 48 ++++++++++++++++++++
common/include/test-array-size.c | 45 ++++++------------
4 files changed, 67 insertions(+), 35 deletions(-)
diff --git a/common/include/Makefile.am b/common/include/Makefile.am
index a9933e819908..bbf00641cd5c 100644
--- a/common/include/Makefile.am
+++ b/common/include/Makefile.am
@@ -49,6 +49,7 @@ EXTRA_DIST = \
nextnonzero.h \
random.h \
rounding.h \
+ static-assert.h \
tvdiff.h \
unique-name.h \
unix-path-max.h \
diff --git a/common/include/checked-overflow.h b/common/include/checked-overflow.h
index a1852adcdc7a..4ec72387b332 100644
--- a/common/include/checked-overflow.h
+++ b/common/include/checked-overflow.h
@@ -53,6 +53,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include "static-assert.h"
#include "unique-name.h"
/* Add "a" and "b", both of (possibly different) unsigned integer
types, and
@@ -173,11 +174,8 @@
*
* The expression "x" is not evaluated, unless it has variably modified type.
*/
-#define STATIC_ASSERT_UNSIGNED_INT(x) \
- do { \
- typedef char NBDKIT_UNIQUE_NAME (_x_has_uint_type)[(typeof (x))-1 > 0 ? 1 : -1] \
- __attribute__ ((__unused__)); \
- } while (0)
+#define STATIC_ASSERT_UNSIGNED_INT(x) \
+ STATIC_ASSERT ((typeof (x))-1 > 0, _x_has_uint_type)
/* Assign the sum "a + b" to "*r", using uintmax_t modular
arithmetic.
*
diff --git a/common/include/static-assert.h b/common/include/static-assert.h
new file mode 100644
index 000000000000..5a564f8946e5
--- /dev/null
+++ b/common/include/static-assert.h
@@ -0,0 +1,48 @@
+/* nbdkit
+ * Copyright (C) 2013-2023 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NBDKIT_STATIC_ASSERT_H
+#define NBDKIT_STATIC_ASSERT_H
+
+#include "unique-name.h"
+
+/* Assert "expression" at compile time. If "expression" evaluates to
zero (at
+ * compile time), produce a compiler error message that includes
+ * "expectation_id".
+ */
+#define STATIC_ASSERT(expression, expectation_id) \
+ do { \
+ typedef char NBDKIT_UNIQUE_NAME (expectation_id)[(expression) ? 1 : -1] \
+ __attribute__ ((__unused__)); \
+ } while (0)
+
+#endif /* NBDKIT_STATIC_ASSERT_H */
diff --git a/common/include/test-array-size.c b/common/include/test-array-size.c
index 8b0972aaabe1..6244ec1e7d9e 100644
--- a/common/include/test-array-size.c
+++ b/common/include/test-array-size.c
@@ -38,6 +38,7 @@
#include <assert.h>
#include "array-size.h"
+#include "static-assert.h"
struct st { const char *s; int i; };
@@ -60,42 +61,26 @@ static struct st st4_0[4] __attribute__ ((__unused__));
int
main (void)
{
- assert (ARRAY_SIZE (s0) == 0);
- assert (ARRAY_SIZE (s1) == 1);
- assert (ARRAY_SIZE (s3) == 3);
- assert (ARRAY_SIZE (s4) == 4);
- assert (ARRAY_SIZE (i0) == 0);
- assert (ARRAY_SIZE (i1) == 1);
- assert (ARRAY_SIZE (i3) == 3);
- assert (ARRAY_SIZE (i4) == 4);
- assert (ARRAY_SIZE (st0) == 0);
- assert (ARRAY_SIZE (st1) == 1);
- assert (ARRAY_SIZE (st3) == 3);
- assert (ARRAY_SIZE (st4) == 4);
- assert (ARRAY_SIZE (st4_0) == 4);
-
-#ifdef static_assert
- static_assert (ARRAY_SIZE (s0) == 0, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (s1) == 1, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (s3) == 3, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (s4) == 4, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (i0) == 0, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (i1) == 1, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (i3) == 3, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (i4) == 4, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (st0) == 0, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (st1) == 1, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (st3) == 3, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (st4) == 4, "ARRAY_SIZE macro does not work");
- static_assert (ARRAY_SIZE (st4_0) == 4, "ARRAY_SIZE macro does not work");
-#endif
+ STATIC_ASSERT (ARRAY_SIZE (s0) == 0, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (s1) == 1, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (s3) == 3, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (s4) == 4, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (i0) == 0, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (i1) == 1, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (i3) == 3, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (i4) == 4, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (st0) == 0, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (st1) == 1, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (st3) == 3, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (st4) == 4, _array_size_macro_works);
+ STATIC_ASSERT (ARRAY_SIZE (st4_0) == 4, _array_size_macro_works);
/* You can uncomment this to test the negative case. Unfortunately
* it's difficult to automate this test.
*/
#if 0
int *p = i4;
- assert (ARRAY_SIZE (p) == 4);
+ STATIC_ASSERT (ARRAY_SIZE (p) == 4, _array_size_macro_is_applied_to_array);
#endif
exit (EXIT_SUCCESS);