[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: Emmanuel Odeke <emm.odeke@gmail.com>
(cherry picked from commit 15b4c71a912846530315c3e854feaaa9d0d54220)
Reviewed-on: https://go-review.googlesource.com/c/go/+/164457
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/text/template/exec.go b/src/text/template/exec.go
index c6ce657..76cce1f 100644
--- a/src/text/template/exec.go
+++ b/src/text/template/exec.go
@@ -576,6 +576,13 @@
 	}
 	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 bfd6d38..d0f7310 100644
--- a/src/text/template/exec_test.go
+++ b/src/text/template/exec_test.go
@@ -58,8 +58,10 @@
 	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 @@
 		{"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 @@
 	{".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 @@
 			"{{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)