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 <gri@golang.org>
diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go
index 841bd4d..5e742a4 100644
--- a/src/cmd/fix/cftype.go
+++ b/src/cmd/fix/cftype.go
@@ -30,17 +30,19 @@
 // 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 29abe0f..75ae570 100644
--- a/src/cmd/fix/jnitype.go
+++ b/src/cmd/fix/jnitype.go
@@ -27,7 +27,8 @@
 // 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 58d9158..a52a542 100644
--- a/src/cmd/fix/typecheck.go
+++ b/src/cmd/fix/typecheck.go
@@ -7,9 +7,14 @@
 import (
 	"fmt"
 	"go/ast"
+	"go/parser"
 	"go/token"
+	"io/ioutil"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"reflect"
+	"runtime"
 	"strings"
 )
 
@@ -74,6 +79,11 @@
 	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 @@
 	*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 @@
 			}
 			// 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