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 <matloob@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/cmd/callgraph/main.go b/cmd/callgraph/main.go
index 7284c4b..2e09bc4 100644
--- a/cmd/callgraph/main.go
+++ b/cmd/callgraph/main.go
@@ -187,7 +187,7 @@
 	}
 
 	// 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 b978249..fee931b 100644
--- a/cmd/ssadump/main.go
+++ b/cmd/ssadump/main.go
@@ -131,7 +131,7 @@
 	}
 
 	// 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 6ca7776..c33b6d6 100644
--- a/go/ssa/example_test.go
+++ b/go/ssa/example_test.go
@@ -155,8 +155,8 @@
 		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 03068d5..72710be 100644
--- a/go/ssa/ssautil/load.go
+++ b/go/ssa/ssautil/load.go
@@ -16,19 +16,52 @@
 	"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 @@
 
 	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 2885ed3..4724e33 100644
--- a/go/ssa/ssautil/load_test.go
+++ b/go/ssa/ssautil/load_test.go
@@ -104,3 +104,17 @@
 		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
+}