Skip to content

Commit c6531fa

Browse files
findleyrgopherbot
authored andcommitted
go/types,types2: avoid data race to object.color_ through dot imports
As described in issue #69912, type checking dot-imported identifiers can result in a call to objDecl on an imported object, which leads to a data race to the color_ field. There are multiple potential fixes for this race. Opt for avoiding the call to objDecl altogether, rather than setting color_ during import. The color_ field is an internal property of objects that should only be valid during the type checking of their package. We should not be calling objDecl on imported objects. Fixes #69912 Change-Id: I55eb652479715f2a7ac84104db2f448091c4e7ac Reviewed-on: https://go-review.googlesource.com/c/go/+/621637 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 067c091 commit c6531fa

File tree

6 files changed

+167
-12
lines changed

6 files changed

+167
-12
lines changed

src/cmd/compile/internal/importer/gcimporter_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package importer
66

77
import (
88
"bytes"
9+
"cmd/compile/internal/syntax"
910
"cmd/compile/internal/types2"
1011
"fmt"
1112
"go/build"
@@ -16,6 +17,7 @@ import (
1617
"path/filepath"
1718
"runtime"
1819
"strings"
20+
"sync"
1921
"testing"
2022
"time"
2123
)
@@ -593,3 +595,66 @@ func lookupObj(t *testing.T, scope *types2.Scope, name string) types2.Object {
593595
t.Fatalf("%s not found", name)
594596
return nil
595597
}
598+
599+
// importMap implements the types2.Importer interface.
600+
type importMap map[string]*types2.Package
601+
602+
func (m importMap) Import(path string) (*types2.Package, error) { return m[path], nil }
603+
604+
func TestIssue69912(t *testing.T) {
605+
testenv.MustHaveGoBuild(t)
606+
607+
// This package only handles gc export data.
608+
if runtime.Compiler != "gc" {
609+
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
610+
}
611+
612+
tmpdir := t.TempDir()
613+
testoutdir := filepath.Join(tmpdir, "testdata")
614+
if err := os.Mkdir(testoutdir, 0700); err != nil {
615+
t.Fatalf("making output dir: %v", err)
616+
}
617+
618+
compile(t, "testdata", "issue69912.go", testoutdir, nil)
619+
620+
issue69912, err := Import(make(map[string]*types2.Package), "./testdata/issue69912", tmpdir, nil)
621+
if err != nil {
622+
t.Fatal(err)
623+
}
624+
625+
check := func(pkgname, src string, imports importMap) (*types2.Package, error) {
626+
f, err := syntax.Parse(syntax.NewFileBase(pkgname), strings.NewReader(src), nil, nil, 0)
627+
if err != nil {
628+
return nil, err
629+
}
630+
config := &types2.Config{
631+
Importer: imports,
632+
}
633+
return config.Check(pkgname, []*syntax.File{f}, nil)
634+
}
635+
636+
// Use the resulting package concurrently, via dot-imports, to exercise the
637+
// race of issue #69912.
638+
const pSrc = `package p
639+
640+
import . "issue69912"
641+
642+
type S struct {
643+
f T
644+
}
645+
`
646+
importer := importMap{
647+
"issue69912": issue69912,
648+
}
649+
var wg sync.WaitGroup
650+
for range 10 {
651+
wg.Add(1)
652+
go func() {
653+
defer wg.Done()
654+
if _, err := check("p", pSrc, importer); err != nil {
655+
t.Errorf("Check failed: %v", err)
656+
}
657+
}()
658+
}
659+
wg.Wait()
660+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package issue69912
6+
7+
// Define an arbitrary type name, which will be used to demonstrate
8+
// the race of issue #69912.
9+
type T int

src/cmd/compile/internal/types2/typexpr.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,16 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
6363
// Type-check the object.
6464
// Only call Checker.objDecl if the object doesn't have a type yet
6565
// (in which case we must actually determine it) or the object is a
66-
// TypeName and we also want a type (in which case we might detect
67-
// a cycle which needs to be reported). Otherwise we can skip the
68-
// call and avoid a possible cycle error in favor of the more
69-
// informative "not a type/value" error that this function's caller
70-
// will issue (see go.dev/issue/25790).
66+
// TypeName from the current package and we also want a type (in which case
67+
// we might detect a cycle which needs to be reported). Otherwise we can skip
68+
// the call and avoid a possible cycle error in favor of the more informative
69+
// "not a type/value" error that this function's caller will issue (see
70+
// go.dev/issue/25790).
71+
//
72+
// Note that it is important to avoid calling objDecl on objects from other
73+
// packages, to avoid races: see issue #69912.
7174
typ := obj.Type()
72-
if typ == nil || gotType && wantType {
75+
if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) {
7376
check.objDecl(obj, def)
7477
typ = obj.Type() // type must have been assigned by Checker.objDecl
7578
}

src/go/internal/gcimporter/gcimporter_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"runtime"
1616
"strings"
17+
"sync"
1718
"testing"
1819
"time"
1920

@@ -750,3 +751,68 @@ func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object {
750751
t.Fatalf("%s not found", name)
751752
return nil
752753
}
754+
755+
// importMap implements the types.Importer interface.
756+
type importMap map[string]*types.Package
757+
758+
func (m importMap) Import(path string) (*types.Package, error) { return m[path], nil }
759+
760+
func TestIssue69912(t *testing.T) {
761+
testenv.MustHaveGoBuild(t)
762+
763+
// This package only handles gc export data.
764+
if runtime.Compiler != "gc" {
765+
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
766+
}
767+
768+
tmpdir := t.TempDir()
769+
testoutdir := filepath.Join(tmpdir, "testdata")
770+
if err := os.Mkdir(testoutdir, 0700); err != nil {
771+
t.Fatalf("making output dir: %v", err)
772+
}
773+
774+
compile(t, "testdata", "issue69912.go", testoutdir, nil)
775+
776+
fset := token.NewFileSet()
777+
778+
issue69912, err := Import(fset, make(map[string]*types.Package), "./testdata/issue69912", tmpdir, nil)
779+
if err != nil {
780+
t.Fatal(err)
781+
}
782+
783+
check := func(pkgname, src string, imports importMap) (*types.Package, error) {
784+
f, err := parser.ParseFile(fset, "a.go", src, 0)
785+
if err != nil {
786+
return nil, err
787+
}
788+
config := &types.Config{
789+
Importer: imports,
790+
}
791+
return config.Check(pkgname, fset, []*ast.File{f}, nil)
792+
}
793+
794+
// Use the resulting package concurrently, via dot-imports, to exercise the
795+
// race of issue #69912.
796+
const pSrc = `package p
797+
798+
import . "issue69912"
799+
800+
type S struct {
801+
f T
802+
}
803+
`
804+
importer := importMap{
805+
"issue69912": issue69912,
806+
}
807+
var wg sync.WaitGroup
808+
for range 10 {
809+
wg.Add(1)
810+
go func() {
811+
defer wg.Done()
812+
if _, err := check("p", pSrc, importer); err != nil {
813+
t.Errorf("Check failed: %v", err)
814+
}
815+
}()
816+
}
817+
wg.Wait()
818+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package issue69912
6+
7+
// Define an arbitrary type name, which will be used to demonstrate
8+
// the race of issue #69912.
9+
type T int

src/go/types/typexpr.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,16 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
6363
// Type-check the object.
6464
// Only call Checker.objDecl if the object doesn't have a type yet
6565
// (in which case we must actually determine it) or the object is a
66-
// TypeName and we also want a type (in which case we might detect
67-
// a cycle which needs to be reported). Otherwise we can skip the
68-
// call and avoid a possible cycle error in favor of the more
69-
// informative "not a type/value" error that this function's caller
70-
// will issue (see go.dev/issue/25790).
66+
// TypeName from the current package and we also want a type (in which case
67+
// we might detect a cycle which needs to be reported). Otherwise we can skip
68+
// the call and avoid a possible cycle error in favor of the more informative
69+
// "not a type/value" error that this function's caller will issue (see
70+
// go.dev/issue/25790).
71+
//
72+
// Note that it is important to avoid calling objDecl on objects from other
73+
// packages, to avoid races: see issue #69912.
7174
typ := obj.Type()
72-
if typ == nil || gotType && wantType {
75+
if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) {
7376
check.objDecl(obj, def)
7477
typ = obj.Type() // type must have been assigned by Checker.objDecl
7578
}

0 commit comments

Comments
 (0)