Commit 4e834cff authored by Todd Neal's avatar Todd Neal Committed by Rob Pike

fmt: handle negative width/prec when supplied as an argument

Negative width arguments now left align the way a minus-width in the
format string aligns. The minus in the format string overrides the sign
of the argument as in C.

Precision behavior is modified to include an error if the argument is
negative.  This differs from a negative precision in a format string
which just terminates the format.

Additional checks for large magnitude widths and precisions are added to
make the runtime behavior (failure, but with different error messages),
more consistent between format string specified width/precision and
argument specified width/precision.

Fixes #11376

Change-Id: I8c7ed21088e9c18128a45d4c487c5ab9fafd13ef
Reviewed-on: https://go-review.googlesource.com/11405Reviewed-by: default avatarRob Pike <r@golang.org>
Run-TryBot: Rob Pike <r@golang.org>
parent 2bcdb5a5
...@@ -832,6 +832,10 @@ var reorderTests = []struct { ...@@ -832,6 +832,10 @@ var reorderTests = []struct {
{"%[5]d %[2]d %d", SE{1, 2, 3}, "%!d(BADINDEX) 2 3"}, {"%[5]d %[2]d %d", SE{1, 2, 3}, "%!d(BADINDEX) 2 3"},
{"%d %[3]d %d", SE{1, 2}, "1 %!d(BADINDEX) 2"}, // Erroneous index does not affect sequence. {"%d %[3]d %d", SE{1, 2}, "1 %!d(BADINDEX) 2"}, // Erroneous index does not affect sequence.
{"%.[]", SE{}, "%!](BADINDEX)"}, // Issue 10675 {"%.[]", SE{}, "%!](BADINDEX)"}, // Issue 10675
{"%.-3d", SE{42}, "%!-(int=42)3d"}, // TODO: Should this set return better error messages?
{"%2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
{"%-2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
{"%.2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
} }
func TestReorder(t *testing.T) { func TestReorder(t *testing.T) {
...@@ -1158,14 +1162,20 @@ var startests = []struct { ...@@ -1158,14 +1162,20 @@ var startests = []struct {
out string out string
}{ }{
{"%*d", args(4, 42), " 42"}, {"%*d", args(4, 42), " 42"},
{"%-*d", args(4, 42), "42 "},
{"%*d", args(-4, 42), "42 "},
{"%-*d", args(-4, 42), "42 "},
{"%.*d", args(4, 42), "0042"}, {"%.*d", args(4, 42), "0042"},
{"%*.*d", args(8, 4, 42), " 0042"}, {"%*.*d", args(8, 4, 42), " 0042"},
{"%0*d", args(4, 42), "0042"}, {"%0*d", args(4, 42), "0042"},
{"%-*d", args(4, 42), "42 "},
// erroneous // erroneous
{"%*d", args(nil, 42), "%!(BADWIDTH)42"}, {"%*d", args(nil, 42), "%!(BADWIDTH)42"},
{"%*d", args(int(1e7), 42), "%!(BADWIDTH)42"},
{"%*d", args(int(-1e7), 42), "%!(BADWIDTH)42"},
{"%.*d", args(nil, 42), "%!(BADPREC)42"}, {"%.*d", args(nil, 42), "%!(BADPREC)42"},
{"%.*d", args(-1, 42), "%!(BADPREC)42"},
{"%.*d", args(int(1e7), 42), "%!(BADPREC)42"},
{"%*d", args(5, "foo"), "%!d(string= foo)"}, {"%*d", args(5, "foo"), "%!d(string= foo)"},
{"%*% %d", args(20, 5), "% 5"}, {"%*% %d", args(20, 5), "% 5"},
{"%*", args(4), "%!(NOVERB)"}, {"%*", args(4), "%!(NOVERB)"},
......
...@@ -285,15 +285,20 @@ func getField(v reflect.Value, i int) reflect.Value { ...@@ -285,15 +285,20 @@ func getField(v reflect.Value, i int) reflect.Value {
return val return val
} }
// tooLarge reports whether the magnitude of the integer is
// too large to be used as a formatting width or precision.
func tooLarge(x int) bool {
const max int = 1e6
return x > max || x < -max
}
// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. // parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present.
func parsenum(s string, start, end int) (num int, isnum bool, newi int) { func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
if start >= end { if start >= end {
return 0, false, end return 0, false, end
} }
for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ {
const maxInt32 = 1<<31 - 1 // 31 bits is plenty for a width. if tooLarge(num) {
max := maxInt32/10 - 1
if num > max {
return 0, false, end // Overflow; crazy long number most likely. return 0, false, end // Overflow; crazy long number most likely.
} }
num = num*10 + int(s[newi]-'0') num = num*10 + int(s[newi]-'0')
...@@ -1025,6 +1030,10 @@ func intFromArg(a []interface{}, argNum int) (num int, isInt bool, newArgNum int ...@@ -1025,6 +1030,10 @@ func intFromArg(a []interface{}, argNum int) (num int, isInt bool, newArgNum int
if argNum < len(a) { if argNum < len(a) {
num, isInt = a[argNum].(int) num, isInt = a[argNum].(int)
newArgNum = argNum + 1 newArgNum = argNum + 1
if tooLarge(num) {
num = 0
isInt = false
}
} }
return return
} }
...@@ -1119,9 +1128,17 @@ func (p *pp) doPrintf(format string, a []interface{}) { ...@@ -1119,9 +1128,17 @@ func (p *pp) doPrintf(format string, a []interface{}) {
if i < end && format[i] == '*' { if i < end && format[i] == '*' {
i++ i++
p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum) p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum)
if !p.fmt.widPresent { if !p.fmt.widPresent {
p.buf.Write(badWidthBytes) p.buf.Write(badWidthBytes)
} }
// We have a negative width, so take its value and ensure
// that the minus flag is set
if p.fmt.wid < 0 {
p.fmt.wid = -p.fmt.wid
p.fmt.minus = true
}
afterIndex = false afterIndex = false
} else { } else {
p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
...@@ -1140,6 +1157,11 @@ func (p *pp) doPrintf(format string, a []interface{}) { ...@@ -1140,6 +1157,11 @@ func (p *pp) doPrintf(format string, a []interface{}) {
if i < end && format[i] == '*' { if i < end && format[i] == '*' {
i++ i++
p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum) p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum)
// Negative precision arguments don't make sense
if p.fmt.prec < 0 {
p.fmt.prec = 0
p.fmt.precPresent = false
}
if !p.fmt.precPresent { if !p.fmt.precPresent {
p.buf.Write(badPrecBytes) p.buf.Write(badPrecBytes)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment