This is the mail archive of the glibc-cvs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

GNU C Library master sources branch master updated. glibc-2.25-633-gedc1686


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU C Library master sources".

The branch, master has been updated
       via  edc1686af0c0fc2eb535f1d38cdf63c1a5a03675 (commit)
       via  12d5853e22c348b537472b55c161e5da327de310 (commit)
       via  cd00e12d31dd9de465a0d5f2677fa71bc2b12d80 (commit)
       via  08d01cae60aa3966ed21e6ec70bbe9fef730e012 (commit)
       via  c11cfe8d7c1e219fa8c819187d8b030ce3b4e947 (commit)
       via  e2390be8e47352147215aff06b23927dc6ededb7 (commit)
      from  1a920d9c26bc2794a22604e94a988f27f8b556f8 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=edc1686af0c0fc2eb535f1d38cdf63c1a5a03675

commit edc1686af0c0fc2eb535f1d38cdf63c1a5a03675
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:37:13 2017 +0200

    vfprintf: Reuse work_buffer in group_number

diff --git a/ChangeLog b/ChangeLog
index 8dfb981..e50c5b9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2017-06-29  Florian Weimer  <fweimer@redhat.com>
 
+	* stdio-common/vfprintf.c (group_number): Add front_ptr argument.
+	Use it to make the temporary copy at the start of the work buffer.
+	(process_arg): Adjust call to group_number.
+
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
 	* stdio-common/vfprintf.c (printf_positional): Use struct
 	scratch_buffer to allocate backing storage for the args_value,
 	args_size, args_type arrays.
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
index 576a6ce..089bcef 100644
--- a/stdio-common/vfprintf.c
+++ b/stdio-common/vfprintf.c
@@ -595,9 +595,8 @@ static const uint8_t jump_table[] =
 	      string = _itoa (number.longlong, workend, base,		      \
 			      spec == L_('X'));				      \
 	      if (group && grouping)					      \
-		string = group_number (string, workend, grouping,	      \
-				       thousands_sep);			      \
-									      \
+		string = group_number (work_buffer, string, workend,	      \
+				       grouping, thousands_sep);	      \
 	      if (use_outdigits && base == 10)				      \
 		string = _i18n_number_rewrite (string, workend, workend);     \
 	    }								      \
@@ -653,9 +652,8 @@ static const uint8_t jump_table[] =
 	      string = _itoa_word (number.word, workend, base,		      \
 				   spec == L_('X'));			      \
 	      if (group && grouping)					      \
-		string = group_number (string, workend, grouping,	      \
-				       thousands_sep);			      \
-									      \
+		string = group_number (work_buffer, string, workend,	      \
+				       grouping, thousands_sep);	      \
 	      if (use_outdigits && base == 10)				      \
 		string = _i18n_number_rewrite (string, workend, workend);     \
 	    }								      \
@@ -1237,8 +1235,8 @@ static int printf_unknown (FILE *, const struct printf_info *,
 			   const void *const *) __THROW;
 
 /* Group digits of number string.  */
-static CHAR_T *group_number (CHAR_T *, CHAR_T *, const char *, THOUSANDS_SEP_T)
-     __THROW internal_function;
+static CHAR_T *group_number (CHAR_T *, CHAR_T *, CHAR_T *, const char *,
+			     THOUSANDS_SEP_T);
 
 /* The function itself.  */
 int
@@ -2123,16 +2121,20 @@ printf_unknown (FILE *s, const struct printf_info *info,
   return done;
 }
 
-/* Group the digits according to the grouping rules of the current locale.
-   The interpretation of GROUPING is as in `struct lconv' from <locale.h>.  */
+/* Group the digits from W to REAR_PTR according to the grouping rules
+   of the current locale.  The interpretation of GROUPING is as in
+   `struct lconv' from <locale.h>.  The grouped number extends from
+   the returned pointer until REAR_PTR.  FRONT_PTR to W is used as a
+   scratch area.  */
 static CHAR_T *
