str: provide checks for ctype.h char functions, and strstr and strchr functions.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 17 Mar 2011 11:42:22 +0000 (22:12 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 17 Mar 2011 11:42:22 +0000 (22:12 +1030)
In the former case, we were bitten by the fact that you don't pass a char
to isalpha() et al: you pass an int.  This means on most platforms you want
to do:

   if (isalpha((unsigned char)c)) ...

Insane?  Yes, but I assure you I'm not making this up.

Similarly, I've always wanted strstr, strchr and strrchr to return
const char * when given a const char * argument to search, to avoid
constness leak.

In both cases, the actual versions from the headers may be macros, so
we need to be very careful overriding them.  The result is that they
become out-of-line functions which is unacceptable for general
performance.

So we only activate these when CCAN_STR_DEBUG is defined.

20 files changed:
ccan/str/_info
ccan/str/debug.c [new file with mode: 0644]
ccan/str/str.h
ccan/str/str_debug.h [new file with mode: 0644]
ccan/str/test/compile_fail-isalnum.c [new file with mode: 0644]
ccan/str/test/compile_fail-isalpha.c [new file with mode: 0644]
ccan/str/test/compile_fail-isascii.c [new file with mode: 0644]
ccan/str/test/compile_fail-isblank.c [new file with mode: 0644]
ccan/str/test/compile_fail-iscntrl.c [new file with mode: 0644]
ccan/str/test/compile_fail-isdigit.c [new file with mode: 0644]
ccan/str/test/compile_fail-islower.c [new file with mode: 0644]
ccan/str/test/compile_fail-isprint.c [new file with mode: 0644]
ccan/str/test/compile_fail-ispunct.c [new file with mode: 0644]
ccan/str/test/compile_fail-isspace.c [new file with mode: 0644]
ccan/str/test/compile_fail-isupper.c [new file with mode: 0644]
ccan/str/test/compile_fail-isxdigit.c [new file with mode: 0644]
ccan/str/test/compile_fail-strchr.c [new file with mode: 0644]
ccan/str/test/compile_fail-strrchr.c [new file with mode: 0644]
ccan/str/test/compile_fail-strstr.c [new file with mode: 0644]
ccan/str/test/debug.c [new file with mode: 0644]

index 0438ccfdbcd2bbff8bd2a87b82baa7fc3a36cead..ea314dbf799a3460db02771aa356991870ec13ce 100644 (file)
@@ -8,6 +8,18 @@
  * This is a grab bag of functions for string operations, designed to enhance
  * the standard string.h.
  *
+ * Note that if you define CCAN_STR_DEBUG, you will get extra compile
+ * checks on common misuses of the following functions (they will now
+ * be out-of-line, so there is a runtime penalty!).
+ *
+ *     strstr, strchr, strrchr:
+ *             Return const char * if first argument is const (gcc only).
+ *
+ *     isalnum, isalpha, isascii, isblank, iscntrl, isdigit, isgraph,
+ *         islower, isprint, ispunct, isspace, isupper, isxdigit:
+ *             Static and runtime check that input is EOF or an *unsigned*
+ *             char, as per C standard (really!).
+ *
  * Example:
  *     #include <stdio.h>
  *     #include <ccan/str/str.h>
@@ -32,6 +44,7 @@ int main(int argc, char *argv[])
                return 1;
 
        if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/build_assert\n");
                return 0;
        }
 
diff --git a/ccan/str/debug.c b/ccan/str/debug.c
new file mode 100644 (file)
index 0000000..004a874
--- /dev/null
@@ -0,0 +1,102 @@
+#include "config.h"
+#include <ccan/str/str_debug.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef CCAN_STR_DEBUG
+/* Because we mug the real ones with macros, we need our own wrappers. */
+int str_isalnum(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isalnum(i);
+}
+
+int str_isalpha(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isalpha(i);
+}
+
+int str_isascii(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isascii(i);
+}
+
+int str_isblank(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isblank(i);
+}
+
+int str_iscntrl(int i)
+{
+       assert(i >= -1 && i < 256);
+       return iscntrl(i);
+}
+
+int str_isdigit(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isdigit(i);
+}
+
+int str_isgraph(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isgraph(i);
+}
+
+int str_islower(int i)
+{
+       assert(i >= -1 && i < 256);
+       return islower(i);
+}
+
+int str_isprint(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isprint(i);
+}
+
+int str_ispunct(int i)
+{
+       assert(i >= -1 && i < 256);
+       return ispunct(i);
+}
+
+int str_isspace(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isspace(i);
+}
+
+int str_isupper(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isupper(i);
+}
+
+int str_isxdigit(int i)
+{
+       assert(i >= -1 && i < 256);
+       return isxdigit(i);
+}
+
+
+char *str_strstr(const char *haystack, const char *needle)
+{
+       return strstr(haystack, needle);
+}
+
+char *str_strchr(const char *haystack, int c)
+{
+       return strchr(haystack, c);
+}
+
+char *str_strrchr(const char *haystack, int c)
+{
+       return strrchr(haystack, c);
+}
+#endif
index bf858f13b69cb1f671a895450004f36a6ad361cd..54afca0f21c2afc671a7d0f5ad356e4fe1604b0f 100644 (file)
@@ -1,7 +1,9 @@
 #ifndef CCAN_STR_H
 #define CCAN_STR_H
