Skip to content

Commit a2f5643

Browse files
authored
interp: fix data race for composite literal creation
This change fixes two data races related to composite literal creation. The first one isn't controversial as it is just about initializing the variable that contains the values in the right place, i.e. within the n.exec, so that this variable is local to each potential goroutine, instead of being racily shared by all goroutines. The second one is more worrying, i.e. having to protect the node typ with a mutex, because a call to func (t *itype) refType actually modifies the itype itself, which means it is not concurrent safe. The change seems to work, and does not seem to introduce regression, but it is still a concern as it probably is a sign that more similar guarding has to be done in several other places.
1 parent 42abedb commit a2f5643

File tree

5 files changed

+98
-1
lines changed

5 files changed

+98
-1
lines changed

interp/interp_eval_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,51 @@ func TestConcurrentEvals3(t *testing.T) {
11111111
}
11121112
}
11131113

1114+
func TestConcurrentComposite1(t *testing.T) {
1115+
testConcurrentComposite(t, "./testdata/concurrent/composite/composite_lit.go")
1116+
}
1117+
1118+
func TestConcurrentComposite2(t *testing.T) {
1119+
testConcurrentComposite(t, "./testdata/concurrent/composite/composite_sparse.go")
1120+
}
1121+
1122+
func testConcurrentComposite(t *testing.T, filePath string) {
1123+
pin, pout := io.Pipe()
1124+
i := interp.New(interp.Options{Stdout: pout})
1125+
i.Use(stdlib.Symbols)
1126+
1127+
errc := make(chan error)
1128+
var output string
1129+
go func() {
1130+
sc := bufio.NewScanner(pin)
1131+
k := 0
1132+
for sc.Scan() {
1133+
output += sc.Text()
1134+
k++
1135+
if k > 1 {
1136+
break
1137+
}
1138+
}
1139+
errc <- nil
1140+
}()
1141+
1142+
if _, err := i.EvalPath(filePath); err != nil {
1143+
t.Fatal(err)
1144+
}
1145+
1146+
_ = pin.Close()
1147+
_ = pout.Close()
1148+
1149+
if err := <-errc; err != nil {
1150+
t.Fatal(err)
1151+
}
1152+
1153+
expected := "{hello}{hello}"
1154+
if output != expected {
1155+
t.Fatalf("unexpected output, want %q, got %q", expected, output)
1156+
}
1157+
}
1158+
11141159
func TestEvalScanner(t *testing.T) {
11151160
type testCase struct {
11161161
desc string

interp/run.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"go/constant"
88
"log"
99
"reflect"
10+
"sync"
1011
"unsafe"
1112
)
1213

@@ -2069,6 +2070,8 @@ func doCompositeLit(n *node, hasType bool) {
20692070
if typ.cat == ptrT || typ.cat == aliasT {
20702071
typ = typ.val
20712072
}
2073+
var mu sync.Mutex
2074+
typ.mu = &mu
20722075
child := n.child
20732076
if hasType {
20742077
child = n.child[1:]
@@ -2095,7 +2098,12 @@ func doCompositeLit(n *node, hasType bool) {
20952098
i := n.findex
20962099
l := n.level
20972100
n.exec = func(f *frame) bltn {
2101+
// TODO: it seems fishy that the typ might be modified post-compilation, and
2102+
// hence that several goroutines might be using the same typ that they all modify.
2103+
// We probably need to revisit that.
2104+
typ.mu.Lock()
20982105
a := reflect.New(typ.TypeOf()).Elem()
2106+
typ.mu.Unlock()
20992107
for i, v := range values {
21002108
a.Field(i).Set(v(f))
21012109
}
@@ -2122,14 +2130,15 @@ func doCompositeSparse(n *node, hasType bool) {
21222130
if typ.cat == ptrT || typ.cat == aliasT {
21232131
typ = typ.val
21242132
}
2133+
var mu sync.Mutex
2134+
typ.mu = &mu
21252135
child := n.child
21262136
if hasType {
21272137
child = n.child[1:]
21282138
}
21292139
destInterface := destType(n).cat == interfaceT
21302140

21312141
values := make(map[int]func(*frame) reflect.Value)
2132-
a, _ := typ.zero()
21332142
for _, c := range child {
21342143
c1 := c.child[1]
21352144
field := typ.fieldIndex(c.child[0].ident)
@@ -2149,6 +2158,9 @@ func doCompositeSparse(n *node, hasType bool) {
21492158
}
21502159

21512160
n.exec = func(f *frame) bltn {
2161+
typ.mu.Lock()
2162+
a, _ := typ.zero()
2163+
typ.mu.Unlock()
21522164
for i, v := range values {
21532165
a.Field(i).Set(v(f))
21542166
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"time"
5+
)
6+
7+
type foo struct {
8+
bar string
9+
}
10+
11+
func main() {
12+
for i := 0; i < 2; i++ {
13+
go func() {
14+
a := foo{"hello"}
15+
println(a)
16+
}()
17+
}
18+
time.Sleep(time.Second)
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"time"
5+
)
6+
7+
type foo struct {
8+
bar string
9+
}
10+
11+
func main() {
12+
for i := 0; i < 2; i++ {
13+
go func() {
14+
a := foo{bar: "hello"}
15+
println(a)
16+
}()
17+
}
18+
time.Sleep(time.Second)
19+
}

interp/type.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"path/filepath"
77
"reflect"
88
"strconv"
9+
"sync"
910
)
1011

1112
// tcat defines interpreter type categories.
@@ -104,6 +105,7 @@ type structField struct {
104105

105106
// itype defines the internal representation of types in the interpreter.
106107
type itype struct {
108+
mu *sync.Mutex
107109
cat tcat // Type category
108110
field []structField // Array of struct fields if structT or interfaceT
109111
key *itype // Type of key element if MapT or nil

0 commit comments

Comments
 (0)