Skip to content

Commit b324da5

Browse files
committed
Store BURNTSUSHI_TOML_110 in parser and lexer
Setting a global is racy when multiple decodes are run in parallel. Fixes #395
1 parent d4c441a commit b324da5

File tree

5 files changed

+51
-29
lines changed

5 files changed

+51
-29
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"uses": "actions/checkout@v3"
2121
}, {
2222
"name": "Test",
23-
"run": "go test ./..."
23+
"run": "go test -race ./..."
2424
}, {
2525
"name": "Test on 32bit",
2626
"if": "runner.os == 'Linux'",

decode_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"reflect"
1212
"strconv"
1313
"strings"
14+
"sync"
1415
"testing"
1516
"time"
1617

@@ -1201,6 +1202,26 @@ func TestMetaKeys(t *testing.T) {
12011202
}
12021203
}
12031204

1205+
func TestDecodeParallel(t *testing.T) {
1206+
doc, err := os.ReadFile("testdata/ja-JP.toml")
1207+
if err != nil {
1208+
t.Fatal(err)
1209+
}
1210+
1211+
var wg sync.WaitGroup
1212+
for i := 0; i < 10; i++ {
1213+
wg.Add(1)
1214+
go func() {
1215+
defer wg.Done()
1216+
err := Unmarshal(doc, new(map[string]interface{}))
1217+
if err != nil {
1218+
t.Fatal(err)
1219+
}
1220+
}()
1221+
}
1222+
wg.Wait()
1223+
}
1224+
12041225
// errorContains checks if the error message in have contains the text in
12051226
// want.
12061227
//

lex.go

+21-19
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ func (p Position) String() string {
4646
}
4747

4848
type lexer struct {
49-
input string
50-
start int
51-
pos int
52-
line int
53-
state stateFn
54-
items chan item
49+
input string
50+
start int
51+
pos int
52+
line int
53+
state stateFn
54+
items chan item
55+
tomlNext bool
5556

5657
// Allow for backing up up to 4 runes. This is necessary because TOML
5758
// contains 3-rune tokens (""" and ''').
@@ -87,13 +88,14 @@ func (lx *lexer) nextItem() item {
8788
}
8889
}
8990