-internal_function
-group_number (CHAR_T *w, CHAR_T *rear_ptr, const char *grouping,
-	      THOUSANDS_SEP_T thousands_sep)
+group_number (CHAR_T *front_ptr, CHAR_T *w, CHAR_T *rear_ptr,
+	      const char *grouping, THOUSANDS_SEP_T thousands_sep)
 {
+  /* Length of the current group.  */
   int len;
-  CHAR_T *src, *s;
 #ifndef COMPILE_WPRINTF
+  /* Length of the separator (in wide mode, the separator is always a
+     single wide character).  */
   int tlen = strlen (thousands_sep);
 #endif
 
@@ -2145,26 +2147,34 @@ group_number (CHAR_T *w, CHAR_T *rear_ptr, const char *grouping,
   len = *grouping++;
 
   /* Copy existing string so that nothing gets overwritten.  */
-  src = (CHAR_T *) alloca ((rear_ptr - w) * sizeof (CHAR_T));
-  s = (CHAR_T *) __mempcpy (src, w,
-			    (rear_ptr - w) * sizeof (CHAR_T));
+  memmove (front_ptr, w, (rear_ptr - w) * sizeof (CHAR_T));
+  CHAR_T *s = front_ptr + (rear_ptr - w);
+
   w = rear_ptr;
 
   /* Process all characters in the string.  */
-  while (s > src)
+  while (s > front_ptr)
     {
       *--w = *--s;
 
-      if (--len == 0 && s > src)
+      if (--len == 0 && s > front_ptr)
 	{
 	  /* A new group begins.  */
 #ifdef COMPILE_WPRINTF
-	  *--w = thousands_sep;
+	  if (w != s)
+	    *--w = thousands_sep;
+	  else
+	    /* Not enough room for the separator.  */
+	    goto copy_rest;
 #else
 	  int cnt = tlen;
-	  do
-	    *--w = thousands_sep[--cnt];
-	  while (cnt > 0);
+	  if (tlen < w - s)
+	    do
+	      *--w = thousands_sep[--cnt];
+	    while (cnt > 0);
+	  else
+	    /* Not enough room for the separator.  */
+	    goto copy_rest;
 #endif
 
 	  if (*grouping == CHAR_MAX
@@ -2173,17 +2183,16 @@ group_number (CHAR_T *w, CHAR_T *rear_ptr, const char *grouping,
 #endif
 		   )
 	    {
-	      /* No further grouping to be done.
-		 Copy the rest of the number.  */
-	      do
-		*--w = *--s;
-	      while (s > src);
+	    copy_rest:
+	      /* No further grouping to be done.  Copy the rest of the
+		 number.  */
+	      memmove (w, s, (front_ptr -s) * sizeof (CHAR_T));
 	      break;
 	    }
 	  else if (*grouping != '\0')
-	    /* The previous grouping repeats ad infinitum.  */
 	    len = *grouping++;
 	  else
+	    /* The previous grouping repeats ad infinitum.  */
 	    len = grouping[-1];
 	}
     }

http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=12d5853e22c348b537472b55c161e5da327de310

commit 12d5853e22c348b537472b55c161e5da327de310
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:37:04 2017 +0200

    vfprintf: Use struct scratch_buffer for positional arguments allocation

diff --git a/ChangeLog b/ChangeLog
index 67c5d07..8dfb981 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2017-06-29  Florian Weimer  <fweimer@redhat.com>
 
+	* stdio-common/vfprintf.c (printf_positional): Use struct
+	scratch_buffer to allocate backing storage for the args_value,
+	args_size, args_type arrays.
+
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
 	* stdio-common/_i18n_number.h (_i18n_number_rewrite): Use struct
 	scratch_buffer.
 
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
index 4e22c75..576a6ce 100644
--- a/stdio-common/vfprintf.c
+++ b/stdio-common/vfprintf.c
@@ -1708,15 +1708,17 @@ printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
 		   CHAR_T *work_buffer, int save_errno,
 		   const char *grouping, THOUSANDS_SEP_T thousands_sep)
 {
-  /* For the argument descriptions, which may be allocated on the heap.  */
-  void *args_malloced = NULL;
-
   /* For positional argument handling.  */
   struct scratch_buffer specsbuf;
   scratch_buffer_init (&specsbuf);
   struct printf_spec *specs = specsbuf.data;
   size_t specs_limit = specsbuf.length / sizeof (specs[0]);
 
+  /* Used as a backing store for args_value, args_size, args_type
+     below.  */
+  struct scratch_buffer argsbuf;
+  scratch_buffer_init (&argsbuf);
+
   /* Array with information about the needed arguments.  This has to
      be dynamically extensible.  */
   size_t nspecs = 0;
@@ -1725,10 +1727,6 @@ printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
      determine the size of the array needed to store the argument
      attributes.  */
   size_t nargs = 0;
-  size_t bytes_per_arg;
-  union printf_arg *args_value;
-  int *args_size;
-  int *args_type;
 
   /* Positional parameters refer to arguments directly.  This could
      also determine the maximum number of arguments.  Track the
@@ -1778,38 +1776,29 @@ printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
 
   /* Determine the number of arguments the format string consumes.  */
   nargs = MAX (nargs, max_ref_arg);
-  /* Calculate total size needed to represent a single argument across
-     all three argument-related arrays.  */
-  bytes_per_arg = (sizeof (*args_value) + sizeof (*args_size)
-		   + sizeof (*args_type));
 
-  /* Check for potential integer overflow.  */
-  if (__glibc_unlikely (nargs > INT_MAX / bytes_per_arg))
-    {
-      __set_errno (EOVERFLOW);
-      done = -1;
-      goto all_done;
-    }
-
-  /* Allocate memory for all three argument arrays.  */
-  if (__libc_use_alloca (nargs * bytes_per_arg))
-    args_value = alloca (nargs * bytes_per_arg);
-  else
-    {
-      args_value = args_malloced = malloc (nargs * bytes_per_arg);
-      if (args_value == NULL)
-	{
-	  done = -1;
-	  goto all_done;
-	}
-    }
-
-  /* Set up the remaining two arrays to each point past the end of the
-     prior array, since space for all three has been allocated now.  */
-  args_size = &args_value[nargs].pa_int;
-  args_type = &args_size[nargs];
-  memset (args_type, s->_flags2 & _IO_FLAGS2_FORTIFY ? '\xff' : '\0',
-	  nargs * sizeof (*args_type));
+  union printf_arg *args_value;
+  int *args_size;
+  int *args_type;
+  {
+    /* Calculate total size needed to represent a single argument
+       across all three argument-related arrays.  */
+    size_t bytes_per_arg
+      = sizeof (*args_value) + sizeof (*args_size) + sizeof (*args_type);
+    if (!scratch_buffer_set_array_size (&argsbuf, nargs, bytes_per_arg))
+      {
+	done = -1;
+	goto all_done;
+      }
+    args_value = argsbuf.data;
+    /* Set up the remaining two arrays to each point past the end of
+       the prior array, since space for all three has been allocated
+       now.  */
+    args_size = &args_value[nargs].pa_int;
+    args_type = &args_size[nargs];
+    memset (args_type, s->_flags2 & _IO_FLAGS2_FORTIFY ? '\xff' : '\0',
+	    nargs * sizeof (*args_type));
+  }
 
   /* XXX Could do sanity check here: If any element in ARGS_TYPE is
      still zero after this loop, format is invalid.  For now we
@@ -2075,10 +2064,9 @@ printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
 		 - specs[nspecs_done].end_of_fmt);
     }
  all_done:
-  if (__glibc_unlikely (args_malloced != NULL))
-    free (args_malloced);
   if (__glibc_unlikely (workstart != NULL))
     free (workstart);
+  scratch_buffer_free (&argsbuf);
   scratch_buffer_free (&specsbuf);
   return done;
 }

http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=cd00e12d31dd9de465a0d5f2677fa71bc2b12d80

commit cd00e12d31dd9de465a0d5f2677fa71bc2b12d80
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:34:45 2017 +0200

    _i18n_number_rewrite: Use struct scratch_buffer

diff --git a/ChangeLog b/ChangeLog
index db0c4ad..67c5d07 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2017-06-29  Florian Weimer  <fweimer@redhat.com>
 
+	* stdio-common/_i18n_number.h (_i18n_number_rewrite): Use struct
+	scratch_buffer.
+
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
 	* stdio-common/vfprintf.c (WORK_BUFFER_SIZE): Reduce size for
 	wchar_t builds.
 
diff --git a/stdio-common/_i18n_number.h b/stdio-common/_i18n_number.h
index 080e814..13d5936 100644
--- a/stdio-common/_i18n_number.h
+++ b/stdio-common/_i18n_number.h
@@ -19,6 +19,7 @@
 #include <stdbool.h>
 #include <wchar.h>
 #include <wctype.h>
+#include <scratch_buffer.h>
 
 #include "../locale/outdigits.h"
 #include "../locale/outdigitswc.h"
@@ -65,17 +66,13 @@ _i18n_number_rewrite (CHAR_T *w, CHAR_T *rear_ptr, CHAR_T *end)
 
   /* Copy existing string so that nothing gets overwritten.  */
   CHAR_T *src;
-  bool use_alloca = __libc_use_alloca ((rear_ptr - w) * sizeof (CHAR_T));
-  if (__builtin_expect (use_alloca, true))
-    src = (CHAR_T *) alloca ((rear_ptr - w) * sizeof (CHAR_T));
-  else
-    {
-      src = (CHAR_T *) malloc ((rear_ptr - w) * sizeof (CHAR_T));
-      if (src == NULL)
-	/* If we cannot allocate the memory don't rewrite the string.
-	   It is better than nothing.  */
-	return w;
-    }
+  struct scratch_buffer buffer;
+  scratch_buffer_init (&buffer);
+  if (!scratch_buffer_set_array_size (&buffer, rear_ptr - w, sizeof (CHAR_T)))
+    /* If we cannot allocate the memory don't rewrite the string.
+       It is better than nothing.  */
+    return w;
+  src = buffer.data;
 
   CHAR_T *s = (CHAR_T *) __mempcpy (src, w,
 				    (rear_ptr - w) * sizeof (CHAR_T));
@@ -110,8 +107,6 @@ _i18n_number_rewrite (CHAR_T *w, CHAR_T *rear_ptr, CHAR_T *end)
 	}
     }
 
-  if (! use_alloca)
-    free (src);
-
+  scratch_buffer_free (&buffer);
   return w;
 }

