blob: 161819c77be936dbf39b6dd22e2b9f39b064ff7b [file]
// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Fuzz target for NFKD (Unicode Normalization Form KD) normalizer.
//
// Tests the NFKD normalizer's robustness against:
// - Invalid UTF-8 sequences (overlong, truncated, surrogate pairs)
// - Combining character sequences (mark ordering, reordering)
// - Long combining sequences (exceeding 32-codepoint stream-safe limit)
// - Hangul syllable decomposition
// - Compatibility decompositions (ligatures, fractions, fullwidth chars)
// - Long decompositions (U+FDFA decomposes to 18 codepoints)
// - Edge cases in canonical decomposition
//
// The fuzzer exercises the normalizer using the streaming process/finalize
// pattern with various chunk sizes to stress state management.
//
// See https://iree.dev/developers/debugging/fuzzing/ for build and run info.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "iree/base/api.h"
#include "iree/tokenizer/normalizer.h"
#include "iree/tokenizer/normalizer/nfkd.h"
// Process fuzz input through NFKD normalizer with streaming chunks.
static void process_with_chunk_size(iree_tokenizer_normalizer_t* normalizer,
const uint8_t* data, size_t size,
size_t chunk_size) {
iree_host_size_t state_size =
iree_tokenizer_normalizer_state_size(normalizer);
if (state_size == 0 || state_size > 64 * 1024) {
return; // Sanity check.
}
void* state_buffer = malloc(state_size);
if (!state_buffer) return;
iree_tokenizer_normalizer_state_t* state = NULL;
iree_status_t status = iree_tokenizer_normalizer_state_initialize(
normalizer, state_buffer, &state);
if (!iree_status_is_ok(status)) {
iree_status_ignore(status);
free(state_buffer);
return;
}
// Output buffer sized relative to input.
// NFKD can expand significantly (U+FDFA -> 18 codepoints).
size_t output_capacity = size * 8 + 128;
if (output_capacity > 128 * 1024) {
output_capacity = 128 * 1024; // Cap to prevent OOM.
}
char* output = (char*)malloc(output_capacity);
if (!output) {
iree_tokenizer_normalizer_state_deinitialize(state);
free(state_buffer);
return;
}
// Process in chunks to stress state management.
size_t offset = 0;
size_t total_written = 0;
size_t stall_count = 0;
while (offset < size) {
size_t remaining = size - offset;
size_t this_chunk = remaining < chunk_size ? remaining : chunk_size;
iree_string_view_t input_chunk = iree_make_string_view(
reinterpret_cast<const char*>(data + offset), this_chunk);
// Advance output pointer to accumulate normalized text.
if (total_written >= output_capacity) break;
iree_mutable_string_view_t output_view = iree_make_mutable_string_view(
output + total_written, output_capacity - total_written);
iree_host_size_t consumed = 0;
iree_host_size_t written = 0;
status = iree_tokenizer_normalizer_state_process(
state, input_chunk, output_view, IREE_TOKENIZER_NORMALIZER_FLAG_NONE,
&consumed, &written);
if (iree_status_is_resource_exhausted(status)) {
// Combining sequence too long or output full - expected behavior.
iree_status_ignore(status);
break;
} else if (!iree_status_is_ok(status)) {
iree_status_ignore(status);
break;
}
total_written += written;
if (consumed == 0) {
if (written == 0) {
break; // No progress.
}
if (++stall_count > 16) {
break; // Abort stalled processing.
}
} else {
stall_count = 0;
}
offset += consumed;
}
// Finalize to flush any buffered combining sequence.
if (total_written < output_capacity) {
iree_host_size_t final_written = 0;
status = iree_tokenizer_normalizer_state_finalize(
state,
iree_make_mutable_string_view(output + total_written,
output_capacity - total_written),
&final_written);
iree_status_ignore(status);
}
iree_tokenizer_normalizer_state_deinitialize(state);
free(output);
free(state_buffer);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Cap input size to prevent excessive memory/time usage.
if (size > 16 * 1024) {
size = 16 * 1024;
}
// Allocate NFKD normalizer.
iree_tokenizer_normalizer_t* normalizer = NULL;
iree_status_t status = iree_tokenizer_normalizer_nfkd_allocate(
iree_allocator_system(), &normalizer);
if (!iree_status_is_ok(status)) {
iree_status_ignore(status);
return 0;
}
// Test with different chunk sizes to stress streaming state.
// Small chunks: stress combining sequence buffering and long decompositions.
// Large chunks: stress bulk processing.
process_with_chunk_size(normalizer, data, size, 1); // Byte at a time.
process_with_chunk_size(normalizer, data, size,
7); // Prime size, misaligned.
process_with_chunk_size(normalizer, data, size, 64); // Small buffer.
process_with_chunk_size(normalizer, data, size, size); // All at once.
iree_tokenizer_normalizer_free(normalizer);
return 0;
}