[ccan] ccan/structeq: rewrite for safety.
Rusty Russell
rusty at rustcorp.com.au
Wed Jul 4 14:11:24 AEST 2018
>From 92be2eff52133138e4975308f6e731c46b53ace1 Mon Sep 17 00:00:00 2001
From: Rusty Russell <rusty at rustcorp.com.au>
Date: Wed, 4 Jul 2018 13:37:28 +0930
Subject: [PATCH] ccan/structeq: make it safe when there's padding.
ccan/cppmagic FTW!
The only issue is that we can't tell if there's padding or they've missed
a member, so we add a padding bytes count, so they'll get an error if it
(for example) the structure adds a new member later.
Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>
---
ccan/crypto/shachain/shachain.h | 4 +-
ccan/structeq/LICENSE | 2 +-
ccan/structeq/_info | 20 ++++----
ccan/structeq/structeq.h | 46 +++++++++++++++----
ccan/structeq/test/compile_fail-different.c | 4 +-
.../test/compile_fail-expect-any-padding.c | 19 ++++++++
.../test/compile_fail-expect-padding.c | 19 ++++++++
.../test/compile_fail-unexpceted-padding.c | 20 ++++++++
ccan/structeq/test/run-with-padding.c | 32 +++++++++++++
ccan/structeq/test/run.c | 8 ++--
10 files changed, 149 insertions(+), 25 deletions(-)
create mode 100644 ccan/structeq/test/compile_fail-expect-any-padding.c
create mode 100644 ccan/structeq/test/compile_fail-expect-padding.c
create mode 100644 ccan/structeq/test/compile_fail-unexpceted-padding.c
create mode 100644 ccan/structeq/test/run-with-padding.c
diff --git a/ccan/crypto/shachain/shachain.h b/ccan/crypto/shachain/shachain.h
index d95b9736..3f9dcf6a 100644
--- a/ccan/crypto/shachain/shachain.h
+++ b/ccan/crypto/shachain/shachain.h
@@ -116,6 +116,8 @@ bool shachain_add_hash(struct shachain *chain,
*
* Example:
* #include <ccan/structeq/structeq.h>
+ * // Defines sha256_eq
+ * STRUCTEQ_DEF(sha256, 0, u);
*
* static void next_hash(const struct sha256 *hash)
* {
@@ -127,7 +129,7 @@ bool shachain_add_hash(struct shachain *chain,
* else {
* struct sha256 check;
* assert(shachain_get_hash(&chain, index+1, &check));
- * assert(structeq(&check, hash));
+ * assert(sha256_eq(&check, hash));
* }
* }
*/
diff --git a/ccan/structeq/LICENSE b/ccan/structeq/LICENSE
index b7951dab..2354d129 120000
--- a/ccan/structeq/LICENSE
+++ b/ccan/structeq/LICENSE
@@ -1 +1 @@
-../../licenses/CC0
\ No newline at end of file
+../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/structeq/_info b/ccan/structeq/_info
index d66e2960..1ac8d56d 100644
--- a/ccan/structeq/_info
+++ b/ccan/structeq/_info
@@ -6,9 +6,11 @@
* structeq - bitwise comparison of structs.
*
* This is a replacement for memcmp, which checks the argument types are the
- * same.
+ * same, and takes into account padding in the structure. When there is no
+ * padding, it becomes a memcmp at compile time (assuming a
+ * constant-optimizing compiler).
*
- * License: CC0 (Public domain)
+ * License: BSD-MIT
* Author: Rusty Russell <rusty at rustcorp.com.au>
*
* Example:
@@ -19,26 +21,22 @@
* struct mydata {
* int start, end;
* };
+ * // Defines mydata_eq(a, b)
+ * STRUCTEQ_DEF(mydata, 0, start, end);
*
* int main(void)
* {
* struct mydata a, b;
*
- * // No padding in struct, otherwise this doesn't work!
- * BUILD_ASSERT(sizeof(a) == sizeof(a.start) + sizeof(a.end));
- *
* a.start = 100;
* a.end = 101;
*
- * b.start = 100;
- * b.end = 101;
- *
* // They are equal.
- * assert(structeq(&a, &b));
+ * assert(mydata_eq(&a, &b));
*
* b.end++;
* // Now they are not.
- * assert(!structeq(&a, &b));
+ * assert(!mydata_eq(&a, &b));
*
* return 0;
* }
@@ -50,6 +48,8 @@ int main(int argc, char *argv[])
return 1;
if (strcmp(argv[1], "depends") == 0) {
+ printf("ccan/build_assert\n");
+ printf("ccan/cppmagic\n");
return 0;
}
diff --git a/ccan/structeq/structeq.h b/ccan/structeq/structeq.h
index 3af20c53..6b90754c 100644
--- a/ccan/structeq/structeq.h
+++ b/ccan/structeq/structeq.h
@@ -1,17 +1,45 @@
-/* CC0 (Public domain) - see LICENSE file for details */
+/* MIT (BSD) license - see LICENSE file for details */
#ifndef CCAN_STRUCTEQ_H
#define CCAN_STRUCTEQ_H
+#include <ccan/build_assert/build_assert.h>
+#include <ccan/cppmagic/cppmagic.h>
#include <string.h>
+#include <stdbool.h>
/**
- * structeq - are two structures bitwise equal (including padding!)
- * @a: a pointer to a structure
- * @b: a pointer to a structure of the same type.
+ * STRUCTEQ_DEF - define an ..._eq function to compare two structures.
+ * @sname: name of the structure, and function (<sname>_eq) to define.
+ * @padbytes: number of bytes of expected padding, or -1 if unknown.
+ * @...: name of every member of the structure.
*
- * If you *know* a structure has no padding, you can memcmp them. At
- * least this way, the compiler will issue a warning if the structs are
- * different types!
+ * This generates a single memcmp() call in the common case where the
+ * structure contains no padding. Since it can't tell the difference between
+ * padding and a missing member, @padbytes can be used to assert that
+ * there isn't any, or how many we expect. -1 means "expect some", since
+ * it can be platform dependent.
*/
-#define structeq(a, b) \
- (memcmp((a), (b), sizeof(*(a)) + 0 * sizeof((a) == (b))) == 0)
+#define STRUCTEQ_DEF(sname, padbytes, ...) \
+static inline bool CPPMAGIC_GLUE2(sname, _eq)(const struct sname *_a, \
+ const struct sname *_b) \
+{ \
+ BUILD_ASSERT(((padbytes) < 0 && \
+ CPPMAGIC_JOIN(+, CPPMAGIC_MAP(STRUCTEQ_MEMBER_SIZE_, \
+ __VA_ARGS__)) \
+ > sizeof(*_a)) \
+ || CPPMAGIC_JOIN(+, CPPMAGIC_MAP(STRUCTEQ_MEMBER_SIZE_, \
+ __VA_ARGS__)) \
+ + (padbytes) == sizeof(*_a)); \
+ if (CPPMAGIC_JOIN(+, CPPMAGIC_MAP(STRUCTEQ_MEMBER_SIZE_, __VA_ARGS__)) \
+ == sizeof(*_a)) \
+ return memcmp(_a, _b, sizeof(*_a)) == 0; \
+ else \
+ return CPPMAGIC_JOIN(&&, \
+ CPPMAGIC_MAP(STRUCTEQ_MEMBER_CMP_, \
+ __VA_ARGS__)); \
+}
+
+/* Helpers */
+#define STRUCTEQ_MEMBER_SIZE_(m) sizeof((_a)->m)
+#define STRUCTEQ_MEMBER_CMP_(m) memcmp(&_a->m, &_b->m, sizeof(_a->m)) == 0
+
#endif /* CCAN_STRUCTEQ_H */
diff --git a/ccan/structeq/test/compile_fail-different.c b/ccan/structeq/test/compile_fail-different.c
index 9a08503f..f09a4454 100644
--- a/ccan/structeq/test/compile_fail-different.c
+++ b/ccan/structeq/test/compile_fail-different.c
@@ -8,6 +8,8 @@ struct mydata2 {
int start, end;
};
+STRUCTEQ_DEF(mydata1, 0, start, end);
+
int main(void)
{
struct mydata1 a = { 0, 100 };
@@ -18,5 +20,5 @@ int main(void)
#endif
b = { 0, 100 };
- return structeq(&a, &b);
+ return mydata1_eq(&a, &b);
}
diff --git a/ccan/structeq/test/compile_fail-expect-any-padding.c b/ccan/structeq/test/compile_fail-expect-any-padding.c
new file mode 100644
index 00000000..321aef3a
--- /dev/null
+++ b/ccan/structeq/test/compile_fail-expect-any-padding.c
@@ -0,0 +1,19 @@
+#include <ccan/structeq/structeq.h>
+
+struct mydata {
+ int start, end;
+};
+#ifdef FAIL
+#define PADDING -1
+#else
+#define PADDING 0
+#endif
+
+STRUCTEQ_DEF(mydata, PADDING, start, end);
+
+int main(void)
+{
+ struct mydata a = { 0, 100 };
+
+ return mydata_eq(&a, &a);
+}
diff --git a/ccan/structeq/test/compile_fail-expect-padding.c b/ccan/structeq/test/compile_fail-expect-padding.c
new file mode 100644
index 00000000..cec9f846
--- /dev/null
+++ b/ccan/structeq/test/compile_fail-expect-padding.c
@@ -0,0 +1,19 @@
+#include <ccan/structeq/structeq.h>
+
+struct mydata {
+ int start, end;
+};
+#ifdef FAIL
+#define PADDING 1
+#else
+#define PADDING 0
+#endif
+
+STRUCTEQ_DEF(mydata, PADDING, start, end);
+
+int main(void)
+{
+ struct mydata a = { 0, 100 };
+
+ return mydata_eq(&a, &a);
+}
diff --git a/ccan/structeq/test/compile_fail-unexpceted-padding.c b/ccan/structeq/test/compile_fail-unexpceted-padding.c
new file mode 100644
index 00000000..af926ce1
--- /dev/null
+++ b/ccan/structeq/test/compile_fail-unexpceted-padding.c
@@ -0,0 +1,20 @@
+#include <ccan/structeq/structeq.h>
+
+struct mydata {
+ int start, end;
+ int pad;
+};
+#ifdef FAIL
+#define PADDING 0
+#else
+#define PADDING sizeof(int)
+#endif
+
+STRUCTEQ_DEF(mydata, PADDING, start, end);
+
+int main(void)
+{
+ struct mydata a = { 0, 100 };
+
+ return mydata_eq(&a, &a);
+}
diff --git a/ccan/structeq/test/run-with-padding.c b/ccan/structeq/test/run-with-padding.c
new file mode 100644
index 00000000..d18b3e55
--- /dev/null
+++ b/ccan/structeq/test/run-with-padding.c
@@ -0,0 +1,32 @@
+#include <ccan/structeq/structeq.h>
+#include <ccan/tap/tap.h>
+
+/* In theory, this could be generated without padding, if alignof(int) were 0,
+ * and test would fail. Call me when that happens. */
+struct mydata {
+ char start;
+ int end;
+};
+
+STRUCTEQ_DEF(mydata, sizeof(int) - sizeof(char), start, end);
+
+int main(void)
+{
+ struct mydata a, b;
+
+ /* This is how many tests you plan to run */
+ plan_tests(3);
+
+ a.start = 0;
+ a.end = 100;
+ ok1(mydata_eq(&a, &a));
+
+ b = a;
+ ok1(mydata_eq(&a, &b));
+
+ b.end++;
+ ok1(!mydata_eq(&a, &b));
+
+ /* This exits depending on whether all tests passed */
+ return exit_status();
+}
diff --git a/ccan/structeq/test/run.c b/ccan/structeq/test/run.c
index 9ecb4b7d..9efab338 100644
--- a/ccan/structeq/test/run.c
+++ b/ccan/structeq/test/run.c
@@ -5,6 +5,8 @@ struct mydata {
int start, end;
};
+STRUCTEQ_DEF(mydata, 0, start, end);
+
int main(void)
{
struct mydata a, b;
@@ -14,13 +16,13 @@ int main(void)
a.start = 0;
a.end = 100;
- ok1(structeq(&a, &a));
+ ok1(mydata_eq(&a, &a));
b = a;
- ok1(structeq(&a, &b));
+ ok1(mydata_eq(&a, &b));
b.end++;
- ok1(!structeq(&a, &b));
+ ok1(!mydata_eq(&a, &b));
/* This exits depending on whether all tests passed */
return exit_status();
--
2.17.1
More information about the ccan
mailing list