diff mbox series

[2/2] Enhanced test coverage for strncmp, wcsncmp

Message ID a6d6b5f87ef4ff2a3e117e5246fef8a332c952d0.1722443838.git.fweimer@redhat.com
State New
Headers show
Series [1/2] Enhance test coverage for strnlen, wcsnlen | expand

Commit Message

Florian Weimer July 31, 2024, 4:38 p.m. UTC
Add string/test-strncmp-nonarray and
wcsmbs/test-wcsncmp-nonarray.

This is the test that uncovered bug 31934.  Test run time
is more than one minute on a fairly current system, so turn
these into xtests that do not run automatically.

I split this from the manual update because I think the new test is
independently useful.

---
 string/Makefile                |   5 +-
 string/test-Xncmp-nonarray.c   | 183 +++++++++++++++++++++++++++++++++
 string/test-strncmp-nonarray.c |   4 +
 wcsmbs/Makefile                |   4 +
 wcsmbs/test-wcsncmp-nonarray.c |   5 +
 5 files changed, 200 insertions(+), 1 deletion(-)
 create mode 100644 string/test-Xncmp-nonarray.c
 create mode 100644 string/test-strncmp-nonarray.c
 create mode 100644 wcsmbs/test-wcsncmp-nonarray.c

Comments

Noah Goldstein July 31, 2024, 5:15 p.m. UTC | #1
On Thu, Aug 1, 2024 at 12:38 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> Add string/test-strncmp-nonarray and
> wcsmbs/test-wcsncmp-nonarray.
>
> This is the test that uncovered bug 31934.  Test run time
> is more than one minute on a fairly current system, so turn
> these into xtests that do not run automatically.
>
> I split this from the manual update because I think the new test is
> independently useful.
>
> ---
>  string/Makefile                |   5 +-
>  string/test-Xncmp-nonarray.c   | 183 +++++++++++++++++++++++++++++++++
>  string/test-strncmp-nonarray.c |   4 +
>  wcsmbs/Makefile                |   4 +
>  wcsmbs/test-wcsncmp-nonarray.c |   5 +
>  5 files changed, 200 insertions(+), 1 deletion(-)
>  create mode 100644 string/test-Xncmp-nonarray.c
>  create mode 100644 string/test-strncmp-nonarray.c
>  create mode 100644 wcsmbs/test-wcsncmp-nonarray.c
>
> diff --git a/string/Makefile b/string/Makefile
> index 2e20fc00fd..1dff405c27 100644
> --- a/string/Makefile
> +++ b/string/Makefile
> @@ -236,7 +236,10 @@ tests-unsupported += $(tests-translation)
>  endif
>
>  # This test allocates a lot of memory and can run for a long time.
> -xtests = tst-strcoll-overflow
> +xtests += tst-strcoll-overflow
> +
> +# This test runs for a long time.
> +xtests += test-strncmp-nonarray
>
>  # This test needs libdl.
>  ifeq (yes,$(build-shared))
> diff --git a/string/test-Xncmp-nonarray.c b/string/test-Xncmp-nonarray.c
> new file mode 100644
> index 0000000000..9f3a3ca75d
> --- /dev/null
> +++ b/string/test-Xncmp-nonarray.c
> @@ -0,0 +1,183 @@
> +/* Test non-array inputs to string comparison functions.
> +   Copyright (C) 2024 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library 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
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +/* This skeleton file is included from string/test-strncmp-nonarray.c and
> +   wcsmbs/test-wcsncmp-nonarray.c to test that reading of the arrays stops
> +   at the first null character.
> +
> +   TEST_IDENTIFIER must be the test function identifier.  TEST_NAME is
> +   the same as a string.
> +
> +   CHAR must be defined as the character type.  */
> +
> +#include <array_length.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/next_to_fault.h>
> +#include <support/test-driver.h>
> +#include <sys/param.h>
> +#include <unistd.h>
> +
> +/* Much shorter than test-Xnlen-nonarray.c because of deeply nested loops.  */
> +enum { buffer_length = 80 };
> +
> +/* The test buffer layout follows what is described test-Xnlen-nonarray.c,
> +   except that there two buffers, left and right.  The variables
> +   a_count, zero_count, start_offset are all duplicated.  */
> +
> +/* Return the maximum string length for a string that starts at
> +   start_offset.  */
> +static int
> +string_length (int a_count, int start_offset)
> +{
> +  if (start_offset == buffer_length || start_offset >= a_count)
pretty sure the `start_offset == buffer_length` is unnecessary.

> +    return 0;
> +  else
> +    return a_count - start_offset;
> +}
> +
> +/* This is the valid maximum length argument computation for
> +   strnlen/wcsnlen.  See text-Xnlen-nonarray.c.  */
> +static int
> +maximum_length (int start_offset, int zero_count)
> +{
> +  if (start_offset == buffer_length)
> +    return 0;
> +  else if (zero_count > 0)
> +    /* Effectively unbounded, but we need to stop fairly low,
> +       otherwise testing takes too long.  */
> +    return buffer_length + 32;
> +  else
> +    return buffer_length - start_offset;
> +}
> +
> +typedef __typeof (TEST_IDENTIFIER) *proto_t;
> +
> +#define TEST_MAIN
> +#include "test-string.h"
> +
> +IMPL (TEST_IDENTIFIER, 1)
> +
> +static int
> +test_main (void)
> +{
> +  TEST_VERIFY_EXIT (sysconf (_SC_PAGESIZE) >= buffer_length);
> +  test_init ();
> +
> +  struct support_next_to_fault left_ntf
> +    = support_next_to_fault_allocate (buffer_length * sizeof (CHAR));
> +  CHAR *left_buffer = (CHAR *) left_ntf.buffer;
> +  struct support_next_to_fault right_ntf
> +    = support_next_to_fault_allocate (buffer_length * sizeof (CHAR));
> +  CHAR *right_buffer = (CHAR *) right_ntf.buffer;
> +
> +  FOR_EACH_IMPL (impl, 0)
> +    {
> +      printf ("info: testing %s\n", impl->name);
> +      for (size_t i = 0; i < buffer_length; ++i)
> +        left_buffer[i] = 'A';
> +
> +      for (int left_zero_count = 0; left_zero_count <= buffer_length;
> +           ++left_zero_count)
> +        {
> +          if (left_zero_count > 0)
> +            left_buffer[buffer_length - left_zero_count] = 0;
> +          int left_a_count = buffer_length - left_zero_count;
> +          for (size_t i = 0; i < buffer_length; ++i)
> +            right_buffer[i] = 'A';
> +          for (int right_zero_count = 0; right_zero_count <= buffer_length;
> +               ++right_zero_count)
> +            {
> +              if (right_zero_count > 0)
> +                right_buffer[buffer_length - right_zero_count] = 0;
> +              int right_a_count = buffer_length - right_zero_count;
> +              for (int left_start_offset = 0;
> +                   left_start_offset <= buffer_length;
> +                   ++left_start_offset)
> +                {
> +                  CHAR *left_start_pointer = left_buffer + left_start_offset;
> +                  int left_maxlen
> +                    = maximum_length (left_start_offset, left_zero_count);
> +                  int left_length
> +                    = string_length (left_a_count, left_start_offset);
> +                  for (int right_start_offset = 0;
> +                       right_start_offset <= buffer_length;
> +                       ++right_start_offset)
> +                    {
> +                      CHAR *right_start_pointer
> +                        = right_buffer + right_start_offset;
> +                      int right_maxlen
> +                        = maximum_length (right_start_offset, right_zero_count);
> +                      int right_length
> +                        = string_length (right_a_count, right_start_offset);
> +
> +                      /* Maximum length is modelled after strnlen/wcsnlen,
> +                         and must be valid for both pointer arguments at
> +                         the same time.  */
> +                      int maxlen = MIN (left_maxlen, right_maxlen);
> +
> +                      for (int length_argument = 0; length_argument <= maxlen;
> +                           ++length_argument)
> +                        {
> +                          if (test_verbose)
> +                            {
> +                              printf ("left: zero_count=%d"
> +                                      " a_count=%d start_offset=%d\n",
> +                                      left_zero_count, left_a_count,
> +                                      left_start_offset);
> +                              printf ("right: zero_count=%d"
> +                                      " a_count=%d start_offset=%d\n",
> +                                      right_zero_count, right_a_count,
> +                                      right_start_offset);
> +                              printf ("length argument: %d\n",
> +                                      length_argument);
> +                            }
> +
> +                          /* Effective lengths bounded by length argument.
> +                             The effective length determines the
> +                             outcome of the comparison.  */
> +                          int left_effective
> +                            = MIN (left_length, length_argument);
> +                          int right_effective
> +                            = MIN (right_length, length_argument);
> +                          if (left_effective == right_effective)
> +                            TEST_COMPARE (CALL (impl,
> +                                                left_start_pointer,
> +                                                right_start_pointer,
> +                                                length_argument), 0);
> +                          else if (left_effective < right_effective)
> +                            TEST_COMPARE (CALL (impl,
> +                                                left_start_pointer,
> +                                                right_start_pointer,
> +                                                length_argument) < 0, 1);
> +                          else
> +                            TEST_COMPARE (CALL (impl,
> +                                                left_start_pointer,
> +                                                right_start_pointer,
> +                                                length_argument) > 0, 1);
> +                        }
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/string/test-strncmp-nonarray.c b/string/test-strncmp-nonarray.c
> new file mode 100644
> index 0000000000..581e52d01b
> --- /dev/null
> +++ b/string/test-strncmp-nonarray.c
> @@ -0,0 +1,4 @@
> +#define TEST_IDENTIFIER strncmp
> +#define TEST_NAME "strncmp"
> +typedef char CHAR;
> +#include "test-Xncmp-nonarray.c"
> diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
> index c51c9b4f1f..63adf0e8ef 100644
> --- a/wcsmbs/Makefile
> +++ b/wcsmbs/Makefile
> @@ -206,6 +206,10 @@ tests := \
>    wcsmbs-tst1 \
>    # tests
>
> +# This test runs for a long time.
> +xtests += test-wcsncmp-nonarray
> +
> +
>  include ../Rules
>
>  ifeq ($(run-built-tests),yes)
> diff --git a/wcsmbs/test-wcsncmp-nonarray.c b/wcsmbs/test-wcsncmp-nonarray.c
> new file mode 100644
> index 0000000000..1ad9ebd8fd
> --- /dev/null
> +++ b/wcsmbs/test-wcsncmp-nonarray.c
> @@ -0,0 +1,5 @@
> +#include <wchar.h>
> +#define TEST_IDENTIFIER wcsncmp
> +#define TEST_NAME "wcsncmp"
> +typedef wchar_t CHAR;
> +#include "../string/test-Xncmp-nonarray.c"
> --
> 2.45.2
>

There very well might be a "right" "left" typo somewhere in the 4th nested loop
but it found a bug and all the presumably non-buggy impls pass so it's fine by
me.

LGTM.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
Florian Weimer Aug. 1, 2024, 7:08 a.m. UTC | #2
* Noah Goldstein:

> There very well might be a "right" "left" typo somewhere in the 4th
> nested loop but it found a bug and all the presumably non-buggy impls
> pass so it's fine by me.

Sorry, I don't see the typo.  I suppose we can fix it if some automation
flags it, and retest on s390x if still exposes the bug.  Thanks.

Florian
Noah Goldstein Aug. 1, 2024, 10:08 a.m. UTC | #3
On Thu, Aug 1, 2024 at 3:09 PM Florian Weimer <fweimer@redhat.com> wrote:
>
> * Noah Goldstein:
>
> > There very well might be a "right" "left" typo somewhere in the 4th
> > nested loop but it found a bug and all the presumably non-buggy impls
> > pass so it's fine by me.
>
> Sorry, I don't see the typo.  I suppose we can fix it if some automation
> flags it, and retest on s390x if still exposes the bug.  Thanks.

I didn't see any issue, what I was trying to say is the test code is pretty
messy i.e there might be a typo I overlooked. Despite this, still think its
worthwhile :)
>
> Florian
>
diff mbox series

Patch

diff --git a/string/Makefile b/string/Makefile
index 2e20fc00fd..1dff405c27 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -236,7 +236,10 @@  tests-unsupported += $(tests-translation)
 endif
 
 # This test allocates a lot of memory and can run for a long time.
-xtests = tst-strcoll-overflow
+xtests += tst-strcoll-overflow
+
+# This test runs for a long time.
+xtests += test-strncmp-nonarray
 
 # This test needs libdl.
 ifeq (yes,$(build-shared))
diff --git a/string/test-Xncmp-nonarray.c b/string/test-Xncmp-nonarray.c
new file mode 100644
index 0000000000..9f3a3ca75d
--- /dev/null
+++ b/string/test-Xncmp-nonarray.c
@@ -0,0 +1,183 @@ 
+/* Test non-array inputs to string comparison functions.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This skeleton file is included from string/test-strncmp-nonarray.c and
+   wcsmbs/test-wcsncmp-nonarray.c to test that reading of the arrays stops
+   at the first null character.
+
+   TEST_IDENTIFIER must be the test function identifier.  TEST_NAME is
+   the same as a string.
+
+   CHAR must be defined as the character type.  */
+
+#include <array_length.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/test-driver.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+/* Much shorter than test-Xnlen-nonarray.c because of deeply nested loops.  */
+enum { buffer_length = 80 };
+
+/* The test buffer layout follows what is described test-Xnlen-nonarray.c,
+   except that there two buffers, left and right.  The variables
+   a_count, zero_count, start_offset are all duplicated.  */
+
+/* Return the maximum string length for a string that starts at
+   start_offset.  */
+static int
+string_length (int a_count, int start_offset)
+{
+  if (start_offset == buffer_length || start_offset >= a_count)
+    return 0;
+  else
+    return a_count - start_offset;
+}
+
+/* This is the valid maximum length argument computation for
+   strnlen/wcsnlen.  See text-Xnlen-nonarray.c.  */
+static int
+maximum_length (int start_offset, int zero_count)
+{
+  if (start_offset == buffer_length)
+    return 0;
+  else if (zero_count > 0)
+    /* Effectively unbounded, but we need to stop fairly low,
+       otherwise testing takes too long.  */
+    return buffer_length + 32;
+  else
+    return buffer_length - start_offset;
+}
+
+typedef __typeof (TEST_IDENTIFIER) *proto_t;
+
+#define TEST_MAIN
+#include "test-string.h"
+
+IMPL (TEST_IDENTIFIER, 1)
+
+static int
+test_main (void)
+{
+  TEST_VERIFY_EXIT (sysconf (_SC_PAGESIZE) >= buffer_length);
+  test_init ();
+
+  struct support_next_to_fault left_ntf
+    = support_next_to_fault_allocate (buffer_length * sizeof (CHAR));
+  CHAR *left_buffer = (CHAR *) left_ntf.buffer;
+  struct support_next_to_fault right_ntf
+    = support_next_to_fault_allocate (buffer_length * sizeof (CHAR));
+  CHAR *right_buffer = (CHAR *) right_ntf.buffer;
+
+  FOR_EACH_IMPL (impl, 0)
+    {
+      printf ("info: testing %s\n", impl->name);
+      for (size_t i = 0; i < buffer_length; ++i)
+        left_buffer[i] = 'A';
+
+      for (int left_zero_count = 0; left_zero_count <= buffer_length;
+           ++left_zero_count)
+        {
+          if (left_zero_count > 0)
+            left_buffer[buffer_length - left_zero_count] = 0;
+          int left_a_count = buffer_length - left_zero_count;
+          for (size_t i = 0; i < buffer_length; ++i)
+            right_buffer[i] = 'A';
+          for (int right_zero_count = 0; right_zero_count <= buffer_length;
+               ++right_zero_count)
+            {
+              if (right_zero_count > 0)
+                right_buffer[buffer_length - right_zero_count] = 0;
+              int right_a_count = buffer_length - right_zero_count;
+              for (int left_start_offset = 0;
+                   left_start_offset <= buffer_length;
+                   ++left_start_offset)
+                {
+                  CHAR *left_start_pointer = left_buffer + left_start_offset;
+                  int left_maxlen
+                    = maximum_length (left_start_offset, left_zero_count);
+                  int left_length
+                    = string_length (left_a_count, left_start_offset);
+                  for (int right_start_offset = 0;
+                       right_start_offset <= buffer_length;
+                       ++right_start_offset)
+                    {
+                      CHAR *right_start_pointer
+                        = right_buffer + right_start_offset;
+                      int right_maxlen
+                        = maximum_length (right_start_offset, right_zero_count);
+                      int right_length
+                        = string_length (right_a_count, right_start_offset);
+
+                      /* Maximum length is modelled after strnlen/wcsnlen,
+                         and must be valid for both pointer arguments at
+                         the same time.  */
+                      int maxlen = MIN (left_maxlen, right_maxlen);
+
+                      for (int length_argument = 0; length_argument <= maxlen;
+                           ++length_argument)
+                        {
+                          if (test_verbose)
+                            {
+                              printf ("left: zero_count=%d"
+                                      " a_count=%d start_offset=%d\n",
+                                      left_zero_count, left_a_count,
+                                      left_start_offset);
+                              printf ("right: zero_count=%d"
+                                      " a_count=%d start_offset=%d\n",
+                                      right_zero_count, right_a_count,
+                                      right_start_offset);
+                              printf ("length argument: %d\n",
+                                      length_argument);
+                            }
+
+                          /* Effective lengths bounded by length argument.
+                             The effective length determines the
+                             outcome of the comparison.  */
+                          int left_effective
+                            = MIN (left_length, length_argument);
+                          int right_effective
+                            = MIN (right_length, length_argument);
+                          if (left_effective == right_effective)
+                            TEST_COMPARE (CALL (impl,
+                                                left_start_pointer,
+                                                right_start_pointer,
+                                                length_argument), 0);
+                          else if (left_effective < right_effective)
+                            TEST_COMPARE (CALL (impl,
+                                                left_start_pointer,
+                                                right_start_pointer,
+                                                length_argument) < 0, 1);
+                          else
+                            TEST_COMPARE (CALL (impl,
+                                                left_start_pointer,
+                                                right_start_pointer,
+                                                length_argument) > 0, 1);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/string/test-strncmp-nonarray.c b/string/test-strncmp-nonarray.c
new file mode 100644
index 0000000000..581e52d01b
--- /dev/null
+++ b/string/test-strncmp-nonarray.c
@@ -0,0 +1,4 @@ 
+#define TEST_IDENTIFIER strncmp
+#define TEST_NAME "strncmp"
+typedef char CHAR;
+#include "test-Xncmp-nonarray.c"
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index c51c9b4f1f..63adf0e8ef 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -206,6 +206,10 @@  tests := \
   wcsmbs-tst1 \
   # tests
 
+# This test runs for a long time.
+xtests += test-wcsncmp-nonarray
+
+
 include ../Rules
 
 ifeq ($(run-built-tests),yes)
diff --git a/wcsmbs/test-wcsncmp-nonarray.c b/wcsmbs/test-wcsncmp-nonarray.c
new file mode 100644
index 0000000000..1ad9ebd8fd
--- /dev/null
+++ b/wcsmbs/test-wcsncmp-nonarray.c
@@ -0,0 +1,5 @@ 
+#include <wchar.h>
+#define TEST_IDENTIFIER wcsncmp
+#define TEST_NAME "wcsncmp"
+typedef wchar_t CHAR;
+#include "../string/test-Xncmp-nonarray.c"