}
argv[i] = s.validateType(final, t)
}
- result := fun.Call(argv)
- // If we have an error that is not nil, stop execution and return that error to the caller.
- if len(result) == 2 && !result[1].IsNil() {
+ v, err := safeCall(fun, argv)
+ // If we have an error that is not nil, stop execution and return that
+ // error to the caller.
+ if err != nil {
s.at(node)
- s.errorf("error calling %s: %s", name, result[1].Interface().(error))
+ s.errorf("error calling %s: %v", name, err)
}
- v := result[0]
if v.Type() == reflectValueType {
v = v.Interface().(reflect.Value)
}
VariadicFuncInt func(int, ...string) string
NilOKFunc func(*int) bool
ErrFunc func() (string, error)
+ PanicFunc func() string
// Template to test evaluation of templates.
Tmpl *Template
// Unexported field; cannot be accessed by template.
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
}
}
}
}
+
+// Check that panics during calls are recovered and returned as errors.
+func TestExecutePanicDuringCall(t *testing.T) {
+ funcs := map[string]interface{}{
+ "doPanic": func() string {
+ panic("custom panic string")
+ },
+ }
+ tests := []struct {
+ name string
+ input string
+ data interface{}
+ wantErr string
+ }{
+ {
+ "direct func call panics",
+ "{{doPanic}}", (*T)(nil),
+ `template: t:1:2: executing "t" at <doPanic>: error calling doPanic: custom panic string`,
+ },
+ {
+ "indirect func call panics",
+ "{{call doPanic}}", (*T)(nil),
+ `template: t:1:7: executing "t" at <doPanic>: error calling doPanic: custom panic string`,
+ },
+ {
+ "direct method call panics",
+ "{{.GetU}}", (*T)(nil),
+ `template: t:1:2: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`,
+ },
+ {
+ "indirect method call panics",
+ "{{call .GetU}}", (*T)(nil),
+ `template: t:1:7: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`,
+ },
+ {
+ "func field call panics",
+ "{{call .PanicFunc}}", tVal,
+ `template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`,
+ },
+ }
+ for _, tc := range tests {
+ b := new(bytes.Buffer)
+ tmpl, err := New("t").Funcs(funcs).Parse(tc.input)
+ if err != nil {
+ t.Fatalf("parse error: %s", err)
+ }
+ err = tmpl.Execute(b, tc.data)
+ if err == nil {
+ t.Errorf("%s: expected error; got none", tc.name)
+ } else if !strings.Contains(err.Error(), tc.wantErr) {
+ if *debug {
+ fmt.Printf("%s: test execute error: %s\n", tc.name, err)
+ }
+ t.Errorf("%s: expected error:\n%s\ngot:\n%s", tc.name, tc.wantErr, err)
+ }
+ }
+}
return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
}
}
- result := v.Call(argv)
- if len(result) == 2 && !result[1].IsNil() {
- return result[0], result[1].Interface().(error)
+ return safeCall(v, argv)
+}
+
+// safeCall runs fun.Call(args), and returns the resulting value and error, if
+// any. If the call panics, the panic value is returned as an error.
+func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ if e, ok := r.(error); ok {
+ err = e
+ } else {
+ err = fmt.Errorf("%v", r)
+ }
+ }
+ }()
+ ret := fun.Call(args)
+ if len(ret) == 2 && !ret[1].IsNil() {
+ return ret[0], ret[1].Interface().(error)
}
- return result[0], nil
+ return ret[0], nil
}
// Boolean logic.