diff options
Diffstat (limited to '')
-rw-r--r-- | src/cmd/cgo/ast.go | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/src/cmd/cgo/ast.go b/src/cmd/cgo/ast.go new file mode 100644 index 0000000..a073407 --- /dev/null +++ b/src/cmd/cgo/ast.go @@ -0,0 +1,539 @@ +// Copyright 2009 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. + +// Parse input AST and prepare Prog structure. + +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "os" + "strings" +) + +func parse(name string, src []byte, flags parser.Mode) *ast.File { + ast1, err := parser.ParseFile(fset, name, src, flags) + if err != nil { + if list, ok := err.(scanner.ErrorList); ok { + // If err is a scanner.ErrorList, its String will print just + // the first error and then (+n more errors). + // Instead, turn it into a new Error that will return + // details for all the errors. + for _, e := range list { + fmt.Fprintln(os.Stderr, e) + } + os.Exit(2) + } + fatalf("parsing %s: %s", name, err) + } + return ast1 +} + +func sourceLine(n ast.Node) int { + return fset.Position(n.Pos()).Line +} + +// ParseGo populates f with information learned from the Go source code +// which was read from the named file. It gathers the C preamble +// attached to the import "C" comment, a list of references to C.xxx, +// a list of exported functions, and the actual AST, to be rewritten and +// printed. +func (f *File) ParseGo(abspath string, src []byte) { + // Two different parses: once with comments, once without. + // The printer is not good enough at printing comments in the + // right place when we start editing the AST behind its back, + // so we use ast1 to look for the doc comments on import "C" + // and on exported functions, and we use ast2 for translating + // and reprinting. + // In cgo mode, we ignore ast2 and just apply edits directly + // the text behind ast1. In godefs mode we modify and print ast2. + ast1 := parse(abspath, src, parser.ParseComments) + ast2 := parse(abspath, src, 0) + + f.Package = ast1.Name.Name + f.Name = make(map[string]*Name) + f.NamePos = make(map[*Name]token.Pos) + + // In ast1, find the import "C" line and get any extra C preamble. + sawC := false + for _, decl := range ast1.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range d.Specs { + s, ok := spec.(*ast.ImportSpec) + if !ok || s.Path.Value != `"C"` { + continue + } + sawC = true + if s.Name != nil { + error_(s.Path.Pos(), `cannot rename import "C"`) + } + cg := s.Doc + if cg == nil && len(d.Specs) == 1 { + cg = d.Doc + } + if cg != nil { + f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), abspath) + f.Preamble += commentText(cg) + "\n" + f.Preamble += "#line 1 \"cgo-generated-wrapper\"\n" + } + } + } + if !sawC { + error_(ast1.Package, `cannot find import "C"`) + } + + // In ast2, strip the import "C" line. + if *godefs { + w := 0 + for _, decl := range ast2.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + ast2.Decls[w] = decl + w++ + continue + } + ws := 0 + for _, spec := range d.Specs { + s, ok := spec.(*ast.ImportSpec) + if !ok || s.Path.Value != `"C"` { + d.Specs[ws] = spec + ws++ + } + } + if ws == 0 { + continue + } + d.Specs = d.Specs[0:ws] + ast2.Decls[w] = d + w++ + } + ast2.Decls = ast2.Decls[0:w] + } else { + for _, decl := range ast2.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range d.Specs { + if s, ok := spec.(*ast.ImportSpec); ok && s.Path.Value == `"C"` { + // Replace "C" with _ "unsafe", to keep program valid. + // (Deleting import statement or clause is not safe if it is followed + // in the source by an explicit semicolon.) + f.Edit.Replace(f.offset(s.Path.Pos()), f.offset(s.Path.End()), `_ "unsafe"`) + } + } + } + } + + // Accumulate pointers to uses of C.x. + if f.Ref == nil { + f.Ref = make([]*Ref, 0, 8) + } + f.walk(ast2, ctxProg, (*File).validateIdents) + f.walk(ast2, ctxProg, (*File).saveExprs) + + // Accumulate exported functions. + // The comments are only on ast1 but we need to + // save the function bodies from ast2. + // The first walk fills in ExpFunc, and the + // second walk changes the entries to + // refer to ast2 instead. + f.walk(ast1, ctxProg, (*File).saveExport) + f.walk(ast2, ctxProg, (*File).saveExport2) + + f.Comments = ast1.Comments + f.AST = ast2 +} + +// Like ast.CommentGroup's Text method but preserves +// leading blank lines, so that line numbers line up. +func commentText(g *ast.CommentGroup) string { + var pieces []string + for _, com := range g.List { + c := com.Text + // Remove comment markers. + // The parser has given us exactly the comment text. + switch c[1] { + case '/': + //-style comment (no newline at the end) + c = c[2:] + "\n" + case '*': + /*-style comment */ + c = c[2 : len(c)-2] + } + pieces = append(pieces, c) + } + return strings.Join(pieces, "") +} + +func (f *File) validateIdents(x interface{}, context astContext) { + if x, ok := x.(*ast.Ident); ok { + if f.isMangledName(x.Name) { + error_(x.Pos(), "identifier %q may conflict with identifiers generated by cgo", x.Name) + } + } +} + +// Save various references we are going to need later. +func (f *File) saveExprs(x interface{}, context astContext) { + switch x := x.(type) { + case *ast.Expr: + switch (*x).(type) { + case *ast.SelectorExpr: + f.saveRef(x, context) + } + case *ast.CallExpr: + f.saveCall(x, context) + } +} + +// Save references to C.xxx for later processing. +func (f *File) saveRef(n *ast.Expr, context astContext) { + sel := (*n).(*ast.SelectorExpr) + // For now, assume that the only instance of capital C is when + // used as the imported package identifier. + // The parser should take care of scoping in the future, so + // that we will be able to distinguish a "top-level C" from a + // local C. + if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" { + return + } + if context == ctxAssign2 { + context = ctxExpr + } + if context == ctxEmbedType { + error_(sel.Pos(), "cannot embed C type") + } + goname := sel.Sel.Name + if goname == "errno" { + error_(sel.Pos(), "cannot refer to errno directly; see documentation") + return + } + if goname == "_CMalloc" { + error_(sel.Pos(), "cannot refer to C._CMalloc; use C.malloc") + return + } + if goname == "malloc" { + goname = "_CMalloc" + } + name := f.Name[goname] + if name == nil { + name = &Name{ + Go: goname, + } + f.Name[goname] = name + f.NamePos[name] = sel.Pos() + } + f.Ref = append(f.Ref, &Ref{ + Name: name, + Expr: n, + Context: context, + }) +} + +// Save calls to C.xxx for later processing. +func (f *File) saveCall(call *ast.CallExpr, context astContext) { + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" { + return + } + c := &Call{Call: call, Deferred: context == ctxDefer} + f.Calls = append(f.Calls, c) +} + +// If a function should be exported add it to ExpFunc. +func (f *File) saveExport(x interface{}, context astContext) { + n, ok := x.(*ast.FuncDecl) + if !ok { + return + } + + if n.Doc == nil { + return + } + for _, c := range n.Doc.List { + if !strings.HasPrefix(c.Text, "//export ") { + continue + } + + name := strings.TrimSpace(c.Text[9:]) + if name == "" { + error_(c.Pos(), "export missing name") + } + + if name != n.Name.Name { + error_(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name) + } + + doc := "" + for _, c1 := range n.Doc.List { + if c1 != c { + doc += c1.Text + "\n" + } + } + + f.ExpFunc = append(f.ExpFunc, &ExpFunc{ + Func: n, + ExpName: name, + Doc: doc, + }) + break + } +} + +// Make f.ExpFunc[i] point at the Func from this AST instead of the other one. +func (f *File) saveExport2(x interface{}, context astContext) { + n, ok := x.(*ast.FuncDecl) + if !ok { + return + } + + for _, exp := range f.ExpFunc { + if exp.Func.Name.Name == n.Name.Name { + exp.Func = n + break + } + } +} + +type astContext int + +const ( + ctxProg astContext = iota + ctxEmbedType + ctxType + ctxStmt + ctxExpr + ctxField + ctxParam + ctxAssign2 // assignment of a single expression to two variables + ctxSwitch + ctxTypeSwitch + ctxFile + ctxDecl + ctxSpec + ctxDefer + ctxCall // any function call other than ctxCall2 + ctxCall2 // function call whose result is assigned to two variables + ctxSelector +) + +// walk walks the AST x, calling visit(f, x, context) for each node. +func (f *File) walk(x interface{}, context astContext, visit func(*File, interface{}, astContext)) { + visit(f, x, context) + switch n := x.(type) { + case *ast.Expr: + f.walk(*n, context, visit) + + // everything else just recurs + default: + error_(token.NoPos, "unexpected type %T in walk", x) + panic("unexpected type") + + case nil: + + // These are ordered and grouped to match ../../go/ast/ast.go + case *ast.Field: + if len(n.Names) == 0 && context == ctxField { + f.walk(&n.Type, ctxEmbedType, visit) + } else { + f.walk(&n.Type, ctxType, visit) + } + case *ast.FieldList: + for _, field := range n.List { + f.walk(field, context, visit) + } + case *ast.BadExpr: + case *ast.Ident: + case *ast.Ellipsis: + f.walk(&n.Elt, ctxType, visit) + case *ast.BasicLit: + case *ast.FuncLit: + f.walk(n.Type, ctxType, visit) + f.walk(n.Body, ctxStmt, visit) + case *ast.CompositeLit: + f.walk(&n.Type, ctxType, visit) + f.walk(n.Elts, ctxExpr, visit) + case *ast.ParenExpr: + f.walk(&n.X, context, visit) + case *ast.SelectorExpr: + f.walk(&n.X, ctxSelector, visit) + case *ast.IndexExpr: + f.walk(&n.X, ctxExpr, visit) + f.walk(&n.Index, ctxExpr, visit) + case *ast.SliceExpr: + f.walk(&n.X, ctxExpr, visit) + if n.Low != nil { + f.walk(&n.Low, ctxExpr, visit) + } + if n.High != nil { + f.walk(&n.High, ctxExpr, visit) + } + if n.Max != nil { + f.walk(&n.Max, ctxExpr, visit) + } + case *ast.TypeAssertExpr: + f.walk(&n.X, ctxExpr, visit) + f.walk(&n.Type, ctxType, visit) + case *ast.CallExpr: + if context == ctxAssign2 { + f.walk(&n.Fun, ctxCall2, visit) + } else { + f.walk(&n.Fun, ctxCall, visit) + } + f.walk(n.Args, ctxExpr, visit) + case *ast.StarExpr: + f.walk(&n.X, context, visit) + case *ast.UnaryExpr: + f.walk(&n.X, ctxExpr, visit) + case *ast.BinaryExpr: + f.walk(&n.X, ctxExpr, visit) + f.walk(&n.Y, ctxExpr, visit) + case *ast.KeyValueExpr: + f.walk(&n.Key, ctxExpr, visit) + f.walk(&n.Value, ctxExpr, visit) + + case *ast.ArrayType: + f.walk(&n.Len, ctxExpr, visit) + f.walk(&n.Elt, ctxType, visit) + case *ast.StructType: + f.walk(n.Fields, ctxField, visit) + case *ast.FuncType: + f.walk(n.Params, ctxParam, visit) + if n.Results != nil { + f.walk(n.Results, ctxParam, visit) + } + case *ast.InterfaceType: + f.walk(n.Methods, ctxField, visit) + case *ast.MapType: + f.walk(&n.Key, ctxType, visit) + f.walk(&n.Value, ctxType, visit) + case *ast.ChanType: + f.walk(&n.Value, ctxType, visit) + + case *ast.BadStmt: + case *ast.DeclStmt: + f.walk(n.Decl, ctxDecl, visit) + case *ast.EmptyStmt: + case *ast.LabeledStmt: + f.walk(n.Stmt, ctxStmt, visit) + case *ast.ExprStmt: + f.walk(&n.X, ctxExpr, visit) + case *ast.SendStmt: + f.walk(&n.Chan, ctxExpr, visit) + f.walk(&n.Value, ctxExpr, visit) + case *ast.IncDecStmt: + f.walk(&n.X, ctxExpr, visit) + case *ast.AssignStmt: + f.walk(n.Lhs, ctxExpr, visit) + if len(n.Lhs) == 2 && len(n.Rhs) == 1 { + f.walk(n.Rhs, ctxAssign2, visit) + } else { + f.walk(n.Rhs, ctxExpr, visit) + } + case *ast.GoStmt: + f.walk(n.Call, ctxExpr, visit) + case *ast.DeferStmt: + f.walk(n.Call, ctxDefer, visit) + case *ast.ReturnStmt: + f.walk(n.Results, ctxExpr, visit) + case *ast.BranchStmt: + case *ast.BlockStmt: + f.walk(n.List, context, visit) + case *ast.IfStmt: + f.walk(n.Init, ctxStmt, visit) + f.walk(&n.Cond, ctxExpr, visit) + f.walk(n.Body, ctxStmt, visit) + f.walk(n.Else, ctxStmt, visit) + case *ast.CaseClause: + if context == ctxTypeSwitch { + context = ctxType + } else { + context = ctxExpr + } + f.walk(n.List, context, visit) + f.walk(n.Body, ctxStmt, visit) + case *ast.SwitchStmt: + f.walk(n.Init, ctxStmt, visit) + f.walk(&n.Tag, ctxExpr, visit) + f.walk(n.Body, ctxSwitch, visit) + case *ast.TypeSwitchStmt: + f.walk(n.Init, ctxStmt, visit) + f.walk(n.Assign, ctxStmt, visit) + f.walk(n.Body, ctxTypeSwitch, visit) + case *ast.CommClause: + f.walk(n.Comm, ctxStmt, visit) + f.walk(n.Body, ctxStmt, visit) + case *ast.SelectStmt: + f.walk(n.Body, ctxStmt, visit) + case *ast.ForStmt: + f.walk(n.Init, ctxStmt, visit) + f.walk(&n.Cond, ctxExpr, visit) + f.walk(n.Post, ctxStmt, visit) + f.walk(n.Body, ctxStmt, visit) + case *ast.RangeStmt: + f.walk(&n.Key, ctxExpr, visit) + f.walk(&n.Value, ctxExpr, visit) + f.walk(&n.X, ctxExpr, visit) + f.walk(n.Body, ctxStmt, visit) + + case *ast.ImportSpec: + case *ast.ValueSpec: + f.walk(&n.Type, ctxType, visit) + if len(n.Names) == 2 && len(n.Values) == 1 { + f.walk(&n.Values[0], ctxAssign2, visit) + } else { + f.walk(n.Values, ctxExpr, visit) + } + case *ast.TypeSpec: + f.walk(&n.Type, ctxType, visit) + + case *ast.BadDecl: + case *ast.GenDecl: + f.walk(n.Specs, ctxSpec, visit) + case *ast.FuncDecl: + if n.Recv != nil { + f.walk(n.Recv, ctxParam, visit) + } + f.walk(n.Type, ctxType, visit) + if n.Body != nil { + f.walk(n.Body, ctxStmt, visit) + } + + case *ast.File: + f.walk(n.Decls, ctxDecl, visit) + + case *ast.Package: + for _, file := range n.Files { + f.walk(file, ctxFile, visit) + } + + case []ast.Decl: + for _, d := range n { + f.walk(d, context, visit) + } + case []ast.Expr: + for i := range n { + f.walk(&n[i], context, visit) + } + case []ast.Stmt: + for _, s := range n { + f.walk(s, context, visit) + } + case []ast.Spec: + for _, s := range n { + f.walk(s, context, visit) + } + } +} |