mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
In gc export data, exported struct field and interface method names appear in unqualified form (i.e., w/o package name). The (gc)importer assumed that unqualified exported names automatically belong to the package being imported. This is not the case if the field or method belongs to a struct or interface that was declared in another package and re-exported. The issue becomes visible if a type T (say an interface with a method M) is declared in a package A, indirectly re-exported by a package B (which imports A), and then imported in C. If C imports both A and B, if A is imported before B, T.M gets associated with the correct package A. If B is imported before A, T.M appears to be exported by B (even though T itself is correctly marked as coming from A). If T.M is imported again via the import of A if gets dropped (as it should) because it was imported already. The fix is to pass down the parent package when we parse imported types so that the importer can use the correct package when creating fields and methods. Fixes #13898. Change-Id: I7ec2ee2dda15859c582b65db221c3841899776e1 Reviewed-on: https://go-review.googlesource.com/18549 Reviewed-by: Alan Donovan <adonovan@google.com>
363 lines
9.7 KiB
Go
363 lines
9.7 KiB
Go
// Copyright 2011 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 gcimporter
|
|
|
|
import (
|
|
"fmt"
|
|
"internal/testenv"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"go/types"
|
|
)
|
|
|
|
// skipSpecialPlatforms causes the test to be skipped for platforms where
|
|
// builders (build.golang.org) don't have access to compiled packages for
|
|
// import.
|
|
func skipSpecialPlatforms(t *testing.T) {
|
|
switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform {
|
|
case "nacl-amd64p32",
|
|
"nacl-386",
|
|
"nacl-arm",
|
|
"darwin-arm",
|
|
"darwin-arm64":
|
|
t.Skipf("no compiled packages available for import on %s", platform)
|
|
}
|
|
}
|
|
|
|
func compile(t *testing.T, dirname, filename string) string {
|
|
testenv.MustHaveGoBuild(t)
|
|
cmd := exec.Command("go", "tool", "compile", filename)
|
|
cmd.Dir = dirname
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatalf("go tool compile %s failed: %s", filename, err)
|
|
}
|
|
// filename should end with ".go"
|
|
return filepath.Join(dirname, filename[:len(filename)-2]+"o")
|
|
}
|
|
|
|
// TODO(gri) Remove this function once we switched to new export format by default.
|
|
func compileNewExport(t *testing.T, dirname, filename string) string {
|
|
testenv.MustHaveGoBuild(t)
|
|
cmd := exec.Command("go", "tool", "compile", "-newexport", filename)
|
|
cmd.Dir = dirname
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatalf("go tool compile %s failed: %s", filename, err)
|
|
}
|
|
// filename should end with ".go"
|
|
return filepath.Join(dirname, filename[:len(filename)-2]+"o")
|
|
}
|
|
|
|
func testPath(t *testing.T, path, srcDir string) *types.Package {
|
|
t0 := time.Now()
|
|
pkg, err := Import(make(map[string]*types.Package), path, srcDir)
|
|
if err != nil {
|
|
t.Errorf("testPath(%s): %s", path, err)
|
|
return nil
|
|
}
|
|
t.Logf("testPath(%s): %v", path, time.Since(t0))
|
|
return pkg
|
|
}
|
|
|
|
const maxTime = 30 * time.Second
|
|
|
|
func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
|
|
dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
|
|
list, err := ioutil.ReadDir(dirname)
|
|
if err != nil {
|
|
t.Fatalf("testDir(%s): %s", dirname, err)
|
|
}
|
|
for _, f := range list {
|
|
if time.Now().After(endTime) {
|
|
t.Log("testing time used up")
|
|
return
|
|
}
|
|
switch {
|
|
case !f.IsDir():
|
|
// try extensions
|
|
for _, ext := range pkgExts {
|
|
if strings.HasSuffix(f.Name(), ext) {
|
|
name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
|
|
if testPath(t, filepath.Join(dir, name), dir) != nil {
|
|
nimports++
|
|
}
|
|
}
|
|
}
|
|
case f.IsDir():
|
|
nimports += testDir(t, filepath.Join(dir, f.Name()), endTime)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func TestImportTestdata(t *testing.T) {
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
if outFn := compile(t, "testdata", "exports.go"); outFn != "" {
|
|
defer os.Remove(outFn)
|
|
}
|
|
|
|
if pkg := testPath(t, "./testdata/exports", "."); pkg != nil {
|
|
// The package's Imports list must include all packages
|
|
// explicitly imported by exports.go, plus all packages
|
|
// referenced indirectly via exported objects in exports.go.
|
|
// With the textual export format, the list may also include
|
|
// additional packages that are not strictly required for
|
|
// import processing alone (they are exported to err "on
|
|
// the safe side").
|
|
got := fmt.Sprint(pkg.Imports())
|
|
for _, want := range []string{"go/ast", "go/token"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(gri) Remove this function once we switched to new export format by default
|
|
// (and update the comment and want list in TestImportTestdata).
|
|
func TestImportTestdataNewExport(t *testing.T) {
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
if outFn := compileNewExport(t, "testdata", "exports.go"); outFn != "" {
|
|
defer os.Remove(outFn)
|
|
}
|
|
|
|
if pkg := testPath(t, "./testdata/exports", "."); pkg != nil {
|
|
// The package's Imports list must include all packages
|
|
// explicitly imported by exports.go, plus all packages
|
|
// referenced indirectly via exported objects in exports.go.
|
|
want := `[package ast ("go/ast") package token ("go/token")]`
|
|
got := fmt.Sprint(pkg.Imports())
|
|
if got != want {
|
|
t.Errorf(`Package("exports").Imports() = %s, want %s`, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImportStdLib(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
dt := maxTime
|
|
if testing.Short() && testenv.Builder() == "" {
|
|
dt = 10 * time.Millisecond
|
|
}
|
|
nimports := testDir(t, "", time.Now().Add(dt)) // installed packages
|
|
t.Logf("tested %d imports", nimports)
|
|
}
|
|
|
|
var importedObjectTests = []struct {
|
|
name string
|
|
want string
|
|
}{
|
|
{"math.Pi", "const Pi untyped float"},
|
|
{"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
|
|
{"io.ReadWriter", "type ReadWriter interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"},
|
|
{"math.Sin", "func Sin(x float64) float64"},
|
|
// TODO(gri) add more tests
|
|
}
|
|
|
|
func TestImportedTypes(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
for _, test := range importedObjectTests {
|
|
s := strings.Split(test.name, ".")
|
|
if len(s) != 2 {
|
|
t.Fatal("inconsistent test data")
|
|
}
|
|
importPath := s[0]
|
|
objName := s[1]
|
|
|
|
pkg, err := Import(make(map[string]*types.Package), importPath, ".")
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
obj := pkg.Scope().Lookup(objName)
|
|
if obj == nil {
|
|
t.Errorf("%s: object not found", test.name)
|
|
continue
|
|
}
|
|
|
|
got := types.ObjectString(obj, types.RelativeTo(pkg))
|
|
if got != test.want {
|
|
t.Errorf("%s: got %q; want %q", test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIssue5815(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
pkg, err := Import(make(map[string]*types.Package), "strings", ".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scope := pkg.Scope()
|
|
for _, name := range scope.Names() {
|
|
obj := scope.Lookup(name)
|
|
if obj.Pkg() == nil {
|
|
t.Errorf("no pkg for %s", obj)
|
|
}
|
|
if tname, _ := obj.(*types.TypeName); tname != nil {
|
|
named := tname.Type().(*types.Named)
|
|
for i := 0; i < named.NumMethods(); i++ {
|
|
m := named.Method(i)
|
|
if m.Pkg() == nil {
|
|
t.Errorf("no pkg for %s", m)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Smoke test to ensure that imported methods get the correct package.
|
|
func TestCorrectMethodPackage(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
imports := make(map[string]*types.Package)
|
|
_, err := Import(imports, "net/http", ".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type()
|
|
mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex
|
|
sel := mset.Lookup(nil, "Lock")
|
|
lock := sel.Obj().(*types.Func)
|
|
if got, want := lock.Pkg().Path(), "sync"; got != want {
|
|
t.Errorf("got package path %q; want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestIssue13566(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
// On windows, we have to set the -D option for the compiler to avoid having a drive
|
|
// letter and an illegal ':' in the import path - just skip it (see also issue #3483).
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("avoid dealing with relative paths/drive letters on windows")
|
|
}
|
|
|
|
if f := compile(t, "testdata", "a.go"); f != "" {
|
|
defer os.Remove(f)
|
|
}
|
|
if f := compile(t, "testdata", "b.go"); f != "" {
|
|
defer os.Remove(f)
|
|
}
|
|
|
|
// import must succeed (test for issue at hand)
|
|
pkg, err := Import(make(map[string]*types.Package), "./testdata/b", ".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make sure all indirectly imported packages have names
|
|
for _, imp := range pkg.Imports() {
|
|
if imp.Name() == "" {
|
|
t.Errorf("no name for %s package", imp.Path())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIssue13898(t *testing.T) {
|
|
skipSpecialPlatforms(t)
|
|
|
|
// This package only handles gc export data.
|
|
if runtime.Compiler != "gc" {
|
|
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
|
return
|
|
}
|
|
|
|
// import go/internal/gcimporter which imports go/types partially
|
|
imports := make(map[string]*types.Package)
|
|
_, err := Import(imports, "go/internal/gcimporter", ".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// look for go/types package
|
|
var goTypesPkg *types.Package
|
|
for path, pkg := range imports {
|
|
if path == "go/types" {
|
|
goTypesPkg = pkg
|
|
break
|
|
}
|
|
}
|
|
if goTypesPkg == nil {
|
|
t.Fatal("go/types not found")
|
|
}
|
|
|
|
// look for go/types.Object type
|
|
obj := goTypesPkg.Scope().Lookup("Object")
|
|
if obj == nil {
|
|
t.Fatal("go/types.Object not found")
|
|
}
|
|
typ, ok := obj.Type().(*types.Named)
|
|
if !ok {
|
|
t.Fatalf("go/types.Object type is %v; wanted named type", typ)
|
|
}
|
|
|
|
// lookup go/types.Object.Pkg method
|
|
m, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Pkg")
|
|
if m == nil {
|
|
t.Fatal("go/types.Object.Pkg not found")
|
|
}
|
|
|
|
// the method must belong to go/types
|
|
if m.Pkg().Path() != "go/types" {
|
|
t.Fatalf("found %v; want go/types", m.Pkg())
|
|
}
|
|
}
|