Commit 92be2eff authored by Rusty Russell's avatar Rusty Russell

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: default avatarRusty Russell <rusty@rustcorp.com.au>
parent ac8694de
...@@ -116,6 +116,8 @@ bool shachain_add_hash(struct shachain *chain, ...@@ -116,6 +116,8 @@ bool shachain_add_hash(struct shachain *chain,
* *
* Example: * Example:
* #include <ccan/structeq/structeq.h> * #include <ccan/structeq/structeq.h>
* // Defines sha256_eq
* STRUCTEQ_DEF(sha256, 0, u);
* *
* static void next_hash(const struct sha256 *hash) * static void next_hash(const struct sha256 *hash)
* { * {
...@@ -127,7 +129,7 @@ bool shachain_add_hash(struct shachain *chain, ...@@ -127,7 +129,7 @@ bool shachain_add_hash(struct shachain *chain,
* else { * else {
* struct sha256 check; * struct sha256 check;
* assert(shachain_get_hash(&chain, index+1, &check)); * assert(shachain_get_hash(&chain, index+1, &check));
* assert(structeq(&check, hash)); * assert(sha256_eq(&check, hash));
* } * }
* } * }
*/ */
......
../../licenses/CC0 ../../licenses/BSD-MIT
\ No newline at end of file \ No newline at end of file
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
* structeq - bitwise comparison of structs. * structeq - bitwise comparison of structs.
* *
* This is a replacement for memcmp, which checks the argument types are the * 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> * Author: Rusty Russell <rusty@rustcorp.com.au>
* *
* Example: * Example:
...@@ -19,26 +21,22 @@ ...@@ -19,26 +21,22 @@
* struct mydata { * struct mydata {
* int start, end; * int start, end;
* }; * };
* // Defines mydata_eq(a, b)
* STRUCTEQ_DEF(mydata, 0, start, end);
* *
* int main(void) * int main(void)
* { * {
* struct mydata a, b; * 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.start = 100;
* a.end = 101; * a.end = 101;
* *
* b.start = 100;
* b.end = 101;
*
* // They are equal. * // They are equal.
* assert(structeq(&a, &b)); * assert(mydata_eq(&a, &b));
* *
* b.end++; * b.end++;
* // Now they are not. * // Now they are not.
* assert(!structeq(&a, &b)); * assert(!mydata_eq(&a, &b));
* *
* return 0; * return 0;
* } * }
...@@ -50,6 +48,8 @@ int main(int argc, char *argv[]) ...@@ -50,6 +48,8 @@ int main(int argc, char *argv[])
return 1; return 1;
if (strcmp(argv[1], "depends") == 0) { if (strcmp(argv[1], "depends") == 0) {
printf("ccan/build_assert\n");
printf("ccan/cppmagic\n");
return 0; return 0;
} }
......
/* CC0 (Public domain) - see LICENSE file for details */ /* MIT (BSD) license - see LICENSE file for details */
#ifndef CCAN_STRUCTEQ_H #ifndef CCAN_STRUCTEQ_H
#define CCAN_STRUCTEQ_H #define CCAN_STRUCTEQ_H
#include <ccan/build_assert/build_assert.h>
#include <ccan/cppmagic/cppmagic.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
/** /**
* structeq - are two structures bitwise equal (including padding!) * STRUCTEQ_DEF - define an ..._eq function to compare two structures.
* @a: a pointer to a structure * @sname: name of the structure, and function (<sname>_eq) to define.
* @b: a pointer to a structure of the same type. * @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 * This generates a single memcmp() call in the common case where the
* least this way, the compiler will issue a warning if the structs are * structure contains no padding. Since it can't tell the difference between
* different types! * 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) \ #define STRUCTEQ_DEF(sname, padbytes, ...) \
(memcmp((a), (b), sizeof(*(a)) + 0 * sizeof((a) == (b))) == 0) 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 */ #endif /* CCAN_STRUCTEQ_H */
...@@ -8,6 +8,8 @@ struct mydata2 { ...@@ -8,6 +8,8 @@ struct mydata2 {
int start, end; int start, end;
}; };
STRUCTEQ_DEF(mydata1, 0, start, end);
int main(void) int main(void)
{ {
struct mydata1 a = { 0, 100 }; struct mydata1 a = { 0, 100 };
...@@ -18,5 +20,5 @@ int main(void) ...@@ -18,5 +20,5 @@ int main(void)
#endif #endif
b = { 0, 100 }; b = { 0, 100 };
return structeq(&a, &b); return mydata1_eq(&a, &b);
} }
#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);
}
#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);
}
#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);
}
#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();
}
...@@ -5,6 +5,8 @@ struct mydata { ...@@ -5,6 +5,8 @@ struct mydata {
int start, end; int start, end;
}; };
STRUCTEQ_DEF(mydata, 0, start, end);
int main(void) int main(void)
{ {
struct mydata a, b; struct mydata a, b;
...@@ -14,13 +16,13 @@ int main(void) ...@@ -14,13 +16,13 @@ int main(void)
a.start = 0; a.start = 0;
a.end = 100; a.end = 100;
ok1(structeq(&a, &a)); ok1(mydata_eq(&a, &a));
b = a; b = a;
ok1(structeq(&a, &b)); ok1(mydata_eq(&a, &b));
b.end++; b.end++;
ok1(!structeq(&a, &b)); ok1(!mydata_eq(&a, &b));
/* This exits depending on whether all tests passed */ /* This exits depending on whether all tests passed */
return exit_status(); return exit_status();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment