go/ssa/ssautil: initialize info when there is syntax

Only initialize *types.Info when there are []*ast.Files available.

Previously, when packages x and z, where x imports y and y imports z,
are loaded with packages.LoadSyntax and built with ssautil.Packages,
then package y will have *types.Info but no files.
If y has any globals initialized from a func variable, the bodies
of the function literals did not have type information and failed
to build.

Fixes golang/go#53604

Change-Id: I3cce608d6a127ac44b65947b68cf0d748fc2dfc6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/422639
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Tim King 2022-08-10 15:14:19 -07:00
parent 0eebaabce7
commit e71c338beb
3 changed files with 62 additions and 1 deletions

View File

@ -2461,6 +2461,9 @@ func (p *Package) build() {
}
// Initialize package-level vars in correct order.
if len(p.info.InitOrder) > 0 && len(p.files) == 0 {
panic("no source files provided for package. cannot initialize globals")
}
for _, varinit := range p.info.InitOrder {
if init.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n",

View File

@ -77,10 +77,12 @@ func doPackages(initial []*packages.Package, mode ssa.BuilderMode, deps bool) (*
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
var files []*ast.File
var info *types.Info
if deps || isInitial[p] {
files = p.Syntax
info = p.TypesInfo
}
ssamap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true)
ssamap[p] = prog.CreatePackage(p.Types, files, info, true)
}
})

View File

@ -12,10 +12,12 @@ import (
"go/token"
"go/types"
"os"
"path"
"strings"
"testing"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
@ -135,3 +137,57 @@ func TestIssue28106(t *testing.T) {
prog, _ := ssautil.Packages(pkgs, ssa.BuilderMode(0))
prog.Build() // no crash
}
func TestIssue53604(t *testing.T) {
// Tests that variable initializers are not added to init() when syntax
// is not present but types.Info is available.
//
// Packages x, y, z are loaded with mode `packages.LoadSyntax`.
// Package x imports y, and y imports z.
// Packages are built using ssautil.Packages() with x and z as roots.
// This setup creates y using CreatePackage(pkg, files, info, ...)
// where len(files) == 0 but info != nil.
//
// Tests that globals from y are not initialized.
e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
{
Name: "golang.org/fake",
Files: map[string]interface{}{
"x/x.go": `package x; import "golang.org/fake/y"; var V = y.F()`,
"y/y.go": `package y; import "golang.org/fake/z"; var F = func () *int { return &z.Z } `,
"z/z.go": `package z; var Z int`,
},
},
})
defer e.Cleanup()
// Load x and z as entry packages using packages.LoadSyntax
e.Config.Mode = packages.LoadSyntax
pkgs, err := packages.Load(e.Config, path.Join(e.Temp(), "fake/x"), path.Join(e.Temp(), "fake/z"))
if err != nil {
t.Fatal(err)
}
for _, p := range pkgs {
if len(p.Errors) > 0 {
t.Fatalf("%v", p.Errors)
}
}
prog, _ := ssautil.Packages(pkgs, ssa.BuilderMode(0))
prog.Build()
// y does not initialize F.
y := prog.ImportedPackage("golang.org/fake/y")
if y == nil {
t.Fatal("Failed to load intermediate package y")
}
yinit := y.Members["init"].(*ssa.Function)
for _, bb := range yinit.Blocks {
for _, i := range bb.Instrs {
if store, ok := i.(*ssa.Store); ok && store.Addr == y.Var("F") {
t.Errorf("y.init() stores to F %v", store)
}
}
}
}