TokenReader subrange bounds enforced inconsistently: peekToken/peekPreviousTokenKind/backtrackToMarker ignore embedded window
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 5k
- Forks
- 162
- Avg merge
- 17h 24m
- Merged PRs (30d)
- 8
Description
Summary
TokenReader respects its embedded subrange (_readerStartIndex/_readerEndIndex) in peekTokenKind()/peekTokenAfterKind()/peekTokenAfterAfterKind(), but three sibling methods ignore those bounds: peekToken() has no end guard at all, and peekPreviousTokenKind() / backtrackToMarker() compare against 0 / allow rewinding before the subrange start. This leaks outer tokens into embedded parses and can return undefined where Token is promised.
Location
- File:
tsdoc/src/parser/TokenReader.ts - Class:
TokenReader - Methods:
peekToken(): Token—return this.tokens[this._currentIndex];with no_readerEndIndexcheckpeekPreviousTokenKind()—if (this._currentIndex === 0)instead of=== this._readerStartIndexbacktrackToMarker(marker)— checksmarker > this._currentIndexbut notmarker < this._readerStartIndex
Contrast with the guarded siblings in the same file:
public peekTokenKind(): TokenKind {
if (this._currentIndex >= this._readerEndIndex) {
return TokenKind.EndOfInput;
}
...
}
Problem
peekToken()past end returnsundefined: return type claimsToken, but past_readerEndIndexthe index expression yieldsundefined. Callers doingpeekToken().range/peekToken().kindthen throwTypeError: Cannot read properties of undefinedinstead of getting a cleanEndOfInputsignal. Current internal hot paths inNodeParser.tshappen to callpeekTokenKind()first (e.g. block/inline tagbadCharacterpaths), which masks the hole, but the public API contract is broken for any external consumer or future internal use.peekPreviousTokenKind()leaks across embedded start: for an embedded reader starting at index N>0, calling it at the embedded start returnstokens[N-1].kind(outer context) instead ofEndOfInput.NodeParser._parseBlockTagand_parseFencedCodeswitch on this to decide start-of-input behavior (AtSignInWord,CodeFenceOpeningIndent); an embedded@or fence at the subrange start can therefore be misclassified using the token before the subrange.backtrackToMarker()can rewind before subrange start: nothing preventsmarker < _readerStartIndex, letting a laterreadToken()consume outer tokens that the embedded reader was explicitly scoped to exclude.
Trigger / Reproduction
Based on static analysis (no execution performed):
- Construct
new TokenReader(parserContext, embeddedSequence)whereembeddedSequence.startIndex > 0, advance to the embedded start, and callpeekPreviousTokenKind()— returns the outer predecessor kind instead ofEndOfInput. - Call
peekToken()when_currentIndex === _readerEndIndex— returnsundefinedinstead of signaling end (comparepeekTokenKind()returningEndOfInputin the same state). - Call
backtrackToMarker(outerMarker)withouterMarker < _readerStartIndexon an embedded reader — accepted, subsequent reads escape the subrange.
Note: this is a static-analysis finding; I did not execute a parser fixture.
Expected Behavior
All TokenReader accessors/mutators should honor the same [ _readerStartIndex, _readerEndIndex ) window: peekToken() should signal end (or throw a clear parser-bug error like readToken() does) instead of returning undefined; peekPreviousTokenKind() should return EndOfInput at the subrange start; backtrackToMarker() should reject markers below the subrange start.
Actual Behavior
Subrange bounds are enforced inconsistently, so embedded parses can observe outer tokens and end-of-input handling differs by method.
Impact
- Potential spurious
AtSignInWord/ fence-indent errors for embedded constructs starting at a subrange boundary. undefined-tokenTypeErrors for API consumers usingpeekToken()at end, bypassing the cleanEndOfInputprotocol every sibling method follows.
Suggested Direction
- Mirror the existing
peekTokenKind()guard inpeekToken()(return theEndOfInputtoken or throw a parser-bug error — maintainer's choice, but document it), change thepeekPreviousTokenKind()zero-check to_readerStartIndex, and add amarker < _readerStartIndexrejection inbacktrackToMarker(). No grammar changes needed.
Evidence
- Source via API:
tsdoc/src/parser/TokenReader.tsshows guardedpeekTokenKind/peekTokenAfterKind/peekTokenAfterAfterKindvs unguardedpeekToken/peekPreviousTokenKind/backtrackToMarker;tsdoc/src/parser/NodeParser.tsshowspeekPreviousTokenKindgatingAtSignInWord/CodeFenceOpeningIndentand embeddedTokenReaderconstruction for scoped parses. - Duplicate check: issue search for
peekToken TokenReader boundsreturnstotal_count: 0, and open issues contain no TokenReader-boundary report — no apparent duplicate. This is a non-security correctness finding, so Microsoft's private-security-disclosure requirement does not apply.
Classification
- FACT: three
TokenReadermethods ignore the subrange window that three sibling methods enforce (verified in source via API). - INFERENCE: embedded parses can observe out-of-range tokens; end-of-input
peekToken()yieldsundefined. - HYPOTHESIS: aligning all methods on the same window fixes the misclassification/TypeError risk with no grammar change.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in tsdoc/src/parser/TokenReader.ts by comparing peekToken(), peekPreviousTokenKind(), and backtrackToMarker() with the guarded sibling methods. Review the embedded-reader behavior and the NodeParser call sites named in the issue. Done means all three methods honor the [_readerStartIndex, _readerEndIndex) window and the chosen end-of-input behavior is covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100