http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=08d01cae60aa3966ed21e6ec70bbe9fef730e012

commit 08d01cae60aa3966ed21e6ec70bbe9fef730e012
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:34:22 2017 +0200

    vfprintf: Reduce WORK_BUFFER_SIZE for wchar_t builds

diff --git a/ChangeLog b/ChangeLog
index 5e2cc46..db0c4ad 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2017-06-29  Florian Weimer  <fweimer@redhat.com>
 
+	* stdio-common/vfprintf.c (WORK_BUFFER_SIZE): Reduce size for
+	wchar_t builds.
+
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
 	* stdio-common/tst-vfprintf-mbs-prec.c: New file.
 	* stdio-common/Makefile (tests): Add tst-vfprintf-mbs-prec.
 
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
index b8c87a5..4e22c75 100644
--- a/stdio-common/vfprintf.c
+++ b/stdio-common/vfprintf.c
@@ -205,7 +205,7 @@ typedef wchar_t THOUSANDS_SEP_T;
 static const CHAR_T null[] = L_("(null)");
 
 /* Size of the work_buffer variable (in characters, not bytes.  */
-enum { WORK_BUFFER_SIZE = 1000 };
+enum { WORK_BUFFER_SIZE = 1000 / sizeof (CHAR_T) };
 
 /* This table maps a character into a number representing a class.  In
    each step there is a destination label for each class.  */

http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=c11cfe8d7c1e219fa8c819187d8b030ce3b4e947

