aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Martí <mvdan@mvdan.cc>2019-02-09 17:50:02 +0000
committerBrad Fitzpatrick <bradfitz@golang.org>2019-03-13 21:00:26 +0000
commitad8ebb9e85131aa1f37664b79f6f02415ba1a6e6 (patch)
tree958e83158a89b8b56f4bf236c4b6581a2d1cd44a
parent6fc1242ea8f8bbacd88cb834ef61dbe9e84a8514 (diff)
downloadgo-ad8ebb9e85131aa1f37664b79f6f02415ba1a6e6.tar.gz
go-ad8ebb9e85131aa1f37664b79f6f02415ba1a6e6.zip
[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>
-rw-r--r--src/text/template/exec.go7
-rw-r--r--src/text/template/exec_test.go49
2 files changed, 36 insertions, 20 deletions
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 <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)