From 60dddc6db1e2796f6af1affbec0d9bbe22354be7 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Thu, 2 Jun 2011 10:51:31 +1000 Subject: [PATCH] fmt: return EOF when out of input in Scan*. Fixes #1840. R=rsc CC=golang-dev https://golang.org/cl/4548077 --- src/pkg/fmt/scan.go | 40 +++++++++++++++++++------- src/pkg/fmt/scan_test.go | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/pkg/fmt/scan.go b/src/pkg/fmt/scan.go index 33ff87182f..2aade027bb 100644 --- a/src/pkg/fmt/scan.go +++ b/src/pkg/fmt/scan.go @@ -466,6 +466,14 @@ func (s *ss) peek(ok string) bool { return strings.IndexRune(ok, rune) >= 0 } +func (s *ss) notEOF() { + // Guarantee there is data to be read. + if rune := s.getRune(); rune == eof { + panic(os.EOF) + } + s.UnreadRune() +} + // accept checks the next rune in the input. If it's a byte (sic) in the string, it puts it in the // buffer and returns true. Otherwise it return false. func (s *ss) accept(ok string) bool { @@ -485,11 +493,13 @@ func (s *ss) okVerb(verb int, okVerbs, typ string) bool { // scanBool returns the value of the boolean represented by the next token. func (s *ss) scanBool(verb int) bool { + s.skipSpace(false) + s.notEOF() if !s.okVerb(verb, "tv", "boolean") { return false } // Syntax-checking a boolean is annoying. We're not fastidious about case. - switch s.mustReadRune() { + switch s.getRune() { case '0': return false case '1': @@ -540,6 +550,7 @@ func (s *ss) getBase(verb int) (base int, digits string) { // scanNumber returns the numerical string with specified digits starting here. func (s *ss) scanNumber(digits string, haveDigits bool) string { + s.notEOF() if !haveDigits && !s.accept(digits) { s.errorString("expected integer") } @@ -550,7 +561,8 @@ func (s *ss) scanNumber(digits string, haveDigits bool) string { // scanRune returns the next rune value in the input. func (s *ss) scanRune(bitSize int) int64 { - rune := int64(s.mustReadRune()) + s.notEOF() + rune := int64(s.getRune()) n := uint(bitSize) x := (rune << (64 - n)) >> (64 - n) if x != rune { @@ -584,6 +596,7 @@ func (s *ss) scanInt(verb int, bitSize int) int64 { return s.scanRune(bitSize) } s.skipSpace(false) + s.notEOF() base, digits := s.getBase(verb) haveDigits := false if verb == 'U' { @@ -616,6 +629,7 @@ func (s *ss) scanUint(verb int, bitSize int) uint64 { return uint64(s.scanRune(bitSize)) } s.skipSpace(false) + s.notEOF() base, digits := s.getBase(verb) haveDigits := false if verb == 'U' { @@ -736,6 +750,7 @@ func (s *ss) scanComplex(verb int, n int) complex128 { return 0 } s.skipSpace(false) + s.notEOF() sreal, simag := s.complexTokens() real := s.convertFloat(sreal, n/2) imag := s.convertFloat(simag, n/2) @@ -749,6 +764,7 @@ func (s *ss) convertString(verb int) (str string) { return "" } s.skipSpace(false) + s.notEOF() switch verb { case 'q': str = s.quotedString() @@ -757,16 +773,13 @@ func (s *ss) convertString(verb int) (str string) { default: str = string(s.token(true, notSpace)) // %s and %v just return the next word } - // Empty strings other than with %q are not OK. - if len(str) == 0 && verb != 'q' && s.maxWid > 0 { - s.errorString("Scan: no data for string") - } return } // quotedString returns the double- or back-quoted string represented by the next input characters. func (s *ss) quotedString() string { - quote := s.mustReadRune() + s.notEOF() + quote := s.getRune() switch quote { case '`': // Back-quoted: Anything goes until EOF or back quote. @@ -836,6 +849,7 @@ func (s *ss) hexByte() (b byte, ok bool) { // hexString returns the space-delimited hexpair-encoded string. func (s *ss) hexString() string { + s.notEOF() for { b, ok := s.hexByte() if !ok { @@ -869,6 +883,7 @@ func (s *ss) scanOne(verb int, field interface{}) { } return } + switch v := field.(type) { case *bool: *v = s.scanBool(verb) @@ -903,11 +918,13 @@ func (s *ss) scanOne(verb int, field interface{}) { case *float32: if s.okVerb(verb, floatVerbs, "float32") { s.skipSpace(false) + s.notEOF() *v = float32(s.convertFloat(s.floatToken(), 32)) } case *float64: if s.okVerb(verb, floatVerbs, "float64") { s.skipSpace(false) + s.notEOF() *v = s.convertFloat(s.floatToken(), 64) } case *string: @@ -945,6 +962,7 @@ func (s *ss) scanOne(verb int, field interface{}) { } case reflect.Float32, reflect.Float64: s.skipSpace(false) + s.notEOF() v.SetFloat(s.convertFloat(s.floatToken(), v.Type().Bits())) case reflect.Complex64, reflect.Complex128: v.SetComplex(s.scanComplex(verb, v.Type().Bits())) @@ -955,13 +973,13 @@ func (s *ss) scanOne(verb int, field interface{}) { } } -// errorHandler turns local panics into error returns. EOFs are benign. +// errorHandler turns local panics into error returns. func errorHandler(errp *os.Error) { if e := recover(); e != nil { if se, ok := e.(scanError); ok { // catch local error - if se.err != os.EOF { - *errp = se.err - } + *errp = se.err + } else if eof, ok := e.(os.Error); ok && eof == os.EOF { // out of input + *errp = eof } else { panic(e) } diff --git a/src/pkg/fmt/scan_test.go b/src/pkg/fmt/scan_test.go index da13eb2d11..a4de8adb15 100644 --- a/src/pkg/fmt/scan_test.go +++ b/src/pkg/fmt/scan_test.go @@ -660,6 +660,68 @@ func TestEOF(t *testing.T) { } } +// Verify that we see an EOF error if we run out of input. +// This was a buglet: we used to get "expected integer". +func TestEOFAtEndOfInput(t *testing.T) { + var i, j int + n, err := Sscanf("23", "%d %d", &i, &j) + if n != 1 || i != 23 { + t.Errorf("Sscanf expected one value of 23; got %d %d", n, i) + } + if err != os.EOF { + t.Errorf("Sscanf expected EOF; got %q", err) + } + n, err = Sscan("234", &i, &j) + if n != 1 || i != 234 { + t.Errorf("Sscan expected one value of 234; got %d %d", n, i) + } + if err != os.EOF { + t.Errorf("Sscan expected EOF; got %q", err) + } + // Trailing space is tougher. + n, err = Sscan("234 ", &i, &j) + if n != 1 || i != 234 { + t.Errorf("Sscan expected one value of 234; got %d %d", n, i) + } + if err != os.EOF { + t.Errorf("Sscan expected EOF; got %q", err) + } +} + +var eofTests = []struct { + format string + v interface{} +}{ + {"%s", &stringVal}, + {"%q", &stringVal}, + {"%x", &stringVal}, + {"%v", &stringVal}, + {"%v", &bytesVal}, + {"%v", &intVal}, + {"%v", &uintVal}, + {"%v", &boolVal}, + {"%v", &float32Val}, + {"%v", &complex64Val}, + {"%v", &renamedStringVal}, + {"%v", &renamedBytesVal}, + {"%v", &renamedIntVal}, + {"%v", &renamedUintVal}, + {"%v", &renamedBoolVal}, + {"%v", &renamedFloat32Val}, + {"%v", &renamedComplex64Val}, +} + +func TestEOFAllTypes(t *testing.T) { + for i, test := range eofTests { + if _, err := Sscanf("", test.format, test.v); err != os.EOF { + t.Errorf("#%d: %s %T not eof on empty string: %s", i, test.format, test.v, err) + } + if _, err := Sscanf(" ", test.format, test.v); err != os.EOF { + t.Errorf("#%d: %s %T not eof on trailing blanks: %s", i, test.format, test.v, err) + } + } +} + // Verify that, at least when using bufio, successive calls to Fscan do not lose runes. func TestUnreadRuneWithBufio(t *testing.T) { r := bufio.NewReader(strings.NewReader("123αb"))