[csslib] Fix StateError on error tokens, polyfill var mutation, and cyclic 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()`.
diff --git a/pkgs/csslib/CHANGELOG.md b/pkgs/csslib/CHANGELOG.md
index 6209079..5b1d280 100644
--- a/pkgs/csslib/CHANGELOG.md
+++ b/pkgs/csslib/CHANGELOG.md
@@ -1,9 +1,10 @@
 ## 1.0.3-wip
 
-- Fix unhandled exceptions (`RangeError`, `FormatException`, `TypeError`,
-  `StackOverflowError`, and `AssertionError`) and infinite loops across the
-  tokenizer, parser, analyzer, polyfill pass, and printers when processing
-  malformed or deeply nested CSS, SCSS, and Less inputs.
+- Fix unhandled exceptions (`RangeError`, `FormatException`, `StateError`,
+  `ConcurrentModificationError`, `TypeError`, `StackOverflowError`, and
+  `AssertionError`) and infinite loops across the tokenizer, parser, analyzer,
+  polyfill pass, and printers when processing malformed or deeply nested CSS,
+  SCSS, and Less inputs.
 
 ## 1.0.2
 
diff --git a/pkgs/csslib/lib/parser.dart b/pkgs/csslib/lib/parser.dart
index 082b8f6..36445a8 100644
--- a/pkgs/csslib/lib/parser.dart
+++ b/pkgs/csslib/lib/parser.dart
@@ -308,6 +308,14 @@
     _error(message, tok.span);
   }
 
+  String _safeTokenString(Token tok) {
+    try {
+      return tok.toString();
+    } catch (_) {
+      return tok.text;
+    }
+  }
+
   void _error(String message, SourceSpan? location) {
     location ??= _peekToken.span;
     messages.error(message, location);
@@ -806,8 +814,11 @@
           if (include is IncludeDirective) {
             newDecls.add(IncludeMixinAtDeclaration(include, include.span));
           } else {
-            _warning('Error mixing of top-level vs declarations mixins',
-                _makeSpan(include.span as FileSpan));
+            _warning(
+                'Error mixing of top-level vs declarations mixins',
+                include.span is FileSpan
+                    ? _makeSpan(include.span as FileSpan)
+                    : include.span);
           }
         }
         declGroup.declarations.insertAll(0, newDecls);
@@ -890,7 +901,8 @@
           var span = _makeSpan(start);
           return VarDefinitionDirective(VarDefinition(name, exprs, span), span);
         } else if (isChecked) {
-          _error('unexpected directive @$_peekToken', _peekToken.span);
+          _error('unexpected directive @${_safeTokenString(_peekToken)}',
+              _peekToken.span);
         }
       }
     } else if (mixinParameter && _peekToken.kind == TokenKind.VAR_DEFINITION) {
@@ -1470,7 +1482,9 @@
           element = identifier();
           break;
         default:
-          _error('expected element name or universal(*), but found $_peekToken',
+          _error(
+              'expected element name or universal(*), '
+              'but found ${_safeTokenString(_peekToken)}',
               _peekToken.span);
           element = Identifier('', _makeSpan(start));
           break;
@@ -2716,7 +2730,8 @@
     if (!TokenKind.isIdentifier(tok.kind) &&
         !TokenKind.isKindIdentifier(tok.kind)) {
       if (isChecked) {
-        _warning('expected identifier, but found $tok', tok.span);
+        _warning('expected identifier, but found ${_safeTokenString(tok)}',
+            tok.span);
       }
       return Identifier('', _makeSpan(tok.span));
     }
diff --git a/pkgs/csslib/lib/src/analyzer.dart b/pkgs/csslib/lib/src/analyzer.dart
index b38ff72..397aebc 100644
--- a/pkgs/csslib/lib/src/analyzer.dart
+++ b/pkgs/csslib/lib/src/analyzer.dart
@@ -482,7 +482,9 @@
   @override
   void visitIncludeDirective(IncludeDirective node) {
     final currDef = this.currDef;
-    if (map.containsKey(node.name)) {
+    if (currDef is MixinRulesetDirective && node.name == currDef.name) {
+      currDef.rulesets.removeWhere((entry) => entry == node);
+    } else if (map.containsKey(node.name)) {
       var mixinDef = map[node.name];
       if (mixinDef is MixinRulesetDirective) {
         _TopLevelIncludeReplacer.replace(_styleSheet!, node, mixinDef.rulesets);
@@ -536,6 +538,7 @@
 
 /// @include as a top-level with ruleset(s).
 class _TopLevelIncludeReplacer extends Visitor {
+  static const int _maxExpansions = 256;
   final IncludeDirective _include;
   final List<TreeNode> _newRules;
 
@@ -553,7 +556,9 @@
   void visitStyleSheet(StyleSheet node) {
     var index = node.topLevels.indexOf(_include);
     if (index != -1) {
-      node.topLevels.insertAll(index + 1, _newRules);
+      if (node.topLevels.length < _maxExpansions) {
+        node.topLevels.insertAll(index + 1, _newRules);
+      }
       node.topLevels.replaceRange(index, index + 1, [NoOp()]);
     }
     super.visitStyleSheet(node);
@@ -563,7 +568,9 @@
   void visitMixinRulesetDirective(MixinRulesetDirective node) {
     var index = node.rulesets.indexOf(_include);
     if (index != -1) {
-      node.rulesets.insertAll(index + 1, _newRules);
+      if (node.rulesets.length < _maxExpansions) {
+        node.rulesets.insertAll(index + 1, _newRules);
+      }
       // Only the resolve the @include once.
       node.rulesets.replaceRange(index, index + 1, [NoOp()]);
     }
@@ -882,6 +889,7 @@
 
 /// @include as a top-level with ruleset(s).
 class _IncludeReplacer extends Visitor {
+  static const int _maxExpansions = 256;
   final TreeNode _include;
   final List<TreeNode> _newDeclarations;
 
@@ -899,7 +907,9 @@
   void visitDeclarationGroup(DeclarationGroup node) {
     var index = _findInclude(node.declarations, _include);
     if (index != -1) {
-      node.declarations.insertAll(index + 1, _newDeclarations);
+      if (node.declarations.length < _maxExpansions) {
+        node.declarations.insertAll(index + 1, _newDeclarations);
+      }
       // Change @include to NoOp so it's processed only once.
       node.declarations.replaceRange(index, index + 1, [NoOp()]);
     }
diff --git a/pkgs/csslib/lib/src/polyfill.dart b/pkgs/csslib/lib/src/polyfill.dart
index e35dc19..5d49213 100644
--- a/pkgs/csslib/lib/src/polyfill.dart
+++ b/pkgs/csslib/lib/src/polyfill.dart
@@ -47,7 +47,7 @@
     // Resolve all definitions to a non-VarUsage (terminal expression).
     mainStyleSheetVarDefs.forEach((key, value) {
       if (value.expression is Expressions) {
-        for (var _ in (value.expression as Expressions).expressions) {
+        for (var _ in (value.expression as Expressions).expressions.toList()) {
           mainStyleSheetVarDefs[key] =
               _findTerminalVarDefinition(_allVarDefinitions, value);
         }
diff --git a/pkgs/csslib/test/error_test.dart b/pkgs/csslib/test/error_test.dart
index b59a817..f5677bd 100644
--- a/pkgs/csslib/test/error_test.dart
+++ b/pkgs/csslib/test/error_test.dart
@@ -431,12 +431,21 @@
       _exerciseAllEntryPoints('@: 1px; a { var-: 2px; }');
       _exerciseAllEntryPoints(
           '@mixin m(@a) { color: @a; } @include ; a { @include m(); }');
+      _exerciseAllEntryPoints(
+          '@mixin m { @media screen { } color: red; } @include m;');
+      _exerciseAllEntryPoints(
+          '@mixin a { color: red; } @mixin a { @include a; } '
+          '@include a; div { @include a; }');
+      _exerciseAllEntryPoints(
+          '@mixin b { color: red; } @mixin a { @include b; } '
+          '@mixin b { @include a; } div { @include b; }');
     });
 
     test('malformed @-moz-document and @supports directives', () {
       _exerciseAllEntryPoints(
           '@-moz-document regexp(".*"), foo(1), url-prefix() '
           '{ a { top: 0; } }');
+      _exerciseAllEntryPoints('@-moz-document\uFFFDfoo(1) { a { top: 0; } }');
       _exerciseAllEntryPoints('@-moz-document { }');
       _exerciseAllEntryPoints('@supports () { } @supports not () { }');
       _exerciseAllEntryPoints('@supports (a: 1) and () { }');
@@ -457,6 +466,7 @@
     test('malformed selectors, namespace selectors, and selector expressions',
         () {
       _exerciseAllEntryPoints('ns| { color: red; } | { color: blue; }');
+      _exerciseAllEntryPoints('ns|\uFFFD { color: red; }');
       _exerciseAllEntryPoints(
           ':not(ns|a) { color: red; } :not(ns|) { color: blue; }');
       expect(NamespaceSelector(null, '', null).clone().toString(), '|');
@@ -478,6 +488,18 @@
       _exerciseAllEntryPoints(
           'a { color: #@foo; width: (@foo); height: [@foo]; top: @; }');
       _exerciseAllEntryPoints('a { color: var(); background: var(123); }');
+      final errors = <Message>[];
+      parse(
+        '@\uFFFD { }',
+        errors: errors,
+        options: const PreprocessorOptions(
+          useColors: false,
+          checked: true,
+          lessSupport: false,
+          inputFile: 'memory',
+        ),
+      );
+      expect(errors, isNotEmpty);
     });
 
     test('nested selectors inside directives and cyclic @extend / var()', () {
@@ -489,6 +511,7 @@
       _exerciseAllEntryPoints(
           '@media screen { @media print { a { b { color: red; } } } }');
       _exerciseAllEntryPoints('a { @extend .b; } .b { @extend a; }');
+      _exerciseAllEntryPoints('@namespefix; @name @name;');
       _exerciseAllEntryPoints('var-a: var(a); var-b: var(c); var-c: var(b); '
           'a { color: var(a); width: var(b, var(c)); '
           'height: var(u, 1px var(a)); }');