From: Eric Blake Date: Thu, 10 Jan 2008 14:38:11 +0000 (-0700) Subject: Avoid quadratic strstr implementations. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96facb24aa17b8d412d2f37fa91d59282d950ebb;p=pspp Avoid quadratic strstr implementations. * lib/strstr.c: New file. * m4/strstr.m4: Likewise. * modules/strstr: Likewise. * modules/strstr-tests: Likewise. * tests/test-strstr.c: Likewise. * lib/string.in.h (rpl_strstr): Declare. (memmem) [GNULIB_POSIXCHECK]: Document speed issue. * m4/string_h.m4 (gl_HEADER_STRING_H_DEFAULTS): Support strstr. * modules/string (Makefile.am): Likewise. * MODULES.html.sh (string handling): Mention new module. * doc/functions/strstr.texi (strstr): Document the bug. Signed-off-by: Eric Blake --- diff --git a/ChangeLog b/ChangeLog index 900413d7e3..44df277558 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-01-10 Eric Blake + + Avoid quadratic strstr implementations. + * lib/strstr.c: New file. + * m4/strstr.m4: Likewise. + * modules/strstr: Likewise. + * modules/strstr-tests: Likewise. + * tests/test-strstr.c: Likewise. + * lib/string.in.h (rpl_strstr): Declare. + (memmem) [GNULIB_POSIXCHECK]: Document speed issue. + * m4/string_h.m4 (gl_HEADER_STRING_H_DEFAULTS): Support strstr. + * modules/string (Makefile.am): Likewise. + * MODULES.html.sh (string handling): Mention new module. + * doc/functions/strstr.texi (strstr): Document the bug. + 2008-01-10 Bruno Haible * lib/relocatable.h (relocate): State whether result is freshly diff --git a/MODULES.html.sh b/MODULES.html.sh index 66e0b01d2a..1e6f16bf04 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -1671,6 +1671,7 @@ func_all_modules () func_module strnlen1 func_module strndup func_module strsep + func_module strstr func_module c-strstr func_module trim func_module fstrcmp diff --git a/doc/functions/strstr.texi b/doc/functions/strstr.texi index 99b3630851..fe0af49b62 100644 --- a/doc/functions/strstr.texi +++ b/doc/functions/strstr.texi @@ -4,10 +4,14 @@ POSIX specification: @url{http://www.opengroup.org/susv3xsh/strstr.html} -Gnulib module: --- +Gnulib module: strstr Portability problems fixed by Gnulib: @itemize +@item +This function has quadratic instead of linear complexity on some +platforms: +glibc <= 2.6.1, cygwin 1.5.x, OpenBSD 4.0, Solaris 9, mingw @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/string.in.h b/lib/string.in.h index 355479a717..4d68cd9d42 100644 --- a/lib/string.in.h +++ b/lib/string.in.h @@ -58,8 +58,9 @@ extern void *memmem (void const *__haystack, size_t __haystack_len, #elif defined GNULIB_POSIXCHECK # undef memmem # define memmem(a,al,b,bl) \ - (GL_LINK_WARNING ("memmem is unportable - " \ - "use gnulib module memmem for portability"), \ + (GL_LINK_WARNING ("memmem is unportable and often quadratic - " \ + "use gnulib module memmem-simple for portability, " \ + "and module memmem for speed" ), \ memmem (a, al, b, bl)) #endif @@ -289,16 +290,24 @@ extern char *strsep (char **restrict __stringp, char const *restrict __delim); strsep (s, d)) #endif -#if defined GNULIB_POSIXCHECK +#if @GNULIB_STRSTR@ +# if @REPLACE_STRSTR@ +# define strstr rpl_strstr +char *strstr (const char *haystack, const char *needle) + __attribute__ ((__pure__)); +# endif +#elif defined GNULIB_POSIXCHECK /* strstr() does not work with multibyte strings if the locale encoding is different from UTF-8: POSIX says that it operates on "strings", and "string" in POSIX is defined as a sequence of bytes, not of characters. */ # undef strstr # define strstr(a,b) \ - (GL_LINK_WARNING ("strstr cannot work correctly on character strings " \ - "in most multibyte locales - " \ - "use mbsstr if you care about internationalization"), \ + (GL_LINK_WARNING ("strstr is quadratic on many systems, and cannot " \ + "work correctly on character strings in most " \ + "multibyte locales - " \ + "use mbsstr if you care about internationalization, " \ + "or use strstr if you care about speed"), \ strstr (a, b)) #endif diff --git a/lib/strstr.c b/lib/strstr.c new file mode 100644 index 0000000000..80b18b02cc --- /dev/null +++ b/lib/strstr.c @@ -0,0 +1,437 @@ +/* Copyright (C) 1991,92,93,94,96,97,98,2000,2004,2007,2008 Free Software + Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* This particular implementation was written by Eric Blake, 2008. */ + +#ifndef _LIBC +# include +#endif + +/* Specification of strstr. */ +#include + +#include +#include +#include +#include + +#ifndef _LIBC +# define __builtin_expect(expr, val) (expr) +#endif + +/* We use the Two-Way string matching algorithm, which guarantees + linear complexity with constant space. Additionally, for long + needles, we also use a bad character shift table similar to the + Boyer-Moore algorithm to achieve better performance. + + See http://www-igm.univ-mlv.fr/~lecroq/string/node26.html#SECTION00260 + and http://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm +*/ + +/* Point at which computing a bad-byte shift table is likely to be + worthwhile. Small needles should not compute a table, since it + adds (1 << CHAR_BIT) + NEEDLE_LEN computations of preparation for a + speedup no greater than a factor of NEEDLE_LEN. The larger the + needle, the better the potential performance gain. On the other + hand, on non-POSIX systems with CHAR_BIT larger than eight, the + memory required for the table is prohibitive. */ +#if CHAR_BIT < 10 +# define LONG_NEEDLE_THRESHOLD 32U +#else +# define LONG_NEEDLE_THRESHOLD SIZE_MAX +#endif + +#define MAX(a, b) ((a < b) ? (b) : (a)) + +/* Perform a critical factorization of NEEDLE, of length NEEDLE_LEN. + Return the index of the first byte in the right half, and set + *PERIOD to the global period of the right half. + + The global period of a string is the smallest index (possibly its + length) at which all remaining bytes in the string are repetitions + of the prefix (the last repetition may be a subset of the prefix). + + When NEEDLE is factored into two halves, a local period is the + length of the smallest word that shares a suffix with the left half + and shares a prefix with the right half. All factorizations of a + non-empty NEEDLE have a local period of at least 1 and no greater + than NEEDLE_LEN. + + A critical factorization has the property that the local period + equals the global period. All strings have at least one critical + factorization with the left half smaller than the global period. + + Given an ordered alphabet, a critical factorization can be computed + in linear time, with 2 * NEEDLE_LEN comparisons, by computing the + larger of two ordered maximal suffixes. The ordered maximal + suffixes are determined by lexicographic comparison of + periodicity. */ +static size_t +critical_factorization (const unsigned char *needle, size_t needle_len, + size_t *period) +{ + /* Index of last byte of left half, or SIZE_MAX. */ + size_t max_suffix, max_suffix_rev; + size_t j; /* Index into NEEDLE for current candidate suffix. */ + size_t k; /* Offset into current period. */ + size_t p; /* Intermediate period. */ + unsigned char a, b; /* Current comparison bytes. */ + + /* Invariants: + 0 <= j < NEEDLE_LEN - 1 + -1 <= max_suffix{,_rev} < j (treating SIZE_MAX as if it were signed) + min(max_suffix, max_suffix_rev) < global period of NEEDLE + 1 <= p <= global period of NEEDLE + p == global period of the substring NEEDLE[max_suffix{,_rev}+1...j] + 1 <= k <= p + */ + + /* Perform lexicographic search. */ + max_suffix = SIZE_MAX; + j = 0; + k = p = 1; + while (j + k < needle_len) + { + a = needle[j + k]; + b = needle[max_suffix + k]; + if (a < b) + { + /* Suffix is smaller, period is entire prefix so far. */ + j += k; + k = 1; + p = j - max_suffix; + } + else if (a == b) + { + /* Advance through repetition of the current period. */ + if (k != p) + ++k; + else + { + j += p; + k = 1; + } + } + else /* b < a */ + { + /* Suffix is larger, start over from current location. */ + max_suffix = j++; + k = p = 1; + } + } + *period = p; + + /* Perform reverse lexicographic search. */ + max_suffix_rev = SIZE_MAX; + j = 0; + k = p = 1; + while (j + k < needle_len) + { + a = needle[j + k]; + b = needle[max_suffix_rev + k]; + if (b < a) + { + /* Suffix is smaller, period is entire prefix so far. */ + j += k; + k = 1; + p = j - max_suffix_rev; + } + else if (a == b) + { + /* Advance through repetition of the current period. */ + if (k != p) + ++k; + else + { + j += p; + k = 1; + } + } + else /* a < b */ + { + /* Suffix is larger, start over from current location. */ + max_suffix_rev = j++; + k = p = 1; + } + } + + /* Choose the longer suffix. Return the first byte of the right + half, rather than the last byte of the left half. */ + if (max_suffix_rev + 1 < max_suffix + 1) + return max_suffix + 1; + *period = p; + return max_suffix_rev + 1; +} + +/* Return the first location of NEEDLE within HAYSTACK, or NULL. This + method requires 0 < NEEDLE_LEN <= HAYSTACK_LEN, and is optimized + for NEEDLE_LEN < LONG_NEEDLE_THRESHOLD. Performance is linear, + with 2 * NEEDLE_LEN comparisons in preparation, and at most 3 * + HAYSTACK_LEN - NEEDLE_LEN comparisons in searching. */ +static char * +two_way_short_needle (const unsigned char *haystack, size_t haystack_len, + const unsigned char *needle, size_t needle_len) +{ + size_t i; /* Index into current byte of NEEDLE. */ + size_t j; /* Index into current window of HAYSTACK. */ + size_t period; /* The period of the right half of needle. */ + size_t suffix; /* The index of the right half of needle. */ + + /* Factor the needle into two halves, such that the left half is + smaller than the global period, and the right half is + periodic (with a period as large as NEEDLE_LEN - suffix). */ + suffix = critical_factorization (needle, needle_len, &period); + + /* Perform the search. Each iteration compares the right half + first. */ + if (memcmp (needle, needle + period, suffix) == 0) + { + /* Entire needle is periodic; a mismatch can only advance by the + period, so use memory to avoid rescanning known occurrences + of the period. */ + size_t memory = 0; + j = 0; + while (!memchr (&haystack[haystack_len], '\0', + j + needle_len - haystack_len) + && (haystack_len = j + needle_len)) + { + /* Scan for matches in right half. */ + i = MAX (suffix, memory); + while (i < needle_len && needle[i] == haystack[i + j]) + ++i; + if (needle_len <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (memory < i + 1 && needle[i] == haystack[i + j]) + --i; + if (i + 1 < memory + 1) + return (char *) (haystack + j); + /* No match, so remember how many repetitions of period + on the right half were scanned. */ + j += period; + memory = needle_len - period; + } + else + { + j += i - suffix + 1; + memory = 0; + } + } + } + else + { + /* The two halves of needle are distinct; no extra memory is + required, and any mismatch results in a maximal shift. */ + period = MAX (suffix, needle_len - suffix) + 1; + j = 0; + while (!memchr (&haystack[haystack_len], '\0', + j + needle_len - haystack_len) + && (haystack_len = j + needle_len)) + { + /* Scan for matches in right half. */ + i = suffix; + while (i < needle_len && needle[i] == haystack[i + j]) + ++i; + if (needle_len <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (i != SIZE_MAX && needle[i] == haystack[i + j]) + --i; + if (i == SIZE_MAX) + return (char *) (haystack + j); + j += period; + } + else + j += i - suffix + 1; + } + } + return NULL; +} + +/* Return the first location of NEEDLE within HAYSTACK, or NULL. This + method requires 0 < NEEDLE_LEN <= HAYSTACK_LEN, and is optimized + for LONG_NEEDLE_THRESHOLD <= NEEDLE_LEN. Performance is linear, + with 3 * NEEDLE_LEN + (1U << CHAR_BIT) operations in preparation, + and at most 3 * HAYSTACK_LEN - NEEDLE_LEN comparisons in searching. + The extra initialization cost allows for as few as HAYSTACK_LEN + + HAYSTACK_LEN / NEEDLE_LEN. */ +static char * +two_way_long_needle (const unsigned char *haystack, size_t haystack_len, + const unsigned char *needle, size_t needle_len) +{ + size_t i; /* Index into current byte of NEEDLE. */ + size_t j; /* Index into current window of HAYSTACK. */ + size_t period; /* The period of the right half of needle. */ + size_t suffix; /* The index of the right half of needle. */ + size_t shift_table[1U << CHAR_BIT]; /* See below. */ + + /* Factor the needle into two halves, such that the left half is + smaller than the global period, and the right half is + periodic (with a period as large as NEEDLE_LEN - suffix). */ + suffix = critical_factorization (needle, needle_len, &period); + + /* Populate shift_table. For each possible byte value c, + shift_table[c] is the distance from the last occurrence of c to + the end of NEEDLE, or NEEDLE_LEN if c is absent from the NEEDLE. + shift_table[NEEDLE[NEEDLE_LEN - 1]] contains the only 0. */ + for (i = 0; i < 1U << CHAR_BIT; i++) + shift_table[i] = needle_len; + for (i = 0; i < needle_len; i++) + shift_table[needle[i]] = needle_len - i - 1; + + /* Perform the search. Each iteration compares the right half + first. */ + if (memcmp (needle, needle + period, suffix) == 0) + { + /* Entire needle is periodic; a mismatch can only advance by the + period, so use memory to avoid rescanning known occurrences + of the period. */ + size_t memory = 0; + j = 0; + while (!memchr (&haystack[haystack_len], '\0', + j + needle_len - haystack_len) + && (haystack_len = j + needle_len)) + { + /* Check the last byte first; if it does not match, then + shift to the next possible match location. */ + size_t shift = shift_table[haystack[j + needle_len - 1]]; + if (0 < shift) + { + if (memory && shift < period) + { + /* Since needle is periodic, but the last period has + a byte out of place, there can be no match until + after the mismatch. */ + shift = needle_len - period; + memory = 0; + } + j += shift; + continue; + } + /* Scan for matches in right half. The last byte has + already been matched, by virtue of the shift table. */ + i = MAX (suffix, memory); + while (i < needle_len - 1 && needle[i] == haystack[i + j]) + ++i; + if (needle_len - 1 <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (memory < i + 1 && needle[i] == haystack[i + j]) + --i; + if (i + 1 < memory + 1) + return (char *) (haystack + j); + /* No match, so remember how many repetitions of period + on the right half were scanned. */ + j += period; + memory = needle_len - period; + } + else + { + j += i - suffix + 1; + memory = 0; + } + } + } + else + { + /* The two halves of needle are distinct; no extra memory is + required, and any mismatch results in a maximal shift. */ + period = MAX (suffix, needle_len - suffix) + 1; + j = 0; + while (!memchr (&haystack[haystack_len], '\0', + j + needle_len - haystack_len) + && (haystack_len = j + needle_len)) + { + /* Check the last byte first; if it does not match, then + shift to the next possible match location. */ + size_t shift = shift_table[haystack[j + needle_len - 1]]; + if (0 < shift) + { + j += shift; + continue; + } + /* Scan for matches in right half. The last byte has + already been matched, by virtue of the shift table. */ + i = suffix; + while (i < needle_len - 1 && needle[i] == haystack[i + j]) + ++i; + if (needle_len - 1 <= i) + { + /* Scan for matches in left half. */ + i = suffix - 1; + while (i != SIZE_MAX && needle[i] == haystack[i + j]) + --i; + if (i == SIZE_MAX) + return (char *) (haystack + j); + j += period; + } + else + j += i - suffix + 1; + } + } + return NULL; +} + +/* Return the first occurrence of NEEDLE in HAYSTACK. Return HAYSTACK + if NEEDLE is empty, otherwise NULL if NEEDLE is not found in + HAYSTACK. */ +char * +strstr (const char *haystack_start, const char *needle_start) +{ + const char *haystack = haystack_start; + const char *needle = needle_start; + size_t needle_len; /* Length of NEEDLE. */ + size_t haystack_len; /* Known minimum length of HAYSTACK. */ + bool ok = true; /* True if NEEDLE is prefix of HAYSTACK. */ + + /* Determine length of NEEDLE, and in the process, make sure + HAYSTACK is at least as long (no point processing all of a long + NEEDLE if HAYSTACK is too short). */ + while (*haystack && *needle) + ok &= *haystack++ == *needle++; + if (*needle) + return NULL; + if (ok) + return (char *) haystack_start; + + /* Reduce the size of haystack using strchr, since it has a smaller + linear coefficient than the Two-Way algorithm. */ + needle_len = needle - needle_start; + haystack = strchr (haystack_start + 1, *needle_start); + if (!haystack || __builtin_expect (needle_len == 1, 0)) + return (char *) haystack; + needle -= needle_len; + haystack_len = (haystack > haystack_start + needle_len ? 1 + : needle_len + haystack_start - haystack); + + /* Perform the search. Abstract memory is considered to be an array + of 'unsigned char' values, not an array of 'char' values. See + ISO C 99 section 6.2.6.1. */ + if (needle_len < LONG_NEEDLE_THRESHOLD) + return two_way_short_needle ((const unsigned char *) haystack, + haystack_len, + (const unsigned char *) needle, needle_len); + return two_way_long_needle ((const unsigned char *) haystack, haystack_len, + (const unsigned char *) needle, needle_len); +} + +#undef LONG_NEEDLE_THRESHOLD +#undef MAX diff --git a/m4/string_h.m4 b/m4/string_h.m4 index 99a0dabbcb..1598c395d9 100644 --- a/m4/string_h.m4 +++ b/m4/string_h.m4 @@ -1,11 +1,11 @@ # Configure a GNU-like replacement for . -# Copyright (C) 2007 Free Software Foundation, Inc. +# Copyright (C) 2007, 2008 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 2 +# serial 3 # Written by Paul Eggert. @@ -43,6 +43,7 @@ AC_DEFUN([gl_HEADER_STRING_H_DEFAULTS], GNULIB_STRNLEN=0; AC_SUBST([GNULIB_STRNLEN]) GNULIB_STRPBRK=0; AC_SUBST([GNULIB_STRPBRK]) GNULIB_STRSEP=0; AC_SUBST([GNULIB_STRSEP]) + GNULIB_STRSTR=0; AC_SUBST([GNULIB_STRSTR]) GNULIB_STRCASESTR=0; AC_SUBST([GNULIB_STRCASESTR]) GNULIB_STRTOK_R=0; AC_SUBST([GNULIB_STRTOK_R]) GNULIB_MBSLEN=0; AC_SUBST([GNULIB_MBSLEN]) @@ -78,4 +79,5 @@ AC_DEFUN([gl_HEADER_STRING_H_DEFAULTS], HAVE_DECL_STRERROR=1; AC_SUBST([HAVE_DECL_STRERROR]) REPLACE_STRERROR=0; AC_SUBST([REPLACE_STRERROR]) REPLACE_MEMMEM=0; AC_SUBST([REPLACE_MEMMEM]) + REPLACE_STRSTR=0; AC_SUBST([REPLACE_STRSTR]) ]) diff --git a/m4/strstr.m4 b/m4/strstr.m4 new file mode 100644 index 0000000000..4fe15bded5 --- /dev/null +++ b/m4/strstr.m4 @@ -0,0 +1,48 @@ +# strstr.m4 serial 1 +dnl Copyright (C) 2008 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Check that strstr is efficient. +AC_DEFUN([gl_FUNC_STRSTR], +[ + AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) + AC_CACHE_CHECK([whether strstr works in linear time], + [gl_cv_func_strstr_linear], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([ +#include /* for memmem */ +#include /* for malloc */ +#include /* for alarm */ +], [[size_t m = 1000000; + char *haystack = (char *) malloc (2 * m + 2); + char *needle = (char *) malloc (m + 2); + void *result = 0; + /* Failure to compile this test due to missing alarm is okay, + since all such platforms (mingw) also have quadratic strstr. */ + alarm (5); + /* Check for quadratic performance. */ + if (haystack && needle) + { + memset (haystack, 'A', 2 * m); + haystack[2 * m] = 'B'; + haystack[2 * m + 1] = 0; + memset (needle, 'A', m); + needle[m] = 'B'; + needle[m + 1] = 0; + result = strstr (haystack, needle); + } + return !result;]])], + [gl_cv_func_strstr_linear=yes], [gl_cv_func_strstr_linear=no], + [dnl pessimistically assume the worst, since even glibc 2.6.1 + dnl has quadratic complexity in its strstr + gl_cv_func_strstr_linear="guessing no"])]) + if test "$gl_cv_func_strstr_linear" != yes; then + REPLACE_STRSTR=1 + AC_LIBOBJ([strstr]) + fi +]) # gl_FUNC_MEMMEM + +# Prerequisites of lib/memmem.c. +AC_DEFUN([gl_PREREQ_MEMMEM], [:]) +]) diff --git a/modules/string b/modules/string index 6f3226d0fe..431a32236c 100644 --- a/modules/string +++ b/modules/string @@ -48,6 +48,7 @@ string.h: string.in.h -e 's|@''GNULIB_STRNLEN''@|$(GNULIB_STRNLEN)|g' \ -e 's|@''GNULIB_STRPBRK''@|$(GNULIB_STRPBRK)|g' \ -e 's|@''GNULIB_STRSEP''@|$(GNULIB_STRSEP)|g' \ + -e 's|@''GNULIB_STRSTR''@|$(GNULIB_STRSTR)|g' \ -e 's|@''GNULIB_STRCASESTR''@|$(GNULIB_STRCASESTR)|g' \ -e 's|@''GNULIB_STRTOK_R''@|$(GNULIB_STRTOK_R)|g' \ -e 's|@''GNULIB_STRERROR''@|$(GNULIB_STRERROR)|g' \ @@ -67,6 +68,7 @@ string.h: string.in.h -e 's|@''HAVE_DECL_STRTOK_R''@|$(HAVE_DECL_STRTOK_R)|g' \ -e 's|@''HAVE_DECL_STRERROR''@|$(HAVE_DECL_STRERROR)|g' \ -e 's|@''REPLACE_MEMMEM''@|$(REPLACE_MEMMEM)|g' \ + -e 's|@''REPLACE_STRSTR''@|$(REPLACE_STRSTR)|g' \ -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \ -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \ < $(srcdir)/string.in.h; \ diff --git a/modules/strstr b/modules/strstr new file mode 100644 index 0000000000..4f93df95e2 --- /dev/null +++ b/modules/strstr @@ -0,0 +1,27 @@ +Description: +strstr() function: efficiently locate first substring in a buffer. + +Files: +lib/strstr.c +m4/strstr.m4 + +Depends-on: +string +stdbool +memchr +memcmp + +configure.ac: +gl_FUNC_STRSTR +gl_STRING_MODULE_INDICATOR([strstr]) + +Makefile.am: + +Include: + + +License: +LGPLv2+ + +Maintainer: +all, Eric Blake diff --git a/modules/strstr-tests b/modules/strstr-tests new file mode 100644 index 0000000000..23e67b0142 --- /dev/null +++ b/modules/strstr-tests @@ -0,0 +1,12 @@ +Files: +tests/test-strstr.c + +Depends-on: + +configure.ac: +AC_CHECK_DECLS_ONCE([alarm]) + +Makefile.am: +TESTS += test-strstr +check_PROGRAMS += test-strstr + diff --git a/tests/test-strstr.c b/tests/test-strstr.c new file mode 100644 index 0000000000..a4cc0fc774 --- /dev/null +++ b/tests/test-strstr.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2004, 2007, 2008 Free Software Foundation + * Written by Bruno Haible and Eric Blake + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +#include + +#include + +#include +#include +#include + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + abort (); \ + } \ + } \ + while (0) + +int +main (int argc, char *argv[]) +{ +#if HAVE_DECL_ALARM + /* Declare failure if test takes too long, by using default abort + caused by SIGALRM. All known platforms that lack alarm also have + a quadratic strstr, and the replacement strstr is known to not + take too long. */ + alarm (10); +#endif + + { + const char input[] = "foo"; + const char *result = strstr (input, ""); + ASSERT (result == input); + } + + { + const char input[] = "foo"; + const char *result = strstr (input, "o"); + ASSERT (result == input + 1); + } + + { + const char input[] = "ABC ABCDAB ABCDABCDABDE"; + const char *result = strstr (input, "ABCDABD"); + ASSERT (result == input + 15); + } + + { + const char input[] = "ABC ABCDAB ABCDABCDABDE"; + const char *result = strstr (input, "ABCDABE"); + ASSERT (result == NULL); + } + + { + const char input[] = "ABC ABCDAB ABCDABCDABDE"; + const char *result = strstr (input, "ABCDABCD"); + ASSERT (result == input + 11); + } + + /* Check that a very long haystack is handled quickly if the needle is + short and occurs near the beginning. */ + { + size_t repeat = 10000; + size_t m = 1000000; + char *needle = + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + char *haystack = (char *) malloc (m + 1); + if (haystack != NULL) + { + memset (haystack, 'A', m); + haystack[0] = 'B'; + haystack[m] = '\0'; + + for (; repeat > 0; repeat--) + { + ASSERT (strstr (haystack, needle) == haystack + 1); + } + + free (haystack); + } + } + + /* Check that a very long needle is discarded quickly if the haystack is + short. */ + { + size_t repeat = 10000; + size_t m = 1000000; + char *haystack = + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "ABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB"; + char *needle = (char *) malloc (m + 1); + if (needle != NULL) + { + memset (needle, 'A', m); + needle[m] = '\0'; + + for (; repeat > 0; repeat--) + { + ASSERT (strstr (haystack, needle) == NULL); + } + + free (needle); + } + } + + /* Check that the asymptotic worst-case complexity is not quadratic. */ + { + size_t m = 1000000; + char *haystack = (char *) malloc (2 * m + 2); + char *needle = (char *) malloc (m + 2); + if (haystack != NULL && needle != NULL) + { + const char *result; + + memset (haystack, 'A', 2 * m); + haystack[2 * m] = 'B'; + haystack[2 * m + 1] = '\0'; + + memset (needle, 'A', m); + needle[m] = 'B'; + needle[m + 1] = '\0'; + + result = strstr (haystack, needle); + ASSERT (result == haystack + m); + } + if (needle != NULL) + free (needle); + if (haystack != NULL) + free (haystack); + } + + /* Sublinear speed is only possible in memmem; strstr must examine + every character of haystack to find its length. */ + + return 0; +}