| // 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 postprocessor template processing. | |
| // | |
| // Tests the postprocessor against adversarial template configurations: | |
| // - Varied prefix/infix/suffix counts (0-7 each, clamped to MAX_PIECES) | |
| // - Adversarial token IDs in templates | |
| // - Type ID assignment to model tokens | |
| // - State machine transitions (PREFIX -> SEQUENCE_A -> SUFFIX -> DONE) | |
| // - Both single and pair templates | |
| // - All flag combinations (TRIM_OFFSETS, ADD_PREFIX_SPACE) | |
| // | |
| // See https://iree.dev/developers/debugging/fuzzing/ for build and run info. | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include "iree/base/api.h" | |
| #include "iree/tokenizer/postprocessor.h" | |
| #include "iree/tokenizer/types.h" | |
| // Parse a template from fuzz data. Returns bytes consumed. | |
| static size_t parse_template(const uint8_t* data, size_t size, | |
| iree_tokenizer_postprocessor_template_t* out) { | |
| memset(out, 0, sizeof(*out)); | |
| if (size < 5) return 0; | |
| // Byte 0: prefix_count (0-7, clamped to max). | |
| out->prefix_count = data[0] % (IREE_TOKENIZER_POSTPROCESSOR_MAX_PIECES + 1); | |
| // Byte 1: suffix_count (0 to remaining capacity). | |
| uint8_t remaining = | |
| IREE_TOKENIZER_POSTPROCESSOR_MAX_PIECES - out->prefix_count; | |
| out->suffix_count = (remaining > 0) ? (data[1] % (remaining + 1)) : 0; | |
| // Byte 2: infix_count (0 to remaining capacity). | |
| remaining = IREE_TOKENIZER_POSTPROCESSOR_MAX_PIECES - out->prefix_count - | |
| out->suffix_count; | |
| out->infix_count = (remaining > 0) ? (data[2] % (remaining + 1)) : 0; | |
| // Byte 3: sequence type IDs. | |
| out->sequence_a_type_id = data[3] & 0x0F; | |
| out->sequence_b_type_id = (data[3] >> 4) & 0x0F; | |
| size_t pos = 4; | |
| uint8_t total = out->prefix_count + out->infix_count + out->suffix_count; | |
| // Fill token IDs from fuzz data (4 bytes each). | |
| for (uint8_t i = 0; i < total && pos + 4 <= size; ++i) { | |
| out->token_ids[i] = | |
| (int32_t)((uint32_t)data[pos] | ((uint32_t)data[pos + 1] << 8) | | |
| ((uint32_t)data[pos + 2] << 16) | | |
| ((uint32_t)data[pos + 3] << 24)); | |
| pos += 4; | |
| } | |
| // Fill type IDs (1 byte each). | |
| for (uint8_t i = 0; i < total && pos < size; ++i) { | |
| out->type_ids[i] = data[pos++]; | |
| } | |
| return pos; | |
| } | |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { | |
| if (size < 12) return 0; | |
| // Byte 0: flags. | |
| iree_tokenizer_postprocessor_flags_t flags = data[0] & 0x03; | |
| // Byte 1: whether to test pair template. | |
| bool test_pair = (data[1] & 0x01) != 0; | |
| data += 2; | |
| size -= 2; | |
| //===--------------------------------------------------------------------===// | |
| // Parse single template from fuzz data | |
| //===--------------------------------------------------------------------===// | |
| iree_tokenizer_postprocessor_template_t single_template; | |
| size_t consumed = parse_template(data, size, &single_template); | |
| if (consumed == 0) return 0; | |
| data += consumed; | |
| size -= consumed; | |
| //===--------------------------------------------------------------------===// | |
| // Optionally parse pair template | |
| //===--------------------------------------------------------------------===// | |
| iree_tokenizer_postprocessor_template_t pair_template; | |
| memset(&pair_template, 0, sizeof(pair_template)); | |
| if (test_pair && size >= 5) { | |
| consumed = parse_template(data, size, &pair_template); | |
| data += consumed; | |
| size -= consumed; | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Initialize postprocessor | |
| //===--------------------------------------------------------------------===// | |
| iree_tokenizer_postprocessor_t postprocessor; | |
| iree_status_t status = iree_tokenizer_postprocessor_initialize( | |
| &single_template, test_pair ? &pair_template : NULL, flags, | |
| &postprocessor); | |
| if (!iree_status_is_ok(status)) { | |
| iree_status_ignore(status); | |
| return 0; | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Test 1: Single-sequence encode state machine | |
| //===--------------------------------------------------------------------===// | |
| { | |
| iree_tokenizer_postprocessor_encode_state_t state; | |
| iree_tokenizer_postprocessor_encode_state_initialize( | |
| &postprocessor, &postprocessor.single, &state); | |
| // Output buffers. | |
| iree_tokenizer_token_id_t token_ids[64]; | |
| uint8_t type_ids[64]; | |
| iree_tokenizer_token_output_t output = | |
| iree_tokenizer_make_token_output(token_ids, NULL, type_ids, 64); | |
| // Emit prefix. | |
| iree_host_size_t prefix_written = | |
| iree_tokenizer_postprocessor_emit_prefix(&state, output, 0); | |
| // Simulate model tokens by assigning type_ids. | |
| iree_host_size_t model_start = prefix_written; | |
| iree_host_size_t model_count = 8; | |
| if (model_start + model_count > 64) { | |
| model_count = 64 - model_start; | |
| } | |
| // Fill fake model token IDs. | |
| for (iree_host_size_t i = 0; i < model_count; ++i) { | |
| token_ids[model_start + i] = (int32_t)(i + 100); | |
| } | |
| iree_tokenizer_postprocessor_assign_type_ids(&state, output, model_start, | |
| model_count); | |
| // Transition to suffix. | |
| iree_tokenizer_postprocessor_begin_suffix(&state); | |
| // Emit suffix. | |
| iree_host_size_t suffix_written = iree_tokenizer_postprocessor_emit_suffix( | |
| &state, output, model_start + model_count); | |
| // Verify state is DONE or IDLE. | |
| (void)iree_tokenizer_postprocessor_encode_state_has_pending(&state); | |
| (void)prefix_written; | |
| (void)suffix_written; | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Test 2: Pair-sequence encode (if pair template exists) | |
| //===--------------------------------------------------------------------===// | |
| if (iree_tokenizer_postprocessor_supports_pair(&postprocessor)) { | |
| iree_tokenizer_postprocessor_encode_state_t state; | |
| iree_tokenizer_postprocessor_encode_state_initialize( | |
| &postprocessor, &postprocessor.pair, &state); | |
| iree_tokenizer_token_id_t token_ids[64]; | |
| uint8_t type_ids[64]; | |
| iree_tokenizer_token_output_t output = | |
| iree_tokenizer_make_token_output(token_ids, NULL, type_ids, 64); | |
| iree_host_size_t offset = 0; | |
| offset += iree_tokenizer_postprocessor_emit_prefix(&state, output, offset); | |
| // Simulate sequence A model tokens. | |
| iree_host_size_t seq_a_count = 4; | |
| if (offset + seq_a_count > 64) seq_a_count = 64 - offset; | |
| for (iree_host_size_t i = 0; i < seq_a_count; ++i) { | |
| token_ids[offset + i] = (int32_t)(i + 200); | |
| } | |
| iree_tokenizer_postprocessor_assign_type_ids(&state, output, offset, | |
| seq_a_count); | |
| offset += seq_a_count; | |
| iree_tokenizer_postprocessor_begin_infix(&state); | |
| offset += iree_tokenizer_postprocessor_emit_infix(&state, output, offset); | |
| // Simulate sequence B model tokens. | |
| iree_host_size_t seq_b_count = 4; | |
| if (offset + seq_b_count > 64) seq_b_count = 64 - offset; | |
| for (iree_host_size_t i = 0; i < seq_b_count; ++i) { | |
| token_ids[offset + i] = (int32_t)(i + 300); | |
| } | |
| iree_tokenizer_postprocessor_assign_type_ids(&state, output, offset, | |
| seq_b_count); | |
| offset += seq_b_count; | |
| iree_tokenizer_postprocessor_begin_suffix(&state); | |
| offset += iree_tokenizer_postprocessor_emit_suffix(&state, output, offset); | |
| (void)iree_tokenizer_postprocessor_encode_state_has_pending(&state); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Test 3: Repeated initialize/emit cycles (stress state reset) | |
| //===--------------------------------------------------------------------===// | |
| for (int iter = 0; iter < 4; ++iter) { | |
| iree_tokenizer_postprocessor_encode_state_t state; | |
| iree_tokenizer_postprocessor_encode_state_initialize( | |
| &postprocessor, &postprocessor.single, &state); | |
| iree_tokenizer_token_id_t token_ids[16]; | |
| uint8_t type_ids[16]; | |
| iree_tokenizer_token_output_t output = | |
| iree_tokenizer_make_token_output(token_ids, NULL, type_ids, 16); | |
| iree_host_size_t offset = 0; | |
| offset += iree_tokenizer_postprocessor_emit_prefix(&state, output, offset); | |
| iree_tokenizer_postprocessor_begin_suffix(&state); | |
| offset += iree_tokenizer_postprocessor_emit_suffix(&state, output, offset); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Test 4: Zero-capacity output (boundary stress) | |
| //===--------------------------------------------------------------------===// | |
| { | |
| iree_tokenizer_postprocessor_encode_state_t state; | |
| iree_tokenizer_postprocessor_encode_state_initialize( | |
| &postprocessor, &postprocessor.single, &state); | |
| iree_tokenizer_token_output_t empty_output = | |
| iree_tokenizer_make_token_output(NULL, NULL, NULL, 0); | |
| iree_tokenizer_postprocessor_emit_prefix(&state, empty_output, 0); | |
| iree_tokenizer_postprocessor_begin_suffix(&state); | |
| iree_tokenizer_postprocessor_emit_suffix(&state, empty_output, 0); | |
| } | |
| iree_tokenizer_postprocessor_deinitialize(&postprocessor); | |
| return 0; | |
| } |