codekingpro/portable-devtools
114k
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5// Parse input AST and prepare Prog structure.6 7package main8 9import (10 "fmt"11 "go/ast"12 "go/format"13 "go/parser"14 "go/scanner"15 "go/token"16 "os"17 "strings"18)19 20func parse(name string, src []byte, flags parser.Mode) *ast.File {21 ast1, err := parser.ParseFile(fset, name, src, flags)22 if err != nil {23 if list, ok := err.(scanner.ErrorList); ok {24 // If err is a scanner.ErrorList, its String will print just25 // the first error and then (+n more errors).26 // Instead, turn it into a new Error that will return27 // details for all the errors.28 for _, e := range list {29 fmt.Fprintln(os.Stderr, e)30 }31 os.Exit(2)32 }33 fatalf("parsing %s: %s", name, err)34 }35 return ast136}37 38func sourceLine(n ast.Node) int {39 return fset.Position(n.Pos()).Line40}41 42// ParseGo populates f with information learned from the Go source code43// which was read from the named file. It gathers the C preamble44// attached to the import "C" comment, a list of references to C.xxx,45// a list of exported functions, and the actual AST, to be rewritten and46// printed.47func (f *File) ParseGo(abspath string, src []byte) {48 // Two different parses: once with comments, once without.49 // The printer is not good enough at printing comments in the50 // right place when we start editing the AST behind its back,51 // so we use ast1 to look for the doc comments on import "C"52 // and on exported functions, and we use ast2 for translating53 // and reprinting.54 // In cgo mode, we ignore ast2 and just apply edits directly55 // the text behind ast1. In godefs mode we modify and print ast2.56 ast1 := parse(abspath, src, parser.SkipObjectResolution|parser.ParseComments)57 ast2 := parse(abspath, src, parser.SkipObjectResolution)58 59 f.Package = ast1.Name.Name60 f.Name = make(map[string]*Name)61 f.NamePos = make(map[*Name]token.Pos)62 63 // In ast1, find the import "C" line and get any extra C preamble.64 sawC := false65 for _, decl := range ast1.Decls {66 switch decl := decl.(type) {67 case *ast.GenDecl:68 for _, spec := range decl.Specs {69 s, ok := spec.(*ast.ImportSpec)70 if !ok || s.Path.Value != `"C"` {71 continue72 }73 sawC = true74 if s.Name != nil {75 error_(s.Path.Pos(), `cannot rename import "C"`)76 }77 cg := s.Doc78 if cg == nil && len(decl.Specs) == 1 {79 cg = decl.Doc80 }81 if cg != nil {82 if strings.ContainsAny(abspath, "\r\n") {83 // This should have been checked when the file path was first resolved,84 // but we double check here just to be sure.85 fatalf("internal error: ParseGo: abspath contains unexpected newline character: %q", abspath)86 }87 f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), abspath)88 f.Preamble += commentText(cg) + "\n"89 f.Preamble += "#line 1 \"cgo-generated-wrapper\"\n"90 }91 }92 93 case *ast.FuncDecl:94 // Also, reject attempts to declare methods on C.T or *C.T.95 // (The generated code would otherwise accept this96 // invalid input; see issue #57926.)97 if decl.Recv != nil && len(decl.Recv.List) > 0 {98 recvType := decl.Recv.List[0].Type99 if recvType != nil {100 t := recvType101 if star, ok := unparen(t).(*ast.StarExpr); ok {102 t = star.X103 }104 if sel, ok := unparen(t).(*ast.SelectorExpr); ok {105 var buf strings.Builder106 format.Node(&buf, fset, recvType)107 error_(sel.Pos(), `cannot define new methods on non-local type %s`, &buf)108 }109 }110 }111 }112 113 }114 if !sawC {115 error_(ast1.Package, `cannot find import "C"`)116 }117 118 // In ast2, strip the import "C" line.119 if *godefs {120 w := 0121 for _, decl := range ast2.Decls {122 d, ok := decl.(*ast.GenDecl)123 if !ok {124 ast2.Decls[w] = decl125 w++126 continue127 }128 ws := 0129 for _, spec := range d.Specs {130 s, ok := spec.(*ast.ImportSpec)131 if !ok || s.Path.Value != `"C"` {132 d.Specs[ws] = spec133 ws++134 }135 }136 if ws == 0 {137 continue138 }139 d.Specs = d.Specs[0:ws]140 ast2.Decls[w] = d141 w++142 }143 ast2.Decls = ast2.Decls[0:w]144 } else {145 for _, decl := range ast2.Decls {146 d, ok := decl.(*ast.GenDecl)147 if !ok {148 continue149 }150 for _, spec := range d.Specs {151 if s, ok := spec.(*ast.ImportSpec); ok && s.Path.Value == `"C"` {152 // Replace "C" with _ "unsafe", to keep program valid.153 // (Deleting import statement or clause is not safe if it is followed154 // in the source by an explicit semicolon.)155 f.Edit.Replace(f.offset(s.Path.Pos()), f.offset(s.Path.End()), `_ "unsafe"`)156 }157 }158 }159 }160 161 // Accumulate pointers to uses of C.x.162 if f.Ref == nil {163 f.Ref = make([]*Ref, 0, 8)164 }165 f.walk(ast2, ctxProg, (*File).validateIdents)166 f.walk(ast2, ctxProg, (*File).saveExprs)167 168 // Accumulate exported functions.169 // The comments are only on ast1 but we need to170 // save the function bodies from ast2.171 // The first walk fills in ExpFunc, and the172 // second walk changes the entries to173 // refer to ast2 instead.174 f.walk(ast1, ctxProg, (*File).saveExport)175 f.walk(ast2, ctxProg, (*File).saveExport2)176 177 f.Comments = ast1.Comments178 f.AST = ast2179}180 181// Like ast.CommentGroup's Text method but preserves182// leading blank lines, so that line numbers line up.183func commentText(g *ast.CommentGroup) string {184 pieces := make([]string, 0, len(g.List))185 for _, com := range g.List {186 c := com.Text187 // Remove comment markers.188 // The parser has given us exactly the comment text.189 switch c[1] {190 case '/':191 //-style comment (no newline at the end)192 c = c[2:] + "\n"193 case '*':194 /*-style comment */195 c = c[2 : len(c)-2]196 }197 pieces = append(pieces, c)198 }199 return strings.Join(pieces, "")200}201 202func (f *File) validateIdents(x any, context astContext) {203 if x, ok := x.(*ast.Ident); ok {204 if f.isMangledName(x.Name) {205 error_(x.Pos(), "identifier %q may conflict with identifiers generated by cgo", x.Name)206 }207 }208}209 210// Save various references we are going to need later.211func (f *File) saveExprs(x any, context astContext) {212 switch x := x.(type) {213 case *ast.Expr:214 switch (*x).(type) {215 case *ast.SelectorExpr:216 f.saveRef(x, context)217 }218 case *ast.CallExpr:219 f.saveCall(x, context)220 }221}222 223// Save references to C.xxx for later processing.224func (f *File) saveRef(n *ast.Expr, context astContext) {225 sel := (*n).(*ast.SelectorExpr)226 // For now, assume that the only instance of capital C is when227 // used as the imported package identifier.228 // The parser should take care of scoping in the future, so229 // that we will be able to distinguish a "top-level C" from a230 // local C.231 if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" {232 return233 }234 if context == ctxAssign2 {235 context = ctxExpr236 }237 if context == ctxEmbedType {238 error_(sel.Pos(), "cannot embed C type")239 }240 goname := sel.Sel.Name241 if goname == "errno" {242 error_(sel.Pos(), "cannot refer to errno directly; see documentation")243 return244 }245 if goname == "_CMalloc" {246 error_(sel.Pos(), "cannot refer to C._CMalloc; use C.malloc")247 return248 }249 if goname == "malloc" {250 goname = "_CMalloc"251 }252 name := f.Name[goname]253 if name == nil {254 name = &Name{255 Go: goname,256 }257 f.Name[goname] = name258 f.NamePos[name] = sel.Pos()259 }260 f.Ref = append(f.Ref, &Ref{261 Name: name,262 Expr: n,263 Context: context,264 })265}266 267// Save calls to C.xxx for later processing.268func (f *File) saveCall(call *ast.CallExpr, context astContext) {269 sel, ok := call.Fun.(*ast.SelectorExpr)270 if !ok {271 return272 }273 if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" {274 return275 }276 c := &Call{Call: call, Deferred: context == ctxDefer}277 f.Calls = append(f.Calls, c)278}279 280// If a function should be exported add it to ExpFunc.281func (f *File) saveExport(x any, context astContext) {282 n, ok := x.(*ast.FuncDecl)283 if !ok {284 return285 }286 287 if n.Doc == nil {288 return289 }290 for _, c := range n.Doc.List {291 if !strings.HasPrefix(c.Text, "//export ") {292 continue293 }294 295 name := strings.TrimSpace(c.Text[9:])296 if name == "" {297 error_(c.Pos(), "export missing name")298 }299 300 if name != n.Name.Name {301 error_(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name)302 }303 304 f.ExpFunc = append(f.ExpFunc, &ExpFunc{305 Func: n,306 ExpName: name,307 // Caution: Do not set the Doc field on purpose308 // to ensure that there are no unintended artifacts309 // in the binary. See https://go.dev/issue/76697.310 })311 break312 }313}314 315// Make f.ExpFunc[i] point at the Func from this AST instead of the other one.316func (f *File) saveExport2(x any, context astContext) {317 n, ok := x.(*ast.FuncDecl)318 if !ok {319 return320 }321 322 for _, exp := range f.ExpFunc {323 if exp.Func.Name.Name == n.Name.Name {324 exp.Func = n325 break326 }327 }328}329 330type astContext int331 332const (333 ctxProg astContext = iota334 ctxEmbedType335 ctxType336 ctxStmt337 ctxExpr338 ctxField339 ctxParam340 ctxAssign2 // assignment of a single expression to two variables341 ctxSwitch342 ctxTypeSwitch343 ctxFile344 ctxDecl345 ctxSpec346 ctxDefer347 ctxCall // any function call other than ctxCall2348 ctxCall2 // function call whose result is assigned to two variables349 ctxSelector350)351 352// walk walks the AST x, calling visit(f, x, context) for each node.353func (f *File) walk(x any, context astContext, visit func(*File, any, astContext)) {354 visit(f, x, context)355 switch n := x.(type) {356 case *ast.Expr:357 f.walk(*n, context, visit)358 359 // everything else just recurs360 default:361 error_(token.NoPos, "unexpected type %T in walk", x)362 panic("unexpected type")363 364 case nil:365 366 // These are ordered and grouped to match ../../go/ast/ast.go367 case *ast.Field:368 if len(n.Names) == 0 && context == ctxField {369 f.walk(&n.Type, ctxEmbedType, visit)370 } else {371 f.walk(&n.Type, ctxType, visit)372 }373 case *ast.FieldList:374 for _, field := range n.List {375 f.walk(field, context, visit)376 }377 case *ast.BadExpr:378 case *ast.Ident:379 case *ast.Ellipsis:380 f.walk(&n.Elt, ctxType, visit)381 case *ast.BasicLit:382 case *ast.FuncLit:383 f.walk(n.Type, ctxType, visit)384 f.walk(n.Body, ctxStmt, visit)385 case *ast.CompositeLit:386 f.walk(&n.Type, ctxType, visit)387 f.walk(n.Elts, ctxExpr, visit)388 case *ast.ParenExpr:389 f.walk(&n.X, context, visit)390 case *ast.SelectorExpr:391 f.walk(&n.X, ctxSelector, visit)392 case *ast.IndexExpr:393 f.walk(&n.X, ctxExpr, visit)394 f.walk(&n.Index, ctxExpr, visit)395 case *ast.IndexListExpr:396 f.walk(&n.X, ctxExpr, visit)397 f.walk(n.Indices, ctxExpr, visit)398 case *ast.SliceExpr:399 f.walk(&n.X, ctxExpr, visit)400 if n.Low != nil {401 f.walk(&n.Low, ctxExpr, visit)402 }403 if n.High != nil {404 f.walk(&n.High, ctxExpr, visit)405 }406 if n.Max != nil {407 f.walk(&n.Max, ctxExpr, visit)408 }409 case *ast.TypeAssertExpr:410 f.walk(&n.X, ctxExpr, visit)411 f.walk(&n.Type, ctxType, visit)412 case *ast.CallExpr:413 if context == ctxAssign2 {414 f.walk(&n.Fun, ctxCall2, visit)415 } else {416 f.walk(&n.Fun, ctxCall, visit)417 }418 f.walk(n.Args, ctxExpr, visit)419 case *ast.StarExpr:420 f.walk(&n.X, context, visit)421 case *ast.UnaryExpr:422 f.walk(&n.X, ctxExpr, visit)423 case *ast.BinaryExpr:424 f.walk(&n.X, ctxExpr, visit)425 f.walk(&n.Y, ctxExpr, visit)426 case *ast.KeyValueExpr:427 f.walk(&n.Key, ctxExpr, visit)428 f.walk(&n.Value, ctxExpr, visit)429 430 case *ast.ArrayType:431 f.walk(&n.Len, ctxExpr, visit)432 f.walk(&n.Elt, ctxType, visit)433 case *ast.StructType:434 f.walk(n.Fields, ctxField, visit)435 case *ast.FuncType:436 if n.TypeParams != nil {437 f.walk(n.TypeParams, ctxParam, visit)438 }439 f.walk(n.Params, ctxParam, visit)440 if n.Results != nil {441 f.walk(n.Results, ctxParam, visit)442 }443 case *ast.InterfaceType:444 f.walk(n.Methods, ctxField, visit)445 case *ast.MapType:446 f.walk(&n.Key, ctxType, visit)447 f.walk(&n.Value, ctxType, visit)448 case *ast.ChanType:449 f.walk(&n.Value, ctxType, visit)450 451 case *ast.BadStmt:452 case *ast.DeclStmt:453 f.walk(n.Decl, ctxDecl, visit)454 case *ast.EmptyStmt:455 case *ast.LabeledStmt:456 f.walk(n.Stmt, ctxStmt, visit)457 case *ast.ExprStmt:458 f.walk(&n.X, ctxExpr, visit)459 case *ast.SendStmt:460 f.walk(&n.Chan, ctxExpr, visit)461 f.walk(&n.Value, ctxExpr, visit)462 case *ast.IncDecStmt:463 f.walk(&n.X, ctxExpr, visit)464 case *ast.AssignStmt:465 f.walk(n.Lhs, ctxExpr, visit)466 if len(n.Lhs) == 2 && len(n.Rhs) == 1 {467 f.walk(n.Rhs, ctxAssign2, visit)468 } else {469 f.walk(n.Rhs, ctxExpr, visit)470 }471 case *ast.GoStmt:472 f.walk(n.Call, ctxExpr, visit)473 case *ast.DeferStmt:474 f.walk(n.Call, ctxDefer, visit)475 case *ast.ReturnStmt:476 f.walk(n.Results, ctxExpr, visit)477 case *ast.BranchStmt:478 case *ast.BlockStmt:479 f.walk(n.List, context, visit)480 case *ast.IfStmt:481 f.walk(n.Init, ctxStmt, visit)482 f.walk(&n.Cond, ctxExpr, visit)483 f.walk(n.Body, ctxStmt, visit)484 f.walk(n.Else, ctxStmt, visit)485 case *ast.CaseClause:486 if context == ctxTypeSwitch {487 context = ctxType488 } else {489 context = ctxExpr490 }491 f.walk(n.List, context, visit)492 f.walk(n.Body, ctxStmt, visit)493 case *ast.SwitchStmt:494 f.walk(n.Init, ctxStmt, visit)495 f.walk(&n.Tag, ctxExpr, visit)496 f.walk(n.Body, ctxSwitch, visit)497 case *ast.TypeSwitchStmt:498 f.walk(n.Init, ctxStmt, visit)499 f.walk(n.Assign, ctxStmt, visit)500 f.walk(n.Body, ctxTypeSwitch, visit)501 case *ast.CommClause:502 f.walk(n.Comm, ctxStmt, visit)503 f.walk(n.Body, ctxStmt, visit)504 case *ast.SelectStmt:505 f.walk(n.Body, ctxStmt, visit)506 case *ast.ForStmt:507 f.walk(n.Init, ctxStmt, visit)508 f.walk(&n.Cond, ctxExpr, visit)509 f.walk(n.Post, ctxStmt, visit)510 f.walk(n.Body, ctxStmt, visit)511 case *ast.RangeStmt:512 f.walk(&n.Key, ctxExpr, visit)513 f.walk(&n.Value, ctxExpr, visit)514 f.walk(&n.X, ctxExpr, visit)515 f.walk(n.Body, ctxStmt, visit)516 517 case *ast.ImportSpec:518 case *ast.ValueSpec:519 f.walk(&n.Type, ctxType, visit)520 if len(n.Names) == 2 && len(n.Values) == 1 {521 f.walk(&n.Values[0], ctxAssign2, visit)522 } else {523 f.walk(n.Values, ctxExpr, visit)524 }525 case *ast.TypeSpec:526 if n.TypeParams != nil {527 f.walk(n.TypeParams, ctxParam, visit)528 }529 f.walk(&n.Type, ctxType, visit)530 531 case *ast.BadDecl:532 case *ast.GenDecl:533 f.walk(n.Specs, ctxSpec, visit)534 case *ast.FuncDecl:535 if n.Recv != nil {536 f.walk(n.Recv, ctxParam, visit)537 }538 f.walk(n.Type, ctxType, visit)539 if n.Body != nil {540 f.walk(n.Body, ctxStmt, visit)541 }542 543 case *ast.File:544 f.walk(n.Decls, ctxDecl, visit)545 546 case *ast.Package:547 for _, file := range n.Files {548 f.walk(file, ctxFile, visit)549 }550 551 case []ast.Decl:552 for _, d := range n {553 f.walk(d, context, visit)554 }555 case []ast.Expr:556 for i := range n {557 f.walk(&n[i], context, visit)558 }559 case []ast.Stmt:560 for _, s := range n {561 f.walk(s, context, visit)562 }563 case []ast.Spec:564 for _, s := range n {565 f.walk(s, context, visit)566 }567 }568}569 570// If x is of the form (T), unparen returns unparen(T), otherwise it returns x.571func unparen(x ast.Expr) ast.Expr {572 if p, isParen := x.(*ast.ParenExpr); isParen {573 x = unparen(p.X)574 }575 return x576}577 