Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

html.template: Panic when calling an uninitialized template function #17983

Closed
DrGo opened this issue Nov 18, 2016 · 11 comments
Closed

html.template: Panic when calling an uninitialized template function #17983

DrGo opened this issue Nov 18, 2016 · 11 comments

Comments

@DrGo
Copy link
Contributor

DrGo commented Nov 18, 2016

What version of Go are you using (go version)?

go1.7 darwin/amd64

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOOS="darwin"

What did you do?
Parse this template with function "Block"
Research Grants {{range Block "Research Funding History"}} {{.Emit "Funding Start Date, Funding End Date, Funding Title" }} {{range .SubSection "Funding Sources" }} {{.Emit "Program Name, Funding Organization" }} {{end}} {{end}}
then executing as follows
tm, err := template.New("test").Parse(templ3)
if err != nil {
t.Errorf("Parsing failed: %s", err)
}
err = tm.Execute(os.Stdout, ccv)
if err != nil {
t.Errorf("execution failed: %s", err)
}

What did you expect to see?
An error message that function does not exist

What did you see instead?
anic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0xed5d4]

goroutine 5 [running]:
panic(0x1a21c0, 0xc42000a090)
/Users/salah/go/src/runtime/panic.go:500 +0x1a1
testing.tRunner.func1(0xc420090180)
/Users/salah/go/src/testing/testing.go:579 +0x25d
panic(0x1a21c0, 0xc42000a090)
/Users/salah/go/src/runtime/panic.go:458 +0x243
html/template.(*Template).escape(0x0, 0x0, 0x0)
/Users/salah/go/src/html/template/template.go:79 +0x44
html/template.(*Template).Execute(0x0, 0x2a6840, 0xc420032010, 0x1a5d00, 0xc420018300, 0xc42000b240, 0x0)
/Users/salah/go/src/html/template/template.go:101 +0x2f
salah/CCV/template.TestGoTemplate(0xc420090180)
/Users/salah/Dropbox/code/go/src/salah/CCV/template/template_test.go:119 +0x279
testing.tRunner(0xc420090180, 0x1ec5a0)
/Users/salah/go/src/testing/testing.go:610 +0x81
created by testing.(*T).Run
/Users/salah/go/src/testing/testing.go:646 +0x2ec
exit status 2

@odeke-em
Copy link
Member

@DrGo your snippet at https://play.golang.org/p/bNEDlBZG_b doesn't compile
screen shot 2016-11-18 at 7 25 04 pm

@DrGo DrGo changed the title html.template: Panic when calling a unitialized function that has the same name as a struct html.template: Panic when calling an uninitialized template function Nov 19, 2016
@DrGo
Copy link
Contributor Author

DrGo commented Nov 19, 2016

@odeke-em Sorry, I edited the issue to report the original error on my machine. Sorry I cannot be of more help. I noticed that if I drop the function name as in
{{range "Research Funding History"}}
I get an error instead of panic
output:
template_test.go:124: execution failed: template: test:4:8: executing "test" at <"Research Funding Hi...>: range can't iterate over Research Funding History

Also the code works flawlessly if I added the Funcs calls as in
tm, err := template.New("test").Funcs(template.FuncMap{"Block": ccv.Block}).Parse(templ3)
output:
Research
Grants

    2014/9, 2019/8, Baseline funding to the Vaccine and Drug Evaluation Centre

            Public Health Division, Manitoba Health

PASS
ok salah/CCV/template 0.006s

@lifesoftserv
Copy link

so closed?

@DrGo
Copy link
Contributor Author

DrGo commented Nov 19, 2016

No, html. Execute should not panic (see original post for details)

@robpike
Copy link
Contributor

robpike commented Nov 19, 2016

Please provide a complete self-contained example that reproduces the problem. You have shown us only incomplete or incorrect programs.

@DrGo
Copy link
Contributor Author

DrGo commented Nov 19, 2016

I cannot share my program, but I managed to replicate the error here
https://play.golang.org/p/hmwSb9Ipds

@odeke-em
Copy link
Member

Okay the problem here is that you are ignoring the error returned that actually says the function is undefined, you just print it but you aren't actually handling it.

--- orig.go 2016-11-19 11:41:22.000000000 -0800
+++ handled.go  2016-11-19 11:41:34.000000000 -0800
@@ -3,6 +3,7 @@
 import (
    "fmt"
    "html/template"
+   "log"
    "os"
 )

@@ -34,7 +35,7 @@
    ccv := &CCV{}
    tm, err := template.New("test").Funcs(template.FuncMap{"List": ccv.List}).Parse(templ3)
    if err != nil {
-       fmt.Printf("Parsing failed: %s", err)
+       log.Fatalf("Parsing failed: %s", err)
    }
    err = tm.Execute(os.Stdout, ccv)
    if err != nil {

and particularly when handled the error returned says the function is not defined.

2016/11/19 11:40:18 Parsing failed: template: test:4: function "Block" not defined
exit status 1

@DrGo
Copy link
Contributor Author

DrGo commented Nov 19, 2016

I am aware of that.
html.Execute also warns correctly about function not defined, but it still seg faults.

@odeke-em
Copy link
Member

tl;dr: you are dereferencing a nil template that already failed to parse and complained in the error returned. The panic isn't in text/template.Execute but in trying to use .Execute on a nil object

Long wind

The return signature of template.Parse is (*text.Template, error)

A common pattern for Go code return signatures, is two states of the form:

  • (zero/nil objects..., non-nil error)
  • (non-nil..., nil error)

which is a pattern that almost all the functions in the stdlib(except for very few if am not mistaken).

Translating that pattern thereby to template.Parse means users only have two states to check for:

  • (a non-nil template, nil error)
  • (nil template, non-nil error)
    so implicitly use results without worries of nil pointer deferences, after no error has been returned.

If we had the state you seem to be expecting ie

  • (non-nil template, non-nil error)
    that also calls for an extra state:
  • (nil template, nil error)
    then callers would also have to first check that the template itself isn't nil even when err == nil.
tmpl, err := template.New("gopher").Parse(tmplStr)
if err != nil {
   // Even if nil, you still have to check below
}

if tmpl != nil {
    // Explicitly check if tmpl is non-nil
}

so in total we'd then have 4 states to check for ie

  • (zero/nil objects..., non-nil error)
  • (non-nil objects..., nil error)
  • (non-nil objects..., non-nil error)
  • (nil objects..., nil error)

Handling the error enables us to implicitly use data in which no errors were returned but also to avoid unnecessary allocations and extra states of errors so we could do

tmpl, err := template.New("gopher").Parse(tmplStr)
if err != nil {
    // Handle it in here, this route is done
    return nil, err
}

// Home free to now use the template

Hope this makes sense.

@DrGo
Copy link
Contributor Author

DrGo commented Nov 19, 2016

Got it Emmanuel
Thanks,

@odeke-em
Copy link
Member

Cool, I'll close this issue then.

@golang golang locked and limited conversation to collaborators Nov 20, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants