diff options
Diffstat (limited to 'src/pkg/text')
-rw-r--r-- | src/pkg/text/scanner/scanner.go | 6 | ||||
-rw-r--r-- | src/pkg/text/scanner/scanner_test.go | 41 | ||||
-rw-r--r-- | src/pkg/text/tabwriter/tabwriter.go | 12 | ||||
-rw-r--r-- | src/pkg/text/tabwriter/tabwriter_test.go | 39 | ||||
-rw-r--r-- | src/pkg/text/template/doc.go | 2 | ||||
-rw-r--r-- | src/pkg/text/template/exec.go | 11 | ||||
-rw-r--r-- | src/pkg/text/template/exec_test.go | 30 | ||||
-rw-r--r-- | src/pkg/text/template/multi_test.go | 12 | ||||
-rw-r--r-- | src/pkg/text/template/template.go | 2 |
9 files changed, 127 insertions, 28 deletions
diff --git a/src/pkg/text/scanner/scanner.go b/src/pkg/text/scanner/scanner.go index e0d86e343..db7ca73c6 100644 --- a/src/pkg/text/scanner/scanner.go +++ b/src/pkg/text/scanner/scanner.go @@ -240,6 +240,9 @@ func (s *Scanner) next() rune { s.srcEnd = i + n s.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinel if err != nil { + if err != io.EOF { + s.error(err.Error()) + } if s.srcEnd == 0 { if s.lastCharLen > 0 { // previous character was not EOF @@ -248,9 +251,6 @@ func (s *Scanner) next() rune { s.lastCharLen = 0 return EOF } - if err != io.EOF { - s.error(err.Error()) - } // If err == EOF, we won't be getting more // bytes; break to avoid infinite loop. If // err is something else, we don't know if diff --git a/src/pkg/text/scanner/scanner_test.go b/src/pkg/text/scanner/scanner_test.go index 496eed4a3..7d3f597eb 100644 --- a/src/pkg/text/scanner/scanner_test.go +++ b/src/pkg/text/scanner/scanner_test.go @@ -360,7 +360,7 @@ func TestScanSelectedMask(t *testing.T) { func TestScanNext(t *testing.T) { const BOM = '\uFEFF' BOMs := string(BOM) - s := new(Scanner).Init(bytes.NewBufferString(BOMs + "if a == bcd /* com" + BOMs + "ment */ {\n\ta += c\n}" + BOMs + "// line comment ending in eof")) + s := new(Scanner).Init(strings.NewReader(BOMs + "if a == bcd /* com" + BOMs + "ment */ {\n\ta += c\n}" + BOMs + "// line comment ending in eof")) checkTok(t, s, 1, s.Scan(), Ident, "if") // the first BOM is ignored checkTok(t, s, 1, s.Scan(), Ident, "a") checkTok(t, s, 1, s.Scan(), '=', "=") @@ -402,7 +402,7 @@ func TestScanWhitespace(t *testing.T) { } func testError(t *testing.T, src, pos, msg string, tok rune) { - s := new(Scanner).Init(bytes.NewBufferString(src)) + s := new(Scanner).Init(strings.NewReader(src)) errorCalled := false s.Error = func(s *Scanner, m string) { if !errorCalled { @@ -462,6 +462,33 @@ func TestError(t *testing.T) { testError(t, `/*/`, "1:4", "comment not terminated", EOF) } +// An errReader returns (0, err) where err is not io.EOF. +type errReader struct{} + +func (errReader) Read(b []byte) (int, error) { + return 0, io.ErrNoProgress // some error that is not io.EOF +} + +func TestIOError(t *testing.T) { + s := new(Scanner).Init(errReader{}) + errorCalled := false + s.Error = func(s *Scanner, msg string) { + if !errorCalled { + if want := io.ErrNoProgress.Error(); msg != want { + t.Errorf("msg = %q, want %q", msg, want) + } + errorCalled = true + } + } + tok := s.Scan() + if tok != EOF { + t.Errorf("tok = %s, want EOF", TokenString(tok)) + } + if !errorCalled { + t.Errorf("error handler not called") + } +} + func checkPos(t *testing.T, got, want Position) { if got.Offset != want.Offset || got.Line != want.Line || got.Column != want.Column { t.Errorf("got offset, line, column = %d, %d, %d; want %d, %d, %d", @@ -491,13 +518,13 @@ func checkScanPos(t *testing.T, s *Scanner, offset, line, column int, char rune) func TestPos(t *testing.T) { // corner case: empty source - s := new(Scanner).Init(bytes.NewBufferString("")) + s := new(Scanner).Init(strings.NewReader("")) checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) s.Peek() // peek doesn't affect the position checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) // corner case: source with only a newline - s = new(Scanner).Init(bytes.NewBufferString("\n")) + s = new(Scanner).Init(strings.NewReader("\n")) checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) checkNextPos(t, s, 1, 2, 1, '\n') // after EOF position doesn't change @@ -509,7 +536,7 @@ func TestPos(t *testing.T) { } // corner case: source with only a single character - s = new(Scanner).Init(bytes.NewBufferString("本")) + s = new(Scanner).Init(strings.NewReader("本")) checkPos(t, s.Pos(), Position{Offset: 0, Line: 1, Column: 1}) checkNextPos(t, s, 3, 1, 2, '本') // after EOF position doesn't change @@ -521,7 +548,7 @@ func TestPos(t *testing.T) { } // positions after calling Next - s = new(Scanner).Init(bytes.NewBufferString(" foo६४ \n\n本語\n")) + s = new(Scanner).Init(strings.NewReader(" foo६४ \n\n本語\n")) checkNextPos(t, s, 1, 1, 2, ' ') s.Peek() // peek doesn't affect the position checkNextPos(t, s, 2, 1, 3, ' ') @@ -546,7 +573,7 @@ func TestPos(t *testing.T) { } // positions after calling Scan - s = new(Scanner).Init(bytes.NewBufferString("abc\n本語\n\nx")) + s = new(Scanner).Init(strings.NewReader("abc\n本語\n\nx")) s.Mode = 0 s.Whitespace = 0 checkScanPos(t, s, 0, 1, 1, 'a') diff --git a/src/pkg/text/tabwriter/tabwriter.go b/src/pkg/text/tabwriter/tabwriter.go index 722ac8d87..c0c32d5de 100644 --- a/src/pkg/text/tabwriter/tabwriter.go +++ b/src/pkg/text/tabwriter/tabwriter.go @@ -434,9 +434,13 @@ func (b *Writer) terminateCell(htab bool) int { return len(*line) } -func handlePanic(err *error) { +func handlePanic(err *error, op string) { if e := recover(); e != nil { - *err = e.(osError).err // re-panics if it's not a local osError + if nerr, ok := e.(osError); ok { + *err = nerr.err + return + } + panic("tabwriter: panic during " + op) } } @@ -447,7 +451,7 @@ func handlePanic(err *error) { // func (b *Writer) Flush() (err error) { defer b.reset() // even in the presence of errors - defer handlePanic(&err) + defer handlePanic(&err, "Flush") // add current cell if not empty if b.cell.size > 0 { @@ -471,7 +475,7 @@ var hbar = []byte("---\n") // while writing to the underlying output stream. // func (b *Writer) Write(buf []byte) (n int, err error) { - defer handlePanic(&err) + defer handlePanic(&err, "Write") // split text into cells n = 0 diff --git a/src/pkg/text/tabwriter/tabwriter_test.go b/src/pkg/text/tabwriter/tabwriter_test.go index ace535647..9d3111e2c 100644 --- a/src/pkg/text/tabwriter/tabwriter_test.go +++ b/src/pkg/text/tabwriter/tabwriter_test.go @@ -14,7 +14,7 @@ type buffer struct { a []byte } -func (b *buffer) init(n int) { b.a = make([]byte, n)[0:0] } +func (b *buffer) init(n int) { b.a = make([]byte, 0, n) } func (b *buffer) clear() { b.a = b.a[0:0] } @@ -613,3 +613,40 @@ func Test(t *testing.T) { check(t, e.testname, e.minwidth, e.tabwidth, e.padding, e.padchar, e.flags, e.src, e.expected) } } + +type panicWriter struct{} + +func (panicWriter) Write([]byte) (int, error) { + panic("cannot write") +} + +func wantPanicString(t *testing.T, want string) { + if e := recover(); e != nil { + got, ok := e.(string) + switch { + case !ok: + t.Errorf("got %v (%T), want panic string", e, e) + case got != want: + t.Errorf("wrong panic message: got %q, want %q", got, want) + } + } +} + +func TestPanicDuringFlush(t *testing.T) { + defer wantPanicString(t, "tabwriter: panic during Flush") + var p panicWriter + w := new(Writer) + w.Init(p, 0, 0, 5, ' ', 0) + io.WriteString(w, "a") + w.Flush() + t.Errorf("failed to panic during Flush") +} + +func TestPanicDuringWrite(t *testing.T) { + defer wantPanicString(t, "tabwriter: panic during Write") + var p panicWriter + w := new(Writer) + w.Init(p, 0, 0, 5, ' ', 0) + io.WriteString(w, "a\n\n") // the second \n triggers a call to w.Write and thus a panic + t.Errorf("failed to panic during Write") +} diff --git a/src/pkg/text/template/doc.go b/src/pkg/text/template/doc.go index f622ac7dc..7c6efd59c 100644 --- a/src/pkg/text/template/doc.go +++ b/src/pkg/text/template/doc.go @@ -20,7 +20,7 @@ The input text for a template is UTF-8-encoded text in any format. "{{" and "}}"; all text outside actions is copied to the output unchanged. Actions may not span newlines, although comments can. -Once constructed, a template may be executed safely in parallel. +Once parsed, a template may be executed safely in parallel. Here is a trivial example that prints "17 items are made of wool". diff --git a/src/pkg/text/template/exec.go b/src/pkg/text/template/exec.go index 43b0b266e..2f3231264 100644 --- a/src/pkg/text/template/exec.go +++ b/src/pkg/text/template/exec.go @@ -108,6 +108,10 @@ func errRecover(errp *error) { // ExecuteTemplate applies the template associated with t that has the given name // to the specified data object and writes the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { tmpl := t.tmpl[name] if tmpl == nil { @@ -118,6 +122,10 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) // Execute applies a parsed template to the specified data object, // and writes the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { defer errRecover(&err) value := reflect.ValueOf(data) @@ -594,6 +602,9 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu switch { case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ): value = value.Elem() + if !value.IsValid() { + s.errorf("dereference of nil pointer of type %s", typ) + } case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr(): value = value.Addr() default: diff --git a/src/pkg/text/template/exec_test.go b/src/pkg/text/template/exec_test.go index f60702de8..868f2cb94 100644 --- a/src/pkg/text/template/exec_test.go +++ b/src/pkg/text/template/exec_test.go @@ -512,6 +512,8 @@ var execTests = []execTest{ {"bug9", "{{.cause}}", "neglect", map[string]string{"cause": "neglect"}, true}, // Field chain starting with function did not work. {"bug10", "{{mapOfThree.three}}-{{(mapOfThree).three}}", "3-3", 0, true}, + // Dereferencing nil pointer while evaluating function arguments should not panic. Issue 7333. + {"bug11", "{{valueString .PS}}", "", T{}, false}, } func zeroArgs() string { @@ -546,6 +548,11 @@ func vfunc(V, *V) string { return "vfunc" } +// valueString takes a string, not a pointer. +func valueString(v string) string { + return "value is ignored" +} + func add(args ...int) int { sum := 0 for _, x := range args { @@ -580,17 +587,18 @@ func mapOfThree() interface{} { func testExecute(execTests []execTest, template *Template, t *testing.T) { b := new(bytes.Buffer) funcs := FuncMap{ - "add": add, - "count": count, - "dddArg": dddArg, - "echo": echo, - "makemap": makemap, - "mapOfThree": mapOfThree, - "oneArg": oneArg, - "stringer": stringer, - "typeOf": typeOf, - "vfunc": vfunc, - "zeroArgs": zeroArgs, + "add": add, + "count": count, + "dddArg": dddArg, + "echo": echo, + "makemap": makemap, + "mapOfThree": mapOfThree, + "oneArg": oneArg, + "stringer": stringer, + "typeOf": typeOf, + "valueString": valueString, + "vfunc": vfunc, + "zeroArgs": zeroArgs, } for _, test := range execTests { var tmpl *Template diff --git a/src/pkg/text/template/multi_test.go b/src/pkg/text/template/multi_test.go index 1f6ed5d8e..e4e804880 100644 --- a/src/pkg/text/template/multi_test.go +++ b/src/pkg/text/template/multi_test.go @@ -259,6 +259,18 @@ func TestAddParseTree(t *testing.T) { } } +// Issue 7032 +func TestAddParseTreeToUnparsedTemplate(t *testing.T) { + master := "{{define \"master\"}}{{end}}" + tmpl := New("master") + tree, err := parse.Parse("master", master, "", "", nil) + if err != nil { + t.Fatalf("unexpected parse err: %v", err) + } + masterTree := tree["master"] + tmpl.AddParseTree("master", masterTree) // used to panic +} + func TestRedefinition(t *testing.T) { var tmpl *Template var err error diff --git a/src/pkg/text/template/template.go b/src/pkg/text/template/template.go index a2b9062ad..249d0cbfb 100644 --- a/src/pkg/text/template/template.go +++ b/src/pkg/text/template/template.go @@ -105,7 +105,7 @@ func (t *Template) copy(c *common) *Template { // AddParseTree creates a new template with the name and parse tree // and associates it with t. func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { - if t.tmpl[name] != nil { + if t.common != nil && t.tmpl[name] != nil { return nil, fmt.Errorf("template: redefinition of template %q", name) } nt := t.New(name) |