commit c11cfe8d7c1e219fa8c819187d8b030ce3b4e947
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:33:43 2017 +0200

    vfprintf: Add test case for multi-byte/wide strings and precision

diff --git a/ChangeLog b/ChangeLog
index 5e81006..5e2cc46 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2017-06-29  Florian Weimer  <fweimer@redhat.com>
 
+	* stdio-common/tst-vfprintf-mbs-prec.c: New file.
+	* stdio-common/Makefile (tests): Add tst-vfprintf-mbs-prec.
+
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
 	* stdio-common/tst-vfprintf-user-type.c: New file.
 	* stdio-common/Makefile (tests): Add tst-vfprintf-user-type.
 
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 63c9ebd..309d83e 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -60,6 +60,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
 	 bug25 tst-printf-round bug23-2 bug23-3 bug23-4 bug26 tst-fmemopen3 \
 	 tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \
 	 tst-vfprintf-user-type \
+	 tst-vfprintf-mbs-prec \
 
 test-srcs = tst-unbputc tst-printf
 
diff --git a/stdio-common/tst-vfprintf-mbs-prec.c b/stdio-common/tst-vfprintf-mbs-prec.c
new file mode 100644
index 0000000..43e4519
--- /dev/null
+++ b/stdio-common/tst-vfprintf-mbs-prec.c
@@ -0,0 +1,543 @@
+/* Test for wchar_t/multi-byte conversion and precision in vfprintf.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <locale.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+#include <wchar.h>
+
+#define DYNARRAY_STRUCT str
+#define DYNARRAY_ELEMENT char
+#define DYNARRAY_PREFIX str_
+#include <malloc/dynarray-skeleton.c>
+
+#define DYNARRAY_STRUCT wstr
+#define DYNARRAY_ELEMENT wchar_t
+#define DYNARRAY_PREFIX wstr_
+#include <malloc/dynarray-skeleton.c>
+
+#define DYNARRAY_STRUCT len
+#define DYNARRAY_ELEMENT size_t
+#define DYNARRAY_PREFIX len_
+#include <malloc/dynarray-skeleton.c>
+
+/* This should be larger than the internal buffer in vfprintf.  The
+   constant needs to be kept in sync with the format strings in
+   test_mbs_long and test_wide_long.  */
+enum { WIDE_STRING_LENGTH = 1000 };
+
+/* Creates two large, random strings used for truncation testing.
+   After the call, *MBS will be encoded in UTF-8, and *WIDE will
+   contain the same string in the internal UCS-32 encoding.  Both
+   strings are null-terminated.  The array *LENGTH counts the number
+   of multi-byte characters for each prefix string of *WIDE: The first
+   N wide characters of *WIDE correspond the first (*LENGTH)[N] bytes
+   of *MBS.  The caller should deallocate all three arrays using
+   free.  */
+static void
+make_random_string (char **mbs, wchar_t **wide, size_t **length)
+{
+  struct str str;
+  str_init (&str);
+  struct wstr wstr;
+  wstr_init (&wstr);
+  struct len len;
+  len_init (&len);
+
+  for (int i = 0; i < WIDE_STRING_LENGTH; ++i)
+    {
+      len_add (&len, str_size (&str));
+      /* Cover some multi-byte UTF-8 sequences.  Avoid the null
+         character.  */
+      uint32_t ch = 1 + (rand () % 521);
+      wstr_add (&wstr, ch);
+
+      /* Limited UTF-8 conversion.  */
+      if (ch <= 127)
+        str_add (&str, ch);
+      else
+        {
+          /* We only implement two-byte sequences.  */
+          uint32_t first = ch >> 6;
+          TEST_VERIFY (first < 32);
+          str_add (&str, 0xC0 | first);
+          str_add (&str, 0x80 | (ch & 0x3f));
+        }
+    }
+  len_add (&len, str_size (&str));
+  wstr_add (&wstr, L'\0');
+  str_add (&str, '\0');
+
+  *mbs = str_finalize (&str, NULL);
+  TEST_VERIFY_EXIT (*mbs != NULL);
+  *wide = wstr_finalize (&wstr, NULL);
+  TEST_VERIFY_EXIT (*wide != NULL);
+  *length = len_finalize (&len, NULL);
+  TEST_VERIFY_EXIT (*length != NULL);
+}
+
+/* snprintf tests (multi-byte result).  */
+static void
+test_mbs_result (void)
+{
+  char buf[200];
+
+  /* ASCII wide string.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%ls", L"xyz") == 3);
+  TEST_VERIFY (strcmp (buf, "xyz") == 0);
+
+  /* Unicode wide string.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%.1ls", L"x\u00DFz") == 1);
+  TEST_VERIFY (strcmp (buf, "x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%.2ls", L"x\u00DFz") == 1);
+  TEST_VERIFY (strcmp (buf, "x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%.3ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 2, right-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%2.1ls", L"x\u00DFz") == 2);
+  TEST_VERIFY (strcmp (buf, " x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%2.2ls", L"x\u00DFz") == 2);
+  TEST_VERIFY (strcmp (buf, " x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%2.3ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%2.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%2.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 2, left-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-2.1ls", L"x\u00DFz") == 2);
+  TEST_VERIFY (strcmp (buf, "x ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-2.2ls", L"x\u00DFz") == 2);
+  TEST_VERIFY (strcmp (buf, "x ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-2.3ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-2.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-2.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 3, right-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%3.1ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "  x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%3.2ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "  x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%3.3ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%3.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%3.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 3, left-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-3.1ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x  ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-3.2ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x  ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-3.3ls", L"x\u00DFz") == 3);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-3.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-3.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 4, right-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%4.1ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "   x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%4.2ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "   x") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%4.3ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, " x\xC3\x9F") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%4.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%4.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+
+  /* Varying precisions with width 4, left-justified.  */
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-4.1ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x   ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-4.2ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x   ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-4.3ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9F ") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-4.4ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+  memset (buf, '@', sizeof (buf));
+  TEST_VERIFY (snprintf (buf, sizeof (buf), "%-4.5ls", L"x\u00DFz") == 4);
+  TEST_VERIFY (strcmp (buf, "x\xC3\x9Fz") == 0);
+}
+
+/* swprintf tests (wide string result).  */
+static void
+test_wide_result (void)
+{
+  enum { size = 20 };
+  wchar_t buf[20];
+
+  /* ASCII wide string.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%s", "xyz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"xyz") == 0);
+
+  /* Unicode wide string.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%.1s", "x\xC3\x9Fz") == 1);
+  TEST_VERIFY (wcscmp (buf, L"x") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%.2s", "x\xC3\x9Fz") == 2);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DF") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%.3s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%.4s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions with width 2, right-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%2.1s", "x\xC3\x9Fz") == 2);
+  TEST_VERIFY (wcscmp (buf, L" x") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%2.2s", "x\xC3\x9Fz") == 2);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DF") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%2.3s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%2.4s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions with width 2, left-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-2.1s", "x\xC3\x9Fz") == 2);
+  TEST_VERIFY (wcscmp (buf, L"x ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-2.2s", "x\xC3\x9Fz") == 2);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DF") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-2.3s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-2.4s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions with width 3, right-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%3.1s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"  x") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%3.2s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L" x\u00DF") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%3.3s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%3.4s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions with width 3, left-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-3.1s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x  ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-3.2s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DF ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-3.3s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-3.4s", "x\xC3\x9Fz") == 3);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz") == 0);
+
+  /* Varying precisions with width 4, right-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%4.1s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"   x") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%4.2s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"  x\u00DF") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%4.3s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L" x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%4.4s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L" x\u00DFz") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%4.5s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L" x\u00DFz") == 0);
+
+  /* Varying precisions with width 4, left-justified.  */
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-4.1s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"x   ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-4.2s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DF  ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-4.3s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-4.4s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz ") == 0);
+  wmemset (buf, '@', size);
+  TEST_VERIFY (swprintf (buf, size, L"%-4.5s", "x\xC3\x9Fz") == 4);
+  TEST_VERIFY (wcscmp (buf, L"x\u00DFz ") == 0);
+}
+
+/* Test with long strings and multi-byte result.  */
+static void
+test_mbs_long (const char *mbs, const wchar_t *wide, const size_t *length)
+{
+  char buf[4000];
+  _Static_assert (sizeof (buf) > 3 * WIDE_STRING_LENGTH,
+                  "buffer size consistent with string length");
+  const char *suffix = "||TERM";
+  TEST_VERIFY_EXIT (sizeof (buf)
+                    > length[WIDE_STRING_LENGTH] + strlen (suffix));
+
+  /* Test formatting of the entire string.  */
+  {
+    int ret = snprintf (buf, sizeof (buf), "%ls%s", wide, suffix);
+    TEST_VERIFY (ret == length[WIDE_STRING_LENGTH] + strlen (suffix));
+    TEST_VERIFY (memcmp (buf, mbs, length[WIDE_STRING_LENGTH]) == 0);
+    TEST_VERIFY (strcmp (buf + length[WIDE_STRING_LENGTH], suffix) == 0);
+
+    /* Left-justified string, printed in full.  */
+    ret = snprintf (buf, sizeof (buf), "%-3500ls%s", wide, suffix);
+    TEST_VERIFY (ret == 3500 + strlen (suffix));
+    TEST_VERIFY (memcmp (buf, mbs, length[WIDE_STRING_LENGTH]) == 0);
+    for (size_t i = length[WIDE_STRING_LENGTH]; i < 3500; ++i)
+      TEST_VERIFY (buf[i] == ' ');
+    TEST_VERIFY (strcmp (buf + 3500, suffix) == 0);
+
+    /* Right-justified string, printed in full.   */
+    ret = snprintf (buf, sizeof (buf), "%3500ls%s", wide, suffix);
+    TEST_VERIFY (ret == 3500 + strlen (suffix));
+    size_t padding = 3500 - length[WIDE_STRING_LENGTH];
+    for (size_t i = 0; i < padding; ++i)
+      TEST_VERIFY (buf[i] == ' ');
+    TEST_VERIFY (memcmp (buf + padding, mbs, length[WIDE_STRING_LENGTH]) == 0);
+    TEST_VERIFY (strcmp (buf + 3500, suffix) == 0);
+  }
+
+  size_t wide_characters_converted = 0;
+  for (int mbs_len = 0; mbs_len <= length[WIDE_STRING_LENGTH] + 1;
+       ++mbs_len)
+    {
+      if (wide_characters_converted < WIDE_STRING_LENGTH
+          && mbs_len >= length[wide_characters_converted + 1])
+        /* The requested prefix length contains room for another wide
+           character.  */
+        ++wide_characters_converted;
+      if (test_verbose > 0)
+        printf ("info: %s: mbs_len=%d wide_chars_converted=%zu length=%zu\n",
+                __func__, mbs_len, wide_characters_converted,
+                length[wide_characters_converted]);
+      TEST_VERIFY (length[wide_characters_converted] <= mbs_len);
+      TEST_VERIFY (wide_characters_converted == 0
+                   || length[wide_characters_converted - 1] < mbs_len);
+
+      int ret = snprintf (buf, sizeof (buf), "%.*ls%s", mbs_len, wide, suffix);
+      TEST_VERIFY (ret == length[wide_characters_converted] + strlen (suffix));
+      TEST_VERIFY (memcmp (buf, mbs, length[wide_characters_converted]) == 0);
+      TEST_VERIFY (strcmp (buf + length[wide_characters_converted],
+                           suffix) == 0);
+
+      /* Left-justified string, printed in full.  */
+      if (test_verbose)
+        printf ("info: %s: left-justified\n", __func__);
+      ret = snprintf (buf, sizeof (buf), "%-3500.*ls%s",
+                      mbs_len, wide, suffix);
+      TEST_VERIFY (ret == 3500 + strlen (suffix));
+      TEST_VERIFY (memcmp (buf, mbs, length[wide_characters_converted]) == 0);
+      for (size_t i = length[wide_characters_converted]; i < 3500; ++i)
+        TEST_VERIFY (buf[i] == ' ');
+      TEST_VERIFY (strcmp (buf + 3500, suffix) == 0);
+
+      /* Right-justified string, printed in full.   */
+      if (test_verbose)
+        printf ("info: %s: right-justified\n", __func__);
+      ret = snprintf (buf, sizeof (buf), "%3500.*ls%s", mbs_len, wide, suffix);
+      TEST_VERIFY (ret == 3500 + strlen (suffix));
+      size_t padding = 3500 - length[wide_characters_converted];
+      for (size_t i = 0; i < padding; ++i)
+        TEST_VERIFY (buf[i] == ' ');
+      TEST_VERIFY (memcmp (buf + padding, mbs,
+                           length[wide_characters_converted]) == 0);
+      TEST_VERIFY (strcmp (buf + 3500, suffix) == 0);
+    }
+}
+
+/* Test with long strings and wide string result.  */
+static void
+test_wide_long (const char *mbs, const wchar_t *wide, const size_t *length)
+{
+  wchar_t buf[2000];
+  _Static_assert (sizeof (buf) > sizeof (wchar_t) * WIDE_STRING_LENGTH,
+                  "buffer size consistent with string length");
+  const wchar_t *suffix = L"||TERM";
+  TEST_VERIFY_EXIT (sizeof (buf)
+                    > length[WIDE_STRING_LENGTH] + wcslen (suffix));
+
+  /* Test formatting of the entire string.  */
+  {
+    int ret = swprintf (buf, sizeof (buf), L"%s%ls", mbs, suffix);
+    TEST_VERIFY (ret == WIDE_STRING_LENGTH + wcslen (suffix));
+    TEST_VERIFY (wmemcmp (buf, wide, WIDE_STRING_LENGTH) == 0);
+    TEST_VERIFY (wcscmp (buf + WIDE_STRING_LENGTH, suffix) == 0);
+
+    /* Left-justified string, printed in full.  */
+    ret = swprintf (buf, sizeof (buf), L"%-1500s%ls", mbs, suffix);
+    TEST_VERIFY (ret == 1500 + wcslen (suffix));
+    TEST_VERIFY (wmemcmp (buf, wide, WIDE_STRING_LENGTH) == 0);
+    for (size_t i = WIDE_STRING_LENGTH; i < 1500; ++i)
+      TEST_VERIFY (buf[i] == L' ');
+    TEST_VERIFY (wcscmp (buf + 1500, suffix) == 0);
+
+    /* Right-justified string, printed in full.   */
+    ret = swprintf (buf, sizeof (buf), L"%1500s%ls", mbs, suffix);
+    TEST_VERIFY (ret == 1500 + wcslen (suffix));
+    size_t padding = 1500 - WIDE_STRING_LENGTH;
+    for (size_t i = 0; i < padding; ++i)
+      TEST_VERIFY (buf[i] == ' ');
+    TEST_VERIFY (wmemcmp (buf + padding, wide, WIDE_STRING_LENGTH) == 0);
+    TEST_VERIFY (wcscmp (buf + 1500, suffix) == 0);
+  }
+
+  for (int wide_len = 0; wide_len <= WIDE_STRING_LENGTH + 1; ++wide_len)
+    {
+      size_t actual_wide_len;
+      if (wide_len < WIDE_STRING_LENGTH)
+        actual_wide_len = wide_len;
+      else
+        actual_wide_len = WIDE_STRING_LENGTH;
+      if (test_verbose > 0)
+        printf ("info: %s: wide_len=%d actual_wide_len=%zu\n",
+                __func__, wide_len, actual_wide_len);
+
+      int ret = swprintf (buf, sizeof (buf), L"%.*s%ls",
+                          wide_len, mbs, suffix);
+      TEST_VERIFY (ret == actual_wide_len + wcslen (suffix));
+      TEST_VERIFY (wmemcmp (buf, wide, actual_wide_len) == 0);
+      TEST_VERIFY (wcscmp (buf + actual_wide_len, suffix) == 0);
+
+      /* Left-justified string, printed in full.  */
+      ret = swprintf (buf, sizeof (buf), L"%-1500.*s%ls",
+                      wide_len, mbs, suffix);
+      TEST_VERIFY (ret == 1500 + wcslen (suffix));
+      TEST_VERIFY (wmemcmp (buf, wide, actual_wide_len) == 0);
+      for (size_t i = actual_wide_len; i < 1500; ++i)
+        TEST_VERIFY (buf[i] == L' ');
+      TEST_VERIFY (wcscmp (buf + 1500, suffix) == 0);
+
+      /* Right-justified string, printed in full.   */
+      ret = swprintf (buf, sizeof (buf), L"%1500.*s%ls",
+                      wide_len, mbs, suffix);
+      TEST_VERIFY (ret == 1500 + wcslen (suffix));
+      size_t padding = 1500 - actual_wide_len;
+      for (size_t i = 0; i < padding; ++i)
+        TEST_VERIFY (buf[i] == L' ');
+      TEST_VERIFY (wmemcmp (buf + padding, wide, actual_wide_len) == 0);
+      TEST_VERIFY (wcscmp (buf + 1500, suffix) == 0);
+    }
+}
+
+static int
+do_test (void)
+{
+  /* This test only covers UTF-8 as a multi-byte character set.  A
+     locale with a multi-byte character set with shift state would be
+     a relevant test target as well, but glibc currently does not ship
+     such a locale.  */
+  TEST_VERIFY (setlocale (LC_CTYPE, "de_DE.UTF-8") != NULL);
+
+  test_mbs_result ();
+  test_wide_result ();
+
+  char *mbs;
+  wchar_t *wide;
+  size_t *length;
+  make_random_string (&mbs, &wide, &length);
+  TEST_VERIFY (strlen (mbs) == length[WIDE_STRING_LENGTH]);
+  if (test_verbose > 0)
+    printf ("info: long multi-byte string contains %zu characters\n",
+            length[WIDE_STRING_LENGTH]);
+  test_mbs_long (mbs, wide, length);
+  test_wide_long (mbs, wide, length);
+  free (mbs);
+  free (wide);
+  free (length);
+
+  return 0;
+}
+
+#include <support/test-driver.c>

