From a4cde3673cf4d442ef00a2df0d7a6f707d9e726b Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Wed, 22 Apr 2020 13:45:12 -0400 Subject: [PATCH] go/packages: enable UsesCgo in go/types Add a new Mode bit, TypecheckCgo. When it is enabled, CompiledGoFiles will contain cgo files before preprocessing, plus the cgo types file generated by the cgo tool. Together, these can be type checked by go/types with UsesCgo enabled. Change-Id: I891a434f0193497d15e8c637910fb52b60455a06 Reviewed-on: https://go-review.googlesource.com/c/tools/+/229778 Run-TryBot: Heschi Kreinick TryBot-Result: Gobot Gobot Reviewed-by: Michael Matloob --- go/packages/golist.go | 19 +++++++++++++ go/packages/packages.go | 20 +++++++++++++- go/packages/packages115_test.go | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/go/packages/golist.go b/go/packages/golist.go index 88ca6691de..a7bd92966f 100644 --- a/go/packages/golist.go +++ b/go/packages/golist.go @@ -544,6 +544,25 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse module: p.Module, } + if (state.cfg.Mode&TypecheckCgo) != 0 && len(p.CgoFiles) != 0 { + if len(p.CompiledGoFiles) > len(p.GoFiles) { + // We need the cgo definitions, which are in the first + // CompiledGoFile after the non-cgo ones. This is a hack but there + // isn't currently a better way to find it. We also need the pure + // Go files and unprocessed cgo files, all of which are already + // in pkg.GoFiles. + cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] + pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) + } else { + // golang/go#38990: go list silently fails to do cgo processing + pkg.CompiledGoFiles = nil + pkg.Errors = append(pkg.Errors, Error{ + Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", + Kind: ListError, + }) + } + } + // Work around https://golang.org/issue/28749: // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. // Filter out any elements of CompiledGoFiles that are also in OtherFiles. diff --git a/go/packages/packages.go b/go/packages/packages.go index 03fd999c0c..1df46ccccf 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -19,6 +19,7 @@ import ( "log" "os" "path/filepath" + "reflect" "strings" "sync" @@ -49,6 +50,10 @@ const ( // NeedCompiledGoFiles adds CompiledGoFiles. NeedCompiledGoFiles + // TypecheckCgo enables full support for type checking cgo. Requires Go 1.15+. + // Has no effect without NeedTypes. + TypecheckCgo + // NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain // "placeholder" Packages with only the ID set. NeedImports @@ -257,7 +262,7 @@ type Package struct { GoFiles []string // CompiledGoFiles lists the absolute file paths of the package's source - // files that were presented to the compiler. + // files that are suitable for type checking. // This may differ from GoFiles if files are processed before compilation. CompiledGoFiles []string @@ -878,6 +883,19 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Error: appendError, Sizes: ld.sizes, } + if (ld.Mode & TypecheckCgo) != 0 { + // TODO: remove this when we stop supporting 1.14. + rtc := reflect.ValueOf(tc).Elem() + usesCgo := rtc.FieldByName("UsesCgo") + if !usesCgo.IsValid() { + appendError(Error{ + Msg: "TypecheckCgo requires Go 1.15+", + Kind: ListError, + }) + return + } + usesCgo.SetBool(true) + } types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) lpkg.importErrors = nil // no longer needed diff --git a/go/packages/packages115_test.go b/go/packages/packages115_test.go index 004de7cc22..3a3187efa0 100644 --- a/go/packages/packages115_test.go +++ b/go/packages/packages115_test.go @@ -7,6 +7,7 @@ package packages_test import ( + "strings" "testing" "golang.org/x/tools/go/packages" @@ -41,3 +42,51 @@ func testInvalidFilesInXTest(t *testing.T, exporter packagestest.Exporter) { t.Errorf("expected 3 packages, got %d", len(initial)) } } + +func TestTypecheckCgo(t *testing.T) { + packagestest.TestAll(t, testTypecheckCgo) +} + +func testTypecheckCgo(t *testing.T, exporter packagestest.Exporter) { + // The android builders have a complex setup which causes this test to fail. See discussion on + // golang.org/cl/214943 for more details. + if !hasGoBuild() { + t.Skip("this test can't run on platforms without go build. See discussion on golang.org/cl/214943 for more details.") + } + + const cgo = `package cgo + import "C" + + func Example() { + C.CString("hi") + } + ` + exported := packagestest.Export(t, exporter, []packagestest.Module{ + { + Name: "golang.org/fake", + Files: map[string]interface{}{ + "cgo/cgo.go": cgo, + }, + }, + }) + defer exported.Cleanup() + + exported.Config.Mode = packages.NeedFiles | packages.NeedCompiledGoFiles | + packages.NeedSyntax | packages.NeedDeps | packages.NeedTypes | + packages.TypecheckCgo + + initial, err := packages.Load(exported.Config, "golang.org/fake/cgo") + if err != nil { + t.Fatal(err) + } + pkg := initial[0] + if len(pkg.Errors) != 0 { + t.Fatalf("package has errors: %v", pkg.Errors) + } + + expos := pkg.Types.Scope().Lookup("Example").Pos() + fname := pkg.Fset.File(expos).Name() + if !strings.HasSuffix(fname, "cgo.go") { + t.Errorf("position for cgo package was loaded from %v, wanted cgo.go", fname) + } +}