From aa46a019962be36460a931a248899f6cba462a67 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 12 Oct 2018 17:57:33 -0400 Subject: [PATCH] go/ssa/ssautil: add AllPackages method In go1.10, go/packages falls back to loading all packages from source but not typechecking function bodies for imports. The ssautil.Packages function would nonetheless provide the partially-typed ASTs to the SSA builder, which would crash. Now Packages only passes syntax trees to the SSA builder for the initial packages, which are the only ones guaranteed to be fully typed. It is impossible to discern whether the caller of Packages intends to build SSA code for dependencies, as in some clients such as cmd/callgraph, so we add a new function, AllPackages, that expresses this intent. Fixes golang/go#28106 Change-Id: I6a88b7c7545e9de90b61f5bee0e6de3d2e21b548 Reviewed-on: https://go-review.googlesource.com/c/141686 Reviewed-by: Michael Matloob Run-TryBot: Michael Matloob TryBot-Result: Gobot Gobot --- cmd/callgraph/main.go | 2 +- cmd/ssadump/main.go | 2 +- go/ssa/example_test.go | 4 +-- go/ssa/ssautil/load.go | 60 +++++++++++++++++++++++++++++++------ go/ssa/ssautil/load_test.go | 14 +++++++++ 5 files changed, 69 insertions(+), 13 deletions(-) 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 +}