Skip to content

test(segment): forge the kind and count of an iterator with no overrides - #269

Merged
siracozmen01 merged 1 commit into
mainfrom
test/fuzz-iterator-kind
Nov 4, 2026
Merged

siracozmen01 merged 1 commit into
mainfrom
test/fuzz-iterator-kind

Conversation

@siracozmen01

Copy link
Copy Markdown
Contributor

Problem

test/fuzz_iterators.c never changed a segment iterator's kind, and changed its class override count only where nothing reads it. So three refusals of the checks every call makes on a segment iterator were never reached by the fuzzer, and no test suite reaches them either, since the suites go through the public headers:

  • a kind that is not one of the four,
  • a word or sentence iterator whose override count is above MAX_OVERRIDES,
  • a word or sentence iterator with an override count but no overrides.

A coverage run of the fuzzer at c6c66ec, from its seeds and then for 300 seconds (7.2M runs), took every other refusal of both iterators' checks; these three directions stayed at 0.

Change

  • The fuzzer keeps the kind and the override count only where the iterator has overrides: there the count goes with a real table, and the kind says which bytes hold it, so forging either would read past the table, which is outside the contract. Without overrides the pointer is null, and the input now sets the kind and both counts like any other byte.
  • Five seeds reach the three refusals, so a run from the committed seeds covers them too: an out-of-range kind, a word iterator's count above the limit, a word and a sentence iterator with a count but no overrides, and a line iterator forged into a word iterator with a count.
  • docs/internals/segmentation.md says which bytes the fuzzer leaves alone.

No library code changes.

Verification

  • Source coverage of fuzz_iterators (clang-20, -fprofile-instr-generate), from the committed seeds alone and after 300 seconds from them (5.7M runs, nothing found): each of the three refusals is now taken from the seeds alone and in the run. The only directions of the checks still at 0 need a forged pointer: a count beside a real table, and a null text with a length.
  • tools/check.sh changed: rules, format and documentation pass; no test suite is reached by the change.

Closes #268

@siracozmen01 siracozmen01 changed the title test(segment): forge the kind and override count of an iterator without overrides test(segment): forge the kind and count of an iterator with no overrides Nov 4, 2026

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of 8460c00: ready. Read, not run. Checks were pending when I looked.

  1. No seed overwrites an existing one. All five are new files in the diff (new file mode, from /dev/null). At the head, test/seeds/fuzz_iterators holds them beside the seven that were there (grapheme-more, line-breaker, line-run-start, script, script-overwritten, sentence, word-overrides), with no name shared.

  2. The forged states are inside the contract.

    • The only iterator the fuzzer gives overrides is a word iterator with choice & 16 (fuzz_iterators.c:127-130). A sentence iterator, or a word iterator without that bit, has a null overrides, and that pointer is always in kept.
    • With a null table, a forged kind is a plain byte the check holds to the four kinds. A forged count, in either rules slot, is a count with no table. The library is meant to refuse both (muni_errorInvalid), and neither involves a pointer.
    • Where there is a table, both the kind and rules.word.overrideCount are kept. Forging either would pair a real table with a count the caller never gave, which the docs put outside the contract with the pointer.
    • Dropping the old sentence branch, which kept rules.sentence.overrideCount, is right, since the sentence iterator never has a table here.
  3. The docs match the code. segmentation.md now says the fuzzer leaves alone the pointers, the lengths and counts that go with them, and the kind of an iterator with overrides, "which says where their count is". That is kept with entries 7 and 8 set only in the choice % 4 == 1 && (choice & 16) case. The file's header comment says the same.

Not verified: what the five seeds hold. The seed fetch here does not return binary files intact, so I rely on the body's coverage run, which reached each refusal from the seeds alone.

The title is test: and the body has Problem, Change and Verification. No line starts with a name followed by (.

@siracozmen01
siracozmen01 merged commit 4ed2034 into main Nov 4, 2026
25 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The iterator fuzzer never forges a segment iterator's kind or override count

1 participant