symfony/css-selector · error · SyntaxErrorException

Got too deeply nested :has().

Error message

Got too deeply nested :has().

What it means

SyntaxErrorException thrown by Parser::parseRelativeSelector() when :has() selectors are nested deeper than HAS_NESTING_LIMIT (16). The parser tracks hasNestingDepth and aborts to prevent unbounded recursion / DoS on pathological inputs like 'a:has(b:has(c:has(...)))'.

Solutions

  1. Flatten the selector: reduce :has() nesting to 16 levels or fewer by restructuring the query.
  2. Add a pre-parse depth check counting ':has(' occurrences in the input string and reject inputs >16 before calling parse().
  3. Catch SyntaxErrorException and report 'selector too complex' to the user instead of crashing.
  4. If the deep nesting is generated by your code, add an explicit depth counter in the generator.

Example fix

// before
$parser->parse(str_repeat('div:has(', 20) . 'p' . str_repeat(')', 20));

// after
if (substr_count($selector, ':has(') > 16) {
    throw new \InvalidArgumentException('Selector exceeds :has() nesting limit');
}
$parser->parse($selector);
Defensive patterns

Strategy: validation

Validate before calling

if (substr_count($selector, ':has(') > 16) {
    throw new \InvalidArgumentException('Selector exceeds the :has() nesting limit of 16');
}

Try / catch

try {
    $nodes = (new Parser())->parse($selector);
} catch (SyntaxErrorException $e) {
    if (str_contains($e->getMessage(), 'nested')) { /* too complex */ }
}

Prevention

When it happens

Trigger: Parsing a selector with more than 16 levels of nested :has(), e.g. building ':has(' repeated >16 times such as 'div:has(> div:has(> div:has(...)))'. Triggered whenever parseRelativeSelector is entered while hasNestingDepth >= 16.

Common situations: Programmatic/generative selector construction (template loops appending :has()), malicious or fuzzed user input to an HTML-to-XPath converter, recursive data-driven selector builders without depth limiting.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/4c84262df3972d58. Report an issue: GitHub.

Appendix: source

Thrown at Parser/Parser.php:162

            }

            [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument, $insideRelativeSelector);
            $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
        }

        return new Node\SelectorNode($result, $pseudoElement);
    }

    /**
     * @return list<array{0: string, 1: Node\SelectorNode}>
     *
     * @throws SyntaxErrorException
     * @throws InternalErrorException
     */
    private function parseRelativeSelector(TokenStream $stream): array
    {
        if ($this->hasNestingDepth >= self::HAS_NESTING_LIMIT) {
            throw SyntaxErrorException::nestedHas();
        }

        ++$this->hasNestingDepth;

        try {
            $arguments = [];
            while (true) {
                $stream->skipWhitespace();
                $peek = $stream->getPeek();

                if ($peek->isDelimiter(['+', '>', '~'])) {
                    $combinator = $stream->getNext()->getValue();
                    $stream->skipWhitespace();
                    $peek = $stream->getPeek();
                } else {
                    $combinator = ' ';
                }

View on GitHub (pinned to 08e2905152)