Page MenuHomeFreeBSD

D59213.id185205.diff
No OneTemporary

D59213.id185205.diff

diff --git a/include/uchar.h b/include/uchar.h
--- a/include/uchar.h
+++ b/include/uchar.h
@@ -32,7 +32,7 @@
#include <sys/cdefs.h>
#include <sys/_types.h>
-#if __ISO_C_VISIBLE >= 2023 && !defined(_CHAR8_T_DECLARED)
+#ifndef _CHAR8_T_DECLARED
typedef unsigned char char8_t;
#define _CHAR8_T_DECLARED
#endif
@@ -58,6 +58,16 @@
#endif
__BEGIN_DECLS
+/*
+ * TODO: libc++ sets _LIBCPP_HAS_C8RTOMB_MBRTOC8 only for glibc at the time,
+ * so std::mbrtoc8 / std::c8rtomb are not provided by <cuchar> yet and needs
+ * us to submit a PR to libc++ to add them.
+ */
+#if !defined(__cplusplus) || defined(__cpp_char8_t)
+size_t c8rtomb(char * __restrict, char8_t, mbstate_t * __restrict);
+size_t mbrtoc8(char8_t * __restrict, const char * __restrict, size_t,
+ mbstate_t * __restrict);
+#endif
size_t c16rtomb(char * __restrict, char16_t, mbstate_t * __restrict);
size_t c32rtomb(char * __restrict, char32_t, mbstate_t * __restrict);
size_t mbrtoc16(char16_t * __restrict, const char * __restrict, size_t,
diff --git a/include/xlocale/_uchar.h b/include/xlocale/_uchar.h
--- a/include/xlocale/_uchar.h
+++ b/include/xlocale/_uchar.h
@@ -34,6 +34,12 @@
#ifndef _XLOCALE_UCHAR_H_
#define _XLOCALE_UCHAR_H_
+#if !defined(__cplusplus) || defined(__cpp_char8_t)
+size_t c8rtomb_l(char * __restrict, char8_t, mbstate_t * __restrict,
+ locale_t);
+size_t mbrtoc8_l(char8_t * __restrict, const char * __restrict, size_t,
+ mbstate_t * __restrict, locale_t);
+#endif
size_t c16rtomb_l(char * __restrict, char16_t, mbstate_t * __restrict,
locale_t);
size_t c32rtomb_l(char * __restrict, char32_t, mbstate_t * __restrict,
diff --git a/lib/libc/locale/Makefile.inc b/lib/libc/locale/Makefile.inc
--- a/lib/libc/locale/Makefile.inc
+++ b/lib/libc/locale/Makefile.inc
@@ -25,6 +25,7 @@
.else
SRCS+= c16rtomb.c c32rtomb.c mbrtoc16.c mbrtoc32.c
.endif
+SRCS+= c8rtomb.c mbrtoc8.c
SYM_MAPS+=${LIBC_SRCTOP}/locale/Symbol.map
@@ -90,9 +91,9 @@
MLINKS+=isxdigit.3 ishexnumber.3 isxdigit.3 isxdigit_l.3 \
isxdigit.3 ishexnumber_l.3
MLINKS+=localeconv.3 localeconv_l.3
-MLINKS+=mbrtowc.3 mbrtoc16.3 mbrtowc.3 mbrtoc32.3
+MLINKS+=mbrtowc.3 mbrtoc8.3 mbrtowc.3 mbrtoc16.3 mbrtowc.3 mbrtoc32.3
MLINKS+=mbsrtowcs.3 mbsnrtowcs.3
-MLINKS+=wcrtomb.3 c16rtomb.3 wcrtomb.3 c32rtomb.3
+MLINKS+=wcrtomb.3 c8rtomb.3 wcrtomb.3 c16rtomb.3 wcrtomb.3 c32rtomb.3
MLINKS+=wcsrtombs.3 wcsnrtombs.3
MLINKS+=wcstod.3 wcstof.3 wcstod.3 wcstold.3
MLINKS+=wcstol.3 wcstoul.3 wcstol.3 wcstoll.3 wcstol.3 wcstoull.3 \
diff --git a/lib/libc/locale/Symbol.map b/lib/libc/locale/Symbol.map
--- a/lib/libc/locale/Symbol.map
+++ b/lib/libc/locale/Symbol.map
@@ -205,6 +205,13 @@
mbrtoc32_l;
};
+FBSD_1.9 {
+ c8rtomb;
+ c8rtomb_l;
+ mbrtoc8;
+ mbrtoc8_l;
+};
+
FBSDprivate_1.0 {
_PathLocale;
__detect_path_locale;
diff --git a/lib/libc/locale/c8rtomb.c b/lib/libc/locale/c8rtomb.c
new file mode 100644
--- /dev/null
+++ b/lib/libc/locale/c8rtomb.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <uchar.h>
+#include "mblocal.h"
+
+/*
+ * UTF-8 accumulation lives in the last bytes of mbstate_t so that the
+ * c32rtomb conversion state, stored at the front, remains a complete
+ * mbstate_t (C23 §7.30.1p2: char8_t sequences are UTF-8).
+ */
+typedef struct {
+ unsigned char want; /* expected length; 0 if idle */
+ unsigned char seen;
+ unsigned char buf[4];
+} _C8rtombExtra;
+
+_Static_assert(sizeof(_C8rtombExtra) < sizeof(mbstate_t),
+ "UTF-8 extra state must fit in mbstate_t");
+_Static_assert(sizeof(mbstate_t) - sizeof(_C8rtombExtra) >= 64,
+ "locale conversion state lives at the front of mbstate_t");
+
+static _C8rtombExtra *
+c8rtomb_extra(mbstate_t *ps)
+{
+ return ((_C8rtombExtra *)((char *)ps + sizeof(*ps) -
+ sizeof(_C8rtombExtra)));
+}
+
+static int
+utf8_invalid_second(unsigned char lead, unsigned char c8)
+{
+ if (c8 < 0x80 || c8 > 0xbf)
+ return (1);
+ if (lead == 0xe0 && c8 < 0xa0)
+ return (1); /* overlong */
+ if (lead == 0xed && c8 > 0x9f)
+ return (1); /* surrogate */
+ if (lead == 0xf0 && c8 < 0x90)
+ return (1); /* overlong */
+ if (lead == 0xf4 && c8 > 0x8f)
+ return (1); /* > U+10FFFF */
+ return (0);
+}
+
+static char32_t
+utf8_to_c32(const unsigned char *b, unsigned char n)
+{
+ char32_t c32;
+
+ switch (n) {
+ case 2:
+ c32 = ((b[0] & 0x1f) << 6) | (b[1] & 0x3f);
+ break;
+ case 3:
+ c32 = ((b[0] & 0x0f) << 12) | ((b[1] & 0x3f) << 6) |
+ (b[2] & 0x3f);
+ break;
+ default:
+ c32 = ((char32_t)(b[0] & 0x07) << 18) |
+ ((char32_t)(b[1] & 0x3f) << 12) |
+ ((b[2] & 0x3f) << 6) | (b[3] & 0x3f);
+ break;
+ }
+ return (c32);
+}
+
+size_t
+c8rtomb_l(char * __restrict s, char8_t c8, mbstate_t * __restrict ps,
+ locale_t locale)
+{
+ _C8rtombExtra extra, *ep;
+ char32_t c32;
+ char nbuf[MB_LEN_MAX];
+ unsigned char uc8;
+
+ FIX_LOCALE(locale);
+ if (ps == NULL)
+ ps = &(XLOCALE_CTYPE(locale)->c8rtomb);
+ ep = c8rtomb_extra(ps);
+ extra = *ep;
+ uc8 = c8;
+
+ /*
+ * C23 §7.30.1.2p2: if s is null, the function is equivalent to the
+ * call c8rtomb(buf, u8'\0', ps) with the current conversion state.
+ * Incomplete UTF-8 sequence followed by U+0000 is an encoding error.
+ */
+ if (s == NULL) {
+ uc8 = 0;
+ s = nbuf;
+ }
+
+ if (extra.want == 0) {
+ if (uc8 <= 0x7f) {
+ c32 = uc8;
+ goto convert;
+ }
+ if ((uc8 >= 0x80 && uc8 <= 0xc1) || uc8 >= 0xf5) {
+ extra.want = 0;
+ extra.seen = 0;
+ *ep = extra;
+ errno = EILSEQ;
+ return ((size_t)-1);
+ }
+ extra.buf[0] = uc8;
+ extra.seen = 1;
+ if (uc8 <= 0xdf)
+ extra.want = 2;
+ else if (uc8 <= 0xef)
+ extra.want = 3;
+ else
+ extra.want = 4;
+ *ep = extra;
+ return (0);
+ }
+
+ if (extra.seen == 1) {
+ if (utf8_invalid_second(extra.buf[0], uc8)) {
+ extra.want = 0;
+ extra.seen = 0;
+ *ep = extra;
+ errno = EILSEQ;
+ return ((size_t)-1);
+ }
+ } else if (uc8 < 0x80 || uc8 > 0xbf) {
+ extra.want = 0;
+ extra.seen = 0;
+ *ep = extra;
+ errno = EILSEQ;
+ return ((size_t)-1);
+ }
+
+ extra.buf[extra.seen++] = uc8;
+ if (extra.seen < extra.want) {
+ *ep = extra;
+ return (0);
+ }
+
+ c32 = utf8_to_c32(extra.buf, extra.want);
+ extra.want = 0;
+ extra.seen = 0;
+
+convert:
+ *ep = extra;
+ return (c32rtomb_l(s, c32, ps, locale));
+}
+
+size_t
+c8rtomb(char * __restrict s, char8_t c8, mbstate_t * __restrict ps)
+{
+ return (c8rtomb_l(s, c8, ps, __get_locale()));
+}
diff --git a/lib/libc/locale/mblocal.h b/lib/libc/locale/mblocal.h
--- a/lib/libc/locale/mblocal.h
+++ b/lib/libc/locale/mblocal.h
@@ -62,6 +62,8 @@
__mbstate_t mblen;
/** Persistent state used by mbrlen() calls. */
__mbstate_t mbrlen;
+ /** Persistent state used by mbrtoc8() calls. */
+ __mbstate_t mbrtoc8;
/** Persistent state used by mbrtoc16() calls. */
__mbstate_t mbrtoc16;
/** Persistent state used by mbrtoc32() calls. */
@@ -74,6 +76,8 @@
__mbstate_t mbsrtowcs;
/** Persistent state used by mbtowc() calls. */
__mbstate_t mbtowc;
+ /** Persistent state used by c8rtomb() calls. */
+ __mbstate_t c8rtomb;
/** Persistent state used by c16rtomb() calls. */
__mbstate_t c16rtomb;
/** Persistent state used by c32rtomb() calls. */
diff --git a/lib/libc/locale/mbrtoc8.c b/lib/libc/locale/mbrtoc8.c
new file mode 100644
--- /dev/null
+++ b/lib/libc/locale/mbrtoc8.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <uchar.h>
+#include "mblocal.h"
+
+/*
+ * UTF-8 restart state lives in the last bytes of mbstate_t so that the
+ * mbrtoc32 conversion state, stored at the front, remains a complete
+ * mbstate_t (C23 §7.30.1p2: char8_t sequences are UTF-8).
+ */
+typedef struct {
+ unsigned char nleft;
+ unsigned char next;
+ unsigned char incomp; /* mbrtoc32 returned (size_t)-2 */
+ char8_t buf[3];
+} _Mbrtoc8Extra;
+
+_Static_assert(sizeof(_Mbrtoc8Extra) < sizeof(mbstate_t),
+ "UTF-8 extra state must fit in mbstate_t");
+_Static_assert(sizeof(mbstate_t) - sizeof(_Mbrtoc8Extra) >= 64,
+ "locale conversion state lives at the front of mbstate_t");
+
+static _Mbrtoc8Extra *
+mbrtoc8_extra(mbstate_t *ps)
+{
+ return ((_Mbrtoc8Extra *)((char *)ps + sizeof(*ps) -
+ sizeof(_Mbrtoc8Extra)));
+}
+
+static int
+utf8_from_c32(char32_t c32, char8_t out[4])
+{
+ if (c32 <= 0x7f) {
+ out[0] = (char8_t)c32;
+ return (1);
+ }
+ if (c32 <= 0x7ff) {
+ out[0] = (char8_t)(0xc0 | (c32 >> 6));
+ out[1] = (char8_t)(0x80 | (c32 & 0x3f));
+ return (2);
+ }
+ if (c32 <= 0xffff) {
+ out[0] = (char8_t)(0xe0 | (c32 >> 12));
+ out[1] = (char8_t)(0x80 | ((c32 >> 6) & 0x3f));
+ out[2] = (char8_t)(0x80 | (c32 & 0x3f));
+ return (3);
+ }
+ out[0] = (char8_t)(0xf0 | (c32 >> 18));
+ out[1] = (char8_t)(0x80 | ((c32 >> 12) & 0x3f));
+ out[2] = (char8_t)(0x80 | ((c32 >> 6) & 0x3f));
+ out[3] = (char8_t)(0x80 | (c32 & 0x3f));
+ return (4);
+}
+
+size_t
+mbrtoc8_l(char8_t * __restrict pc8, const char * __restrict s, size_t n,
+ mbstate_t * __restrict ps, locale_t locale)
+{
+ _Mbrtoc8Extra extra, *ep;
+ char32_t c32;
+ char8_t utf8[4];
+ size_t len;
+ int nu;
+
+ FIX_LOCALE(locale);
+ if (ps == NULL)
+ ps = &(XLOCALE_CTYPE(locale)->mbrtoc8);
+ ep = mbrtoc8_extra(ps);
+ extra = *ep;
+
+ /*
+ * C23 §7.30.1.1p2: if s is null, the function is equivalent to the
+ * call mbrtoc8(nullptr, "", 1, ps). pc8 and n are ignored.
+ */
+ if (s == NULL) {
+ pc8 = NULL;
+ s = "";
+ n = 1;
+ }
+
+ /* Emit the next UTF-8 code unit from a previous conversion. */
+ if (extra.nleft > 0) {
+ if (pc8 != NULL)
+ *pc8 = extra.buf[extra.next];
+ extra.next++;
+ extra.nleft--;
+ if (extra.nleft == 0)
+ extra.next = 0;
+ *ep = extra;
+ return ((size_t)-3);
+ }
+
+ /*
+ * mbrtoc32 with MK_ICONV uses HIDE_INVALID, so an incomplete
+ * sequence plus U+0000 would be swallowed. C23 §7.30.1.1 treats
+ * that as an encoding error (null s is mbrtoc8(nullptr, "", 1, ps)).
+ */
+ if (extra.incomp && n > 0 && (unsigned char)*s == '\0') {
+ extra.incomp = 0;
+ *ep = extra;
+ errno = EILSEQ;
+ return ((size_t)-1);
+ }
+
+ len = mbrtoc32_l(&c32, s, n, ps, locale);
+ extra.nleft = 0;
+ extra.next = 0;
+ if (len == (size_t)-2) {
+ extra.incomp = 1;
+ *ep = extra;
+ return (len);
+ }
+ extra.incomp = 0;
+ if (len == (size_t)-1) {
+ *ep = extra;
+ return (len);
+ }
+ /*
+ * Successful conversion or (size_t)-3 both yield a scalar in
+ * c32. Encode it as UTF-8 (C23 §7.30.1p2); surrogates and values
+ * above U+10FFFF are not UTF-8.
+ */
+ if ((c32 >= 0xd800 && c32 <= 0xdfff) || c32 > 0x10ffff) {
+ *ep = extra;
+ errno = EILSEQ;
+ return ((size_t)-1);
+ }
+ nu = utf8_from_c32(c32, utf8);
+ if (pc8 != NULL)
+ *pc8 = utf8[0];
+ if (nu > 1) {
+ memcpy(extra.buf, utf8 + 1, nu - 1);
+ extra.nleft = nu - 1;
+ extra.next = 0;
+ }
+ *ep = extra;
+ return (len);
+}
+
+size_t
+mbrtoc8(char8_t * __restrict pc8, const char * __restrict s, size_t n,
+ mbstate_t * __restrict ps)
+{
+ return (mbrtoc8_l(pc8, s, n, ps, __get_locale()));
+}
diff --git a/lib/libc/locale/setrunelocale.c b/lib/libc/locale/setrunelocale.c
--- a/lib/libc/locale/setrunelocale.c
+++ b/lib/libc/locale/setrunelocale.c
@@ -156,10 +156,12 @@
/* Free the old runes if it exists. */
free_runes(saved.runes);
/* Reset the mbstates */
+ memset(&l->c8rtomb, 0, sizeof(l->c8rtomb));
memset(&l->c16rtomb, 0, sizeof(l->c16rtomb));
memset(&l->c32rtomb, 0, sizeof(l->c32rtomb));
memset(&l->mblen, 0, sizeof(l->mblen));
memset(&l->mbrlen, 0, sizeof(l->mbrlen));
+ memset(&l->mbrtoc8, 0, sizeof(l->mbrtoc8));
memset(&l->mbrtoc16, 0, sizeof(l->mbrtoc16));
memset(&l->mbrtoc32, 0, sizeof(l->mbrtoc32));
memset(&l->mbrtowc, 0, sizeof(l->mbrtowc));
diff --git a/lib/libc/tests/locale/Makefile b/lib/libc/tests/locale/Makefile
--- a/lib/libc/tests/locale/Makefile
+++ b/lib/libc/tests/locale/Makefile
@@ -1,10 +1,12 @@
.include <bsd.own.mk>
ATF_TESTS_C+= btowc_test
+ATF_TESTS_C+= c8rtomb_test
ATF_TESTS_C+= c16rtomb_test
ATF_TESTS_C+= iswctype_test
ATF_TESTS_C+= mblen_test
ATF_TESTS_C+= mbrlen_test
+ATF_TESTS_C+= mbrtoc8_test
ATF_TESTS_C+= mbrtoc16_test
ATF_TESTS_C+= mbrtowc_2_test
ATF_TESTS_C+= mbsnrtowcs_2_test
diff --git a/lib/libc/tests/locale/c8rtomb_test.c b/lib/libc/tests/locale/c8rtomb_test.c
new file mode 100644
--- /dev/null
+++ b/lib/libc/tests/locale/c8rtomb_test.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Test program for c8rtomb() as specified by ISO/IEC 9899:2024, §7.30.1.2.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE(strcmp(lc_ctype_set, locale_name) == 0);
+}
+
+static mbstate_t s;
+static char buf[MB_LEN_MAX + 1];
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_c_locale_test);
+ATF_TC_BODY(c8rtomb_c_locale_test, tc)
+{
+ require_lc_ctype("C");
+
+ /*
+ * If s is null, c8 is ignored and the call is equivalent to
+ * c8rtomb(buf, u8'\0', ps) with the current conversion state
+ * (C23 §7.30.1.2p2). Idle state therefore stores a null byte.
+ */
+ ATF_REQUIRE(c8rtomb(NULL, 0, NULL) == 1);
+ ATF_REQUIRE(c8rtomb(NULL, 0x80, NULL) == 1);
+
+ /* Null character. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0, &s) == 1);
+ ATF_REQUIRE((unsigned char)buf[0] == 0 &&
+ (unsigned char)buf[1] == 0xcc);
+
+ /* Latin letter A, internal state. */
+ ATF_REQUIRE(c8rtomb(NULL, 0, NULL) == 1);
+ ATF_REQUIRE(c8rtomb(NULL, 'A', NULL) == 1);
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 'A', &s) == 1);
+ ATF_REQUIRE((unsigned char)buf[0] == 'A' &&
+ (unsigned char)buf[1] == 0xcc);
+
+ /* Isolated UTF-8 continuation byte. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0x80, &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xcc);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_iso_8859_1_test);
+ATF_TC_BODY(c8rtomb_iso_8859_1_test, tc)
+{
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Euro sign cannot be represented. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xe2, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0x82, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0xac, &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xcc);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_iso_8859_15_test);
+ATF_TC_BODY(c8rtomb_iso_8859_15_test, tc)
+{
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Euro sign U+20AC. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xe2, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0x82, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0xac, &s) == 1);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xa4 &&
+ (unsigned char)buf[1] == 0xcc);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_utf_8_test);
+ATF_TC_BODY(c8rtomb_utf_8_test, tc)
+{
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Two-byte character U+00E9. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xc3, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0xa9, &s) == 2);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xc3 &&
+ (unsigned char)buf[1] == 0xa9 &&
+ (unsigned char)buf[2] == 0xcc);
+
+ /* Four-byte character U+1F4A9. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xf0, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0x9f, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0x92, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0xa9, &s) == 4);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xf0 &&
+ (unsigned char)buf[1] == 0x9f &&
+ (unsigned char)buf[2] == 0x92 &&
+ (unsigned char)buf[3] == 0xa9 &&
+ (unsigned char)buf[4] == 0xcc);
+
+ /* Incomplete sequence followed by ASCII. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xc3, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 'A', &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xcc);
+
+ /* Overlong encoding of NUL (C0 80). */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xc0, &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xcc);
+
+ /* Isolated trail surrogate code unit as UTF-8 (ED A0 80 is U+D800). */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xed, &s) == 0);
+ ATF_REQUIRE(c8rtomb(buf, 0xa0, &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+ ATF_REQUIRE((unsigned char)buf[0] == 0xcc);
+
+ /*
+ * If s is null, the function is equivalent to the call
+ * c8rtomb(buf, u8'\0', ps) with the current conversion state,
+ * so an incomplete sequence is an encoding error.
+ */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_REQUIRE(c8rtomb(buf, 0xc3, &s) == 0);
+ ATF_REQUIRE(c8rtomb(NULL, 'A', &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, c8rtomb_c_locale_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/locale/mbrtoc8_test.c b/lib/libc/tests/locale/mbrtoc8_test.c
new file mode 100644
--- /dev/null
+++ b/lib/libc/tests/locale/mbrtoc8_test.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Test program for mbrtoc8() as specified by ISO/IEC 9899:2024, §7.30.1.1.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE(strcmp(lc_ctype_set, locale_name) == 0);
+}
+
+static mbstate_t s;
+static char8_t c8;
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_c_locale_test);
+ATF_TC_BODY(mbrtoc8_c_locale_test, tc)
+{
+ require_lc_ctype("C");
+
+ /* Null character, internal state. */
+ ATF_REQUIRE(mbrtoc8(&c8, "", 1, NULL) == 0);
+ ATF_REQUIRE(c8 == 0);
+
+ /* Null character. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "", 1, &s) == 0);
+ ATF_REQUIRE(c8 == 0);
+
+ /* Latin letter A, internal state. */
+ ATF_REQUIRE(mbrtoc8(NULL, "", 1, NULL) == 0);
+ ATF_REQUIRE(mbrtoc8(&c8, "A", 1, NULL) == 1);
+ ATF_REQUIRE(c8 == 'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "A", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 'A');
+
+ /* Incomplete character sequence. */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-2);
+ ATF_REQUIRE(c8 == 'z');
+
+ /* Check that mbrtoc8() doesn't read ahead aggressively. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "AB", 2, &s) == 1);
+ ATF_REQUIRE(c8 == 'A');
+ ATF_REQUIRE(mbrtoc8(&c8, "C", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 'C');
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_iso_8859_1_test);
+ATF_TC_BODY(mbrtoc8_iso_8859_1_test, tc)
+{
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Currency sign U+00A4 -> UTF-8 C2 A4. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "\xa4", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 0xc2);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xa4);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_iso_8859_15_test);
+ATF_TC_BODY(mbrtoc8_iso_8859_15_test, tc)
+{
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Euro sign U+20AC -> UTF-8 E2 82 AC. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "\xa4", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 0xe2);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x82);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xac);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_utf_8_test);
+ATF_TC_BODY(mbrtoc8_utf_8_test, tc)
+{
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Null character, internal state. */
+ ATF_REQUIRE(mbrtoc8(NULL, "", 1, NULL) == 0);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 1, NULL) == 0);
+ ATF_REQUIRE(c8 == 0);
+
+ /* Null character. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "", 1, &s) == 0);
+ ATF_REQUIRE(c8 == 0);
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "A", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 'A');
+
+ /* Incomplete character sequence (zero length). */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-2);
+ ATF_REQUIRE(c8 == 'z');
+
+ /* Incomplete character sequence (truncated double-byte). */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3", 1, &s) == (size_t)-2);
+
+ /*
+ * Null s is equivalent to mbrtoc8(NULL, "", 1, ps), so a NUL
+ * is fed into the current state rather than discarding it.
+ */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3", 1, &s) == (size_t)-2);
+ errno = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, NULL, 1, &s) == (size_t)-1);
+ ATF_REQUIRE(errno == EILSEQ);
+
+ /* Same as above, but complete: U+00C4 -> C3 84. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3\x84", 2, &s) == 2);
+ ATF_REQUIRE(c8 == 0xc3);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x84);
+
+ /* Test restarting behaviour. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3", 1, &s) == (size_t)-2);
+ ATF_REQUIRE(c8 == 0);
+ ATF_REQUIRE(mbrtoc8(&c8, "\xb7", 1, &s) == 1);
+ ATF_REQUIRE(c8 == 0xc3);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xb7);
+
+ /* Four-byte sequence: U+1F4A9. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xf0\x9f\x92\xa9", 4, &s) == 4);
+ ATF_REQUIRE(c8 == 0xf0);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x9f);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x92);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xa9);
+
+ /* Letter e with acute, precomposed. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3\xa9", 2, &s) == 2);
+ ATF_REQUIRE(c8 == 0xc3);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xa9);
+
+ /* Letter e with acute, combined. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_REQUIRE(mbrtoc8(&c8, "\x65\xcc\x81", 3, &s) == 1);
+ ATF_REQUIRE(c8 == 0x65);
+ ATF_REQUIRE(mbrtoc8(&c8, "\xcc\x81", 2, &s) == 2);
+ ATF_REQUIRE(c8 == 0xcc);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x81);
+
+ /* pc8 == NULL still records remaining UTF-8 units. */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(NULL, "\xc3\x84", 2, &s) == 2);
+ ATF_REQUIRE(mbrtoc8(&c8, "", 0, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0x84);
+
+ /*
+ * Null s is equivalent to mbrtoc8(NULL, "", 1, ps): pending
+ * units are returned as (size_t)-3 and pc8 is ignored.
+ */
+ memset(&s, 0, sizeof(s));
+ ATF_REQUIRE(mbrtoc8(&c8, "\xc3\x84", 2, &s) == 2);
+ c8 = 0xff;
+ ATF_REQUIRE(mbrtoc8(&c8, NULL, 99, &s) == (size_t)-3);
+ ATF_REQUIRE(c8 == 0xff);
+ ATF_REQUIRE(mbrtoc8(&c8, NULL, 99, &s) == 0);
+ ATF_REQUIRE(c8 == 0xff);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, mbrtoc8_c_locale_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/sys/sys/_types.h b/sys/sys/_types.h
--- a/sys/sys/_types.h
+++ b/sys/sys/_types.h
@@ -209,9 +209,8 @@
#define _CHAR16_T_DECLARED
#define _CHAR32_T_DECLARED
#endif
-/* and so is char8_t in C++20 */
-#if defined(__cplusplus) && __cplusplus >= 202002L
-#define _CHAR8_T_DECLARED
+#if defined(__cpp_char8_t)
+#define _CHAR8_T_DECLARED
#endif
typedef struct {

File Metadata

Mime Type
text/plain
Expires
Thu, Sep 3, 4:25 PM (9 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38132667
Default Alt Text
D59213.id185205.diff (23 KB)

Event Timeline