aboutsummaryrefslogtreecommitdiffstats
path: root/libutf8_decode_mem.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-10-23 21:09:14 +0200
committerMattias Andrée <m@maandree.se>2026-10-23 21:09:14 +0200
commit9d247dc831569a4db75ec9466d1a988a82dfba29 (patch)
treebaf2b21897788f630cb2001beb4f9e5bed9940a7 /libutf8_decode_mem.c
downloadlibutf8-1.0.tar.gz
libutf8-1.0.tar.bz2
libutf8-1.0.tar.xz
First commitHEAD1.0master
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--libutf8_decode_mem.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/libutf8_decode_mem.c b/libutf8_decode_mem.c
new file mode 100644
index 0000000..5d3b9ea
--- /dev/null
+++ b/libutf8_decode_mem.c
@@ -0,0 +1,279 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+size_t
+libutf8_decode_mem(const char *text, size_t text_len, uint_least32_t *codepoint_out, unsigned flags)
+{
+ if (!text_len)
+ return 0u;
+
+ DECODE(if (n > text_len) return n;);
+}
+
+
+#else
+# ifndef FUZZ
+
+
+# define NONE (uint_least32_t)~(uint_least32_t)0
+
+
+static int
+long_encode(char *out_buffer, uint_least32_t codepoint, size_t n)
+{
+ if (codepoint >> 31) {
+ if (n != 6u)
+ return 0;
+ } else {
+ if (libutf8_encode_measure(codepoint) > n || n > 6u)
+ return 0;
+ }
+ ENCODE(n);
+ return 1;
+}
+
+
+static void
+check(const char *buf, uint_least32_t codepoint, size_t decode_avail, size_t encode_len, unsigned flags, int overlong)
+{
+ uint_least32_t expect_codepoint, replacement, res;
+ size_t decode_len;
+ int extraultimate;
+
+ extraultimate = (codepoint > (uint_least32_t)LIBUTF8_ULTIMATE_CODEPOINT && !(codepoint >> 31));
+
+ if (decode_avail >= encode_len) {
+ expect_codepoint = codepoint;
+ replacement = (uint_least32_t)LIBUTF8_REPLACEMENT_CHARACTER_CODEPOINT;
+ } else {
+ expect_codepoint = NONE;
+ replacement = expect_codepoint;
+ }
+
+ res = NONE;
+ decode_len = libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_RETURN_INVALID);
+ if (overlong &&
+ !(flags & LIBUTF8_ALLOW_LONG_ENCODING) &&
+ !(encode_len == 2u && !codepoint && (flags & LIBUTF8_ALLOW_2BYTE_NULL)) &&
+ !decode_len)
+ goto expect_zero;
+ if (extraultimate && !decode_len)
+ goto expect_zero;
+ if (codepoint >> 31)
+ goto expect_zero;
+ assert(decode_len == encode_len);
+ assert(res == expect_codepoint);
+
+ assert(libutf8_decode_mem(buf, decode_avail, NULL, flags | LIBUTF8_RETURN_INVALID) == encode_len);
+
+ if (extraultimate && (flags & LIBUTF8_ALLOW_31_BITS))
+ goto normal;
+
+ if (libutf8_is_invalid(codepoint)) {
+ decode_len = libutf8_decode_mem(buf, decode_avail, &res, 0u);
+ if ((decode_len && decode_avail < encode_len) || decode_avail < 2u)
+ goto normal;
+
+ res = NONE;
+ assert(decode_len == 0u);
+ assert(res == NONE);
+
+ res = NONE;
+ assert(libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID) == encode_len);
+ assert(res == replacement);
+
+ res = NONE;
+ decode_len = libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID
+ | LIBUTF8_RETURN_INVALID);
+ assert(!decode_len || decode_len == encode_len);
+ if (decode_len)
+ assert(res == replacement);
+ else
+ assert(res == NONE);
+ } else {
+ normal:
+ res = NONE;
+ assert(libutf8_decode_mem(buf, decode_avail, &res, flags) == encode_len);
+ assert(res == expect_codepoint);
+
+ res = NONE;
+ assert(libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID) == encode_len);
+ assert(res == expect_codepoint);
+
+ res = NONE;
+ assert(libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID
+ | LIBUTF8_RETURN_INVALID) == encode_len);
+ assert(res == expect_codepoint);
+ }
+
+ return;
+
+expect_zero:
+ assert(!libutf8_decode_mem(buf, decode_avail, &res, flags));
+ assert(!libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID));
+ assert(!libutf8_decode_mem(buf, decode_avail, &res, flags | LIBUTF8_REPLACE_INVALID
+ | LIBUTF8_RETURN_INVALID));
+}
+
+
+static void
+check_codepoint(uint_least32_t codepoint)
+{
+ static const unsigned extra_flags[] = {
+ 0u,
+ LIBUTF8_ALLOW_31_BITS,
+ LIBUTF8_ALLOW_2BYTE_NULL,
+ LIBUTF8_ALLOW_LONG_ENCODING,
+ LIBUTF8_ALLOW_31_BITS | LIBUTF8_ALLOW_2BYTE_NULL,
+ LIBUTF8_ALLOW_31_BITS | LIBUTF8_ALLOW_LONG_ENCODING,
+ LIBUTF8_ALLOW_2BYTE_NULL | LIBUTF8_ALLOW_LONG_ENCODING,
+ LIBUTF8_ALLOW_31_BITS | LIBUTF8_ALLOW_2BYTE_NULL | LIBUTF8_ALLOW_LONG_ENCODING
+ };
+
+ static size_t corruption = 0u;
+
+ size_t flags_i, decode_avail, encode_len;
+ char buf[128];
+ int overlong;
+ unsigned flags;
+ size_t q, r;
+
+ for (flags_i = 0u; flags_i < sizeof(extra_flags) / sizeof(*extra_flags); flags_i++) {
+ flags = extra_flags[flags_i];
+
+ overlong = 0;
+ for (encode_len = 1u; encode_len <= 6u; encode_len++) {
+ if (!long_encode(buf, codepoint, encode_len))
+ continue;
+
+ for (decode_avail = 1u; decode_avail < encode_len + 4u; decode_avail++)
+ check(buf, codepoint, decode_avail, encode_len, flags, overlong);
+
+ overlong = 1;
+ }
+ }
+
+ if (codepoint >> 7) {
+ encode_len = libutf8_encode_measure(codepoint);
+ encode_len = encode_len ? encode_len : 6u;
+ long_encode(buf, codepoint, encode_len);
+ q = corruption / 3u;
+ r = corruption % 3u;
+ q %= encode_len - 1u;
+ buf[q + 1u] = (char)((unsigned char)buf[q + 1u] ^ ((r ^ 3u) << 6));
+ corruption += 1u;
+ assert(libutf8_decode_mem(buf, sizeof(buf), &(uint_least32_t){0u}, ~0u) == 0u);
+ }
+}
+
+
+int
+main(void)
+{
+ static const char *rejected[] = {
+ "\x80",
+ "\x80\x80",
+ "\x80\x80\x80",
+ "\x80\x80\x80\x80",
+ "\x80\x80\x80\x80\x80",
+ "\x80\x80\x80\x80\x80\x80",
+ "\x80\x80\x80\x80\x80\x80\x80",
+ "\x80\x80\x80\x80\x80\x80\x80\x80",
+ "\xFE",
+ "\xFE\x80",
+ "\xFE\x80\x80",
+ "\xFE\x80\x80\x80",
+ "\xFE\x80\x80\x80\x80",
+ "\xFE\x80\x80\x80\x80\x80",
+ "\xFE\x80\x80\x80\x80\x80\x80",
+ "\xFE\x80\x80\x80\x80\x80\x80\x80",
+ "\xFF",
+ "\xFF\x80",
+ "\xFF\x80\x80",
+ "\xFF\x80\x80\x80",
+ "\xFF\x80\x80\x80\x80",
+ "\xFF\x80\x80\x80\x80\x80",
+ "\xFF\x80\x80\x80\x80\x80\x80",
+ "\xFF\x80\x80\x80\x80\x80\x80\x80",
+ NULL
+ };
+
+ uint_least32_t cp, res;
+ char buf[128];
+ size_t i;
+
+ SET_UP_TEST();
+
+ assert(libutf8_decode_mem(buf, 0u, &cp, LIBUTF8_RETURN_INVALID | LIBUTF8_ALLOW_31_BITS) == 0u);
+
+ for (cp = 0u; cp <= (uint_least32_t)LIBUTF8_ULTIMATE_CODEPOINT;) {
+ check_codepoint(cp);
+ if (!(++cp & 0x7FFFu))
+ ARM_ALARM();
+ }
+
+ check_codepoint((uint_least32_t)0x110000ul);
+ check_codepoint((uint_least32_t)(1ul << 22));
+ check_codepoint((uint_least32_t)(1ul << 23));
+ check_codepoint((uint_least32_t)(1ul << 24));
+ check_codepoint((uint_least32_t)(1ul << 25));
+ check_codepoint((uint_least32_t)(1ul << 26));
+ check_codepoint((uint_least32_t)(1ul << 27));
+ check_codepoint((uint_least32_t)(1ul << 28));
+ check_codepoint((uint_least32_t)(1ul << 29));
+ check_codepoint((uint_least32_t)(1ul << 30));
+
+ check_codepoint((uint_least32_t)0x11FFFFul);
+ check_codepoint((uint_least32_t)((1ul << 22) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 23) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 24) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 25) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 26) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 27) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 28) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 29) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 30) - 1ul));
+ check_codepoint((uint_least32_t)((1ul << 31) - 1ul));
+
+ check_codepoint((uint_least32_t)(1ul << 31));
+ check_codepoint((uint_least32_t)0xFFFFFFFFul);
+
+ for (i = 0u; rejected[i]; i++) {
+ res = NONE;
+ assert(libutf8_decode_mem(rejected[i], strlen(rejected[i]), &res, ~0u) == 0u);
+ assert(res == NONE);
+ assert(libutf8_decode_mem(rejected[i], strlen(rejected[i]), NULL, ~0u) == 0u);
+ }
+
+ return 0;
+}
+
+
+# else
+
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char encoded[6];
+ uint_least32_t cp;
+ size_t n;
+
+ n = libutf8_decode_mem((const char *)data, size, &cp,
+ LIBUTF8_RETURN_INVALID | LIBUTF8_ALLOW_31_BITS | LIBUTF8_ALLOW_LONG_ENCODING);
+
+ if (n && n <= size) {
+ if (libutf8_decode_mem((const char *)data, size, &cp, LIBUTF8_RETURN_INVALID | LIBUTF8_ALLOW_31_BITS) == n) {
+ assert(libutf8_encode(encoded, cp) == n);
+ assert(!memcmp(encoded, data, n));
+ }
+ }
+
+ return 0;
+}
+
+
+# endif
+#endif