cmd/goyacc: panic ("ungetc - 2nd unget") on grammar file ending without a newline
Summary
A 10-byte grammar file with no trailing newline crashes goyacc with a panic instead of
reporting a syntax error:
$ printf '%%\nS: A; B' > v2.y && goyacc -o /dev/null v2.y
panic: ungetc - 2nd unget
goroutine 1 [running]:
main.ungetrune(...)
cmd/goyacc/yacc.go:3160
main.gettok()
cmd/goyacc/yacc.go:990
main.setup()
cmd/goyacc/yacc.go:656
main.main()
cmd/goyacc/yacc.go:349
Any grammar whose last token is an identifier at EOF triggers it — e.g. %token A
followed by %% and S: A with no final newline.
Root cause
getrune keeps EOF sticky in the single-slot peekrune buffer: when peekrune == EOF
it returns EOF without clearing the slot (so EOF stays pushed back for the next
reader). But gettok's IDENTIFIER/IDENTCOLON look-ahead (yacc.go:975-992) ends by
ungetting whatever rune it consumed — including EOF. If EOF is already sitting in
peekrune (left by getword ungetting the identifier's terminator), the unget hits
the peekrune != 0 guard in ungetrune (yacc.go:3159-3161) and panics.
Fix
Ungetting EOF is a no-op — an exhausted reader returns EOF again on the next read
regardless, so behavior for well-formed files is identical:
func ungetrune(f *bufio.Reader, c rune) {
if f != finput {
panic("ungetc - not finput")
}
if c == EOF {
return
}
if peekrune != 0 {
panic("ungetc - 2nd unget")
}
peekrune = c
}
With the fix, the example above reports the proper illegal rule: missing semicolon or | error and exits 1, and a valid grammar compiles to identical output. I have
the patch ready with the repro cases verified (crash pre-fix, clean errors post-fix,
valid grammar byte-identical).
Duplicate check
Searched golang/go issues for "ungetc - 2nd unget" and goyacc panic reports: no hits.
cmd/goyacc: panic ("ungetc - 2nd unget") on grammar file ending without a newline
Summary
A 10-byte grammar file with no trailing newline crashes goyacc with a panic instead of
reporting a syntax error:
Any grammar whose last token is an identifier at EOF triggers it — e.g.
%token Afollowed by
%%andS: Awith no final newline.Root cause
getrunekeeps EOF sticky in the single-slotpeekrunebuffer: whenpeekrune == EOFit returns EOF without clearing the slot (so EOF stays pushed back for the next
reader). But
gettok's IDENTIFIER/IDENTCOLON look-ahead (yacc.go:975-992) ends byungetting whatever rune it consumed — including EOF. If EOF is already sitting in
peekrune(left bygetwordungetting the identifier's terminator), the unget hitsthe
peekrune != 0guard inungetrune(yacc.go:3159-3161) and panics.Fix
Ungetting EOF is a no-op — an exhausted reader returns EOF again on the next read
regardless, so behavior for well-formed files is identical:
With the fix, the example above reports the proper
illegal rule: missing semicolon or |error and exits 1, and a valid grammar compiles to identical output. I havethe patch ready with the repro cases verified (crash pre-fix, clean errors post-fix,
valid grammar byte-identical).
Duplicate check
Searched golang/go issues for "ungetc - 2nd unget" and goyacc panic reports: no hits.