+#include "config.h"
 #include <string.h>
 #include <stdbool.h>
+#include <ctype.h>
 
 /**
  * streq - Are two strings equal?
@@ -69,4 +71,65 @@ static inline bool strends(const char *str, const char *postfix)
  */
 size_t strcount(const char *haystack, const char *needle);
 
+#include <ccan/str/str_debug.h>
+
+/* These checks force things out of line, hence they are under DEBUG. */
+#ifdef CCAN_STR_DEBUG
+#include <ccan/build_assert/build_assert.h>
+
+/* These are commonly misused: they take -1 or an *unsigned* char value. */
+#undef isalnum
+#undef isalpha
+#undef isascii
+#undef isblank
+#undef iscntrl
+#undef isdigit
+#undef isgraph
+#undef islower
+#undef isprint
+#undef ispunct
+#undef isspace
+#undef isupper
+#undef isxdigit
+
+/* You can use a char if char is unsigned. */
+#if HAVE_BUILTIN_TYPES_COMPATIBLE_P && HAVE_TYPEOF
+#define str_check_arg_(i)                                              \
+       ((i) + BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(typeof(i), \
+                                                                 char) \
+                                   || (char)255 > 0))
+#else
+#define str_check_arg_(i) (i)
+#endif
+
+#define isalnum(i) str_isalnum(str_check_arg_(i))
+#define isalpha(i) str_isalpha(str_check_arg_(i))
+#define isascii(i) str_isascii(str_check_arg_(i))
+#define isblank(i) str_isblank(str_check_arg_(i))
+#define iscntrl(i) str_iscntrl(str_check_arg_(i))
+#define isdigit(i) str_isdigit(str_check_arg_(i))
+#define isgraph(i) str_isgraph(str_check_arg_(i))
+#define islower(i) str_islower(str_check_arg_(i))
+#define isprint(i) str_isprint(str_check_arg_(i))
+#define ispunct(i) str_ispunct(str_check_arg_(i))
+#define isspace(i) str_isspace(str_check_arg_(i))
+#define isupper(i) str_isupper(str_check_arg_(i))
+#define isxdigit(i) str_isxdigit(str_check_arg_(i))
+
+#if HAVE_TYPEOF
+/* With GNU magic, we can make const-respecting standard string functions. */
+#undef strstr
+#undef strchr
+#undef strrchr
+
+/* + 0 is needed to decay array into pointer. */
+#define strstr(haystack, needle)                                       \
+       ((typeof((haystack) + 0))str_strstr((haystack), (needle)))
+#define strchr(haystack, c)                                    \
+       ((typeof((haystack) + 0))str_strchr((haystack), (c)))
+#define strrchr(haystack, c)                                   \
+       ((typeof((haystack) + 0))str_strrchr((haystack), (c)))
+#endif
+#endif /* CCAN_STR_DEBUG */
+
 #endif /* CCAN_STR_H */
