mirror of https://github.com/golang/go.git
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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Findley <rfindley@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
067c091564
commit
c6531fae58
|
|
@ -6,6 +6,7 @@ package importer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmd/compile/internal/syntax"
|
||||||
"cmd/compile/internal/types2"
|
"cmd/compile/internal/types2"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
|
@ -16,6 +17,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -593,3 +595,66 @@ func lookupObj(t *testing.T, scope *types2.Scope, name string) types2.Object {
|
||||||
t.Fatalf("%s not found", name)
|
t.Fatalf("%s not found", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importMap implements the types2.Importer interface.
|
||||||
|
type importMap map[string]*types2.Package
|
||||||
|
|
||||||
|
func (m importMap) Import(path string) (*types2.Package, error) { return m[path], nil }
|
||||||
|
|
||||||
|
func TestIssue69912(t *testing.T) {
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
|
// This package only handles gc export data.
|
||||||
|
if runtime.Compiler != "gc" {
|
||||||
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpdir := t.TempDir()
|
||||||
|
testoutdir := filepath.Join(tmpdir, "testdata")
|
||||||
|
if err := os.Mkdir(testoutdir, 0700); err != nil {
|
||||||
|
t.Fatalf("making output dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
compile(t, "testdata", "issue69912.go", testoutdir, nil)
|
||||||
|
|
||||||
|
issue69912, err := Import(make(map[string]*types2.Package), "./testdata/issue69912", tmpdir, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
check := func(pkgname, src string, imports importMap) (*types2.Package, error) {
|
||||||
|
f, err := syntax.Parse(syntax.NewFileBase(pkgname), strings.NewReader(src), nil, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config := &types2.Config{
|
||||||
|
Importer: imports,
|
||||||
|
}
|
||||||
|
return config.Check(pkgname, []*syntax.File{f}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the resulting package concurrently, via dot-imports, to exercise the
|
||||||
|
// race of issue #69912.
|
||||||
|
const pSrc = `package p
|
||||||
|
|
||||||
|
import . "issue69912"
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
f T
|
||||||
|
}
|
||||||
|
`
|
||||||
|
importer := importMap{
|
||||||
|
"issue69912": issue69912,
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for range 10 {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if _, err := check("p", pSrc, importer); err != nil {
|
||||||
|
t.Errorf("Check failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issue69912
|
||||||
|
|
||||||
|
// Define an arbitrary type name, which will be used to demonstrate
|
||||||
|
// the race of issue #69912.
|
||||||
|
type T int
|
||||||
|
|
@ -63,13 +63,16 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
|
||||||
// Type-check the object.
|
// Type-check the object.
|
||||||
// Only call Checker.objDecl if the object doesn't have a type yet
|
// Only call Checker.objDecl if the object doesn't have a type yet
|
||||||
// (in which case we must actually determine it) or the object is a
|
// (in which case we must actually determine it) or the object is a
|
||||||
// TypeName and we also want a type (in which case we might detect
|
// TypeName from the current package and we also want a type (in which case
|
||||||
// a cycle which needs to be reported). Otherwise we can skip the
|
// we might detect a cycle which needs to be reported). Otherwise we can skip
|
||||||
// call and avoid a possible cycle error in favor of the more
|
// the call and avoid a possible cycle error in favor of the more informative
|
||||||
// informative "not a type/value" error that this function's caller
|
// "not a type/value" error that this function's caller will issue (see
|
||||||
// will issue (see go.dev/issue/25790).
|
// go.dev/issue/25790).
|
||||||
|
//
|
||||||
|
// Note that it is important to avoid calling objDecl on objects from other
|
||||||
|
// packages, to avoid races: see issue #69912.
|
||||||
typ := obj.Type()
|
typ := obj.Type()
|
||||||
if typ == nil || gotType && wantType {
|
if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) {
|
||||||
check.objDecl(obj, def)
|
check.objDecl(obj, def)
|
||||||
typ = obj.Type() // type must have been assigned by Checker.objDecl
|
typ = obj.Type() // type must have been assigned by Checker.objDecl
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -750,3 +751,68 @@ func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object {
|
||||||
t.Fatalf("%s not found", name)
|
t.Fatalf("%s not found", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importMap implements the types.Importer interface.
|
||||||
|
type importMap map[string]*types.Package
|
||||||
|
|
||||||
|
func (m importMap) Import(path string) (*types.Package, error) { return m[path], nil }
|
||||||
|
|
||||||
|
func TestIssue69912(t *testing.T) {
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
|
// This package only handles gc export data.
|
||||||
|
if runtime.Compiler != "gc" {
|
||||||
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpdir := t.TempDir()
|
||||||
|
testoutdir := filepath.Join(tmpdir, "testdata")
|
||||||
|
if err := os.Mkdir(testoutdir, 0700); err != nil {
|
||||||
|
t.Fatalf("making output dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
compile(t, "testdata", "issue69912.go", testoutdir, nil)
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
issue69912, err := Import(fset, make(map[string]*types.Package), "./testdata/issue69912", tmpdir, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
check := func(pkgname, src string, imports importMap) (*types.Package, error) {
|
||||||
|
f, err := parser.ParseFile(fset, "a.go", src, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config := &types.Config{
|
||||||
|
Importer: imports,
|
||||||
|
}
|
||||||
|
return config.Check(pkgname, fset, []*ast.File{f}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the resulting package concurrently, via dot-imports, to exercise the
|
||||||
|
// race of issue #69912.
|
||||||
|
const pSrc = `package p
|
||||||
|
|
||||||
|
import . "issue69912"
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
f T
|
||||||
|
}
|
||||||
|
`
|
||||||
|
importer := importMap{
|
||||||
|
"issue69912": issue69912,
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for range 10 {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if _, err := check("p", pSrc, importer); err != nil {
|
||||||
|
t.Errorf("Check failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issue69912
|
||||||
|
|
||||||
|
// Define an arbitrary type name, which will be used to demonstrate
|
||||||
|
// the race of issue #69912.
|
||||||
|
type T int
|
||||||
|
|
@ -63,13 +63,16 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
|
||||||
// Type-check the object.
|
// Type-check the object.
|
||||||
// Only call Checker.objDecl if the object doesn't have a type yet
|
// Only call Checker.objDecl if the object doesn't have a type yet
|
||||||
// (in which case we must actually determine it) or the object is a
|
// (in which case we must actually determine it) or the object is a
|
||||||
// TypeName and we also want a type (in which case we might detect
|
// TypeName from the current package and we also want a type (in which case
|
||||||
// a cycle which needs to be reported). Otherwise we can skip the
|
// we might detect a cycle which needs to be reported). Otherwise we can skip
|
||||||
// call and avoid a possible cycle error in favor of the more
|
// the call and avoid a possible cycle error in favor of the more informative
|
||||||
// informative "not a type/value" error that this function's caller
|
// "not a type/value" error that this function's caller will issue (see
|
||||||
// will issue (see go.dev/issue/25790).
|
// go.dev/issue/25790).
|
||||||
|
//
|
||||||
|
// Note that it is important to avoid calling objDecl on objects from other
|
||||||
|
// packages, to avoid races: see issue #69912.
|
||||||
typ := obj.Type()
|
typ := obj.Type()
|
||||||
if typ == nil || gotType && wantType {
|
if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) {
|
||||||
check.objDecl(obj, def)
|
check.objDecl(obj, def)
|
||||||
typ = obj.Type() // type must have been assigned by Checker.objDecl
|
typ = obj.Type() // type must have been assigned by Checker.objDecl
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue