Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F169821564
D59213.id185205.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
D59213.id185205.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D59213: libc: Implement mbrtoc8() and c8rtomb() as per C23
Attached
Detach File
Event Timeline
Log In to Comment