Skip to content

✨ feat(fuzz): stop XPath and XSLT at an op limit - #1219

Merged
gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:feat/xml-operation-limit-1016
Oct 8, 2026
Merged

gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:feat/xml-operation-limit-1016

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

The #1016 differential fuzzing of XPath and XSLT needs a bound on work that a slow input cannot dodge: a Python timeout fires only after the native call returns. Fuzz builds now compile operation counters into XPath evaluation, XSLT apply and stylesheet compilation, and an input that exceeds the limit raises ValueError. Closes #1016. ⏱️

The counters exist only when the build defines TH_OPERATION_LIMIT, which the fuzz, smoke, oracle and round-trip builds do with a limit of 100000, the XPath limit libxslt's own fuzzer sets. XPath charges each evaluated node and each node a step yields, as libxml2's xmlXPathCheckOpLimit does, and predicate sub-contexts share the counter; XSLT charges each instantiated instruction, as xsltApplySequenceConstructor does in libxslt, and compilation charges each stylesheet element.

Production builds compile none of it: callgrind counts 2,441,131 instructions for transform and 570,300 for xpath-compare-unequal on both main and this branch. The amplify job keeps its unlimited build because it measures time growth.

@gaborbernat gaborbernat added the enhancement New feature or request label Oct 7, 2026
@codspeed

codspeed Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 579 untouched benchmarks
⏩ 32 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ test_feature[select-relative-sibling] 42.7 µs 45.3 µs -5.74%
⚡ test_feature[shadow-slot-comments] 137.9 µs 83.6 µs +64.81%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing gaborbernat:feat/xml-operation-limit-1016 (dc40c2f) with main (2ffa2f0)

Open in CodSpeed

Footnotes

  1. 32 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@gaborbernat
gaborbernat force-pushed the feat/xml-operation-limit-1016 branch 3 times, most recently from a490024 to aaa3c6a Compare October 8, 2026 01:34
A super-linear XPath expression or XSLT stylesheet runs until the fuzz
harness times out, and the timeout hides every other finding in that run.
libxslt bounds its own fuzzers the same way: it charges each parsed and
each applied instruction (xslt.c, transform.c) and libxml2 charges each
XPath operation and yielded node (xpath.c xmlXPathCheckOpLimit).

The fuzz environments now compile with -DTH_OPERATION_LIMIT=100000, the
XPath limit libxslt's fuzzer sets (tests/fuzz/fuzz.c). XPath evaluation,
stylesheet compilation and stylesheet application each count steps and
raise ValueError past the limit. Release builds leave the macro undefined;
their preprocessed sources differ from upstream only by trailing commas in
struct initializers, so the CodSpeed gate measures the same code.

fuzz-smoke runs tests/fuzz_build against the limited build; the coverage
matrix skips that directory because its build compiles no counter.

Refs tox-dev#1016
@gaborbernat
gaborbernat force-pushed the feat/xml-operation-limit-1016 branch from aaa3c6a to dc40c2f Compare October 8, 2026 02:37
@gaborbernat
gaborbernat merged commit 60a5960 into tox-dev:main Oct 8, 2026
57 of 58 checks passed
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Oct 8, 2026
Once the live removal paths run the NodeIterator pre-removing steps, no
public call leaves an iterator pointer outside its root, so no test showed
that _tree_verify's iterator check reports one. Deleting that check would
pass the whole suite.

The -Dfuzzing=true build gains _fuzz_escape_iterators, which points the
registered iterators at the document, in the style of tox-dev#1217's _fuzz_crash,
and a test in tests/fuzz_build, which fuzz-smoke runs since tox-dev#1219, requires
the verifier to count the escaped pointer. With the reference check deleted
from th_tree_verify, fuzz-smoke fails.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Differential-fuzz XPath, XSLT, XSD and RELAX NG against libxml2 and libxslt

1 participant