diff --git a/cmd/callgraph/main.go b/cmd/callgraph/main.go index 7284c4b386..2e09bc446b 100644 --- a/cmd/callgraph/main.go +++ b/cmd/callgraph/main.go @@ -187,7 +187,7 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er } // Create and build SSA-form program representation. - prog, pkgs := ssautil.Packages(initial, 0) + prog, pkgs := ssautil.AllPackages(initial, 0) prog.Build() // -- call graph construction ------------------------------------------ diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go index b978249503..fee931b1c3 100644 --- a/cmd/ssadump/main.go +++ b/cmd/ssadump/main.go @@ -131,7 +131,7 @@ func doMain() error { } // Create SSA-form program representation. - prog, pkgs := ssautil.Packages(initial, mode) + prog, pkgs := ssautil.AllPackages(initial, mode) for i, p := range pkgs { if p == nil { diff --git a/go/ssa/example_test.go b/go/ssa/example_test.go index 6ca777686b..c33b6d624c 100644 --- a/go/ssa/example_test.go +++ b/go/ssa/example_test.go @@ -155,8 +155,8 @@ func ExampleLoadWholeProgram() { log.Fatal(err) } - // Create SSA packages for all well-typed packages. - prog, pkgs := ssautil.Packages(initial, ssa.PrintPackages) + // Create SSA packages for well-typed packages and their dependencies. + prog, pkgs := ssautil.AllPackages(initial, ssa.PrintPackages) _ = pkgs // Build SSA code for the whole program. diff --git a/go/ssa/ssautil/load.go b/go/ssa/ssautil/load.go index 03068d50b2..72710becf8 100644 --- a/go/ssa/ssautil/load.go +++ b/go/ssa/ssautil/load.go @@ -16,19 +16,52 @@ import ( "golang.org/x/tools/go/ssa" ) -// Packages creates an SSA program for a set of packages loaded from -// source syntax using the golang.org/x/tools/go/packages.Load function. -// It creates and returns an SSA package for each well-typed package in -// the initial list. The resulting list of packages has the same length -// as initial, and contains a nil if SSA could not be constructed for -// the corresponding initial package. +// Packages creates an SSA program for a set of packages. // -// Code for bodies of functions is not built until Build is called -// on the resulting Program. +// The packages must have been loaded from source syntax using the +// golang.org/x/tools/go/packages.Load function in LoadSyntax or +// LoadAllSyntax mode. +// +// Packages creates an SSA package for each well-typed package in the +// initial list, plus all their dependencies. The resulting list of +// packages corresponds to the list of initial packages, and may contain +// a nil if SSA code could not be constructed for the corresponding initial +// package due to type errors. +// +// Code for bodies of functions is not built until Build is called on +// the resulting Program. SSA code is constructed only for the initial +// packages with well-typed syntax trees. // // The mode parameter controls diagnostics and checking during SSA construction. // func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) { + return doPackages(initial, mode, false) +} + +// AllPackages creates an SSA program for a set of packages plus all +// their dependencies. +// +// The packages must have been loaded from source syntax using the +// golang.org/x/tools/go/packages.Load function in LoadAllSyntax mode. +// +// AllPackages creates an SSA package for each well-typed package in the +// initial list, plus all their dependencies. The resulting list of +// packages corresponds to the list of intial packages, and may contain +// a nil if SSA code could not be constructed for the corresponding +// initial package due to type errors. +// +// Code for bodies of functions is not built until Build is called on +// the resulting Program. SSA code is constructed for all packages with +// well-typed syntax trees. +// +// The mode parameter controls diagnostics and checking during SSA construction. +// +func AllPackages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) { + return doPackages(initial, mode, true) +} + +func doPackages(initial []*packages.Package, mode ssa.BuilderMode, deps bool) (*ssa.Program, []*ssa.Package) { + var fset *token.FileSet if len(initial) > 0 { fset = initial[0].Fset @@ -36,10 +69,19 @@ func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, prog := ssa.NewProgram(fset, mode) + isInitial := make(map[*packages.Package]bool, len(initial)) + for _, p := range initial { + isInitial[p] = true + } + ssamap := make(map[*packages.Package]*ssa.Package) packages.Visit(initial, nil, func(p *packages.Package) { if p.Types != nil && !p.IllTyped { - ssamap[p] = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true) + var files []*ast.File + if deps || isInitial[p] { + files = p.Syntax + } + ssamap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true) } }) diff --git a/go/ssa/ssautil/load_test.go b/go/ssa/ssautil/load_test.go index 2885ed3965..4724e33e8d 100644 --- a/go/ssa/ssautil/load_test.go +++ b/go/ssa/ssautil/load_test.go @@ -104,3 +104,17 @@ func TestBuildPackage_MissingImport(t *testing.T) { t.Fatal("BuildPackage succeeded unexpectedly") } } + +func TestIssue28106(t *testing.T) { + // In go1.10, go/packages loads all packages from source, not + // export data, but does not type check function bodies of + // imported packages. This test ensures that we do not attempt + // to run the SSA builder on functions without type information. + cfg := &packages.Config{Mode: packages.LoadSyntax} + pkgs, err := packages.Load(cfg, "runtime") + if err != nil { + t.Fatal(err) + } + prog, _ := ssautil.Packages(pkgs, 0) + prog.Build() // no crash +}