What's wrong
SpanSplitEnumerator.MoveNext() (Semantics.Strings/SemanticString.cs, ~line 1020) skips empty entries by calling itself recursively:
if (_options == StringSplitOptions.RemoveEmptyEntries && Current.IsEmpty)
{
return MoveNext(); // Recursively skip empty entries
}
Each empty entry adds a stack frame, so a long run of separators overflows the stack.
Reproduction (net10.0 console app against the built ktsu.Semantics.Strings.dll):
var s = MyString.Create(new string(',', 100_000) + "x");
foreach (var part in s.Split(',', StringSplitOptions.RemoveEmptyEntries)) { }
- Actual:
Stack overflow. The process aborts with exit code 134. A StackOverflowException can't be caught, so the caller has no way to recover.
- Expected: one entry,
"x", which is what string.Split returns.
- 10,000 commas still works; 100,000 and 1,000,000 crash.
The same check has two more defects:
- Combined options are ignored. The code compares with
== instead of testing the flag, so RemoveEmptyEntries | TrimEntries removes nothing: "a,,b" gives [a, "", b].
TrimEntries is never applied. "a, ,b" with RemoveEmptyEntries | TrimEntries gives [a, " ", b], where string.Split gives [a, b].
Why it matters
This enumerator is documented as the zero-allocation replacement for Split() in performance-critical code. That is exactly where large or untrusted input, such as CSV or log lines, arrives. One malformed line with a long run of delimiters is enough to terminate the host process.
Suggested fix
Replace the recursion with a loop, and test the options as flags:
while (!_remaining.IsEmpty)
{
// slice the next segment into Current (as today)
if ((_options & StringSplitOptions.TrimEntries) != 0) Current = Current.Trim();
if ((_options & StringSplitOptions.RemoveEmptyEntries) != 0 && Current.IsEmpty) continue;
return true;
}
return false;
Acceptance criteria
Out of scope: the enumerator also drops a trailing empty entry ("a,b," gives 2 parts, where string.Split gives 3). The existing tests in SemanticStringTests.cs (~1374–1405) document related empty-entry behaviour as current, so any change there is a separate decision.
What's wrong
SpanSplitEnumerator.MoveNext()(Semantics.Strings/SemanticString.cs, ~line 1020) skips empty entries by calling itself recursively:Each empty entry adds a stack frame, so a long run of separators overflows the stack.
Reproduction (net10.0 console app against the built
ktsu.Semantics.Strings.dll):Stack overflow.The process aborts with exit code 134. AStackOverflowExceptioncan't be caught, so the caller has no way to recover."x", which is whatstring.Splitreturns.The same check has two more defects:
==instead of testing the flag, soRemoveEmptyEntries | TrimEntriesremoves nothing:"a,,b"gives[a, "", b].TrimEntriesis never applied."a, ,b"withRemoveEmptyEntries | TrimEntriesgives[a, " ", b], wherestring.Splitgives[a, b].Why it matters
This enumerator is documented as the zero-allocation replacement for
Split()in performance-critical code. That is exactly where large or untrusted input, such as CSV or log lines, arrives. One malformed line with a long run of delimiters is enough to terminate the host process.Suggested fix
Replace the recursion with a loop, and test the options as flags:
Acceptance criteria
"x"withRemoveEmptyEntriesyields exactly["x"]and does not crash.RemoveEmptyEntries | TrimEntrieson"a, ,b"yields["a", "b"].TrimEntriesalone trims each entry.Out of scope: the enumerator also drops a trailing empty entry (
"a,b,"gives 2 parts, wherestring.Splitgives 3). The existing tests inSemanticStringTests.cs(~1374–1405) document related empty-entry behaviour as current, so any change there is a separate decision.