Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
ast.go577 linesDownload Raw Back to cgo
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 
codekingpro/portable-devtools · Team Ai