Repository navigation
[csslib] Fix parser, analyzer, polyfill, and printer crashes - #2638
Conversation
- Clamp out-of-range hex escape values (`> 0x10FFFF`) to `0xFFFD` in `Tokenizer.finishIdentifier` to prevent `RangeError` in `String.fromCharCodes`. - Guard null/missing names, selectors, and expressions across `@import`, `@keyframes`, `@stylet`, `@mixin`, `@include`, `@-moz-document`, `@supports`, `@page`, namespace selectors, and `var()` in `lib/parser.dart`, `lib/src/tree.dart`, `lib/visitor.dart`, and `lib/src/css_printer.dart`. - Fix `processMediaQuery` unary media operator warning check (`&&` instead of `||`) so `not` and `only` do not emit bogus warnings in checked mode. - Prevent infinite loops in `processMixin` parameter lists and `processMarginsDeclarations` on unexpected tokens, and bound mutual recursion in `processSupportsCondition` to avoid `StackOverflowError`. - Replace `int.parse` with `int.tryParse` on arbitrary-length digit/hex tokens in selector expressions, `\9` IE hacks, integer terms, and unicode ranges. - Guard `buildDartStyleNode`, `ExpressionsProcessor.processFontSize`, and `processTerm` (`ItemTerm`) against empty expressions, non-numeric values, and non-`px` length units. - Guard `ExpandNestedSelectors`, `_MediaRulesReplacer`, `CallMixin`, `DeclarationIncludes`, `AllExtends`, `InheritExtends`, and `PolyFill` against null selector groups, rule sets inside non-ruleset blocks, and cyclic `@extend` / `var()` definitions. - Deduplicate Dart style declaration pruning, quoted literal term construction, and hex digit helpers in `lib/parser.dart` and `lib/src/tokenizer_base.dart`. - Add regression tests in `test/error_test.dart`.
PR HealthLicense Headers ✔️
All source files should start with a license header. Unrelated files missing license headers
This check can be disabled by tagging the PR with Breaking changes ✔️
This check can be disabled by tagging the PR with Unused Dependencies ✔️
For details on how to fix these, see dependency_validator. This check can be disabled by tagging the PR with
Coverage
|
| File | Coverage |
|---|---|
| pkgs/csslib/lib/parser.dart | 💚 96 % ⬆️ 4 % |
| pkgs/csslib/lib/src/analyzer.dart | 💔 95 % ⬇️ 2 % |
| pkgs/csslib/lib/src/css_printer.dart | 💚 94 % ⬆️ 10 % |
| pkgs/csslib/lib/src/polyfill.dart | 💚 95 % ⬆️ 5 % |
| pkgs/csslib/lib/src/tokenizer.dart | 💚 93 % ⬆️ 0 % |
| pkgs/csslib/lib/src/tokenizer_base.dart | 💚 30 % ⬆️ 17 % |
| pkgs/csslib/lib/src/tree.dart | 💚 72 % ⬆️ 26 % |
| pkgs/csslib/lib/visitor.dart | 💚 84 % ⬆️ 1 % |
This check for test coverage is informational (issues shown here will not fail the PR).
This check can be disabled by tagging the PR with skip-coverage-check.
API leaks ⚠️
The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.
| Package | Leaked API symbol | Leaking sources |
|---|---|---|
| csslib | Messages | csslib/parser.dart::Analyzer::new::_messages messages.dart::Messages::mergeMessages::newMessages csslib/parser.dart::TopLevelIncludes::expand::messages csslib/parser.dart::TopLevelIncludes::new::_messages csslib/parser.dart::DeclarationIncludes::expand::messages csslib/parser.dart::DeclarationIncludes::new::_messages csslib/parser.dart::InheritExtends::new::_messages csslib/parser.dart::PolyFill::new::_messages |
This check can be disabled by tagging the PR with skip-leaking-check.
Changelog Entry ✔️
| Package | Changed Files |
|---|
Changes to files need to be accounted for in their respective changelogs.
This check can be disabled by tagging the PR with skip-changelog-check (for example, for non-user-visible changes when the package version is already at -wip).
There was a problem hiding this comment.
Code Review
This pull request improves the resilience of the CSS parser, tokenizer, analyzer, polyfill pass, and printers against malformed or deeply nested inputs, resolving various unhandled exceptions and infinite loops. Key changes include adding null checks, replacing unsafe parsing with tryParse, implementing cycle-detection to prevent stack overflows, and adding a suite of fuzz regression tests. A review comment points out a potential runtime TypeError in NamespaceSelector.clone() where using an empty string fallback violates type constraints, suggesting a dummy ElementSelector instead.
…yclic mixins
- Guard `Token.toString()` in `identifier()`, `simpleSelector()`, and `processVariableOrDirective()` via `_safeTokenString` so `ERROR` and `UNTERMINATED_COMMENT` tokens do not throw `StateError('Unknown TOKEN')`.
- Snapshot `Expressions.expressions` in `PolyFill.processVars` before `_findTerminalVarDefinition` mutates the list via `replaceRange`.
- Prevent infinite node expansion on self-referential and cyclic `@mixin` / `@include` definitions in `TopLevelIncludes` and `DeclarationIncludes`, and guard `include.span is FileSpan` in `processMixin()`.
davidmorgan
left a comment
There was a problem hiding this comment.
Thanks for the fuzz work: on a 1500-input differential fuzz, main throws on 380 inputs and times out on 9; this PR throws on none. But some fixes swap a crash for silent wrong output on valid CSS, so requesting changes. Line comments cover the regressions; the rest is here.
Regressions without a line to anchor on
• @keyframes k { 0% { @extend .a; a { color: red; } } }: main throws a null-check error; this PR compiles with 0 errors and drops color: red. A crash became silent data loss.
• With an empty nested rule, @keyframes k { 0% { @extend .a; a { } } } or @font-face { @extend .a; a { } }, this PR still crashes: main throws a null-check error, and this PR throws RangeError (length) in AllExtends.visitDeclarationGroup. _extendsToRemove is shared across the nested visitDeclarationGroup calls, so the inner group removes the outer @extend index.
Description vs code at b2e2424
• @supports nesting cap: there is no _maxSupportsDepth in the code. 10k nested parens in @supports still overflow the stack, on main and on this PR.
• _pruneConditionalAt, _createQuotedLiteralTerm and TokenizerBase.hexValue don't exist, and there is no UNIT_LENGTH_PX assertion in processFontSize to move.
Pre-existing, not caused by this PR
• A self-referential var() overflows the stack on main and on this PR.
• A var() fallback case that crashed on main now leaks var(a, 1px) into the output; better than a crash, still wrong.
Checked: test suites on main and head, dart analyze and dart format clean, probes for each finding run on both main and head, and a 1500-input seeded differential fuzz. Of the 25 inputs whose output changed without a throw, all are malformed input except the [ident] case below.
- Widen ItemTerm guard to `term is LiteralTerm` so CSS Grid `[ident]` line names are preserved. - Add `_maxSupportsDepth = 512` in `processSupportsConditionInParens` to prevent stack overflow on deeply nested `@supports` conditions. - Count actual `@include` and `@extend` expansions rather than container list lengths so large stylesheets expand normally. - Scope `AllExtends._extendsToRemove` per `visitDeclarationGroup` call so nested rulesets inside `@keyframes` or `@font-face` do not lose declarations or throw `RangeError`. - Fix `_VarDefAndUsage.visitExpressions` and `resolveUsageTerminal` so removing undefined `var()` usages does not skip subsequent expressions.
|
Thanks for the thorough review and differential fuzz probes! Addressed all inline and top-level findings in d4327e4 ( |
|
I'm nervous re: LGTM'ing so much on a package I'm not familiar with; maybe @natebosch for an additional opinion? |
|
Removed global caps; limits are now tracked locally where possible, bumped to 100k, and explicitly emit an Analyzer error on breach. PTAL! |
…obal caps * Unify `_maxExpansionDepth` (`64`) and `_maxExpansions` (`4096`) across `analyzer.dart` and `polyfill.dart`. * Track active `@include` stacks via internal `_ExitInclude` sentinels and bound per-root expanded node counts in `TopLevelIncludes` and `DeclarationIncludes`. * Track per-selector `@extend` derivation chains using formatted selector text in `InheritExtends` and bound `var()` resolution per `Expressions` list in `PolyFill`. * Add regression tests for large non-recursive stylesheets (`300` `@include` and `@extend` rules, `130` `var()` rules) and exponential `@mixin` DAGs.
…sion - Remove synthetic `_ExitInclude` AST sentinel nodes, `visitNoOp` overrides, `.removeWhere((x) => x is _ExitInclude)` passes, and `_inIncludeAtDeclaration` from `TopLevelIncludes` and `DeclarationIncludes`, tracking active mixins and the 1024-node expansion cap across recursive `try`/`finally` expansion helpers. - Simplify `InheritExtends.visitSelectorGroup` by extracting `_deriveSelector` and replacing per-selector `Set<String>` chain copies with a single per-group depth map (`55 -> 22` cognitive complexity). - Narrow `_Parser._safeTokenString` catch clause to `on StateError`. - Assert in `test/error_test.dart` that `TopLevelIncludes.expand` and `DeclarationIncludes.expand` leave no synthetic `NoOp` nodes in the AST.
davidmorgan
left a comment
There was a problem hiding this comment.
The round-two findings are fixed: 300 top-level @includes, 300 declaration @includes, 300 @extends of one group, 130 polyfilled var() rules and 5000 var()s in one list all give the same output as main. Where a limit is hit, an error is now reported. The suite, analyze and format are clean, and the new error_test cases fail 3 times against the previous head's lib.
Two regressions, so requesting changes:
A forward-referenced nested declaration mixin is dropped silently. @mixin d1 { color: red; @include d2; } @mixin d2 { width: 1px; } .a { @include d1; } gives .a{color:red;width:1px} on main and on the previous head, but .a{color:red} here, with no error. DeclarationIncludes fills map as it visits, so when d1's body is visited d2 isn't defined yet. The new undefined-mixin branch then replaces the @include with a NoOp permanently. If d2 is defined first, the output is correct.
@include of an undefined mixin inside @page now throws. @page { @include nope; } throws type 'List' is not a subtype of type 'Iterable'. On main and the previous head it compiles. @page { @include ; } threw a null-check error on main; the previous head fixed that, and it now throws the same TypeError. The new branch passes const [] to _IncludeReplacer, which calls insertAll on the page's List. A 1500-input fuzz finds this as its only throw, with 0 timeouts.
Pre-existing, also on main: @mixin d { color: red; } @page { @include d; } throws the same TypeError, from the same insertAll.
Smaller points:
The limits in the code are _maxExpansionDepth = 64 and _maxExpansions = 4096, not the 100k in the PR comment. Valid input above them now reports an error instead of losing output silently, which is the important part. Two examples: a mixin with 5000 rulesets is dropped completely, with "Circular or too deep @include for big", although it is neither circular nor deep. A 70-step @extend chain loses its last 6 selectors, with "Maximum @extend depth reached". A message that names the limit that was hit would be clearer.
@extend now skips derived selectors that are already in the group: .a, .b { color: red; } .b { @extend .a; } prints .a,.b,.b{...} on main and .a,.b{...} here. It looks like an improvement, but it changes output and no test covers it.
An @include of an undefined mixin is now removed from the output instead of being printed back. 5 of the fuzz inputs differ from the previous head only in this way.
- Keep forward-referenced and undefined declaration `@include` nodes intact during mixin definition analysis, and allocate `<TreeNode>[]` in `_Parser.processMarginsDeclarations()` so `@include` inside `@page` does not throw a `TypeError`. - Report distinct error messages for circular `@include`, max depth (`64`), and max expansions (`4096`) in `@include` and `@extend`. - Track per-selector ancestor chains in `InheritExtends` so sibling selectors in a selector group match `main`.
|
Addressed the inline threads and top-level review findings in 73c2506: preserved forward-referenced and undefined declaration @include nodes, changed @page declarations to [], restored sibling @extend selector parity (.a, .b), and split limit messages into circularity, max depth (64), and max expansions (4096). PTAL! |
davidmorgan
left a comment
There was a problem hiding this comment.
The round-three findings are fixed. The forward-referenced nested declaration mixin now expands: .a{color:red;width:1px}. @page { @include nope; } and @page { @include ; } compile. The limit messages are distinct. The @extend dedupe is reverted, and an @include of an undefined mixin is printed back as on main. The suite passes (133), analyze and format are clean, and the new error_test case fails against the previous head's lib. A 1500-input fuzz gives 0 throws and 0 timeouts; the previous head had 1 throw. The 6 inputs whose output differs from the previous head differ only in printing back an empty or undefined @include, which matches main.
Minor, not blocking: with mutually recursive declaration mixins defined a before b, @mixin a { color: red; @include b; } @mixin b { width: 1px; @include a; } .y { @include a; } gives .y{color:red;width:1px;color:red} with "Circular @include for b". The cycle is entered from a, so the duplicated color:red and the name b in the message are surprising. With b defined first, the output is .y{color:red;width:1px} with "Circular @include for a". main throws a RangeError on both, so this is still an improvement.
Pre-existing, also on main: a forward-referenced top-level ruleset mixin loses its rules. @mixin d1 { .q { color: red; } @include d2; } @mixin d2 { .r { width: 1px; } } @include d1; gives .q{color:red}; with d2 defined first it gives .q{color:red}.r{width:1px}.
…est, tools, webkit_inspection_protocol Revisions updated by `dart tools/rev_sdk_deps.dart`. core (https://github.com/dart-lang/core/compare/04da676..d7bd45c): d7bd45c1 Wed Oct 28 13:23:54 2026 +0530 Chandra ObulaReddy [args] Wrap separators to usageLineLength (dart-lang/core#1032) 6903c6c7 Fri Oct 23 20:10:24 2026 +0000 Jonas Finnemann Jensen [crypto] Prepare to publish 3.0.8 (dart-lang/core#1026) 7c1c3700 Fri Oct 23 11:14:15 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/core#1029) f01024fc Thu Oct 22 16:31:50 2026 -0700 Kevin Moore [collection] Share Iterable and Set tests across wrapper_test.dart (dart-lang/core#1028) c782eab4 Thu Oct 22 13:25:34 2026 +0200 Jonas Finnemann Jensen Update convert package README and add examples (dart-lang/core#999) dartdoc (https://github.com/dart-lang/dartdoc/compare/c0e19cf..db94437): db94437c Wed Oct 28 11:37:17 2026 +0100 Sarah Zakarias Add the doc-reference-target-differs warning (dart-lang/dartdoc#4306) b6e3980c Tue Oct 27 14:59:21 2026 +0100 Sarah Zakarias Only treat a doc comment as library documentation when it precedes a library directive (dart-lang/dartdoc#4303) 84a0141f Tue Oct 27 11:22:04 2026 +0100 Sarah Zakarias Add the unscoped-doc-reference warning (dart-lang/dartdoc#4305) b25bcd99 Thu Oct 22 15:56:17 2026 +0300 Sergey G. Grekhov Update/replace TODO in co19 tests (dart-lang/dartdoc#4299) 1e9e771e Thu Oct 22 12:18:48 2026 +0300 Sergey G. Grekhov `#4134`. Add more placement tests for `library` directives (dart-lang/dartdoc#4297) 17629ced Wed Oct 21 10:29:05 2026 -0700 Bob Nystrom Use newer function type syntax for parameters. (dart-lang/dartdoc#4290) f5928b1d Wed Oct 21 15:44:39 2026 +0300 Sergey G. Grekhov `#4134`. Add placement tests for enum values (dart-lang/dartdoc#4223) 24ae37f3 Wed Oct 21 11:18:07 2026 +0200 dependabot[bot] Bump test_reflective_loader from 0.4.0 to 0.6.0 (dart-lang/dartdoc#4296) ecosystem (https://github.com/dart-lang/ecosystem/compare/0ab0cfd..24b9ba8): 24b9ba8 Mon Oct 26 18:40:19 2026 -0700 Kevin Moore fix(repo_manage): increment patch version when transitioning to -wip (dart-lang/ecosystem#474) e1d4a04 Fri Oct 23 20:18:20 2026 +0000 dependabot[bot] Bump the github-actions group across 1 directory with 8 updates (dart-lang/ecosystem#473) 4b44357 Fri Oct 23 11:13:26 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/ecosystem#472) i18n (https://github.com/dart-lang/i18n/compare/1f5ea2f..4c7b4ad): 4c7b4ad1 Fri Oct 23 11:14:00 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/i18n#1113) 899251b1 Thu Oct 8 11:30:18 2026 +0200 Moritz [intl4x] Fix doc comment examples and verify them via {@example} in tests (dart-lang/i18n#1107) 5c8eb9d5 Thu Oct 8 10:27:58 2026 +0200 Moritz [intl4x] Fix YearStyle.withEra on ECMA when length is short or omitted (dart-lang/i18n#1105) b80bb04a Wed Sep 23 14:37:42 2026 +0200 Michael Thomsen Remove experiment flag no longer needed (dart-lang/i18n#1097) 766fd507 Wed Sep 23 13:59:56 2026 +0200 Moritz [intl4x] Mark DisplayNames API as @experimental and trigger CI on PR base edits (dart-lang/i18n#1098) 08de2f6f Fri Sep 18 12:49:50 2026 +0200 Moritz Rev package:intl4x to 1.0.0 and update status to preview (dart-lang/i18n#1088) native (https://github.com/dart-lang/native/compare/3cbe319..e0ca7c6): e0ca7c60f Wed Oct 28 12:08:25 2026 +0100 Michael Goderbauer [infra] Pin ubuntu-24.04 in workflows installing clang-15 (dart-lang/native#3771) eb09430a9 Tue Oct 27 10:43:22 2026 +0100 Michael Goderbauer [jni] Publish v1.1.1 (dart-lang/native#3765) 94b5f4337 Tue Oct 27 10:21:16 2026 +0100 Michael Goderbauer [jnigen] Pin ubuntu-24.04 in CI workflow (dart-lang/native#3766) 74cc078ee Tue Oct 27 09:29:23 2026 +0100 Michael Goderbauer [jni] Add ProGuard consumer rules for Kotlin classes used by suspend functions (dart-lang/native#3756) 0aa672fed Fri Oct 23 11:13:43 2026 -0700 Kevin Moore [infra] Tighten pull_request_label workflow permissions (dart-lang/native#3764) 8b11b6e01 Fri Oct 23 13:38:47 2026 +0200 Michael Goderbauer [jnigen] Bump AGP to 9.1.1 and KGP to 2.4.10 for Flutter beta (dart-lang/native#3758) protobuf (https://github.com/dart-lang/protobuf/compare/a73edc9..1d52301): 1d52301 Wed Oct 28 08:18:02 2026 -0700 Kevin Moore test(protoc_plugin): replace dart:mirrors in reserved_names_test (google/protobuf.dart#1104) shelf (https://github.com/dart-lang/shelf/compare/b66665b..73b2382): 73b2382 Thu Oct 22 10:43:29 2026 -0700 Kevin Moore shelf_router_generator: ignore invalid_use_of_internal_member on RouterEntry (dart-lang/shelf#548) shelf is held back at 73b2382, before dart-lang/shelf#547, because `pkgs/shelf/test/shelf_io_test.dart` now imports the unpublished `package:_shelf_compliance`, which breaks the shared `shelf_tests` target in google3. test (https://github.com/dart-lang/test/compare/d1afeb1..5967927): 5967927e Mon Oct 26 17:50:52 2026 -0700 Nate Bosch Diagnose browser connection failures and time them out with the suite load timeout (dart-lang/test#2788) 0692e8ae Mon Oct 26 16:29:27 2026 -0700 Kevin Moore Remove mirror_matchers.dart from package:matcher (dart-lang/test#2802) da21f85a Fri Oct 23 11:13:38 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/test#2804) e18fd112 Thu Oct 22 15:38:51 2026 -0700 Nate Bosch Report tests that pass on retry as flaky (dart-lang/test#2801) b4ef80c0 Fri Oct 23 02:44:25 2026 +0600 Pranta Dutta Fix compact reporter crash when the terminal is too narrow (dart-lang/test#2799) e3a7f2b6 Tue Oct 20 15:51:34 2026 -0700 Nate Bosch Check that web entrypoints don't import VM-only libraries (dart-lang/test#2797) tools (https://github.com/dart-lang/tools/compare/eee87b1..3980dca): 3980dca39 Wed Oct 28 11:43:38 2026 +0100 Jonas Finnemann Jensen [html] Move contributors list from LICENSE to AUTHORS (dart-lang/tools#2733) 2866a7745 Wed Oct 28 11:13:19 2026 +0100 Jonas Finnemann Jensen [extension_discovery] Prepare for v2.1.1 release (dart-lang/tools#2727) 530feb74e Wed Oct 28 13:38:07 2026 +0530 Chandra ObulaReddy [csslib] Fix the aliceblue, antiquewhite and peru color values (dart-lang/tools#2732) d5c210271 Tue Oct 27 15:07:32 2026 -0700 Kevin Moore [source_span] Fix CR/CRLF highlighting, point-span context, and subspan (dart-lang/tools#2634) cbea5b08e Tue Oct 27 15:07:27 2026 -0700 Kevin Moore [string_scanner] Validate error() span bounds and test eager lastSpan (dart-lang/tools#2715) 22fd7a6a8 Tue Oct 27 15:07:20 2026 -0700 Kevin Moore [csslib] Fix parser, analyzer, polyfill, and printer crashes (dart-lang/tools#2638) webkit_inspection_protocol (https://github.com/google/webkit_inspection_protocol.dart/compare/762115a..2c255f2): 2c255f2 Thu Oct 22 14:18:44 2026 -0700 Kevin Moore Remove dart:mirrors and deprecate dom_model.dart and forwarder.dart (google/webkit_inspection_protocol.dart#141) web is held back at 4e60e61, before dart-lang/web#596, because it changes `package:web` signatures (e.g. `innerHTML=`, `parseFromString`, `Worker()`) from `JSAny` to `String` in ways that break callers in google3. Change-Id: Ia4253b35a91d7c05e34fe0e150ea4cb3e6628015 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/569660 Commit-Queue: Nate Bosch <[email protected]> Reviewed-by: Brian Quinlan <[email protected]>
Fixes several unhandled crashes, infinite loops, and stack overflows across the CSS tokenizer, parser, analyzer, polyfill pass, and printer on malformed or edge-case stylesheets, and removes duplicate internal helpers:
lib/src/tokenizer.dart): Clamp hexadecimal character escapes exceeding0x10FFFF(e.g.,\110000..\ffffff) to the Unicode replacement character (U+FFFD) per CSS Syntax § 4.3.7 instead of throwingRangeErrorinString.fromCharCodes.lib/parser.dart): Guardint.parseinprocessSelectorExpression(:nth-child(...)),processTerm, andprocessExprso overflowing integer literals fall back or emit parser diagnostics instead of throwingFormatException.lib/parser.dart): Consume unexpected tokens inprocessMixinandprocessMarginsDeclarationsto prevent infinite loops, and cap@supportsnesting depth (_maxSupportsDepth = 512) to preventStackOverflowError.lib/parser.dart,lib/src/css_printer.dart): Guard against null identifiers/spans and empty expression lists on malformed@charset,@import,@namespace,@document,@media,@font-face,@stylet,var(), andns|selectors; fixop != 'NOT' || op != 'ONLY'condition inprocessMediaQuery.lib/parser.dart): Handle empty declaration expressions and[ident]ItemTerms inbuildDartStyleNode, and delegate_fontPartSizetoExpressionsProcessor.processFontSize(with anumvalue guard) socm/mm/in/pt/pclengths do not triggerLengthTerm'sUNIT_LENGTH_PXassertion.lib/src/analyzer.dart,lib/src/polyfill.dart): Bound@includeand@extendexpansion counts against cyclic definitions, scopeAllExtends._extendsToRemoveper declaration group, guard against recursivevar()definitions, and handle nested rulesets inside@keyframes,@page,@supports, and@hostwithout assertion failures._recordDeclarationWithDartStyle/_pruneInactiveDartStyles), quoted string literal construction (_processQuotedLiteralTerm), and hex digit parsing (TokenizerBase._hexDigit).