Commit ecea0736 authored by Rusty Russell's avatar Rusty Russell

str: provide checks for ctype.h char functions, and strstr and strchr functions.

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.
parent 3f4e83d8
......@@ -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;
}
......
#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
#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 */
#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 */
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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;
}
#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;
}
#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;
}
/* 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>
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