Find already-present U+FFFD markers without mistaking valid UTF-8 decoding for evidence of original data quality. Work only with supplied input; this does not contact external services or modify input.
Before you start
- Save as recipe.rs and compile with Rust 1.99.0 using: rustc --edition=2021 recipe.rs -o recipe
- Run ./recipe with input supplied through stdin. No command-line arguments are accepted, including help flags.
- Reads at most 65537 bytes and rejects input exceeding 65536 bytes. Requires strict UTF-8, rejects a leading UTF-8 BOM, and rejects every CR character, including CRLF.
- Splits only at LF and discards exactly empty segments. Retains whitespace-only segments without trimming or normalization. Before classification, validates at most 100 retained lines and at most 256 UTF-8 bytes per retained line.
- Successful output is exactly one JSON object followed by a newline, with the form {"positions":[1,3]}. Positions are ascending, one-based indices among retained nonempty lines. Each matching line appears once, regardless of its number of U+FFFD characters. Empty input or no matching lines produces {"positions":[]}.
- All input validation finishes before successful stdout is written. Success exits 0 without stderr. Invalid arguments, invalid input, or stdin-read failures exit 2 with no stdout and exactly error: invalid input followed by a newline on stderr, subject to the failure limitations below.
Build and check
- Save the example as recipe.rs.
- Compile with Rust 1.99.0 using rustc --edition=2021 recipe.rs -o recipe.
- Prepare UTF-8 input.txt following the documented operation-specific conventions.
- Run ./recipe < input.txt.
- Inspect the JSON result. Exit 2 with exactly error: invalid input followed by a newline on stderr and no stdout identifies rejected input or stdin-read failure. Output-write failures may also exit nonzero, without that diagnostic and potentially after partial stdout; these are outside the input-error guarantee.
Limits to understand
- Memory use is bounded independently of input length: a 65537-byte stack buffer, a small output string and temporary index strings, plus standard-library I/O buffers and runtime overhead. Lines are borrowed from the input buffer rather than copied.
- Stdin may block while waiting for data or EOF. Input of exactly 65536 bytes requires a subsequent read to establish EOF; observing a 65537th byte immediately establishes oversize input.
- Only already-present U+FFFD markers are located. Valid UTF-8 decoding neither establishes original data quality nor reveals whether a marker was intentional or introduced earlier. Malformed UTF-8 is rejected, never decoded with replacement.
- Successful output reveals matching retained-line positions and may itself be sensitive. Error diagnostics do not disclose input values.
- Memory-allocation failures and output-write failures, including stderr failures, are outside the input-error guarantee. A stdout-write failure attempts exit status 2 and may leave partial output.
- A replacement marker may have been intentionally supplied. This does not recover or diagnose the original encoding.
Run it
rustc --edition=2021 recipe.rs -o recipe
./recipe < input.txtExample input
ok
�
x�y
Expected output
{"positions":[2,3]}
