TemplateEnumerator - Optimize parsing of single digit holename#6152
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughConstructor now pre-scans for the first Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ba91fa1 to
5b7808f
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/NLog/MessageTemplates/TemplateEnumerator.cs (2)
65-81: Constructor pre-scan is correct — minor nit on_lengthreuse.Traced the pre-scan against the callers in
Logger.cs,LogMessageTemplateFormatter.cs, andMessageTemplateParameters.cs, as well as the negative test cases inParserTests.ThrowsTemplateParserException— all exception paths still fire viaMoveNext, and the literal-only and empty-template cases degrade gracefully.Small consistency nit: the
elsebranch reassigns_template.Lengthtwice when_lengthis already cached one line above.♻️ Optional consistency tweak
else { - _position = _template.Length; - _literalLength = _template.Length; + _position = _length; + _literalLength = _length; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 65 - 81, The constructor in TemplateEnumerator caches _length but the else branch redundantly calls _template.Length again; update the else branch to use the cached _length when assigning _position and _literalLength to avoid inconsistent access patterns—edit the TemplateEnumerator(string template) constructor to set _position = _length and _literalLength = _length (referencing the _length field, not _template.Length) while leaving the existing first-branch logic and other fields (_template, _position, _literalLength, _current) unchanged.
374-411: Signed/unsigned split looks good; note silent overflow beyond 10 digits.The contract (caller guarantees
cis'0'..'9'before invokingReadUnsignedInt) is honored by both call sites (ReadSignedIntand the positional branch ofParseName). The 12-iteration cap yields up to 13 consumed digits before throwing — values in the 11–13 digit range silently wrap (intoverflow is unchecked). This matches the priorReadIntbehavior in practice, so it is not blocking, but acheckedblock (or tightening the cap to 10) would produce a clearerTemplateParserExceptionfor pathological inputs instead of a garbage index/alignment.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 374 - 411, ReadUnsignedInt can silently overflow for 11–13 digit inputs; update it so it fails with a TemplateParserException instead of producing a wrapped/garbage int. Either (a) enforce a max of 10 digits (first digit is handled before the loop — e.g. loop at most 9 more digits) and throw TemplateParserException when more digits are present, or (b) perform the multiplication/add in a checked context and catch OverflowException to rethrow as TemplateParserException. Apply this change in ReadUnsignedInt (and ensure ReadSignedInt behavior remains consistent) so pathological numeric token lengths are rejected deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/NLog/MessageTemplates/TemplateEnumerator.cs`:
- Around line 65-81: The constructor in TemplateEnumerator caches _length but
the else branch redundantly calls _template.Length again; update the else branch
to use the cached _length when assigning _position and _literalLength to avoid
inconsistent access patterns—edit the TemplateEnumerator(string template)
constructor to set _position = _length and _literalLength = _length (referencing
the _length field, not _template.Length) while leaving the existing first-branch
logic and other fields (_template, _position, _literalLength, _current)
unchanged.
- Around line 374-411: ReadUnsignedInt can silently overflow for 11–13 digit
inputs; update it so it fails with a TemplateParserException instead of
producing a wrapped/garbage int. Either (a) enforce a max of 10 digits (first
digit is handled before the loop — e.g. loop at most 9 more digits) and throw
TemplateParserException when more digits are present, or (b) perform the
multiplication/add in a checked context and catch OverflowException to rethrow
as TemplateParserException. Apply this change in ReadUnsignedInt (and ensure
ReadSignedInt behavior remains consistent) so pathological numeric token lengths
are rejected deterministically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 9ef4dae2-55cf-4544-8edb-ac9eb4c9d1ab
📒 Files selected for processing (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs
72bc448 to
8023404
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs (1)
392-410:⚠️ Potential issue | 🟡 MinorMisleading error message and silent overflow for very long digit runs.
Two small concerns on the rewritten helper:
- After consuming 12 valid digits, the loop exits with a 13th digit in
cand throwsTemplateParserException("An integer is expected", …). At that point digits were found; the real condition is "integer too long / overflow". The message will confuse users who hit this with a long numeric hole name like{1234567890123}.- Because
i = i * 10 + (c - '0')is uncheckedintarithmetic, any 11–12 digit numeric index silently wraps before we reach the length check (e.g."999999999999"parses to a negative value and is then cast toshort).Consider tightening the message and/or using a digit-count/overflow guard.
🛡️ Suggested message/overflow tightening
- throw new TemplateParserException("An integer is expected", _position, _template); + throw new TemplateParserException("Integer is too large or malformed", _position, _template);Or use
checked { i = i * 10 + (c - '0'); }and translateOverflowExceptioninto aTemplateParserException.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 392 - 410, In ReadUnsignedInt, the code currently throws a misleading "An integer is expected" after consuming many digits and also allows silent int overflow; update the method to detect and report "integer too long" (or "integer overflow") instead of the current message and prevent/translate overflow: either perform the accumulation inside a checked block and catch OverflowException to rethrow a TemplateParserException with a clear "integer too long/overflow" message (include _position and _template), or add an explicit digit-count/limit guard before multiplying so you throw TemplateParserException("Integer too long", _position, _template) when the value would exceed int range; keep references to ReadUnsignedInt, TemplateParserException, _position and _template so the error is raised with correct context.
🧹 Nitpick comments (2)
src/NLog/MessageTemplates/TemplateEnumerator.cs (2)
192-206: Losing theSkip('}')invariant check.Replacing
Skip('}')with a bare_position++drops theDebug.Assert(_template[_position] == '}')that guarded the parser invariant "afterParseName/format parsing we are sitting on}". Given every path leading here (format parser unread, direct}/:peek, alignment+}) is supposed to land exactly on}, keeping the assert has zero release-build cost and would catch regressions in future refactors.♻️ Retain the debug-only invariant check
- _position++; // Consume closing '}' + Skip('}'); // Consume closing '}' (Debug.Assert validates the invariant)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 192 - 206, The replacement of Skip('}') with a bare _position++ in ParseHole removed the debug-only invariant that ensures the parser is positioned on '}' after ParseName/format parsing; restore the guard by asserting the current character is '}' before advancing (e.g., reintroduce Debug.Assert(_template[_position] == '}') or call the existing Skip('}') helper) so ParseHole (and its usage of _position++/Skip) re-establishes the invariant and then increments the position.
103-108:Reset()no longer mirrors the constructor's fast-forward.After this change,
Reset()leaves_position = 0, _literalLength = 0, so the firstMoveNext()after a reset will re-scan viaParseTextPartinstead of jumping straight to the first delimiter. This is functionally correct but gives up the optimization you just added for the reset path. Consider either reusing the same pre-scan, or documenting thatReset()is a slow-path reset.♻️ Optional alignment with constructor
public void Reset() { - _position = 0; - _literalLength = 0; _current = default(LiteralHole); + var firstDelimiter = _template.IndexOfAny(TextDelimiters); + _position = firstDelimiter >= 0 ? firstDelimiter : _length; + _literalLength = _position; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 103 - 108, Reset() currently sets _position=0 and _literalLength=0 which forces the next MoveNext() to re-scan via ParseTextPart and loses the constructor's fast-forward optimization; update Reset() in TemplateEnumerator to re-run the same initial pre-scan the constructor performs (or call the same initialization helper) so _position/_literalLength/_current are advanced to the first delimiter like the constructor does, or explicitly document that Reset() is a slow-path reset; reference Reset(), the constructor, MoveNext(), and ParseTextPart when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/NLog/MessageTemplates/TemplateEnumerator.cs`:
- Around line 392-410: In ReadUnsignedInt, the code currently throws a
misleading "An integer is expected" after consuming many digits and also allows
silent int overflow; update the method to detect and report "integer too long"
(or "integer overflow") instead of the current message and prevent/translate
overflow: either perform the accumulation inside a checked block and catch
OverflowException to rethrow a TemplateParserException with a clear "integer too
long/overflow" message (include _position and _template), or add an explicit
digit-count/limit guard before multiplying so you throw
TemplateParserException("Integer too long", _position, _template) when the value
would exceed int range; keep references to ReadUnsignedInt,
TemplateParserException, _position and _template so the error is raised with
correct context.
---
Nitpick comments:
In `@src/NLog/MessageTemplates/TemplateEnumerator.cs`:
- Around line 192-206: The replacement of Skip('}') with a bare _position++ in
ParseHole removed the debug-only invariant that ensures the parser is positioned
on '}' after ParseName/format parsing; restore the guard by asserting the
current character is '}' before advancing (e.g., reintroduce
Debug.Assert(_template[_position] == '}') or call the existing Skip('}') helper)
so ParseHole (and its usage of _position++/Skip) re-establishes the invariant
and then increments the position.
- Around line 103-108: Reset() currently sets _position=0 and _literalLength=0
which forces the next MoveNext() to re-scan via ParseTextPart and loses the
constructor's fast-forward optimization; update Reset() in TemplateEnumerator to
re-run the same initial pre-scan the constructor performs (or call the same
initialization helper) so _position/_literalLength/_current are advanced to the
first delimiter like the constructor does, or explicitly document that Reset()
is a slow-path reset; reference Reset(), the constructor, MoveNext(), and
ParseTextPart when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b1c91489-a4d1-4915-b54f-316eaec7ec81
📒 Files selected for processing (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs
1397b50 to
f62a4e4
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs (1)
400-410:⚠️ Potential issue | 🟡 MinorMisleading exception message on the >10-digit overflow guard.
When this
throwfires, the issue isn't "an integer is expected" (we've already consumed ≥10 digits) — it's that the integer is too large / has too many digits. Consider differentiating the message from the "no digit found" case at Line 386 to aid diagnosis of malformed templates like{12345678901}.✏️ Suggested message tweak
- throw new TemplateParserException("An integer is expected", _position, _template); + throw new TemplateParserException("Integer value is too large", _position, _template);Side note (not introduced by this PR, flagging for awareness):
i = i * 10 + (c - '0')can silently overflowinton 10-digit values exceedingint.MaxValue(e.g.,9999999999) before the 11-digit guard trips. Unlikely to matter for realistic parameter indices/alignments, so fine to leave as-is.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 400 - 410, The thrown TemplateParserException at the 10-digit overflow guard should indicate the value is too large rather than "An integer is expected"; update the throw in TemplateEnumerator (the block using i = i * 10 + (c - '0'), _position, Peek(), and _template) to use a distinct message like "Integer value too large or too many digits" (or similar) so it differs from the earlier "no digit found" case, keeping the same exception type and passing _position and _template.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/NLog/MessageTemplates/TemplateEnumerator.cs`:
- Around line 400-410: The thrown TemplateParserException at the 10-digit
overflow guard should indicate the value is too large rather than "An integer is
expected"; update the throw in TemplateEnumerator (the block using i = i * 10 +
(c - '0'), _position, Peek(), and _template) to use a distinct message like
"Integer value too large or too many digits" (or similar) so it differs from the
earlier "no digit found" case, keeping the same exception type and passing
_position and _template.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5e2a2422-ad99-47d8-aee1-2bde20a9eecb
📒 Files selected for processing (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs
8b283d7 to
21d5a88
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs (1)
103-108: Optional: mirror the constructor's fast-forward inReset().After
Reset()the enumerator is left at_position = 0, _literalLength = 0rather than fast-forwarded to the first delimiter, so the firstMoveNextafter aResettakes the slowerParseTextPartpath. Functionally correct (behavior is identical), but it leaves the two entry points semantically out of sync. If you want to keep them aligned, factor the init into a shared helper.♻️ Possible alignment
public void Reset() { - _position = 0; - _literalLength = 0; _current = default(LiteralHole); + var firstDelimiter = _template.IndexOfAny(TextDelimiters); + if (firstDelimiter >= 0) + { + _position = firstDelimiter; + _literalLength = firstDelimiter; + } + else + { + _position = _length; + _literalLength = _length; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/NLog/MessageTemplates/TemplateEnumerator.cs` around lines 103 - 108, Reset() currently just zeroes _position/_literalLength/_current leaving the enumerator before the first delimiter so the first MoveNext() hits ParseTextPart; update Reset() to mirror the constructor’s fast-forward logic by extracting the initial fast-forward into a shared helper (e.g., Initialize or FastForwardToDelimiter) and call it from both the constructor and Reset(), ensuring Reset(), the constructor, and MoveNext()/ParseTextPart are semantically aligned while preserving existing fields (_position, _literalLength, _current, LiteralHole).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/NLog/MessageTemplates/TemplateEnumerator.cs`:
- Around line 103-108: Reset() currently just zeroes
_position/_literalLength/_current leaving the enumerator before the first
delimiter so the first MoveNext() hits ParseTextPart; update Reset() to mirror
the constructor’s fast-forward logic by extracting the initial fast-forward into
a shared helper (e.g., Initialize or FastForwardToDelimiter) and call it from
both the constructor and Reset(), ensuring Reset(), the constructor, and
MoveNext()/ParseTextPart are semantically aligned while preserving existing
fields (_position, _literalLength, _current, LiteralHole).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a13dfc25-7dc0-4e80-a126-bdb020bd8e74
📒 Files selected for processing (1)
src/NLog/MessageTemplates/TemplateEnumerator.cs
…gle digit holename
21d5a88 to
2116cde
Compare
|
|



No description provided.