summaryrefslogtreecommitdiff
path: root/src/pkg/text
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/text')
-rw-r--r--src/pkg/text/scanner/scanner.go6
-rw-r--r--src/pkg/text/scanner/scanner_test.go41
-rw-r--r--src/pkg/text/tabwriter/tabwriter.go12
-rw-r--r--src/pkg/text/tabwriter/tabwriter_test.go39
-rw-r--r--src/pkg/text/template/doc.go2
-rw-r--r--src/pkg/text/template/exec.go11
-rw-r--r--src/pkg/text/template/exec_test.go30
-rw-r--r--src/pkg/text/template/multi_test.go12
-rw-r--r--src/pkg/text/template/template.go2
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)