go.tools/cmd/vet: check indexed arguments in printf

Refactor the printf parser to be easier to understand.

R=gri
CC=golang-dev
https://golang.org/cl/9909043
This commit is contained in:
Rob Pike 2013-05-31 16:31:01 -04:00
Родитель be28dbb86f
Коммит 838e9a8987
2 изменённых файлов: 139 добавлений и 48 удалений

Просмотреть файл

@ -104,13 +104,21 @@ func (f *File) literal(value ast.Expr) *ast.BasicLit {
return nil
}
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d"
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
// It is constructed by parsePrintfVerb.
type formatState struct {
verb rune // the format verb: 'd' for "%d"
format string // the full format string
flags []byte // the list of # + etc.
argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call
nbytes int // number of bytes of the format string consumed.
indexed bool // whether an indexing expression appears: %[1]d.
// Used only during parse.
file *File
call *ast.CallExpr
firstArg int // Index of first argument after the format in the Printf call.
argNum int // Which argument we're expecting to format now.
indexPending bool // Whether we have an indexed argument that has not resolved.
}
// checkPrintf checks a call to a formatted print routine such as Printf.
@ -147,7 +155,10 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, formatIndex int) {
for i, w := 0, 0; i < len(format); i += w {
w = 1
if format[i] == '%' {
state := f.parsePrintfVerb(call, format[i:], argNum)
state := f.parsePrintfVerb(call, format[i:], firstArg, argNum)
if state == nil {
return
}
w = state.nbytes
if state.indexed {
indexed = true
@ -172,60 +183,128 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string, formatIndex int) {
}
}
// parsePrintfVerb returns the verb that begins the format string, along with its flags,
// the number of bytes to advance the format to step past the verb, and number of
// arguments it consumes. It returns a formatState that encodes what the formatting
// directive wants, without looking at the actual arguments present in the call.
func (f *File) parsePrintfVerb(call *ast.CallExpr, format string, argNum int) *formatState {
// There's guaranteed a percent sign.
flags := make([]byte, 0, 5)
nbytes := 1
end := len(format)
// There may be flags.
FlagLoop:
for nbytes < end {
switch format[nbytes] {
// parseFlags accepts any printf flags.
func (s *formatState) parseFlags() {
for s.nbytes < len(s.format) {
switch c := s.format[s.nbytes]; c {
case '#', '0', '+', '-', ' ':
flags = append(flags, format[nbytes])
nbytes++
s.flags = append(s.flags, c)
s.nbytes++
default:
break FlagLoop
return
}
}
argNums := make([]int, 0, 1)
getNum := func() {
if nbytes < end && format[nbytes] == '*' {
nbytes++
argNums = append(argNums, argNum)
argNum++
} else {
for nbytes < end && '0' <= format[nbytes] && format[nbytes] <= '9' {
nbytes++
}
}
// scanNum advances through a decimal number if present.
func (s *formatState) scanNum() {
for ; s.nbytes < len(s.format); s.nbytes++ {
c := s.format[s.nbytes]
if c < '0' || '9' < c {
return
}
}
}
// parseIndex scans an index expression. It returns false if there is a syntax error.
func (s *formatState) parseIndex() bool {
if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
return true
}
// Argument index present.
s.indexed = true
s.nbytes++ // skip '['
start := s.nbytes
s.scanNum()
if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index")
return false
}
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
if err != nil {
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index: %s", err)
return false
}
s.nbytes++ // skip ']'
arg := int(arg32)
arg += s.firstArg - 1 // We want to zero-index the actual arguments.
s.argNum = arg
s.indexPending = true
return true
}
// parseNum scans a width or precision (or *). It returns false if there's a bad index expression.
func (s *formatState) parseNum() bool {
if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
if s.indexPending { // Absorb it.
s.indexPending = false
}
s.nbytes++
s.argNums = append(s.argNums, s.argNum)
s.argNum++
} else {
s.scanNum()
}
return true
}
// parsePrecision scans for a precision. It returns false if there's a bad index expression.
func (s *formatState) parsePrecision() bool {
// If there's a period, there may be a precision.
if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
s.flags = append(s.flags, '.') // Treat precision as a flag.
s.nbytes++
if !s.parseIndex() {
return false
}
if !s.parseNum() {
return false
}
}
return true
}
// parsePrintfVerb looks the formatting directive that begins the format string
// and returns a formatState that encodes what the directive wants, without looking
// at the actual arguments present in the call. The result is nil if there is an error.
func (f *File) parsePrintfVerb(call *ast.CallExpr, format string, firstArg, argNum int) *formatState {
state := &formatState{
format: format,
flags: make([]byte, 0, 5),
argNum: argNum,
argNums: make([]int, 0, 1),
nbytes: 1, // There's guaranteed to be a percent sign.
indexed: false,
firstArg: firstArg,
file: f,
call: call,
}
// There may be flags.
state.parseFlags()
indexPending := false
// There may be an index.
if !state.parseIndex() {
return nil
}
// There may be a width.
getNum()
// If there's a period, there may be a precision.
if nbytes < end && format[nbytes] == '.' {
flags = append(flags, '.') // Treat precision as a flag.
nbytes++
getNum()
if !state.parseNum() {
return nil
}
// Now a verb.
verb, w := utf8.DecodeRuneInString(format[nbytes:])
nbytes += w
// There may be a precision.
if !state.parsePrecision() {
return nil
}
// Now a verb, possibly prefixed by an index (which we may already have).
if !indexPending && !state.parseIndex() {
return nil
}
verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
state.verb = verb
state.nbytes += w
if verb != '%' {
argNums = append(argNums, argNum)
argNum++
}
return &formatState{
verb: verb,
flags: flags,
argNums: argNums,
nbytes: nbytes,
indexed: false, // TODO
state.argNums = append(state.argNums, state.argNum)
}
return state
}
// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.

16
cmd/vet/testdata/print.go поставляемый
Просмотреть файл

@ -8,6 +8,7 @@ package testdata
import (
"fmt"
"os"
"unsafe" // just for test case printing unsafe.Pointer
)
@ -120,6 +121,17 @@ func PrintfTests() {
f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args for format in Warnf call"
f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb"
f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag"
// Good argument reorderings.
Printf("%[1]d", 3)
Printf("%[1]*d", 3, 1)
Printf("%[2]*[1]d", 1, 3)
Printf("%[2]*.[1]*[3]d", 2, 3, 4)
fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly.
// Bad argument reorderings.
Printf("%[xd", 3) // ERROR "illegal syntax for printf argument index"
Printf("%[x]d", 3) // ERROR "illegal syntax for printf argument index"
Printf("%[2]d", 3) // ERROR "wrong number of args for format in Printf call"
Printf("%[2]*.[1]*[3]d", 2, "hi", 4) // ERROR "arg .hi. for \* in printf format not of type int"
// Something that satisfies the error interface.
var e error
fmt.Println(e.Error()) // ok
@ -141,8 +153,8 @@ func PrintfTests() {
et5.error() // ok, not an error method.
}
// printf is used by the test.
func printf(format string, args ...interface{}) {
// Printf is used by the test so we must declare it.
func Printf(format string, args ...interface{}) {
panic("don't call - testing only")
}