From: Daniel Martí Date: Sat, 9 Feb 2019 17:50:02 +0000 (+0000) Subject: [release-branch.go1.12] text/template: error on method calls on nil interfaces X-Git-Tag: go1.12.1~6 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ad8ebb9e85131aa1f37664b79f6f02415ba1a6e6;p=gostls13.git [release-branch.go1.12] 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 #30464. Change-Id: Iffb21b40e14ba5fea0fcdd179cd80d1f23cabbab Reviewed-on: https://go-review.googlesource.com/c/161761 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Emmanuel Odeke (cherry picked from commit 15b4c71a912846530315c3e854feaaa9d0d54220) Reviewed-on: https://go-review.googlesource.com/c/go/+/164457 Reviewed-by: Brad Fitzpatrick --- diff --git a/src/text/template/exec.go b/src/text/template/exec.go index c6ce657cf6..76cce1f52f 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -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 diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index bfd6d38bf4..d0f73103f4 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -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}, @@ -1492,6 +1496,11 @@ func TestExecutePanicDuringCall(t *testing.T) { "{{call .PanicFunc}}", tVal, `template: t:1:2: executing "t" at : 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)