ccan/structeq: make it safe when there's padding.
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 4 Jul 2018 04:07:28 +0000 (13:37 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 4 Jul 2018 04:08:48 +0000 (13:38 +0930)
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@rustcorp.com.au>
ccan/crypto/shachain/shachain.h
ccan/structeq/LICENSE
ccan/structeq/_info
ccan/structeq/structeq.h
ccan/structeq/test/compile_fail-different.c
ccan/structeq/test/compile_fail-expect-any-padding.c [new file with mode: 0644]
ccan/structeq/test/compile_fail-expect-padding.c [new file with mode: 0644]
ccan/structeq/test/compile_fail-unexpceted-padding.c [new file with mode: 0644]
ccan/structeq/test/run-with-padding.c [new file with mode: 0644]
ccan/structeq/test/run.c

index d95b97363f7d8e0bb243b6ebba9f35c8db3b5364..3f9dcf6a1716cc72e4a53fe35ad9b07e58348462 100644 (file)
@@ -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));
  *     }
  * }
  */
index b7951dabdc82e45d46d3cf1bd8c7ed8b3a6f7c0f..2354d12945d3221df57d9a44c26a24770a133109 120000 (symlink)
@@ -1 +1 @@
-../../licenses/CC0
\ No newline at end of file
+../../licenses/BSD-MIT
\ No newline at end of file
index d66e2960c7183b97e3a5934f787890aa4e9fb9e9..1ac8d56ddcbf5e1defa01d86ace0f92f3002e3b3 100644 (file)
@@ -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@rustcorp.com.au>
  *
  * Example:
  *     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;
        }
 
index 3af20c53938ce73276da8afa7dfa441d9f3766ed..6b90754c73a92fe3d203644ba9b075c8b0f5fcbd 100644 (file)
@@ -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 */
index 9a08503fbb1fb4a947f63efb5f804ab03124ec26..f09a4454ebec9514de0ff63349e4ede2f19851ee 100644 (file)
@@ -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 (file)
index 0000000..321aef3
--- /dev/null
@@ -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 (file)
index 0000000..cec9f84
--- /dev/null
@@ -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 (file)
index 0000000..af926ce
--- /dev/null
@@ -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 (file)
index 0000000..d18b3e5
--- /dev/null
@@ -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();
+}
index 9ecb4b7da46c7f3a4b4207665f82adafef7757e2..9efab338c0cb174259aa0ce3ea6164da8a29a272 100644 (file)
@@ -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();