diff --git a/ccan/str/str_debug.h b/ccan/str/str_debug.h
new file mode 100644 (file)
index 0000000..4545cca
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef CCAN_STR_DEBUG_H
+#define CCAN_STR_DEBUG_H
+
+/* #define CCAN_STR_DEBUG 1 */
+
+#ifdef CCAN_STR_DEBUG
+/* Because we mug the real ones with macros, we need our own wrappers. */
+int str_isalnum(int i);
+int str_isalpha(int i);
+int str_isascii(int i);
+int str_isblank(int i);
+int str_iscntrl(int i);
+int str_isdigit(int i);
+int str_isgraph(int i);
+int str_islower(int i);
+int str_isprint(int i);
+int str_ispunct(int i);
+int str_isspace(int i);
+int str_isupper(int i);
+int str_isxdigit(int i);
+
+char *str_strstr(const char *haystack, const char *needle);
+char *str_strchr(const char *s, int c);
+char *str_strrchr(const char *s, int c);
+#endif /* CCAN_STR_DEBUG */
+
+#endif /* CCAN_STR_DEBUG_H */
diff --git a/ccan/str/test/compile_fail-isalnum.c b/ccan/str/test/compile_fail-isalnum.c
new file mode 100644 (file)
index 0000000..1b6a363
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isalnum.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isalnum(c);
+}
diff --git a/ccan/str/test/compile_fail-isalpha.c b/ccan/str/test/compile_fail-isalpha.c
new file mode 100644 (file)
index 0000000..76de150
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isalpha.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isalpha(c);
+}
diff --git a/ccan/str/test/compile_fail-isascii.c b/ccan/str/test/compile_fail-isascii.c
new file mode 100644 (file)
index 0000000..af6e64d
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isascii.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isascii(c);
+}
diff --git a/ccan/str/test/compile_fail-isblank.c b/ccan/str/test/compile_fail-isblank.c
new file mode 100644 (file)
index 0000000..86a3350
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isblank.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isblank(c);
+}
diff --git a/ccan/str/test/compile_fail-iscntrl.c b/ccan/str/test/compile_fail-iscntrl.c
new file mode 100644 (file)
index 0000000..5ae783b
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check iscntrl.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return iscntrl(c);
+}
diff --git a/ccan/str/test/compile_fail-isdigit.c b/ccan/str/test/compile_fail-isdigit.c
new file mode 100644 (file)
index 0000000..68d81ba
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isdigit.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isdigit(c);
+}
diff --git a/ccan/str/test/compile_fail-islower.c b/ccan/str/test/compile_fail-islower.c
new file mode 100644 (file)
index 0000000..9dec95f
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check islower.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return islower(c);
+}
diff --git a/ccan/str/test/compile_fail-isprint.c b/ccan/str/test/compile_fail-isprint.c
new file mode 100644 (file)
index 0000000..30f7639
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isprint.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isprint(c);
+}
diff --git a/ccan/str/test/compile_fail-ispunct.c b/ccan/str/test/compile_fail-ispunct.c
new file mode 100644 (file)
index 0000000..f1ae13a
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check ispunct.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return ispunct(c);
+}
diff --git a/ccan/str/test/compile_fail-isspace.c b/ccan/str/test/compile_fail-isspace.c
new file mode 100644 (file)
index 0000000..c94c49e
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isspace.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isspace(c);
+}
diff --git a/ccan/str/test/compile_fail-isupper.c b/ccan/str/test/compile_fail-isupper.c
new file mode 100644 (file)
index 0000000..f23dd65
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isupper.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isupper(c);
+}
diff --git a/ccan/str/test/compile_fail-isxdigit.c b/ccan/str/test/compile_fail-isxdigit.c
new file mode 100644 (file)
index 0000000..6a7377f
--- /dev/null
@@ -0,0 +1,23 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_BUILTIN_TYPES_COMPATIBLE_P || !HAVE_TYPEOF
+#error We need typeof to check isxdigit.
+#endif
+       char
+#else
+       unsigned char
+#endif
+               c = argv[0][0];
+
+#ifdef FAIL
+       /* Fake fail on unsigned char platforms. */
+       c = 255;
+       BUILD_ASSERT(c < 0);
+#endif
+
+       return isxdigit(c);
+}
diff --git a/ccan/str/test/compile_fail-strchr.c b/ccan/str/test/compile_fail-strchr.c
new file mode 100644 (file)
index 0000000..74a7314
--- /dev/null
@@ -0,0 +1,18 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_TYPEOF
+       #error We need typeof to check strstr.
+#endif
+#else
+       const
+#endif
+               char *ret;
+       const char *str = "hello";
+
+       ret = strchr(str, 'l');
+       return ret ? 0 : 1;
+}
diff --git a/ccan/str/test/compile_fail-strrchr.c b/ccan/str/test/compile_fail-strrchr.c
new file mode 100644 (file)
index 0000000..ba7d17e
--- /dev/null
@@ -0,0 +1,18 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_TYPEOF
+       #error We need typeof to check strstr.
+#endif
+#else
+       const
+#endif
+               char *ret;
+       const char *str = "hello";
+
+       ret = strrchr(str, 'l');
+       return ret ? 0 : 1;
+}
diff --git a/ccan/str/test/compile_fail-strstr.c b/ccan/str/test/compile_fail-strstr.c
new file mode 100644 (file)
index 0000000..deefef6
--- /dev/null
@@ -0,0 +1,18 @@
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/str.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef FAIL
+#if !HAVE_TYPEOF
+       #error We need typeof to check strstr.
+#endif
+#else
+       const
+#endif
+               char *ret;
+       const char *str = "hello";
+
+       ret = strstr(str, "hell");
+       return ret ? 0 : 1;
+}
diff --git a/ccan/str/test/debug.c b/ccan/str/test/debug.c
new file mode 100644 (file)
index 0000000..4bd384f
--- /dev/null
@@ -0,0 +1,5 @@
+/* We can't use the normal "#include the .c file" trick, since this is
+   contaminated by str.h's macro overrides.  So we put it in all tests
+   like this. */
+#define CCAN_STR_DEBUG 1
+#include <ccan/str/debug.c>