90-
func lex(input string) *lexer {
91+
func lex(input string, tomlNext bool) *lexer {
9192
lx := &lexer{
92-
input: input,
93-
state: lexTop,
94-
items: make(chan item, 10),
95-
stack: make([]stateFn, 0, 10),
96-
line: 1,
93+
input: input,
94+
state: lexTop,
95+
items: make(chan item, 10),
96+
stack: make([]stateFn, 0, 10),
97+
line: 1,
98+
tomlNext: tomlNext,
9799
}
98100
return lx
99101
}
@@ -408,7 +410,7 @@ func lexTableNameEnd(lx *lexer) stateFn {
408410
// Lexes only one part, e.g. only 'a' inside 'a.b'.
409411
func lexBareName(lx *lexer) stateFn {
410412
r := lx.next()
411-
if isBareKeyChar(r) {
413+
if isBareKeyChar(r, lx.tomlNext) {
412414
return lexBareName
413415
}
414416
lx.backup()
@@ -618,7 +620,7 @@ func lexInlineTableValue(lx *lexer) stateFn {
618620
case isWhitespace(r):
619621
return lexSkip(lx, lexInlineTableValue)
620622
case isNL(r):
621-
if tomlNext {
623+
if lx.tomlNext {
622624
return lexSkip(lx, lexInlineTableValue)
623625
}
624626
return lx.errorPrevLine(errLexInlineTableNL{})
@@ -643,7 +645,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
643645
case isWhitespace(r):
644646
return lexSkip(lx, lexInlineTableValueEnd)
645647
case isNL(r):
646-
if tomlNext {
648+
if lx.tomlNext {
647649
return lexSkip(lx, lexInlineTableValueEnd)
648650
}
649651
return lx.errorPrevLine(errLexInlineTableNL{})
@@ -654,7 +656,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
654656
lx.ignore()
655657
lx.skip(isWhitespace)
656658
if lx.peek() == '}' {
657-
if tomlNext {
659+
if lx.tomlNext {
658660
return lexInlineTableValueEnd
659661
}
660662
return lx.errorf("trailing comma not allowed in inline tables")
@@ -838,7 +840,7 @@ func lexStringEscape(lx *lexer) stateFn {
838840
r := lx.next()
839841
switch r {
840842
case 'e':
841-
if !tomlNext {
843+
if !lx.tomlNext {
842844
return lx.error(errLexEscape{r})
843845
}
844846
fallthrough
@@ -861,7 +863,7 @@ func lexStringEscape(lx *lexer) stateFn {
861863
case '\\':
862864
return lx.pop()
863865
case 'x':
864-
if !tomlNext {
866+
if !lx.tomlNext {
865867
return lx.error(errLexEscape{r})
866868
}
867869
return lexHexEscape
@@ -1258,7 +1260,7 @@ func isHexadecimal(r rune) bool {
12581260
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
12591261
}
12601262

1261-
func isBareKeyChar(r rune) bool {
1263+
func isBareKeyChar(r rune, tomlNext bool) bool {
12621264
if tomlNext {
12631265
return (r >= 'A' && r <= 'Z') ||
12641266
(r >= 'a' && r <= 'z') ||

meta.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (k Key) maybeQuoted(i int) string {
106106
return `""`
107107
}
108108
for _, c := range k[i] {
109-
if !isBareKeyChar(c) {
109+
if !isBareKeyChar(c, false) {
110110
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
111111
}
112112
}

parse.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import (
1111
"github.com/BurntSushi/toml/internal"
1212
)
1313

14-
var tomlNext bool
15-
1614
type parser struct {
1715
lx *lexer
1816
context Key // Full key for the current hash in scope.
1917
currentKey string // Base key name for everything except hashes.
2018
pos Position // Current position in the TOML file.
19+
tomlNext bool
2120

2221
ordered []Key // List of keys in the order that they appear in the TOML data.
2322

@@ -32,8 +31,7 @@ type keyInfo struct {
3231
}
3332

3433
func parse(data string) (p *parser, err error) {
35-
_, ok := os.LookupEnv("BURNTSUSHI_TOML_110")
36-
tomlNext = ok
34+
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
3735

3836
defer func() {
3937
if r := recover(); r != nil {
@@ -74,9 +72,10 @@ func parse(data string) (p *parser, err error) {
7472
p = &parser{
7573
keyInfo: make(map[string]keyInfo),
7674
mapping: make(map[string]interface{}),
77-
lx: lex(data),
75+
lx: lex(data, tomlNext),
7876
ordered: make([]Key, 0),
7977
implicits: make(map[string]struct{}),
78+
tomlNext: tomlNext,
8079
}
8180
for {
8281
item := p.next()
@@ -361,7 +360,7 @@ func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
361360
err error
362361
)
363362
for _, dt := range dtTypes {
364-
if dt.next && !tomlNext {
363+
if dt.next && !p.tomlNext {
365364
continue
366365
}
367366
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
@@ -764,7 +763,7 @@ func (p *parser) replaceEscapes(it item, str string) string {
764763
replaced = append(replaced, rune(0x000D))
765764
r += 1
766765
case 'e':
767-
if tomlNext {
766+
if p.tomlNext {
768767
replaced = append(replaced, rune(0x001B))
769768
r += 1
770769
}
@@ -775,7 +774,7 @@ func (p *parser) replaceEscapes(it item, str string) string {
775774
replaced = append(replaced, rune(0x005C))
776775
r += 1
777776
case 'x':
778-
if tomlNext {
777+
if p.tomlNext {
779778
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+3])
780779
replaced = append(replaced, escaped)
781780
r += 3

0 commit comments

Comments
 (0)