]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.12] text/template: error on method calls on nil interfaces
authorDaniel Martí <mvdan@mvdan.cc>
Sat, 9 Feb 2019 17:50:02 +0000 (17:50 +0000)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 13 Mar 2019 21:00:26 +0000 (21:00 +0000)
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>
src/text/template/exec.go
src/text/template/exec_test.go

index c6ce657cf64effcb27766c1afc1eea2646ccb63f..76cce1f52f2d772329c0e272cdef83deefa002fc 100644 (file)
@@ -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
index bfd6d38bf42cfee7c91a2637994fc586dba787f8..d0f73103f461b2cd8ae082e3bec1d166d91bab66 100644 (file)
@@ -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)