Phase 8 #7 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md ("PDF/UA structure tree"). Design and merge criteria in REVIEWS/adr/0011-pdf-structure-tree.md. Investigating the accessibility gap surfaced four defects in the marked-content operators the structure tree depends on. The first is not an accessibility problem at all. 1. BMC never parsed, and corrupted the colour of everything after it. The dispatcher matched b'B'+b'M' only when the third byte was a delimiter; BMC's third byte is 'C', so the arm never fired. Because the operator was never recognised it never CLEARED ITS OPERAND, and the leftover /Tag shifted the operands of whatever came next: 1 0 0 rg -> RgbFill(1.0, 0.0, 0.0) red /Span BMC 1 0 0 rg -> RgbFill(0.0, 1.0, 0.0) green Tagged documents are precisely the ones containing BMC, so the documents that tried hardest to be accessible rendered wrong colours. This is the fifth instance of the operator-shadowing family already fixed for cm, rg, gs, b/b* and end-of-stream text operators. ADR 0009's every_multi_char_operator_parses_as_itself test exists to stop exactly this - and would have caught it, except BMC was one of two operators excluded from its table as "genuinely unimplemented". Excluding a known-broken operator from the test whose job is finding broken operators is how it survived. The exclusion list is gone. 2. BDC discarded its property list, which carries /MCID - the only link between a run of page content and the structure element describing it. Without it a tree can be parsed but never attached to anything. 3. The op produced by BDC was named MarkContentBmc, and BMC produced nothing. The names were the wrong way round, which is how the missing arm survived review: the enum looked like it had a BMC case. 4. Found while fixing 2: the content lexer had no dictionary support at all. It read `<` as a hex string without checking for a second `<`, so `<</MCID 0>>` parsed as the string "0C0D0". Content streams now parse direct objects properly, bounded at 16 levels; a single `<` is still a hex string and a test pins that. On top of that, new pdf-document/src/structure.rs: /StructTreeRoot, /StructElem trees, /RoleMap resolution, depth-first reading order, /Alt, /ActualText, /E, /Lang, and MCID-to-element lookup. Cycles in /K are cut at the first repeat and reported by object number rather than expanded to the depth bound. Accessibility findings are mechanical checks reported as findings, NOT a conformance verdict: there is no is_pdf_ua and no ComplianceReport, and a mutation-checked tripwire test fails if either appears. Real PDF/UA conformance needs human judgement - whether /Alt text is accurate is not mechanically decidable - so claiming it from six checks would be exactly the overclaim this codebase keeps removing. 7 corpus fixtures, 14 acceptance tests, 22 unit tests, and a parse_content_dict fuzz target for the new object parser. TEST_TARGET=pdf 536 -> 575, TEST_TARGET=pdf-ui 580 -> 619. rustfmt and clippy -D warnings clean.
93 lines
1.6 KiB
TOML
93 lines
1.6 KiB
TOML
[package]
|
|
name = "nigig-pdf-fuzz"
|
|
version = "0.0.0"
|
|
publish = false
|
|
edition = "2021"
|
|
|
|
# Not a workspace member: cargo-fuzz builds this standalone with a nightly
|
|
# toolchain, and it must not drag libfuzzer into the normal build graph.
|
|
[workspace]
|
|
|
|
[package.metadata]
|
|
cargo-fuzz = true
|
|
|
|
[dependencies]
|
|
libfuzzer-sys = "0.4"
|
|
nigig-pdf-cos = { path = ".." }
|
|
nigig-pdf-document = { path = "../../pdf-document" }
|
|
nigig-pdf-graphics = { path = "../../pdf-graphics" }
|
|
|
|
[profile.release]
|
|
debug = 1
|
|
|
|
[[bin]]
|
|
name = "parse_object"
|
|
path = "fuzz_targets/parse_object.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_xref"
|
|
path = "fuzz_targets/parse_xref.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_content_stream"
|
|
path = "fuzz_targets/parse_content_stream.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_document"
|
|
path = "fuzz_targets/parse_document.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "decode_stream"
|
|
path = "fuzz_targets/decode_stream.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_revision_chain"
|
|
path = "fuzz_targets/parse_revision_chain.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "decrypt"
|
|
path = "fuzz_targets/decrypt.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "eval_function"
|
|
path = "fuzz_targets/eval_function.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_colorspace"
|
|
path = "fuzz_targets/parse_colorspace.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_ext_gstate"
|
|
path = "fuzz_targets/parse_ext_gstate.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_signature"
|
|
path = "fuzz_targets/parse_signature.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_content_dict"
|
|
path = "fuzz_targets/parse_content_dict.rs"
|
|
test = false
|
|
doc = false
|