Repository navigation
perf(xml): one lexical pass before the parser - #381
Conversation
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 20 minutes. View limit details
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e9d114662
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
2e9d114 to
4fdb94c
Compare
…e it A document without a DOCTYPE parsed with xmloxide no longer takes the lexical limit pass before the parser. The parser counts semantic nodes, element nesting and in-scope namespace bindings as it reads, with the pass's semantics (the root is a node, a run of text, references and CDATA is one node, whitespace outside the document element is none; an element applies its declarations, then counts, then nests), and refuses at the first limit it passes before allocating the node. The refusal keeps the document error and the peak namespace bindings the pass reported. The absolute nesting ceilings are checked after the policy limits, so a policy depth equal to them still reports its own refusal. Parse work no longer charges a pass that does not run. Documents with a DOCTYPE, which need entity expansion bounded first, and the roxmltree backend keep the limit pass. Tests: every limit swept from zero over documents covering every counted construct must refuse where the pass refused, from the parser itself, and accept with the same peak; precedence when one element passes several limits; exact and one-over boundaries through the policy entry point. SAML 16384 verify: user CPU 24.0 -> 21.8 ms, minimum 28.3 -> 26.1 ms. Part of #347
A document the parser does not read positions from by itself (one with a DOCTYPE, or any document for the roxmltree backend) took two lexical passes before the parser: one enforcing the policy limits, one recording source positions and DTD attribute types. The strict scan now takes an observer that checks the limits on each event before recording it, so one pass serves both, on the native and the accounted entry points and on the public DOM parse. - The limit pass becomes LimitPass: its counts go through the same LimitCounter the parser uses, and it expands internal entities in content and attribute values as before. It also runs by itself over a whole document for the XSLT input parser, which reads no positions. - The DOCTYPE is visited once: entities, attribute defaults and the attribute types the positions need come from the same walk. - Whether a document requires its DTD comes from that one parse instead of parsing it again with DTDs disallowed. - Every parse now charges its allowance, the pass and the parser, of twice the source; the separate pass and the provenance parse are gone. Tests: for DOCTYPE documents (entities expanding markup and namespace declarations, attribute defaults, references in attribute values) and for roxmltree, every limit swept from zero must refuse where the separate pass refuses, from the scan itself, and accept with the same peak bindings; DTD provenance from the one parse; exact parse-work allowances. Part of #347
A start tag's omitted attributes take their first declared default. The scan found a default an earlier one or the tag already supplied by comparing against every preceding default, which is quadratic in the defaults of one element and charged nothing to the work allowance. It now looks names up in an index the storage meters, as the separate limit pass does. Regression test: the_scan_charges_the_entity_work_the_limit_pass_charges compares the entity expansion work of the scan and of the separate pass, over a DTD that declares one attribute's default twice. Part of #347
The module gate names xmlenc as well as xmldsig, which reads as if an xmlenc-only build could compile it without the verification policy. xmlenc enables xmldsig through xmldsig-sign, so no such build exists. Part of #347
The maintained xmloxide source gained the parse that enforces limits as it reads, but the recorded delta against xmloxide 0.5.0 still described the previous source, so the kernel verification failed. The delta is regenerated from the maintained files. Part of #347
7427fb4 to
af60543
Compare
Summary
verifyuser CPU: 24.1 -> 21.8 ms.Changes
LimitCounterholds the limit semantics (the root is a node, a run of character data is one node, whitespace outside the document element is none; an element applies its declarations, then counts, then nests). The parser, the scan and the standalone pass all count through it.ScanObserver.LimitPassobserves it, expands internal entities in content and attribute values, and applies DTD attribute defaults. The DOCTYPE is walked once, for entities, defaults and the attribute types the positions need.Testing
Tests, lints, formatting and the per-feature profiles pass on macOS; CI runs the full platform and feature matrix. New tests sweep every limit from zero, for the parser path, for DOCTYPE documents and for roxmltree. Each case must refuse where the standalone pass refuses, from the parser or scan itself, or accept with the same peak bindings. Policy entry points are checked at exact and one-over boundaries.
Closes #347
Summary by CodeRabbit