http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=e2390be8e47352147215aff06b23927dc6ededb7

commit e2390be8e47352147215aff06b23927dc6ededb7
Author: Florian Weimer <fweimer@redhat.com>
Date:   Thu Jun 29 09:33:09 2017 +0200

    vfprintf: Add test case for user-defined types and format specifiers

diff --git a/ChangeLog b/ChangeLog
index 504f73c..5e81006 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2017-06-29  Florian Weimer  <fweimer@redhat.com>
+
+	* stdio-common/tst-vfprintf-user-type.c: New file.
+	* stdio-common/Makefile (tests): Add tst-vfprintf-user-type.
+
 2017-06-28  Adhemerval Zanella  <adhemerval.zanella@linaro.org>
 
 	* posix/tst-spawn2.c (do_test): Use libsupport.
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 01a6dd0..63c9ebd 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -58,7 +58,8 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
 	 scanf16 scanf17 tst-setvbuf1 tst-grouping bug23 bug24 \
 	 bug-vfprintf-nargs tst-long-dbl-fphex tst-fphex-wide tst-sprintf3 \
 	 bug25 tst-printf-round bug23-2 bug23-3 bug23-4 bug26 tst-fmemopen3 \
-	 tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4
+	 tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \
+	 tst-vfprintf-user-type \
 
 test-srcs = tst-unbputc tst-printf
 
