From 2dc025e4e171ee99d31d11efe5ccf53794e89020 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 12 Jan 2018 10:11:01 -0800 Subject: [PATCH] cmd/fix: extend typechecker to use cgo types If a file uses cgo, incorporate the types generated by running cgo. Update #23091 Change-Id: I10958fa7fd6027c2c96a9fd8a9658de35439719f Reviewed-on: https://go-review.googlesource.com/87616 Reviewed-by: Robert Griesemer --- src/cmd/fix/cftype.go | 10 +++--- src/cmd/fix/jnitype.go | 3 +- src/cmd/fix/typecheck.go | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go index 841bd4dccb..5e742a4fdf 100644 --- a/src/cmd/fix/cftype.go +++ b/src/cmd/fix/cftype.go @@ -30,17 +30,19 @@ var cftypeFix = fix{ // and similar for other *Ref types. // This fix finds nils initializing these types and replaces the nils with 0s. func cftypefix(f *ast.File) bool { - return typefix(f, func(s string) bool { - return strings.HasPrefix(s, "C.") && strings.HasSuffix(s, "Ref") + var tc TypeConfig + return typefix(f, &tc, func(s string) bool { + return strings.HasPrefix(s, "C.") && strings.HasSuffix(s, "Ref") && + (s == "C.CFTypeRef" || tc.External[s[:len(s)-3]+"GetTypeID"] == "func() C.CFTypeID") }) } // typefix replaces nil with 0 for all nils whose type, when passed to badType, returns true. -func typefix(f *ast.File, badType func(string) bool) bool { +func typefix(f *ast.File, tc *TypeConfig, badType func(string) bool) bool { if !imports(f, "C") { return false } - typeof, _ := typecheck(&TypeConfig{}, f) + typeof, _ := typecheck(tc, f) // step 1: Find all the nils with the offending types. // Compute their replacement. diff --git a/src/cmd/fix/jnitype.go b/src/cmd/fix/jnitype.go index 29abe0f007..75ae570c4d 100644 --- a/src/cmd/fix/jnitype.go +++ b/src/cmd/fix/jnitype.go @@ -27,7 +27,8 @@ var jniFix = fix{ // and similar for subtypes of jobject. // This fix finds nils initializing these types and replaces the nils with 0s. func jnifix(f *ast.File) bool { - return typefix(f, func(s string) bool { + var tc TypeConfig + return typefix(f, &tc, func(s string) bool { switch s { case "C.jobject": return true diff --git a/src/cmd/fix/typecheck.go b/src/cmd/fix/typecheck.go index 58d915869d..a52a54202d 100644 --- a/src/cmd/fix/typecheck.go +++ b/src/cmd/fix/typecheck.go @@ -7,9 +7,14 @@ package main import ( "fmt" "go/ast" + "go/parser" "go/token" + "io/ioutil" "os" + "os/exec" + "path/filepath" "reflect" + "runtime" "strings" ) @@ -74,6 +79,11 @@ type TypeConfig struct { Type map[string]*Type Var map[string]string Func map[string]string + + // External maps from a name to its type. + // It provides additional typings not present in the Go source itself. + // For now, the only additional typings are those generated by cgo. + External map[string]string } // typeof returns the type of the given name, which may be of @@ -140,6 +150,66 @@ func typecheck(cfg *TypeConfig, f *ast.File) (typeof map[interface{}]string, ass *cfg1 = *cfg // make copy so we can add locally copied := false + // If we import "C", add types of cgo objects. + cfg.External = map[string]string{} + if imports(f, "C") { + // Run cgo on gofmtFile(f) + // Parse, extract decls from _cgo_gotypes.go + // Map _Ctype_* types to C.* types. + err := func() error { + txt, err := gofmtFile(f) + if err != nil { + return err + } + dir, err := ioutil.TempDir(os.TempDir(), "fix_cgo_typecheck") + if err != nil { + return err + } + defer os.Remove(dir) + err = ioutil.WriteFile(filepath.Join(dir, "in.go"), txt, 0600) + if err != nil { + return err + } + cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "tool", "cgo", "-objdir", dir, "-srcdir", dir, "in.go") + err = cmd.Run() + if err != nil { + return err + } + out, err := ioutil.ReadFile(filepath.Join(dir, "_cgo_gotypes.go")) + if err != nil { + return err + } + cgo, err := parser.ParseFile(token.NewFileSet(), "cgo.go", out, 0) + if err != nil { + return err + } + for _, decl := range cgo.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + if strings.HasPrefix(fn.Name.Name, "_Cfunc_") { + var params, results []string + for _, p := range fn.Type.Params.List { + t := gofmt(p.Type) + t = strings.Replace(t, "_Ctype_", "C.", -1) + params = append(params, t) + } + for _, r := range fn.Type.Results.List { + t := gofmt(r.Type) + t = strings.Replace(t, "_Ctype_", "C.", -1) + results = append(results, t) + } + cfg.External["C."+fn.Name.Name[7:]] = joinFunc(params, results) + } + } + return nil + }() + if err != nil { + fmt.Printf("warning: no cgo types: %s\n", err) + } + } + // gather function declarations for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) @@ -434,6 +504,9 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string, a } // Otherwise, use type of function to determine arguments. t := typeof[n.Fun] + if t == "" { + t = cfg.External[gofmt(n.Fun)] + } in, out := splitFunc(t) if in == nil && out == nil { return -- 2.48.1