Skip to content

Commit ad8ebb9

Browse files
mvdanbradfitz
authored andcommitted
[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í <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> (cherry picked from commit 15b4c71) Reviewed-on: https://go-review.googlesource.com/c/go/+/164457 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 6fc1242 commit ad8ebb9

File tree

2 files changed

+36
-20
lines changed

2 files changed

+36
-20
lines changed

src/text/template/exec.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,13 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
576576
}
577577
typ := receiver.Type()
578578
receiver, isNil := indirect(receiver)
579+
if receiver.Kind() == reflect.Interface && isNil {
580+
// Calling a method on a nil interface can't work. The
581+
// MethodByName method call below would panic.
582+
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
583+
return zero
584+
}
585+
579586
// Unless it's an interface, need to get to a value of type *T to guarantee
580587
// we see all methods of T and *T.
581588
ptr := receiver

src/text/template/exec_test.go

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ type T struct {
5858
Empty3 interface{}
5959
Empty4 interface{}
6060
// Non-empty interfaces.
61-
NonEmptyInterface I
62-
NonEmptyInterfacePtS *I
61+
NonEmptyInterface I
62+
NonEmptyInterfacePtS *I
63+
NonEmptyInterfaceNil I
64+
NonEmptyInterfaceTypedNil I
6365
// Stringer.
6466
Str fmt.Stringer
6567
Err error
@@ -141,24 +143,25 @@ var tVal = &T{
141143
{"one": 1, "two": 2},
142144
{"eleven": 11, "twelve": 12},
143145
},
144-
Empty1: 3,
145-
Empty2: "empty2",
146-
Empty3: []int{7, 8},
147-
Empty4: &U{"UinEmpty"},
148-
NonEmptyInterface: &T{X: "x"},
149-
NonEmptyInterfacePtS: &siVal,
150-
Str: bytes.NewBuffer([]byte("foozle")),
151-
Err: errors.New("erroozle"),
152-
PI: newInt(23),
153-
PS: newString("a string"),
154-
PSI: newIntSlice(21, 22, 23),
155-
BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
156-
VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
157-
VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
158-
NilOKFunc: func(s *int) bool { return s == nil },
159-
ErrFunc: func() (string, error) { return "bla", nil },
160-
PanicFunc: func() string { panic("test panic") },
161-
Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
146+
Empty1: 3,
147+
Empty2: "empty2",
148+
Empty3: []int{7, 8},
149+
Empty4: &U{"UinEmpty"},
150+
NonEmptyInterface: &T{X: "x"},
151+
NonEmptyInterfacePtS: &siVal,
152+
NonEmptyInterfaceTypedNil: (*T)(nil),
153+
Str: bytes.NewBuffer([]byte("foozle")),
154+
Err: errors.New("erroozle"),
155+
PI: newInt(23),
156+
PS: newString("a string"),
157+
PSI: newIntSlice(21, 22, 23),
158+
BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
159+
VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
160+
VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
161+
NilOKFunc: func(s *int) bool { return s == nil },
162+
ErrFunc: func() (string, error) { return "bla", nil },
163+
PanicFunc: func() string { panic("test panic") },
164+
Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
162165
}
163166

164167
var tSliceOfNil = []*T{nil}
@@ -365,6 +368,7 @@ var execTests = []execTest{
365368
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
366369
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
367370
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
371+
{"method on typed nil interface value", "{{.NonEmptyInterfaceTypedNil.Method0}}", "M0", tVal, true},
368372

369373
// Function call builtin.
370374
{".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true},
@@ -1492,6 +1496,11 @@ func TestExecutePanicDuringCall(t *testing.T) {
14921496
"{{call .PanicFunc}}", tVal,
14931497
`template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`,
14941498
},
1499+
{
1500+
"method call on nil interface",
1501+
"{{.NonEmptyInterfaceNil.Method0}}", tVal,
1502+
`template: t:1:23: executing "t" at <.NonEmptyInterfaceNil.Method0>: nil pointer evaluating template.I.Method0`,
1503+
},
14951504
}
14961505
for _, tc := range tests {
14971506
b := new(bytes.Buffer)

0 commit comments

Comments
 (0)