diff --git a/stdio-common/tst-vfprintf-user-type.c b/stdio-common/tst-vfprintf-user-type.c
new file mode 100644
index 0000000..e9596c3
--- /dev/null
+++ b/stdio-common/tst-vfprintf-user-type.c
@@ -0,0 +1,217 @@
+/* Test for user-defined types in vfprintf.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+/* This test contains a printf format specifier, %P, with a custom
+   type which is a long/double pair.  If a precision is specified,
+   this indicates the number of such pairs which constitute the
+   argument.  */
+
+#include <locale.h>
+#include <printf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+/* Initialized by do_test using register_printf_type.  */
+static int user_type;
+
+struct two_argument
+{
+  long i;
+  double d;
+};
+
+static void
+my_va_arg_function (void *mem, va_list *ap)
+{
+  if (test_verbose > 0)
+    printf ("info: %s (%p) called\n", __func__, mem);
+
+  struct two_argument *pair = mem;
+  pair->i = va_arg (*ap, long);
+  pair->d = va_arg (*ap, double);
+}
+
+static int
+my_printf_function (FILE *fp, const struct printf_info *info,
+                    const void *const *args)
+{
+  if (test_verbose > 0)
+    printf ("info: %s (%p, %p, {%p}@%p) called for %%%c (prec %d)\n",
+            __func__, fp, info, args[0], args, info->spec, info->prec);
+
+  TEST_VERIFY (info->spec == 'P');
+  size_t nargs;
+  int printed;
+  if (info->prec >= 0)
+    {
+      if (fputc ('{', fp) < 0)
+        return -1;
+      nargs = info->prec;
+      printed = 1;
+    }
+  else
+    {
+      nargs = 1;
+      printed = 0;
+    }
+
+  for (size_t i = 0; i < nargs; ++i)
+    {
+      if (i != 0)
+        {
+          if (fputc (',', fp) < 0)
+            return -1;
+          ++printed;
+        }
+
+      /* NB: Triple pointer indirection.  ARGS is an array of void *,
+         and those pointers point to a pointer to the memory area
+         supplied to my_va_arg_function.  */
+      struct two_argument *pair = *(void **) args[i];
+      int ret = fprintf (fp, "(%ld, %f)", pair->i, pair->d);
+      if (ret < 0)
+        return -1;
+      printed += ret;
+    }
+  if (info->prec >= 0)
+    {
+      if (fputc ('}', fp) < 0)
+        return -1;
+      ++printed;
+    }
+  return printed;
+}
+
+static int
+my_arginfo_function (const struct printf_info *info,
+                     size_t n, int *argtypes, int *size)
+{
+  /* Avoid recursion.  */
+  if (info->spec != 'P')
+    return -1;
+  if (test_verbose > 0)
+    printf ("info: %s (%p, %zu, %p, %p) called for %%%c (prec %d)\n",
+            __func__, info, n, argtypes, size, info->spec, info->prec);
+
+  TEST_VERIFY_EXIT (n >= 1);
+  size_t nargs;
+  if (info->prec >= 0)
+    nargs = info->prec;
+  else
+    nargs = 1;
+
+  size_t to_fill = nargs;
+  if (to_fill > n)
+    to_fill = n;
+  for (size_t i = 0; i < to_fill; ++i)
+    {
+      argtypes[i] = user_type;
+      size[i] = sizeof (struct two_argument);
+    }
+  if (test_verbose > 0)
+    printf ("info:   %s return value: %zu\n", __func__, nargs);
+  return nargs;
+}
+
+static int
+do_test (void)
+{
+  user_type = register_printf_type (my_va_arg_function);
+  if (test_verbose > 0)
+    printf ("info: allocated user type: %d\n", user_type);
+  TEST_VERIFY_EXIT (user_type >= PA_LAST);
+  TEST_VERIFY_EXIT (register_printf_specifier
+                    ('P', my_printf_function, my_arginfo_function) >= 0);
+
+  /* Alias declaration for asprintf, to avoid the format string
+     attribute and the associated warning.  */
+  extern int asprintf_alias (char **, const char *, ...) __asm__ ("asprintf");
+  TEST_VERIFY (asprintf_alias == asprintf);
+  char *str = NULL;
+  TEST_VERIFY (asprintf_alias (&str, "[[%P]]", 123L, 456.0) >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str, "[[(123, 456.000000)]]") == 0);
+  free (str);
+
+  str = NULL;
+  TEST_VERIFY (asprintf_alias (&str, "[[%1$P %1$P]]", 123L, 457.0) >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str, "[[(123, 457.000000) (123, 457.000000)]]") == 0);
+  free (str);
+
+  str = NULL;
+  TEST_VERIFY (asprintf_alias (&str, "[[%.1P]]", 1L, 2.0) >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000)}]]") == 0);
+  free (str);
+
+  str = NULL;
+  TEST_VERIFY (asprintf_alias (&str, "[[%.2P]]", 1L, 2.0, 3L, 4.0) >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str, "[[{(1, 2.000000),(3, 4.000000)}]]") == 0);
+  free (str);
+
+  str = NULL;
+  TEST_VERIFY (asprintf_alias
+               (&str, "[[%.2P | %.3P]]",
+                /* argument 1: */ 1L, 2.0, 3L, 4.0,
+                /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
+               >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str,
+                       "[["
+                       "{(1, 2.000000),(3, 4.000000)}"
+                       " | "
+                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
+                       "]]") == 0);
+  free (str);
+
+  /* The following subtest fails due to bug 21534.  */
+#if 0
+  str = NULL;
+  TEST_VERIFY (asprintf_alias
+               (&str, "[[%1$.2P | %2$.3P | %1$.2P]]",
+                /* argument 1: */ 1L, 2.0, 3L, 4.0,
+                /* argument 2: */ 5L, 6.0, 7L, 8.0, 9L, 10.0)
+               >= 0);
+  if (test_verbose > 0)
+    printf ("info: %s\n", str);
+  TEST_VERIFY (strcmp (str,
+                       "[["
+                       "{(1, 2.000000),(3, 4.000000)}"
+                       " | "
+                       "{(5, 6.000000),(7, 8.000000),(9, 10.000000)}"
+                       " | "
+                       "{(1, 2.000000),(3, 4.000000)}"
+                       "]]") == 0);
+  free (str);
+#endif
+
+  return 0;
+}
+
+#include <support/test-driver.c>

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                             |   32 ++
 stdio-common/Makefile                 |    4 +-
 stdio-common/_i18n_number.h           |   23 +-
 stdio-common/tst-vfprintf-mbs-prec.c  |  543 +++++++++++++++++++++++++++++++++
 stdio-common/tst-vfprintf-user-type.c |  217 +++++++++++++
 stdio-common/vfprintf.c               |  137 ++++-----
 6 files changed, 871 insertions(+), 85 deletions(-)
 create mode 100644 stdio-common/tst-vfprintf-mbs-prec.c
 create mode 100644 stdio-common/tst-vfprintf-user-type.c


hooks/post-receive
-- 
GNU C Library master sources


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]