Commit 20d9fd3a authored by Rob Pike's avatar Rob Pike

text/template/parse: fix data race

The situation only affects diagnostics but is easy to fix.
When computing lineNumber, use the position of the last item
returned by nextItem rather than the current state of the lexer.
This is internal only and does not affect the API.

Fixes #3886.

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/6445061
parent 4783ad82
...@@ -13,8 +13,9 @@ import ( ...@@ -13,8 +13,9 @@ import (
// item represents a token or text string returned from the scanner. // item represents a token or text string returned from the scanner.
type item struct { type item struct {
typ itemType typ itemType // The type of this item.
val string pos int // The starting position, in bytes, of this item in the input string.
val string // The value of this item.
} }
func (i item) String() string { func (i item) String() string {
...@@ -127,6 +128,7 @@ type lexer struct { ...@@ -127,6 +128,7 @@ type lexer struct {
pos int // current position in the input. pos int // current position in the input.
start int // start position of this item. start int // start position of this item.
width int // width of last rune read from input. width int // width of last rune read from input.
lastPos int // position of nost recent item returned by nextItem
items chan item // channel of scanned items. items chan item // channel of scanned items.
} }
...@@ -155,7 +157,7 @@ func (l *lexer) backup() { ...@@ -155,7 +157,7 @@ func (l *lexer) backup() {
// emit passes an item back to the client. // emit passes an item back to the client.
func (l *lexer) emit(t itemType) { func (l *lexer) emit(t itemType) {
l.items <- item{t, l.input[l.start:l.pos]} l.items <- item{t, l.start, l.input[l.start:l.pos]}
l.start = l.pos l.start = l.pos
} }
...@@ -180,22 +182,25 @@ func (l *lexer) acceptRun(valid string) { ...@@ -180,22 +182,25 @@ func (l *lexer) acceptRun(valid string) {
l.backup() l.backup()
} }
// lineNumber reports which line we're on. Doing it this way // lineNumber reports which line we're on, based on the position of
// the previous item returned by nextItem. Doing it this way
// means we don't have to worry about peek double counting. // means we don't have to worry about peek double counting.
func (l *lexer) lineNumber() int { func (l *lexer) lineNumber() int {
return 1 + strings.Count(l.input[:l.pos], "\n") return 1 + strings.Count(l.input[:l.lastPos], "\n")
} }
// error returns an error token and terminates the scan by passing // error returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem. // back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn { func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.items <- item{itemError, fmt.Sprintf(format, args...)} l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
return nil return nil
} }
// nextItem returns the next item from the input. // nextItem returns the next item from the input.
func (l *lexer) nextItem() item { func (l *lexer) nextItem() item {
return <-l.items item := <-l.items
l.lastPos = item.pos
return item
} }
// lex creates a new scanner for the input string. // lex creates a new scanner for the input string.
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package parse package parse
import ( import (
"reflect"
"testing" "testing"
) )
...@@ -16,31 +15,31 @@ type lexTest struct { ...@@ -16,31 +15,31 @@ type lexTest struct {
} }
var ( var (
tEOF = item{itemEOF, ""} tEOF = item{itemEOF, 0, ""}
tLeft = item{itemLeftDelim, "{{"} tLeft = item{itemLeftDelim, 0, "{{"}
tRight = item{itemRightDelim, "}}"} tRight = item{itemRightDelim, 0, "}}"}
tRange = item{itemRange, "range"} tRange = item{itemRange, 0, "range"}
tPipe = item{itemPipe, "|"} tPipe = item{itemPipe, 0, "|"}
tFor = item{itemIdentifier, "for"} tFor = item{itemIdentifier, 0, "for"}
tQuote = item{itemString, `"abc \n\t\" "`} tQuote = item{itemString, 0, `"abc \n\t\" "`}
raw = "`" + `abc\n\t\" ` + "`" raw = "`" + `abc\n\t\" ` + "`"
tRawQuote = item{itemRawString, raw} tRawQuote = item{itemRawString, 0, raw}
) )
var lexTests = []lexTest{ var lexTests = []lexTest{
{"empty", "", []item{tEOF}}, {"empty", "", []item{tEOF}},
{"spaces", " \t\n", []item{{itemText, " \t\n"}, tEOF}}, {"spaces", " \t\n", []item{{itemText, 0, " \t\n"}, tEOF}},
{"text", `now is the time`, []item{{itemText, "now is the time"}, tEOF}}, {"text", `now is the time`, []item{{itemText, 0, "now is the time"}, tEOF}},
{"text with comment", "hello-{{/* this is a comment */}}-world", []item{ {"text with comment", "hello-{{/* this is a comment */}}-world", []item{
{itemText, "hello-"}, {itemText, 0, "hello-"},
{itemText, "-world"}, {itemText, 0, "-world"},
tEOF, tEOF,
}}, }},
{"punctuation", "{{,@%}}", []item{ {"punctuation", "{{,@%}}", []item{
tLeft, tLeft,
{itemChar, ","}, {itemChar, 0, ","},
{itemChar, "@"}, {itemChar, 0, "@"},
{itemChar, "%"}, {itemChar, 0, "%"},
tRight, tRight,
tEOF, tEOF,
}}, }},
...@@ -50,139 +49,139 @@ var lexTests = []lexTest{ ...@@ -50,139 +49,139 @@ var lexTests = []lexTest{
{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}}, {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{ {"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{
tLeft, tLeft,
{itemNumber, "1"}, {itemNumber, 0, "1"},
{itemNumber, "02"}, {itemNumber, 0, "02"},
{itemNumber, "0x14"}, {itemNumber, 0, "0x14"},
{itemNumber, "-7.2i"}, {itemNumber, 0, "-7.2i"},
{itemNumber, "1e3"}, {itemNumber, 0, "1e3"},
{itemNumber, "+1.2e-4"}, {itemNumber, 0, "+1.2e-4"},
{itemNumber, "4.2i"}, {itemNumber, 0, "4.2i"},
{itemComplex, "1+2i"}, {itemComplex, 0, "1+2i"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{ {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
tLeft, tLeft,
{itemCharConstant, `'a'`}, {itemCharConstant, 0, `'a'`},
{itemCharConstant, `'\n'`}, {itemCharConstant, 0, `'\n'`},
{itemCharConstant, `'\''`}, {itemCharConstant, 0, `'\''`},
{itemCharConstant, `'\\'`}, {itemCharConstant, 0, `'\\'`},
{itemCharConstant, `'\u00FF'`}, {itemCharConstant, 0, `'\u00FF'`},
{itemCharConstant, `'\xFF'`}, {itemCharConstant, 0, `'\xFF'`},
{itemCharConstant, `'本'`}, {itemCharConstant, 0, `'本'`},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"bools", "{{true false}}", []item{ {"bools", "{{true false}}", []item{
tLeft, tLeft,
{itemBool, "true"}, {itemBool, 0, "true"},
{itemBool, "false"}, {itemBool, 0, "false"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"dot", "{{.}}", []item{ {"dot", "{{.}}", []item{
tLeft, tLeft,
{itemDot, "."}, {itemDot, 0, "."},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"dots", "{{.x . .2 .x.y}}", []item{ {"dots", "{{.x . .2 .x.y}}", []item{
tLeft, tLeft,
{itemField, ".x"}, {itemField, 0, ".x"},
{itemDot, "."}, {itemDot, 0, "."},
{itemNumber, ".2"}, {itemNumber, 0, ".2"},
{itemField, ".x.y"}, {itemField, 0, ".x.y"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"keywords", "{{range if else end with}}", []item{ {"keywords", "{{range if else end with}}", []item{
tLeft, tLeft,
{itemRange, "range"}, {itemRange, 0, "range"},
{itemIf, "if"}, {itemIf, 0, "if"},
{itemElse, "else"}, {itemElse, 0, "else"},
{itemEnd, "end"}, {itemEnd, 0, "end"},
{itemWith, "with"}, {itemWith, 0, "with"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{ {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
tLeft, tLeft,
{itemVariable, "$c"}, {itemVariable, 0, "$c"},
{itemColonEquals, ":="}, {itemColonEquals, 0, ":="},
{itemIdentifier, "printf"}, {itemIdentifier, 0, "printf"},
{itemVariable, "$"}, {itemVariable, 0, "$"},
{itemVariable, "$hello"}, {itemVariable, 0, "$hello"},
{itemVariable, "$23"}, {itemVariable, 0, "$23"},
{itemVariable, "$"}, {itemVariable, 0, "$"},
{itemVariable, "$var.Field"}, {itemVariable, 0, "$var.Field"},
{itemField, ".Method"}, {itemField, 0, ".Method"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{ {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
{itemText, "intro "}, {itemText, 0, "intro "},
tLeft, tLeft,
{itemIdentifier, "echo"}, {itemIdentifier, 0, "echo"},
{itemIdentifier, "hi"}, {itemIdentifier, 0, "hi"},
{itemNumber, "1.2"}, {itemNumber, 0, "1.2"},
tPipe, tPipe,
{itemIdentifier, "noargs"}, {itemIdentifier, 0, "noargs"},
tPipe, tPipe,
{itemIdentifier, "args"}, {itemIdentifier, 0, "args"},
{itemNumber, "1"}, {itemNumber, 0, "1"},
{itemString, `"hi"`}, {itemString, 0, `"hi"`},
tRight, tRight,
{itemText, " outro"}, {itemText, 0, " outro"},
tEOF, tEOF,
}}, }},
{"declaration", "{{$v := 3}}", []item{ {"declaration", "{{$v := 3}}", []item{
tLeft, tLeft,
{itemVariable, "$v"}, {itemVariable, 0, "$v"},
{itemColonEquals, ":="}, {itemColonEquals, 0, ":="},
{itemNumber, "3"}, {itemNumber, 0, "3"},
tRight, tRight,
tEOF, tEOF,
}}, }},
{"2 declarations", "{{$v , $w := 3}}", []item{ {"2 declarations", "{{$v , $w := 3}}", []item{
tLeft, tLeft,
{itemVariable, "$v"}, {itemVariable, 0, "$v"},
{itemChar, ","}, {itemChar, 0, ","},
{itemVariable, "$w"}, {itemVariable, 0, "$w"},
{itemColonEquals, ":="}, {itemColonEquals, 0, ":="},
{itemNumber, "3"}, {itemNumber, 0, "3"},
tRight, tRight,
tEOF, tEOF,
}}, }},
// errors // errors
{"badchar", "#{{\x01}}", []item{ {"badchar", "#{{\x01}}", []item{
{itemText, "#"}, {itemText, 0, "#"},
tLeft, tLeft,
{itemError, "unrecognized character in action: U+0001"}, {itemError, 0, "unrecognized character in action: U+0001"},
}}, }},
{"unclosed action", "{{\n}}", []item{ {"unclosed action", "{{\n}}", []item{
tLeft, tLeft,
{itemError, "unclosed action"}, {itemError, 0, "unclosed action"},
}}, }},
{"EOF in action", "{{range", []item{ {"EOF in action", "{{range", []item{
tLeft, tLeft,
tRange, tRange,
{itemError, "unclosed action"}, {itemError, 0, "unclosed action"},
}}, }},
{"unclosed quote", "{{\"\n\"}}", []item{ {"unclosed quote", "{{\"\n\"}}", []item{
tLeft, tLeft,
{itemError, "unterminated quoted string"}, {itemError, 0, "unterminated quoted string"},
}}, }},
{"unclosed raw quote", "{{`xx\n`}}", []item{ {"unclosed raw quote", "{{`xx\n`}}", []item{
tLeft, tLeft,
{itemError, "unterminated raw quoted string"}, {itemError, 0, "unterminated raw quoted string"},
}}, }},
{"unclosed char constant", "{{'\n}}", []item{ {"unclosed char constant", "{{'\n}}", []item{
tLeft, tLeft,
{itemError, "unterminated character constant"}, {itemError, 0, "unterminated character constant"},
}}, }},
{"bad number", "{{3k}}", []item{ {"bad number", "{{3k}}", []item{
tLeft, tLeft,
{itemError, `bad number syntax: "3k"`}, {itemError, 0, `bad number syntax: "3k"`},
}}, }},
// Fixed bugs // Fixed bugs
...@@ -213,10 +212,28 @@ func collect(t *lexTest, left, right string) (items []item) { ...@@ -213,10 +212,28 @@ func collect(t *lexTest, left, right string) (items []item) {
return return
} }
func equal(i1, i2 []item, checkPos bool) bool {
if len(i1) != len(i2) {
return false
}
for k := range i1 {
if i1[k].typ != i2[k].typ {
return false
}
if i1[k].val != i2[k].val {
return false
}
if checkPos && i1[k].pos != i2[k].pos {
return false
}
}
return true
}
func TestLex(t *testing.T) { func TestLex(t *testing.T) {
for _, test := range lexTests { for _, test := range lexTests {
items := collect(&test, "", "") items := collect(&test, "", "")
if !reflect.DeepEqual(items, test.items) { if !equal(items, test.items, false) {
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
} }
} }
...@@ -226,13 +243,13 @@ func TestLex(t *testing.T) { ...@@ -226,13 +243,13 @@ func TestLex(t *testing.T) {
var lexDelimTests = []lexTest{ var lexDelimTests = []lexTest{
{"punctuation", "$$,@%{{}}@@", []item{ {"punctuation", "$$,@%{{}}@@", []item{
tLeftDelim, tLeftDelim,
{itemChar, ","}, {itemChar, 0, ","},
{itemChar, "@"}, {itemChar, 0, "@"},
{itemChar, "%"}, {itemChar, 0, "%"},
{itemChar, "{"}, {itemChar, 0, "{"},
{itemChar, "{"}, {itemChar, 0, "{"},
{itemChar, "}"}, {itemChar, 0, "}"},
{itemChar, "}"}, {itemChar, 0, "}"},
tRightDelim, tRightDelim,
tEOF, tEOF,
}}, }},
...@@ -243,15 +260,57 @@ var lexDelimTests = []lexTest{ ...@@ -243,15 +260,57 @@ var lexDelimTests = []lexTest{
} }
var ( var (
tLeftDelim = item{itemLeftDelim, "$$"} tLeftDelim = item{itemLeftDelim, 0, "$$"}
tRightDelim = item{itemRightDelim, "@@"} tRightDelim = item{itemRightDelim, 0, "@@"}
) )
func TestDelims(t *testing.T) { func TestDelims(t *testing.T) {
for _, test := range lexDelimTests { for _, test := range lexDelimTests {
items := collect(&test, "$$", "@@") items := collect(&test, "$$", "@@")
if !reflect.DeepEqual(items, test.items) { if !equal(items, test.items, false) {
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
} }
} }
} }
var lexPosTests = []lexTest{
{"empty", "", []item{tEOF}},
{"punctuation", "{{,@%#}}", []item{
{itemLeftDelim, 0, "{{"},
{itemChar, 2, ","},
{itemChar, 3, "@"},
{itemChar, 4, "%"},
{itemChar, 5, "#"},
{itemRightDelim, 6, "}}"},
{itemEOF, 8, ""},
}},
{"sample", "0123{{hello}}xyz", []item{
{itemText, 0, "0123"},
{itemLeftDelim, 4, "{{"},
{itemIdentifier, 6, "hello"},
{itemRightDelim, 11, "}}"},
{itemText, 13, "xyz"},
{itemEOF, 16, ""},
}},
}
// The other tests don't check position, to make the test cases easier to construct.
// This one does.
func TestPos(t *testing.T) {
for _, test := range lexPosTests {
items := collect(&test, "", "")
if !equal(items, test.items, true) {
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
if len(items) == len(test.items) {
// Detailed print; avoid item.String() to expose the position value.
for i := range items {
if !equal(items[i:i+1], test.items[i:i+1], true) {
i1 := items[i]
i2 := test.items[i]
t.Errorf("\t#%d: got {%v %d %q} expected {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val)
}
}
}
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment