mirror of
https://github.com/golang/go
synced 2024-11-12 09:30:25 -07:00
text/template: error on method calls on nil interfaces
Trying to call a method on a nil interface is a panic in Go. For example: var stringer fmt.Stringer println(stringer.String()) // nil pointer dereference In https://golang.org/cl/143097 we started recovering panics encountered during function and method calls. However, we didn't handle this case, as text/template panics before evalCall is ever run. In particular, reflect's MethodByName will panic if the receiver is of interface kind and nil: panic: reflect: Method on nil interface value Simply add a check for that edge case, and have Template.Execute return a helpful error. Note that Execute shouldn't just error if the interface contains a typed nil, since we're able to find a method to call in that case. Finally, add regression tests for both the nil and typed nil interface cases. Fixes #30143. Change-Id: Iffb21b40e14ba5fea0fcdd179cd80d1f23cabbab Reviewed-on: https://go-review.googlesource.com/c/161761 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
This commit is contained in:
parent
acf786f4fb
commit
15b4c71a91
@ -576,6 +576,13 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, isNil := indirect(receiver)
|
||||
if receiver.Kind() == reflect.Interface && isNil {
|
||||
// Calling a method on a nil interface can't work. The
|
||||
// MethodByName method call below would panic.
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
return zero
|
||||
}
|
||||
|
||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||
// we see all methods of T and *T.
|
||||
ptr := receiver
|
||||
|
@ -58,8 +58,10 @@ type T struct {
|
||||
Empty3 interface{}
|
||||
Empty4 interface{}
|
||||
// Non-empty interfaces.
|
||||
NonEmptyInterface I
|
||||
NonEmptyInterfacePtS *I
|
||||
NonEmptyInterface I
|
||||
NonEmptyInterfacePtS *I
|
||||
NonEmptyInterfaceNil I
|
||||
NonEmptyInterfaceTypedNil I
|
||||
// Stringer.
|
||||
Str fmt.Stringer
|
||||
Err error
|
||||
@ -141,24 +143,25 @@ var tVal = &T{
|
||||
{"one": 1, "two": 2},
|
||||
{"eleven": 11, "twelve": 12},
|
||||
},
|
||||
Empty1: 3,
|
||||
Empty2: "empty2",
|
||||
Empty3: []int{7, 8},
|
||||
Empty4: &U{"UinEmpty"},
|
||||
NonEmptyInterface: &T{X: "x"},
|
||||
NonEmptyInterfacePtS: &siVal,
|
||||
Str: bytes.NewBuffer([]byte("foozle")),
|
||||
Err: errors.New("erroozle"),
|
||||
PI: newInt(23),
|
||||
PS: newString("a string"),
|
||||
PSI: newIntSlice(21, 22, 23),
|
||||
BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
|
||||
VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
|
||||
VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
|
||||
NilOKFunc: func(s *int) bool { return s == nil },
|
||||
ErrFunc: func() (string, error) { return "bla", nil },
|
||||
PanicFunc: func() string { panic("test panic") },
|
||||
Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
|
||||
Empty1: 3,
|
||||
Empty2: "empty2",
|
||||
Empty3: []int{7, 8},
|
||||
Empty4: &U{"UinEmpty"},
|
||||
NonEmptyInterface: &T{X: "x"},
|
||||
NonEmptyInterfacePtS: &siVal,
|
||||
NonEmptyInterfaceTypedNil: (*T)(nil),
|
||||
Str: bytes.NewBuffer([]byte("foozle")),
|
||||
Err: errors.New("erroozle"),
|
||||
PI: newInt(23),
|
||||
PS: newString("a string"),
|
||||
PSI: newIntSlice(21, 22, 23),
|
||||
BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
|
||||
VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
|
||||
VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
|
||||
NilOKFunc: func(s *int) bool { return s == nil },
|
||||
ErrFunc: func() (string, error) { return "bla", nil },
|
||||
PanicFunc: func() string { panic("test panic") },
|
||||
Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
|
||||
}
|
||||
|
||||
var tSliceOfNil = []*T{nil}
|
||||
@ -365,6 +368,7 @@ var execTests = []execTest{
|
||||
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
|
||||
{"method on typed nil interface value", "{{.NonEmptyInterfaceTypedNil.Method0}}", "M0", tVal, true},
|
||||
|
||||
// Function call builtin.
|
||||
{".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true},
|
||||
@ -1557,6 +1561,11 @@ func TestExecutePanicDuringCall(t *testing.T) {
|
||||
"{{call .PanicFunc}}", tVal,
|
||||
`template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`,
|
||||
},
|
||||
{
|
||||
"method call on nil interface",
|
||||
"{{.NonEmptyInterfaceNil.Method0}}", tVal,
|
||||
`template: t:1:23: executing "t" at <.NonEmptyInterfaceNil.Method0>: nil pointer evaluating template.I.Method0`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
b := new(bytes.Buffer)
|
||||
|
Loading…
Reference in New Issue
Block a user