From rusty at rustcorp.com.au Wed Jul 4 14:11:24 2018 From: rusty at rustcorp.com.au (Rusty Russell) Date: Wed, 04 Jul 2018 13:41:24 +0930 Subject: [ccan] ccan/structeq: rewrite for safety. Message-ID: <874lhf4p3n.fsf@rustcorp.com.au> >From 92be2eff52133138e4975308f6e731c46b53ace1 Mon Sep 17 00:00:00 2001 From: Rusty Russell 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 --- 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 + * // 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 * * 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 +#include #include +#include /** - * 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 (_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 + +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 + +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 + +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 +#include + +/* 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