Commit ad8ebb9e authored by Daniel Martí's avatar Daniel Martí Committed by Brad Fitzpatrick

[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í <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarEmmanuel Odeke <emm.odeke@gmail.com>
(cherry picked from commit 15b4c71a)
Reviewed-on: https://go-review.googlesource.com/c/go/+/164457Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 6fc1242e
...@@ -576,6 +576,13 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, ...@@ -576,6 +576,13 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
} }
typ := receiver.Type() typ := receiver.Type()
receiver, isNil := indirect(receiver) 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 // Unless it's an interface, need to get to a value of type *T to guarantee
// we see all methods of T and *T. // we see all methods of T and *T.
ptr := receiver ptr := receiver
......
...@@ -60,6 +60,8 @@ type T struct { ...@@ -60,6 +60,8 @@ type T struct {
// Non-empty interfaces. // Non-empty interfaces.
NonEmptyInterface I NonEmptyInterface I
NonEmptyInterfacePtS *I NonEmptyInterfacePtS *I
NonEmptyInterfaceNil I
NonEmptyInterfaceTypedNil I
// Stringer. // Stringer.
Str fmt.Stringer Str fmt.Stringer
Err error Err error
...@@ -147,6 +149,7 @@ var tVal = &T{ ...@@ -147,6 +149,7 @@ var tVal = &T{
Empty4: &U{"UinEmpty"}, Empty4: &U{"UinEmpty"},
NonEmptyInterface: &T{X: "x"}, NonEmptyInterface: &T{X: "x"},
NonEmptyInterfacePtS: &siVal, NonEmptyInterfacePtS: &siVal,
NonEmptyInterfaceTypedNil: (*T)(nil),
Str: bytes.NewBuffer([]byte("foozle")), Str: bytes.NewBuffer([]byte("foozle")),
Err: errors.New("erroozle"), Err: errors.New("erroozle"),
PI: newInt(23), PI: newInt(23),
...@@ -365,6 +368,7 @@ var execTests = []execTest{ ...@@ -365,6 +368,7 @@ var execTests = []execTest{
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true}, {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", 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 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. // Function call builtin.
{".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true}, {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true},
...@@ -1492,6 +1496,11 @@ func TestExecutePanicDuringCall(t *testing.T) { ...@@ -1492,6 +1496,11 @@ func TestExecutePanicDuringCall(t *testing.T) {
"{{call .PanicFunc}}", tVal, "{{call .PanicFunc}}", tVal,
`template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`, `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 { for _, tc := range tests {
b := new(bytes.Buffer) b := new(bytes.Buffer)
......
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