// Name = identifier | "?" | QualifiedName . // // If materializePkg is set, the returned package is guaranteed to be set. // For fully qualified names, the returned package may be a fake package // (without name, scope, and not in the p.imports map), created for the // sole purpose of providing a package path. Fake packages are created // when the package id is not found in the p.imports map; in that case // we cannot create a real package because we don't have a package name. // For non-qualified names, the returned package is the imported package. // func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) { switch p.tok { case scanner.Ident: pkg = p.imports[p.id] name = p.lit p.next() case '?': // anonymous pkg = p.imports[p.id] p.next() case '@': // exported name prefixed with package path var id string id, name = p.parseQualifiedName() if materializePkg { // we don't have a package name - if the package // doesn't exist yet, create a fake package instead pkg = p.getPkg(id, "") if pkg == nil { pkg = types.NewPackage(id, "") } } default: p.error("name expected") } return }
func (p *importer) fieldName(parent *types.Package) (*types.Package, string) { name := p.string() pkg := parent if pkg == nil { // use the imported package instead pkg = p.pkgList[0] } if p.version == 0 && name == "_" { // version 0 didn't export a package for _ fields // see issue #15514 // For bug-compatibility with gc, pretend all imported // blank fields belong to the same dummy package. // This avoids spurious "cannot assign A to B" errors // from go/types caused by types changing as they are // re-exported. const blankpkg = "<_>" pkg := p.imports[blankpkg] if pkg == nil { pkg = types.NewPackage(blankpkg, blankpkg) p.imports[blankpkg] = pkg } return pkg, name } if name != "" && !exported(name) { if name == "?" { name = "" } pkg = p.pkg() } return pkg, name }
func NewPackage(path, name string) *Package { return &Package{ types.NewPackage(path, name), token.NewFileSet(), []Type{}, } }
// getPkg returns the package for a given id. If the package is // not found, create the package and add it to the p.localPkgs // and p.sharedPkgs maps. name is the (expected) name of the // package. If name == "", the package name is expected to be // set later via an import clause in the export data. // // id identifies a package, usually by a canonical package path like // "encoding/json" but possibly by a non-canonical import path like // "./json". // func (p *parser) getPkg(id, name string) *types.Package { // package unsafe is not in the packages maps - handle explicitly if id == "unsafe" { return types.Unsafe } pkg := p.localPkgs[id] if pkg == nil { // first import of id from this package pkg = p.sharedPkgs[id] if pkg == nil { // first import of id by this importer; // add (possibly unnamed) pkg to shared packages pkg = types.NewPackage(id, name) p.sharedPkgs[id] = pkg } // add (possibly unnamed) pkg to local packages if p.localPkgs == nil { p.localPkgs = make(map[string]*types.Package) } p.localPkgs[id] = pkg } else if name != "" { // package exists already and we have an expected package name; // make sure names match or set package name if necessary if pname := pkg.Name(); pname == "" { setName(pkg, name) } else if pname != name { p.errorf("%s package name mismatch: %s (given) vs %s (expected)", id, pname, name) } } return pkg }
func (p *importer) pkg() *types.Package { // if the package was seen before, i is its index (>= 0) i := p.int() if i >= 0 { return p.pkgList[i] } // otherwise, i is the package tag (< 0) if i != packageTag { panic(fmt.Sprintf("unexpected package tag %d", i)) } // read package data name := p.string() path := p.string() // if the package was imported before, use that one; otherwise create a new one pkg := p.imports[path] if pkg == nil { pkg = types.NewPackage(path, name) p.imports[path] = pkg } p.pkgList = append(p.pkgList, pkg) return pkg }
func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { pkg := types.NewPackage(path, "") info := &PackageInfo{ Pkg: pkg, Info: types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), }, errorFunc: imp.conf.TypeChecker.Error, dir: dir, } // Copy the types.Config so we can vary it across PackageInfos. tc := imp.conf.TypeChecker tc.IgnoreFuncBodies = false if f := imp.conf.TypeCheckFuncBodies; f != nil { tc.IgnoreFuncBodies = !f(path) } tc.Importer = closure{imp, info} tc.Error = info.appendError // appendError wraps the user's Error function info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) imp.progMu.Lock() imp.prog.AllPackages[pkg] = info imp.progMu.Unlock() return info }
// TestDevendorizeImportPaths checks if vendored // import paths are devendorized correctly. func TestDevendorizeImportPaths(t *testing.T) { i := imports.New("github.com/ernesto-jimenez/gogen/imports") pkg := types.NewPackage("github.com/ernesto-jimenez/gogen/vendor/github.com/stretchr/testify/mock", "mock") named := types.NewNamed(types.NewTypeName(token.Pos(0), pkg, "", &types.Array{}), &types.Array{}, nil) i.AddImportsFrom(named) require.Equal(t, map[string]string{"github.com/stretchr/testify/mock": "mock"}, i.Imports()) }
// getPkg returns the package for a given path. If the package is // not found but we have a package name, create the package and // add it to the p.imports map. // func (p *parser) getPkg(pkgpath, name string) *types.Package { // package unsafe is not in the imports map - handle explicitly if pkgpath == "unsafe" { return types.Unsafe } pkg := p.imports[pkgpath] if pkg == nil && name != "" { pkg = types.NewPackage(pkgpath, name) p.imports[pkgpath] = pkg } return pkg }
func loadExportData(pkgs []*build.Package, env []string, args ...string) ([]*types.Package, error) { // Compile the package. This will produce good errors if the package // doesn't typecheck for some reason, and is a necessary step to // building the final output anyway. paths := make([]string, len(pkgs)) for i, p := range pkgs { paths[i] = p.ImportPath } if err := goInstall(paths, env, args...); err != nil { return nil, err } goos, goarch := getenv(env, "GOOS"), getenv(env, "GOARCH") // Assemble a fake GOPATH and trick go/importer into using it. // Ideally the importer package would let us provide this to // it somehow, but this works with what's in Go 1.5 today and // gives us access to the gcimporter package without us having // to make a copy of it. fakegopath := filepath.Join(tmpdir, "fakegopath") if err := removeAll(fakegopath); err != nil { return nil, err } if err := mkdir(filepath.Join(fakegopath, "pkg")); err != nil { return nil, err } typePkgs := make([]*types.Package, len(pkgs)) imp := importer.Default() for i, p := range pkgs { importPath := p.ImportPath src := filepath.Join(pkgdir(env), importPath+".a") dst := filepath.Join(fakegopath, "pkg/"+goos+"_"+goarch+"/"+importPath+".a") if err := copyFile(dst, src); err != nil { return nil, err } if buildN { typePkgs[i] = types.NewPackage(importPath, path.Base(importPath)) continue } oldDefault := build.Default build.Default = ctx // copy build.Default.GOARCH = goarch build.Default.GOPATH = fakegopath p, err := imp.Import(importPath) build.Default = oldDefault if err != nil { return nil, err } typePkgs[i] = p } return typePkgs, nil }
func TestBuildPackage_MissingImport(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) if err != nil { t.Fatal(err) } pkg := types.NewPackage("bad", "") ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) if err == nil || ssapkg != nil { t.Fatal("BuildPackage succeeded unexpectedly") } }
func TestSetName(t *testing.T) { pkg := types.NewPackage("path", "foo") scope := pkg.Scope() // verify setName setName(pkg, "bar") if name := pkg.Name(); name != "bar" { t.Fatalf(`got package name %q; want "bar"`, name) } // verify no other fields are changed if pkg.Path() != "path" || pkg.Scope() != scope || pkg.Complete() || pkg.Imports() != nil { t.Fatalf("setName changed other fields") } }
func (p *importer) pkg() *types.Package { // if the package was seen before, i is its index (>= 0) i := p.tagOrIndex() if i >= 0 { return p.pkgList[i] } // otherwise, i is the package tag (< 0) if i != packageTag { panic(fmt.Sprintf("unexpected package tag %d", i)) } // read package data name := p.string() path := p.string() // we should never see an empty package name if name == "" { panic("empty package name in import") } // an empty path denotes the package we are currently importing; // it must be the first package we see if (path == "") != (len(p.pkgList) == 0) { panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList))) } // if the package was imported before, use that one; otherwise create a new one if path == "" { path = p.path } pkg := p.imports[path] if pkg == nil { pkg = types.NewPackage(path, name) p.imports[path] = pkg } else if pkg.Name() != name { panic(fmt.Sprintf("conflicting names %s and %s for package %q", pkg.Name(), name, path)) } p.pkgList = append(p.pkgList, pkg) return pkg }
func TestBuildPackage(t *testing.T) { // There is a more substantial test of BuildPackage and the // SSA program it builds in ../ssa/builder_test.go. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, 0) if err != nil { t.Fatal(err) } pkg := types.NewPackage("hello", "") ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, pkg, []*ast.File{f}, 0) if err != nil { t.Fatal(err) } if pkg.Name() != "main" { t.Errorf("pkg.Name() = %s, want main", pkg.Name()) } if ssapkg.Func("main") == nil { ssapkg.WriteTo(os.Stderr) t.Errorf("ssapkg has no main function") } }
// getPkg returns the package for a given id. If the package is // not found but we have a package name, create the package and // add it to the p.localPkgs and p.sharedPkgs maps. // // id identifies a package, usually by a canonical package path like // "encoding/json" but possibly by a non-canonical import path like // "./json". // func (p *parser) getPkg(id, name string) *types.Package { // package unsafe is not in the packages maps - handle explicitly if id == "unsafe" { return types.Unsafe } pkg := p.localPkgs[id] if pkg == nil && name != "" { // first import of id from this package pkg = p.sharedPkgs[id] if pkg == nil { // first import of id by this importer pkg = types.NewPackage(id, name) p.sharedPkgs[id] = pkg } if p.localPkgs == nil { p.localPkgs = make(map[string]*types.Package) } p.localPkgs[id] = pkg } return pkg }
"go/types" "reflect" "unsafe" "golang.org/x/tools/go/ssa" ) type opaqueType struct { types.Type name string } func (t *opaqueType) String() string { return t.name } // A bogus "reflect" type-checker package. Shared across interpreters. var reflectTypesPackage = types.NewPackage("reflect", "reflect") // rtype is the concrete type the interpreter uses to implement the // reflect.Type interface. // // type rtype <opaque> var rtypeType = makeNamedType("rtype", &opaqueType{nil, "rtype"}) // error is an (interpreted) named type whose underlying type is string. // The interpreter uses it for all implementations of the built-in error // interface that it creates. // We put it in the "reflect" package for expedience. // // type error string var errorType = makeNamedType("error", &opaqueType{nil, "error"})
// BImportData imports a package from the serialized package data // and returns the number of bytes consumed and a reference to the package. // If data is obviously malformed, an error is returned but in // general it is not recommended to call BImportData on untrusted data. func BImportData(imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) { p := importer{ imports: imports, data: data, } p.buf = p.bufarray[:] // read low-level encoding format switch format := p.byte(); format { case 'c': // compact format - nothing to do case 'd': p.debugFormat = true default: return p.read, nil, fmt.Errorf("invalid encoding format in export data: got %q; want 'c' or 'd'", format) } // --- generic export data --- if v := p.string(); v != "v0" { return p.read, nil, fmt.Errorf("unknown version: %s", v) } // populate typList with predeclared "known" types p.typList = append(p.typList, predeclared...) // read package data // TODO(gri) clean this up i := p.tagOrIndex() if i != packageTag { panic(fmt.Sprintf("package tag expected, got %d", i)) } name := p.string() if s := p.string(); s != "" { panic(fmt.Sprintf("empty path expected, got %s", s)) } pkg := p.imports[path] if pkg == nil { pkg = types.NewPackage(path, name) p.imports[path] = pkg } p.pkgList = append(p.pkgList, pkg) if debug && p.pkgList[0] != pkg { panic("imported packaged not found in pkgList[0]") } // read compiler-specific flags p.string() // discard // read consts for i := p.int(); i > 0; i-- { name := p.string() typ := p.typ(nil) val := p.value() p.declare(types.NewConst(token.NoPos, pkg, name, typ, val)) } // read vars for i := p.int(); i > 0; i-- { name := p.string() typ := p.typ(nil) p.declare(types.NewVar(token.NoPos, pkg, name, typ)) } // read funcs for i := p.int(); i > 0; i-- { name := p.string() sig := p.typ(nil).(*types.Signature) p.int() // read and discard index of inlined function body p.declare(types.NewFunc(token.NoPos, pkg, name, sig)) } // read types for i := p.int(); i > 0; i-- { // name is parsed as part of named type and the // type object is added to scope via respective // named type _ = p.typ(nil).(*types.Named) } // ignore compiler-specific import data // complete interfaces for _, typ := range p.typList { if it, ok := typ.(*types.Interface); ok { it.Complete() } } // record all referenced packages as imports list := append(([]*types.Package)(nil), p.pkgList[1:]...) sort.Sort(byPath(list)) pkg.SetImports(list) // package was imported completely and without errors pkg.MarkComplete() return p.read, pkg, nil }
// CreateTestMainPackage creates and returns a synthetic "main" // package that runs all the tests of the supplied packages, similar // to the one that would be created by the 'go test' tool. // // It returns nil if the program contains no tests. // func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package { pkgs, tests, benchmarks, examples := FindTests(pkgs) if len(pkgs) == 0 { return nil } testmain := &Package{ Prog: prog, Members: make(map[string]Member), values: make(map[types.Object]Value), Pkg: types.NewPackage("test$main", "main"), } // Build package's init function. init := &Function{ name: "init", Signature: new(types.Signature), Synthetic: "package initializer", Pkg: testmain, Prog: prog, } init.startBody() if testMainStartBodyHook != nil { testMainStartBodyHook(init) } // Initialize packages to test. var pkgpaths []string for _, pkg := range pkgs { var v Call v.Call.Value = pkg.init v.setType(types.NewTuple()) init.emit(&v) pkgpaths = append(pkgpaths, pkg.Pkg.Path()) } sort.Strings(pkgpaths) init.emit(new(Return)) init.finishBody() testmain.init = init testmain.Pkg.MarkComplete() testmain.Members[init.name] = init // For debugging convenience, define an unexported const // that enumerates the packages. packagesConst := types.NewConst(token.NoPos, testmain.Pkg, "packages", tString, exact.MakeString(strings.Join(pkgpaths, " "))) memberFromObject(testmain, packagesConst, nil) // Create main *types.Func and *ssa.Function mainFunc := types.NewFunc(token.NoPos, testmain.Pkg, "main", new(types.Signature)) memberFromObject(testmain, mainFunc, nil) main := testmain.Func("main") main.Synthetic = "test main function" main.startBody() if testMainStartBodyHook != nil { testMainStartBodyHook(main) } if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { testingMain := testingPkg.Func("Main") testingMainParams := testingMain.Signature.Params() // The generated code is as if compiled from this: // // func main() { // match := func(_, _ string) (bool, error) { return true, nil } // tests := []testing.InternalTest{{"TestFoo", TestFoo}, ...} // benchmarks := []testing.InternalBenchmark{...} // examples := []testing.InternalExample{...} // testing.Main(match, tests, benchmarks, examples) // } matcher := &Function{ name: "matcher", Signature: testingMainParams.At(0).Type().(*types.Signature), Synthetic: "test matcher predicate", parent: main, Pkg: testmain, Prog: prog, } main.AnonFuncs = append(main.AnonFuncs, matcher) matcher.startBody() matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}}) matcher.finishBody() // Emit call: testing.Main(matcher, tests, benchmarks, examples). var c Call c.Call.Value = testingMain c.Call.Args = []Value{ matcher, testMainSlice(main, tests, testingMainParams.At(1).Type()), testMainSlice(main, benchmarks, testingMainParams.At(2).Type()), testMainSlice(main, examples, testingMainParams.At(3).Type()), } emitTailCall(main, &c) } else { // The program does not import "testing", but FindTests // returned non-nil, which must mean there were Examples // but no Tests or Benchmarks. // We'll simply call them from testmain.main; this will // ensure they don't panic, but will not check any // "Output:" comments. for _, eg := range examples { var c Call c.Call.Value = eg c.setType(types.NewTuple()) main.emit(&c) } main.emit(&Return{}) main.currentBlock = nil } main.finishBody() testmain.Members["main"] = main if prog.mode&PrintPackages != 0 { printMu.Lock() testmain.WriteTo(os.Stdout) printMu.Unlock() } if prog.mode&SanityCheckFunctions != 0 { sanityCheckPackage(testmain) } prog.packages[testmain.Pkg] = testmain return testmain }
// CreateTestMainPackage creates and returns a synthetic "testmain" // package for the specified package if it defines tests, benchmarks or // executable examples, or nil otherwise. The new package is named // "main" and provides a function named "main" that runs the tests, // similar to the one that would be created by the 'go test' tool. // // Subsequent calls to prog.AllPackages include the new package. // The package pkg must belong to the program prog. func (prog *Program) CreateTestMainPackage(pkg *Package) *Package { if pkg.Prog != prog { log.Fatal("Package does not belong to Program") } tests, benchmarks, examples, testMainFunc := FindTests(pkg) if testMainFunc == nil && tests == nil && benchmarks == nil && examples == nil { return nil } testmain := &Package{ Prog: prog, Members: make(map[string]Member), values: make(map[types.Object]Value), Pkg: types.NewPackage(pkg.Pkg.Path()+"$testmain", "main"), } // Build package's init function. init := &Function{ name: "init", Signature: new(types.Signature), Synthetic: "package initializer", Pkg: testmain, Prog: prog, } init.startBody() if testMainStartBodyHook != nil { testMainStartBodyHook(init) } // Initialize package under test. var v Call v.Call.Value = pkg.init v.setType(types.NewTuple()) init.emit(&v) init.emit(new(Return)) init.finishBody() testmain.init = init testmain.Pkg.MarkComplete() testmain.Members[init.name] = init // Create main *types.Func and *Function mainFunc := types.NewFunc(token.NoPos, testmain.Pkg, "main", new(types.Signature)) memberFromObject(testmain, mainFunc, nil) main := testmain.Func("main") main.Synthetic = "test main function" main.startBody() if testMainStartBodyHook != nil { testMainStartBodyHook(main) } if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { testingMain := testingPkg.Func("Main") testingMainParams := testingMain.Signature.Params() // The generated code is as if compiled from this: // // func main() { // match := func(_, _ string) (bool, error) { return true, nil } // tests := []testing.InternalTest{{"TestFoo", TestFoo}, ...} // benchmarks := []testing.InternalBenchmark{...} // examples := []testing.InternalExample{...} // if TestMain is defined { // m := testing.MainStart(match, tests, benchmarks, examples) // return TestMain(m) // } else { // return testing.Main(match, tests, benchmarks, examples) // } // } matcher := &Function{ name: "matcher", Signature: testingMainParams.At(0).Type().(*types.Signature), Synthetic: "test matcher predicate", parent: main, Pkg: testmain, Prog: prog, } main.AnonFuncs = append(main.AnonFuncs, matcher) matcher.startBody() matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}}) matcher.finishBody() var c Call c.Call.Args = []Value{ matcher, testMainSlice(main, tests, testingMainParams.At(1).Type()), testMainSlice(main, benchmarks, testingMainParams.At(2).Type()), testMainSlice(main, examples, testingMainParams.At(3).Type()), } if testMainFunc != nil { // Emit: m := testing.MainStart(matcher, tests, benchmarks, examples). // (Main and MainStart have the same parameters.) mainStart := testingPkg.Func("MainStart") c.Call.Value = mainStart c.setType(mainStart.Signature.Results().At(0).Type()) // *testing.M m := main.emit(&c) // Emit: return TestMain(m) var c2 Call c2.Call.Value = testMainFunc c2.Call.Args = []Value{m} emitTailCall(main, &c2) } else { // Emit: return testing.Main(matcher, tests, benchmarks, examples) c.Call.Value = testingMain emitTailCall(main, &c) } } else { // The program does not import "testing", but FindTests // returned non-nil, which must mean there were Examples // but no Test, Benchmark, or TestMain functions. // We'll simply call them from testmain.main; this will // ensure they don't panic, but will not check any // "Output:" comments. // (We should not execute an Example that has no // "Output:" comment, but it's impossible to tell here.) for _, eg := range examples { var c Call c.Call.Value = eg c.setType(types.NewTuple()) main.emit(&c) } main.emit(&Return{}) main.currentBlock = nil } main.finishBody() testmain.Members["main"] = main if prog.mode&PrintPackages != 0 { printMu.Lock() testmain.WriteTo(os.Stdout) printMu.Unlock() } if prog.mode&SanityCheckFunctions != 0 { sanityCheckPackage(testmain) } prog.packages[testmain.Pkg] = testmain return testmain }
// This program demonstrates how to run the SSA builder on a single // package of one or more already-parsed files. Its dependencies are // loaded from compiler export data. This is what you'd typically use // for a compiler; it does not depend on golang.org/x/tools/go/loader. // // It shows the printed representation of packages, functions, and // instructions. Within the function listing, the name of each // BasicBlock such as ".0.entry" is printed left-aligned, followed by // the block's Instructions. // // For each instruction that defines an SSA virtual register // (i.e. implements Value), the type of that value is shown in the // right column. // // Build and run the ssadump.go program if you want a standalone tool // with similar functionality. It is located at // golang.org/x/tools/cmd/ssadump. // func ExampleBuildPackage() { // Parse the source files. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments) if err != nil { fmt.Print(err) // parse error return } files := []*ast.File{f} // Create the type-checker's package. pkg := types.NewPackage("hello", "") // Type-check the package, load dependencies. // Create and build the SSA program. hello, _, err := ssautil.BuildPackage( &types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions) if err != nil { fmt.Print(err) // type error in some package return } // Print out the package. hello.WriteTo(os.Stdout) // Print out the package-level functions. hello.Func("init").WriteTo(os.Stdout) hello.Func("main").WriteTo(os.Stdout) // Output: // // package hello: // func init func() // var init$guard bool // func main func() // const message message = "Hello, World!":untyped string // // # Name: hello.init // # Package: hello // # Synthetic: package initializer // func init(): // 0: entry P:0 S:2 // t0 = *init$guard bool // if t0 goto 2 else 1 // 1: init.start P:1 S:1 // *init$guard = true:bool // t1 = fmt.init() () // jump 2 // 2: init.done P:2 S:0 // return // // # Name: hello.main // # Package: hello // # Location: hello.go:8:6 // func main(): // 0: entry P:0 S:0 // t0 = new [1]interface{} (varargs) *[1]interface{} // t1 = &t0[0:int] *interface{} // t2 = make interface{} <- string ("Hello, World!":string) interface{} // *t1 = t2 // t3 = slice t0[:] []interface{} // t4 = fmt.Println(t3...) (n int, err error) // return }
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types. func TestRuntimeTypes(t *testing.T) { tests := []struct { input string want []string }{ // An exported package-level type is needed. {`package A; type T struct{}; func (T) f() {}`, []string{"*p.T", "p.T"}, }, // An unexported package-level type is not needed. {`package B; type t struct{}; func (t) f() {}`, nil, }, // Subcomponents of type of exported package-level var are needed. {`package C; import "bytes"; var V struct {*bytes.Buffer}`, []string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level var are not needed. {`package D; import "bytes"; var v struct {*bytes.Buffer}`, nil, }, // Subcomponents of type of exported package-level function are needed. {`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`, []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported package-level function are not needed. {`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`, nil, }, // Subcomponents of type of exported method of uninstantiated unexported type are not needed. {`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`, nil, }, // ...unless used by MakeInterface. {`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`, []string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"}, }, // Subcomponents of type of unexported method are not needed. {`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, []string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"}, }, // Local types aren't needed. {`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, nil, }, // ...unless used by MakeInterface. {`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, []string{"*bytes.Buffer", "*p.T", "p.T"}, }, // Types used as operand of MakeInterface are needed. {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, }, // MakeInterface is optimized away when storing to a blank. {`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, nil, }, } for _, test := range tests { // Parse the file. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "input.go", test.input, 0) if err != nil { t.Errorf("test %q: %s", test.input[:15], err) continue } // Create a single-file main package. // Load dependencies from gc binary export data. ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) if err != nil { t.Errorf("test %q: %s", test.input[:15], err) continue } var typstrs []string for _, T := range ssapkg.Prog.RuntimeTypes() { typstrs = append(typstrs, T.String()) } sort.Strings(typstrs) if !reflect.DeepEqual(typstrs, test.want) { t.Errorf("test 'package %s': got %q, want %q", f.Name.Name, typstrs, test.want) } } }
// Tests that programs partially loaded from gc object files contain // functions with no code for the external portions, but are otherwise ok. func TestBuildPackage(t *testing.T) { input := ` package main import ( "bytes" "io" "testing" ) func main() { var t testing.T t.Parallel() // static call to external declared method t.Fail() // static call to promoted external declared method testing.Short() // static call to external package-level function var w io.Writer = new(bytes.Buffer) w.Write(nil) // interface invoke of external declared method } ` // Parse the file. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "input.go", input, 0) if err != nil { t.Error(err) return } // Build an SSA program from the parsed file. // Load its dependencies from gc binary export data. mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions) if err != nil { t.Error(err) return } // The main package, its direct and indirect dependencies are loaded. deps := []string{ // directly imported dependencies: "bytes", "io", "testing", // indirect dependencies mentioned by // the direct imports' export data "sync", "unicode", "time", } prog := mainPkg.Prog all := prog.AllPackages() if len(all) <= len(deps) { t.Errorf("unexpected set of loaded packages: %q", all) } for _, path := range deps { pkg := prog.ImportedPackage(path) if pkg == nil { t.Errorf("package not loaded: %q", path) continue } // External packages should have no function bodies (except for wrappers). isExt := pkg != mainPkg // init() if isExt && !isEmpty(pkg.Func("init")) { t.Errorf("external package %s has non-empty init", pkg) } else if !isExt && isEmpty(pkg.Func("init")) { t.Errorf("main package %s has empty init", pkg) } for _, mem := range pkg.Members { switch mem := mem.(type) { case *ssa.Function: // Functions at package level. if isExt && !isEmpty(mem) { t.Errorf("external function %s is non-empty", mem) } else if !isExt && isEmpty(mem) { t.Errorf("function %s is empty", mem) } case *ssa.Type: // Methods of named types T. // (In this test, all exported methods belong to *T not T.) if !isExt { t.Fatalf("unexpected name type in main package: %s", mem) } mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { m := prog.MethodValue(mset.At(i)) // For external types, only synthetic wrappers have code. expExt := !strings.Contains(m.Synthetic, "wrapper") if expExt && !isEmpty(m) { t.Errorf("external method %s is non-empty: %s", m, m.Synthetic) } else if !expExt && isEmpty(m) { t.Errorf("method function %s is empty: %s", m, m.Synthetic) } } } } } expectedCallee := []string{ "(*testing.T).Parallel", "(*testing.common).Fail", "testing.Short", "N/A", } callNum := 0 for _, b := range mainPkg.Func("main").Blocks { for _, instr := range b.Instrs { switch instr := instr.(type) { case ssa.CallInstruction: call := instr.Common() if want := expectedCallee[callNum]; want != "N/A" { got := call.StaticCallee().String() if want != got { t.Errorf("call #%d from main.main: got callee %s, want %s", callNum, got, want) } } callNum++ } } } if callNum != 4 { t.Errorf("in main.main: got %d calls, want %d", callNum, 4) } }