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) | 
