// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package bind import ( "bytes" "fmt" "go/ast" "go/token" "go/types" "io" "regexp" "strings" "unicode" "unicode/utf8" ) type ( ErrorList []error // varMode describes the lifetime of an argument or // return value. Modes are used to guide the conversion // of string and byte slice values accross the language // barrier. The same conversion mode must be used for // both the conversion before a foreign call and the // corresponding conversion after the call. // See the mode* constants for a description of // each mode. varMode int ) const ( // modeTransient are for function arguments that // are not used after the function returns. // Transient byte slices don't need copying // when passed accross the language barrier. modeTransient varMode = iota // modeRetained are for returned values and for function // arguments that are used after the function returns. // Retained byte slices need an intermediate copy. modeRetained ) func (list ErrorList) Error() string { buf := new(bytes.Buffer) for i, err := range list { if i > 0 { buf.WriteRune('\n') } io.WriteString(buf, err.Error()) } return buf.String() } // interfaceInfo comes from Init and collects the auxillary information // needed to generate bindings for an exported Go interface in a bound // package. type interfaceInfo struct { obj *types.TypeName t *types.Interface summary ifaceSummary } // structInfo comes from Init and collects the auxillary information // needed to generate bindings for an exported Go struct in a bound // package. type structInfo struct { obj *types.TypeName t *types.Struct } // Generator contains the common Go package information // needed for the specific Go, Java, ObjC generators. // // After setting Printer, Fset, AllPkg, Pkg, the Init // method is used to initialize the auxiliary information // about the package to be generated, Pkg. type Generator struct { *Printer Fset *token.FileSet AllPkg []*types.Package Files []*ast.File Pkg *types.Package err ErrorList // fields set by init. pkgName string pkgPrefix string funcs []*types.Func constants []*types.Const vars []*types.Var interfaces []interfaceInfo structs []structInfo otherNames []*types.TypeName // allIntf contains interfaces from all bound packages. allIntf []interfaceInfo docs pkgDocs } // A pkgDocs maps the name of each exported package-level declaration to its extracted documentation. type pkgDocs map[string]*pkgDoc type pkgDoc struct { doc string // Struct or interface fields and methods. members map[string]string } // pkgPrefix returns a prefix that disambiguates symbol names for binding // multiple packages. // // TODO(elias.naur): Avoid (and test) name clashes from multiple packages // with the same name. Perhaps use the index from the order the package is // generated. func pkgPrefix(pkg *types.Package) string { // The error type has no package if pkg == nil { return "" } return pkg.Name() } func (g *Generator) Init() { if g.Pkg != nil { g.pkgName = g.Pkg.Name() } g.pkgPrefix = pkgPrefix(g.Pkg) if g.Pkg != nil { g.parseDocs() scope := g.Pkg.Scope() hasExported := false for _, name := range scope.Names() { obj := scope.Lookup(name) if !obj.Exported() { continue } hasExported = true switch obj := obj.(type) { case *types.Func: if isCallable(obj) { g.funcs = append(g.funcs, obj) } case *types.TypeName: named, ok := obj.Type().(*types.Named) if !ok { continue } switch t := named.Underlying().(type) { case *types.Struct: g.structs = append(g.structs, structInfo{obj, t}) case *types.Interface: g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)}) default: g.otherNames = append(g.otherNames, obj) } case *types.Const: g.constants = append(g.constants, obj) case *types.Var: g.vars = append(g.vars, obj) default: g.errorf("unsupported exported type for %s: %T", obj.Name(), obj) } } if !hasExported { g.errorf("no exported names in the package %q", g.Pkg.Path()) } } else { // Bind the single supported type from the universe scope, error. errType := types.Universe.Lookup("error").(*types.TypeName) t := errType.Type().Underlying().(*types.Interface) g.interfaces = append(g.interfaces, interfaceInfo{errType, t, makeIfaceSummary(t)}) } for _, p := range g.AllPkg { scope := p.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) if !obj.Exported() { continue } if obj, ok := obj.(*types.TypeName); ok { named, ok := obj.Type().(*types.Named) if !ok { continue } if t, ok := named.Underlying().(*types.Interface); ok { g.allIntf = append(g.allIntf, interfaceInfo{obj, t, makeIfaceSummary(t)}) } } } } } // parseDocs extracts documentation from a package in a form useful for lookups. func (g *Generator) parseDocs() { d := make(pkgDocs) for _, f := range g.Files { for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.GenDecl: for _, spec := range decl.Specs { switch spec := spec.(type) { case *ast.TypeSpec: d.addType(spec, decl.Doc) case *ast.ValueSpec: d.addValue(spec, decl.Doc) } } case *ast.FuncDecl: d.addFunc(decl) } } } g.docs = d } func (d pkgDocs) addValue(t *ast.ValueSpec, outerDoc *ast.CommentGroup) { for _, n := range t.Names { if !ast.IsExported(n.Name) { continue } doc := t.Doc if doc == nil { doc = outerDoc } if doc != nil { d[n.Name] = &pkgDoc{doc: doc.Text()} } } } func (d pkgDocs) addFunc(f *ast.FuncDecl) { doc := f.Doc if doc == nil { return } fn := f.Name.Name if !ast.IsExported(fn) { return } if r := f.Recv; r != nil { // f is a method. n := typeName(r.List[0].Type) pd, exists := d[n] if !exists { pd = &pkgDoc{members: make(map[string]string)} d[n] = pd } pd.members[fn] = doc.Text() } else { // f is a function. d[fn] = &pkgDoc{doc: doc.Text()} } } func (d pkgDocs) addType(t *ast.TypeSpec, outerDoc *ast.CommentGroup) { if !ast.IsExported(t.Name.Name) { return } doc := t.Doc if doc == nil { doc = outerDoc } pd := d[t.Name.Name] pd = &pkgDoc{members: make(map[string]string)} d[t.Name.Name] = pd if doc != nil { pd.doc = doc.Text() } var fields *ast.FieldList switch t := t.Type.(type) { case *ast.StructType: fields = t.Fields case *ast.InterfaceType: fields = t.Methods } if fields != nil { for _, field := range fields.List { if field.Doc != nil { if field.Names == nil { // Anonymous field. Extract name from its type. if n := typeName(field.Type); ast.IsExported(n) { pd.members[n] = field.Doc.Text() } } for _, n := range field.Names { if ast.IsExported(n.Name) { pd.members[n.Name] = field.Doc.Text() } } } } } } // typeName returns the type name T for expressions on the // T, *T, **T (etc.) form. func typeName(t ast.Expr) string { switch t := t.(type) { case *ast.StarExpr: return typeName(t.X) case *ast.Ident: return t.Name case *ast.SelectorExpr: return t.Sel.Name default: return "" } } func (d *pkgDoc) Doc() string { if d == nil { return "" } return d.doc } func (d *pkgDoc) Member(n string) string { if d == nil { return "" } return d.members[n] } // constructorType returns the type T for a function of the forms: // // func NewT...(...) *T // func NewT...(...) (*T, error) func (g *Generator) constructorType(f *types.Func) *types.TypeName { sig := f.Type().(*types.Signature) res := sig.Results() if res.Len() != 1 && !(res.Len() == 2 && isErrorType(res.At(1).Type())) { return nil } rt := res.At(0).Type() pt, ok := rt.(*types.Pointer) if !ok { return nil } nt, ok := pt.Elem().(*types.Named) if !ok { return nil } obj := nt.Obj() if !strings.HasPrefix(f.Name(), "New"+obj.Name()) { return nil } return obj } func toCFlag(v bool) int { if v { return 1 } return 0 } func (g *Generator) errorf(format string, args ...interface{}) { g.err = append(g.err, fmt.Errorf(format, args...)) } // cgoType returns the name of a Cgo type suitable for converting a value of // the given type. func (g *Generator) cgoType(t types.Type) string { switch t := t.(type) { case *types.Basic: switch t.Kind() { case types.Bool, types.UntypedBool: return "char" case types.Int: return "nint" case types.Int8: return "int8_t" case types.Int16: return "int16_t" case types.Int32, types.UntypedRune: // types.Rune return "int32_t" case types.Int64, types.UntypedInt: return "int64_t" case types.Uint8: // types.Byte return "uint8_t" // TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64: case types.Float32: return "float" case types.Float64, types.UntypedFloat: return "double" case types.String: return "nstring" default: g.errorf("unsupported basic type: %s", t) } case *types.Slice: switch e := t.Elem().(type) { case *types.Basic: switch e.Kind() { case types.Uint8: // Byte. return "nbyteslice" default: g.errorf("unsupported slice type: %s", t) } default: g.errorf("unsupported slice type: %s", t) } case *types.Pointer: if _, ok := t.Elem().(*types.Named); ok { return g.cgoType(t.Elem()) } g.errorf("unsupported pointer to type: %s", t) case *types.Named: return "int32_t" default: g.errorf("unsupported type: %s", t) } return "TODO" } func (g *Generator) genInterfaceMethodSignature(m *types.Func, iName string, header bool, g_paramName func(*types.Tuple, int) string) { sig := m.Type().(*types.Signature) params := sig.Params() res := sig.Results() if res.Len() == 0 { g.Printf("void ") } else { if res.Len() == 1 { g.Printf("%s ", g.cgoType(res.At(0).Type())) } else { if header { g.Printf("typedef struct cproxy%s_%s_%s_return {\n", g.pkgPrefix, iName, m.Name()) g.Indent() for i := 0; i < res.Len(); i++ { t := res.At(i).Type() g.Printf("%s r%d;\n", g.cgoType(t), i) } g.Outdent() g.Printf("} cproxy%s_%s_%s_return;\n", g.pkgPrefix, iName, m.Name()) } g.Printf("struct cproxy%s_%s_%s_return ", g.pkgPrefix, iName, m.Name()) } } g.Printf("cproxy%s_%s_%s(int32_t refnum", g.pkgPrefix, iName, m.Name()) for i := 0; i < params.Len(); i++ { t := params.At(i).Type() g.Printf(", %s %s", g.cgoType(t), g_paramName(params, i)) } g.Printf(")") if header { g.Printf(";\n") } else { g.Printf(" {\n") } } func (g *Generator) validPkg(pkg *types.Package) bool { for _, p := range g.AllPkg { if p == pkg { return true } } return false } // isSigSupported reports whether the generators can handle a given // function signature. func (g *Generator) isSigSupported(t types.Type) bool { sig := t.(*types.Signature) params := sig.Params() for i := 0; i < params.Len(); i++ { if !g.isSupported(params.At(i).Type()) { return false } } res := sig.Results() for i := 0; i < res.Len(); i++ { if !g.isSupported(res.At(i).Type()) { return false } } return true } // isSupported reports whether the generators can handle the type. func (g *Generator) isSupported(t types.Type) bool { if isErrorType(t) || isWrapperType(t) { return true } switch t := t.(type) { case *types.Basic: switch t.Kind() { case types.Bool, types.UntypedBool, types.Int, types.Int8, types.Uint8, // types.Byte types.Int16, types.Int32, types.UntypedRune, // types.Rune types.Int64, types.UntypedInt, types.Float32, types.Float64, types.UntypedFloat, types.String, types.UntypedString: return true } return false case *types.Slice: switch e := t.Elem().(type) { case *types.Basic: return e.Kind() == types.Uint8 } case *types.Pointer: switch t := t.Elem().(type) { case *types.Named: return g.validPkg(t.Obj().Pkg()) } case *types.Named: switch t.Underlying().(type) { case *types.Interface, *types.Pointer: return g.validPkg(t.Obj().Pkg()) } } return false } var paramRE = regexp.MustCompile(`^p[0-9]*$`) // basicParamName replaces incompatible name with a p0-pN name. // Missing names, or existing names of the form p[0-9] are incompatible. func basicParamName(params *types.Tuple, pos int) string { name := params.At(pos).Name() if name == "" || name[0] == '_' || paramRE.MatchString(name) { name = fmt.Sprintf("p%d", pos) } return name } func lowerFirst(s string) string { if s == "" { return "" } var conv []rune for len(s) > 0 { r, n := utf8.DecodeRuneInString(s) if !unicode.IsUpper(r) { if l := len(conv); l > 1 { conv[l-1] = unicode.ToUpper(conv[l-1]) } return string(conv) + s } conv = append(conv, unicode.ToLower(r)) s = s[n:] } return string(conv) } // newNameSanitizer returns a functions that replaces all dashes and dots // with underscores, as well as avoiding reserved words by suffixing such // identifiers with underscores. func newNameSanitizer(res []string) func(s string) string { reserved := make(map[string]bool) for _, word := range res { reserved[word] = true } symbols := strings.NewReplacer( "-", "_", ".", "_", ) return func(s string) string { if reserved[s] { return s + "_" } return symbols.Replace(s) } }