mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-27 12:05:21 +08:00 
			
		
		
		
	 4372ce0d27
			
		
	
	4372ce0d27
	
	
	
		
			
			Adds some rtype annotations for g.atomicstatus and update _scripts/rtype.go to handle types outside of the runtime package.
		
			
				
	
	
		
			703 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			703 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // This script checks that the Go runtime hasn't changed in ways that Delve
 | |
| // doesn't understand. It accomplishes this task by parsing the pkg/proc
 | |
| // package and extracting rules from all the comments starting with the
 | |
| // magic string '+rtype'.
 | |
| //
 | |
| // COMMAND LINE
 | |
| //
 | |
| // go run _scripts/rtype.go (report [output-file]|check)
 | |
| //
 | |
| // Invoked with the command 'report' it will extract rules from pkg/proc and
 | |
| // print them to stdout.
 | |
| // Invoked with the command 'check' it will actually check that the runtime
 | |
| // conforms to the rules in pkg/proc.
 | |
| //
 | |
| // RTYPE RULES
 | |
| //
 | |
| // // +rtype -var V T
 | |
| //
 | |
| // 	checks that variable runtime.V exists and has type T
 | |
| //
 | |
| // // +rtype -field S.F T
 | |
| //
 | |
| // 	checks that struct runtime.S has a field called F of type T
 | |
| //
 | |
| // const C1 = V // +rtype C2
 | |
| //
 | |
| // 	checks that constant runtime.C2 exists and has value V
 | |
| //
 | |
| // case "F": // +rtype -fieldof S T
 | |
| //
 | |
| // 	checks that struct runtime.S has a field called F of type T
 | |
| //
 | |
| // v := ... // +rtype T
 | |
| //
 | |
| // 	if v is declared as *proc.Variable it will assume that it has type
 | |
| // 	runtime.T and it will then parse the enclosing function, searching for
 | |
| // 	all calls to:
 | |
| //		v.loadFieldNamed
 | |
| //		v.fieldVariable
 | |
| //		v.structMember
 | |
| //	and check that type T has the specified fields.
 | |
| //
 | |
| // v.loadFieldNamed("F") // +rtype T
 | |
| // v.loadFieldNamed("F") // +rtype -opt T
 | |
| //
 | |
| // 	checks that field F of the struct type declared for v has type T. Can
 | |
| // 	also be used for fieldVariable, structMember and, inside parseG,
 | |
| // 	loadInt64Maybe.
 | |
| // 	The -opt flag specifies that the field can be missing (but if it exists
 | |
| // 	it must have type T).
 | |
| //
 | |
| //
 | |
| // Anywhere a type is required the following expressions can be used:
 | |
| //
 | |
| //  - any builtin type
 | |
| //  - a type defined in the runtime package, without the 'runtime.' prefix
 | |
| //  - anytype to match all possible types
 | |
| //  - an expression of the form T1|T2 where both T1 and T2 can be arbitrary type expressions
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/constant"
 | |
| 	"go/printer"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/go/packages"
 | |
| )
 | |
| 
 | |
| const magicCommentPrefix = "+rtype"
 | |
| 
 | |
| var fset = &token.FileSet{}
 | |
| var checkVarTypeRules = []*checkVarType{}
 | |
| var checkFieldTypeRules = map[string][]*checkFieldType{}
 | |
| var checkConstValRules = map[string][]*checkConstVal{}
 | |
| var showRuleOrigin = false
 | |
| 
 | |
| // rtypeCmnt represents a +rtype comment
 | |
| type rtypeCmnt struct {
 | |
| 	slash    token.Pos
 | |
| 	txt      string
 | |
| 	node     ast.Node // associated node
 | |
| 	toplevel ast.Decl // toplevel declaration that contains the Slash of the comment
 | |
| 	stmt     ast.Stmt
 | |
| }
 | |
| 
 | |
| type checkVarType struct {
 | |
| 	V, T string // V must have type T
 | |
| 	pos  token.Pos
 | |
| }
 | |
| 
 | |
| func (c *checkVarType) String() string {
 | |
| 	if showRuleOrigin {
 | |
| 		pos := fset.Position(c.pos)
 | |
| 		return fmt.Sprintf("var %s %s // %s:%d", c.V, c.T, relative(pos.Filename), pos.Line)
 | |
| 	}
 | |
| 	return fmt.Sprintf("var %s %s", c.V, c.T)
 | |
| }
 | |
| 
 | |
| type checkFieldType struct {
 | |
| 	S, F, T string // S.F must have type T
 | |
| 	opt     bool
 | |
| 	pos     token.Pos
 | |
| }
 | |
| 
 | |
| func (c *checkFieldType) String() string {
 | |
| 	pos := fset.Position(c.pos)
 | |
| 	return fmt.Sprintf("field %s.%s %s // %s:%d", c.S, c.F, c.T, relative(pos.Filename), pos.Line)
 | |
| }
 | |
| 
 | |
| type checkConstVal struct {
 | |
| 	C   string // const C = V
 | |
| 	V   constant.Value
 | |
| 	pos token.Pos
 | |
| }
 | |
| 
 | |
| func (c *checkConstVal) String() string {
 | |
| 	if showRuleOrigin {
 | |
| 		pos := fset.Position(c.pos)
 | |
| 		return fmt.Sprintf("const %s = %s // %s:%d", c.C, c.V, relative(pos.Filename), pos.Line)
 | |
| 	}
 | |
| 	return fmt.Sprintf("const %s = %s", c.C, c.V)
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	if len(os.Args) < 2 {
 | |
| 		fmt.Fprintf(os.Stderr, "Wrong number of arguments.\n\trtype (report [output-file]|check)\n")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	command := os.Args[1]
 | |
| 
 | |
| 	setup()
 | |
| 
 | |
| 	switch command {
 | |
| 	case "report":
 | |
| 		if len(os.Args) > 2 {
 | |
| 			fh, err := os.Create(os.Args[2])
 | |
| 			if err != nil {
 | |
| 				log.Fatalf("error creating output file: %v", err)
 | |
| 			}
 | |
| 			defer fh.Close()
 | |
| 			os.Stdout = fh
 | |
| 		}
 | |
| 		report()
 | |
| 	case "check":
 | |
| 		check()
 | |
| 	default:
 | |
| 		fmt.Fprintf(os.Stderr, "Wrong argument %s\n", command)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // setup parses the proc package, extracting all +rtype comments and
 | |
| // converting them into rules.
 | |
| func setup() {
 | |
| 	pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadSyntax, Fset: fset}, "github.com/go-delve/delve/pkg/proc")
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("could not load proc package: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	for _, file := range pkgs[0].Syntax {
 | |
| 		cmntmap := ast.NewCommentMap(fset, file, file.Comments)
 | |
| 		rtypeCmnts := getRtypeCmnts(file, cmntmap)
 | |
| 		for _, rtcmnt := range rtypeCmnts {
 | |
| 			if rtcmnt == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 			process(pkgs[0], rtcmnt, cmntmap, rtypeCmnts)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // getRtypeCmnts returns all +rtype comments inside 'file'. It also
 | |
| // decorates them with the toplevel declaration that contains them as well
 | |
| // as the statement they are associated with (where applicable).
 | |
| func getRtypeCmnts(file *ast.File, cmntmap ast.CommentMap) []*rtypeCmnt {
 | |
| 	r := []*rtypeCmnt{}
 | |
| 
 | |
| 	for n, cmntgrps := range cmntmap {
 | |
| 		for _, cmntgrp := range cmntgrps {
 | |
| 			if len(cmntgrp.List) == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			for _, cmnt := range cmntgrp.List {
 | |
| 				txt := cleanupCommentText(cmnt.Text)
 | |
| 				if !strings.HasPrefix(txt, magicCommentPrefix) {
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				r = append(r, &rtypeCmnt{slash: cmnt.Slash, txt: txt, node: n})
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(r, func(i, j int) bool { return r[i].slash < r[j].slash })
 | |
| 
 | |
| 	// assign each comment to the toplevel declaration that contains it
 | |
| 	for i, j := 0, 0; i < len(r) && j < len(file.Decls); {
 | |
| 		decl := file.Decls[j]
 | |
| 		if decl.Pos() <= r[i].slash && r[i].slash < decl.End() {
 | |
| 			r[i].toplevel = decl
 | |
| 			i++
 | |
| 		} else {
 | |
| 			j++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// for comments declared inside a function also find the statement that contains them.
 | |
| 	for i := range r {
 | |
| 		fndecl, ok := r[i].toplevel.(*ast.FuncDecl)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		var lastStmt ast.Stmt
 | |
| 		ast.Inspect(fndecl, func(n ast.Node) bool {
 | |
| 			if stmt, _ := n.(ast.Stmt); stmt != nil {
 | |
| 				lastStmt = stmt
 | |
| 			}
 | |
| 			if n == r[i].node {
 | |
| 				r[i].stmt = lastStmt
 | |
| 			}
 | |
| 			return true
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| func cleanupCommentText(txt string) string {
 | |
| 	if strings.HasPrefix(txt, "/*") || strings.HasPrefix(txt, "//") {
 | |
| 		txt = txt[2:]
 | |
| 	}
 | |
| 	return strings.TrimSpace(strings.TrimSuffix(txt, "*/"))
 | |
| }
 | |
| 
 | |
| // process processes a single +rtype comment, turning it into a rule.
 | |
| // If the +rtype comment is associated with a *proc.Variable declaration
 | |
| // then it also checks the containing function for all uses of that
 | |
| // variable.
 | |
| func process(pkg *packages.Package, rtcmnt *rtypeCmnt, cmntmap ast.CommentMap, rtcmnts []*rtypeCmnt) {
 | |
| 	tinfo := pkg.TypesInfo
 | |
| 	fields := strings.Split(rtcmnt.txt, " ")
 | |
| 
 | |
| 	switch fields[1] {
 | |
| 	case "-var":
 | |
| 		// -var V T
 | |
| 		// requests that variable V is of type T
 | |
| 		addCheckVarType(fields[2], fields[3], rtcmnt.slash)
 | |
| 	case "-field":
 | |
| 		// -field S.F T
 | |
| 		// requests that field F of type S is of type T
 | |
| 		v := strings.Split(fields[2], ".")
 | |
| 		addCheckFieldType(v[0], v[1], fields[3], false, rtcmnt.slash)
 | |
| 	default:
 | |
| 		ok := false
 | |
| 		if ident := isProcVariableDecl(rtcmnt.stmt, tinfo); ident != nil {
 | |
| 			if len(fields) == 2 {
 | |
| 				processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[1])
 | |
| 				ok = true
 | |
| 			} else if len(fields) == 3 && fields[1] == "-opt" {
 | |
| 				processProcVariableUses(rtcmnt.toplevel, tinfo, ident, cmntmap, rtcmnts, fields[2])
 | |
| 				ok = true
 | |
| 			}
 | |
| 		} else if ident := isConstDecl(rtcmnt.toplevel, rtcmnt.node); len(fields) == 2 && ident != nil {
 | |
| 			addCheckConstVal(fields[1], constValue(tinfo.Defs[ident]), rtcmnt.slash)
 | |
| 			ok = true
 | |
| 		} else if F := isStringCaseClause(rtcmnt.stmt); F != "" && len(fields) == 4 && fields[1] == "-fieldof" {
 | |
| 			addCheckFieldType(fields[2], F, fields[3], false, rtcmnt.slash)
 | |
| 			ok = true
 | |
| 		}
 | |
| 		if !ok {
 | |
| 			pos := fset.Position(rtcmnt.slash)
 | |
| 			log.Fatalf("%s:%d: unrecognized +rtype comment\n", pos.Filename, pos.Line)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // isProcVariableDecl returns true if stmt is a declaration of a
 | |
| // *proc.Variable variable.
 | |
| func isProcVariableDecl(stmt ast.Stmt, tinfo *types.Info) *ast.Ident {
 | |
| 	ass, _ := stmt.(*ast.AssignStmt)
 | |
| 	if ass == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if len(ass.Lhs) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	ident, _ := ass.Lhs[0].(*ast.Ident)
 | |
| 	if ident == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var typ types.Type
 | |
| 	if def := tinfo.Defs[ident]; def != nil {
 | |
| 		typ = def.Type()
 | |
| 	}
 | |
| 	if tv, ok := tinfo.Types[ident]; ok {
 | |
| 		typ = tv.Type
 | |
| 	}
 | |
| 	if typ == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if typ == nil || typ.String() != "*github.com/go-delve/delve/pkg/proc.Variable" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return ident
 | |
| }
 | |
| 
 | |
| func isConstDecl(toplevel ast.Decl, node ast.Node) *ast.Ident {
 | |
| 	gendecl, _ := toplevel.(*ast.GenDecl)
 | |
| 	if gendecl == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if gendecl.Tok != token.CONST {
 | |
| 		return nil
 | |
| 	}
 | |
| 	valspec, _ := node.(*ast.ValueSpec)
 | |
| 	if valspec == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if len(valspec.Names) != 1 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return valspec.Names[0]
 | |
| }
 | |
| 
 | |
| func isStringCaseClause(stmt ast.Stmt) string {
 | |
| 	c, _ := stmt.(*ast.CaseClause)
 | |
| 	if c == nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 	if len(c.List) != 1 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	lit := c.List[0].(*ast.BasicLit)
 | |
| 	if lit == nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 	if lit.Kind != token.STRING {
 | |
| 		return ""
 | |
| 	}
 | |
| 	r, _ := strconv.Unquote(lit.Value)
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // processProcVariableUses scans the body of the function declaration 'decl'
 | |
| // looking for uses of 'procVarIdent' which is assumed to be an identifier
 | |
| // for a *proc.Variable variable.
 | |
| func processProcVariableUses(decl ast.Node, tinfo *types.Info, procVarIdent *ast.Ident, cmntmap ast.CommentMap, rtcmnts []*rtypeCmnt, S string) {
 | |
| 	if len(S) > 0 && S[0] == '*' {
 | |
| 		S = S[1:]
 | |
| 	}
 | |
| 	isParseG := false
 | |
| 	if fndecl, _ := decl.(*ast.FuncDecl); fndecl != nil {
 | |
| 		if fndecl.Name.Name == "parseG" {
 | |
| 			if procVarIdent.Name == "v" {
 | |
| 				isParseG = true
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	var lastStmt ast.Stmt
 | |
| 	ast.Inspect(decl, func(n ast.Node) bool {
 | |
| 		if stmt, _ := n.(ast.Stmt); stmt != nil {
 | |
| 			lastStmt = stmt
 | |
| 		}
 | |
| 
 | |
| 		fncall, _ := n.(*ast.CallExpr)
 | |
| 		if fncall == nil {
 | |
| 			return true
 | |
| 		}
 | |
| 		var methodName string
 | |
| 		if isParseG {
 | |
| 			if xident, _ := fncall.Fun.(*ast.Ident); xident != nil && (xident.Name == "loadInt64Maybe" || xident.Name == "loadUint64Maybe") {
 | |
| 				methodName = "loadInt64Maybe"
 | |
| 			}
 | |
| 		}
 | |
| 		if methodName == "" {
 | |
| 			sel, _ := fncall.Fun.(*ast.SelectorExpr)
 | |
| 			if sel == nil {
 | |
| 				return true
 | |
| 			}
 | |
| 			methodName = sel.Sel.Name
 | |
| 			xident, _ := sel.X.(*ast.Ident)
 | |
| 			if xident == nil {
 | |
| 				return true
 | |
| 			}
 | |
| 			if xident.Obj != procVarIdent.Obj {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		if len(fncall.Args) < 1 {
 | |
| 			return true
 | |
| 		}
 | |
| 		arg0, _ := fncall.Args[0].(*ast.BasicLit)
 | |
| 		if arg0 == nil {
 | |
| 			return true
 | |
| 		}
 | |
| 		if arg0.Kind != token.STRING {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		switch methodName {
 | |
| 		case "loadFieldNamed", "fieldVariable", "loadInt64Maybe", "structMember":
 | |
| 			rtcmntIdx := -1
 | |
| 			if cmntgrps := cmntmap[lastStmt]; len(cmntgrps) > 0 && len(cmntgrps[0].List) > 0 {
 | |
| 				rtcmntIdx = findComment(cmntgrps[0].List[0].Slash, rtcmnts)
 | |
| 			}
 | |
| 			typ := "anytype"
 | |
| 			opt := false
 | |
| 
 | |
| 			if rtcmntIdx >= 0 {
 | |
| 				fields := strings.Split(rtcmnts[rtcmntIdx].txt, " ")
 | |
| 				if len(fields) == 2 {
 | |
| 					typ = fields[1]
 | |
| 				} else if len(fields) == 3 && fields[1] == "-opt" {
 | |
| 					opt = true
 | |
| 					typ = fields[2]
 | |
| 				}
 | |
| 				if isProcVariableDecl(lastStmt, tinfo) == nil {
 | |
| 					// remove it because we have already processed it
 | |
| 					rtcmnts[rtcmntIdx] = nil
 | |
| 				}
 | |
| 			}
 | |
| 			F, _ := strconv.Unquote(arg0.Value)
 | |
| 			addCheckFieldType(S, F, typ, opt, fncall.Pos())
 | |
| 			//printNode(fset, fncall)
 | |
| 		default:
 | |
| 			pos := fset.Position(n.Pos())
 | |
| 			log.Fatalf("unknown node at %s:%d", pos.Filename, pos.Line)
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func findComment(slash token.Pos, rtcmnts []*rtypeCmnt) int {
 | |
| 	for i := range rtcmnts {
 | |
| 		if rtcmnts[i] != nil && rtcmnts[i].slash == slash {
 | |
| 			return i
 | |
| 		}
 | |
| 	}
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| func addCheckVarType(V, T string, pos token.Pos) {
 | |
| 	checkVarTypeRules = append(checkVarTypeRules, &checkVarType{V, T, pos})
 | |
| }
 | |
| 
 | |
| func addCheckFieldType(S, F, T string, opt bool, pos token.Pos) {
 | |
| 	checkFieldTypeRules[S] = append(checkFieldTypeRules[S], &checkFieldType{S, F, T, opt, pos})
 | |
| }
 | |
| 
 | |
| func addCheckConstVal(C string, V constant.Value, pos token.Pos) {
 | |
| 	checkConstValRules[C] = append(checkConstValRules[C], &checkConstVal{C, V, pos})
 | |
| }
 | |
| 
 | |
| // report writes a report of all rules derived from the proc package to stdout.
 | |
| func report() {
 | |
| 	for _, rule := range checkVarTypeRules {
 | |
| 		fmt.Printf("%s\n\n", rule.String())
 | |
| 	}
 | |
| 
 | |
| 	var Ss []string
 | |
| 	for S := range checkFieldTypeRules {
 | |
| 		Ss = append(Ss, S)
 | |
| 	}
 | |
| 	sort.Strings(Ss)
 | |
| 	for _, S := range Ss {
 | |
| 		rules := checkFieldTypeRules[S]
 | |
| 		fmt.Printf("type %s struct {\n", S)
 | |
| 		for _, rule := range rules {
 | |
| 			fmt.Printf("\t%s %s", rule.F, rule.T)
 | |
| 			if rule.opt {
 | |
| 				fmt.Printf(" (optional)")
 | |
| 			}
 | |
| 			pos := fset.Position(rule.pos)
 | |
| 			if showRuleOrigin {
 | |
| 				fmt.Printf("\t// %s:%d", relative(pos.Filename), pos.Line)
 | |
| 			}
 | |
| 			fmt.Printf("\n")
 | |
| 		}
 | |
| 		fmt.Printf("}\n\n")
 | |
| 	}
 | |
| 
 | |
| 	var Cs []string
 | |
| 	for C := range checkConstValRules {
 | |
| 		Cs = append(Cs, C)
 | |
| 	}
 | |
| 	sort.Strings(Cs)
 | |
| 	for _, C := range Cs {
 | |
| 		rules := checkConstValRules[C]
 | |
| 		for i, rule := range rules {
 | |
| 			if i == 0 {
 | |
| 				fmt.Printf("%s\n", rule.String())
 | |
| 			} else {
 | |
| 				fmt.Printf("or %s\n", rule.String())
 | |
| 			}
 | |
| 		}
 | |
| 		fmt.Printf("\n")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func lookupPackage(pkgmap map[string]*packages.Package, name string) *packages.Package {
 | |
| 	if pkgmap[name] != nil {
 | |
| 		return pkgmap[name]
 | |
| 	}
 | |
| 
 | |
| 	pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadSyntax, Fset: fset}, name)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("could not load runtime package: %v", err)
 | |
| 	}
 | |
| 	packages.Visit(pkgs, func(pkg *packages.Package) bool {
 | |
| 		if pkgmap[pkg.ID] == nil {
 | |
| 			pkgmap[pkg.ID] = pkg
 | |
| 		}
 | |
| 		return true
 | |
| 	}, nil)
 | |
| 
 | |
| 	return pkgmap[name]
 | |
| }
 | |
| 
 | |
| func lookupTypeDef(pkgmap map[string]*packages.Package, typ string) types.Object {
 | |
| 	dot := strings.Index(typ, ".")
 | |
| 	if dot < 0 {
 | |
| 		return lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(typ)
 | |
| 	}
 | |
| 
 | |
| 	return lookupPackage(pkgmap, typ[:dot]).Types.Scope().Lookup(typ[dot+1:])
 | |
| }
 | |
| 
 | |
| // check parses the runtime package and checks that all the rules retrieved
 | |
| // from the 'proc' package pass.
 | |
| func check() {
 | |
| 	pkgmap := map[string]*packages.Package{}
 | |
| 	allok := true
 | |
| 
 | |
| 	for _, rule := range checkVarTypeRules {
 | |
| 		//TODO: implement
 | |
| 		pos := fset.Position(rule.pos)
 | |
| 		def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(rule.V)
 | |
| 		if def == nil {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: could not find variable %s\n", pos.Filename, pos.Line, rule.V)
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 		if !matchType(def.Type(), rule.T) {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: wrong type for variable %s, expected %s got %s\n", pos.Filename, pos.Line, rule.V, rule.T, typeStr(def.Type()))
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var Ss []string
 | |
| 	for S := range checkFieldTypeRules {
 | |
| 		Ss = append(Ss, S)
 | |
| 	}
 | |
| 	sort.Strings(Ss)
 | |
| 	for _, S := range Ss {
 | |
| 		rules := checkFieldTypeRules[S]
 | |
| 		pos := fset.Position(rules[0].pos)
 | |
| 
 | |
| 		def := lookupTypeDef(pkgmap, S)
 | |
| 		if def == nil {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: could not find struct %s\n", pos.Filename, pos.Line, S)
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		typ := def.Type()
 | |
| 		if typ == nil {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: could not find struct %s\n", pos.Filename, pos.Line, S)
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 		styp, _ := typ.Underlying().(*types.Struct)
 | |
| 		if styp == nil {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: could not find struct %s\n", pos.Filename, pos.Line, S)
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for _, rule := range rules {
 | |
| 			pos := fset.Position(rule.pos)
 | |
| 			fieldType := fieldTypeByName(styp, rule.F)
 | |
| 			if fieldType == nil {
 | |
| 				if rule.opt {
 | |
| 					continue
 | |
| 				}
 | |
| 				fmt.Fprintf(os.Stderr, "%s:%d: could not find field %s.%s\n", pos.Filename, pos.Line, rule.S, rule.F)
 | |
| 				allok = false
 | |
| 				continue
 | |
| 			}
 | |
| 			if !matchType(fieldType, rule.T) {
 | |
| 				fmt.Fprintf(os.Stderr, "%s:%d: wrong type for field %s.%s, expected %s got %s\n", pos.Filename, pos.Line, rule.S, rule.F, rule.T, typeStr(fieldType))
 | |
| 				allok = false
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var Cs []string
 | |
| 	for C := range checkConstValRules {
 | |
| 		Cs = append(Cs, C)
 | |
| 	}
 | |
| 	sort.Strings(Cs)
 | |
| 	for _, C := range Cs {
 | |
| 		rules := checkConstValRules[C]
 | |
| 		pos := fset.Position(rules[0].pos)
 | |
| 		def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(C)
 | |
| 		if def == nil {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: could not find constant %s\n", pos.Filename, pos.Line, C)
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		val := constValue(def)
 | |
| 		found := false
 | |
| 		for _, rule := range rules {
 | |
| 			if val == rule.V {
 | |
| 				found = true
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			fmt.Fprintf(os.Stderr, "%s:%d: wrong value for constant %s (%s)\n", pos.Filename, pos.Line, C, val.String())
 | |
| 			allok = false
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !allok {
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func fieldTypeByName(typ *types.Struct, name string) types.Type {
 | |
| 	for i := 0; i < typ.NumFields(); i++ {
 | |
| 		field := typ.Field(i)
 | |
| 		if field.Name() == name {
 | |
| 			return field.Type()
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func matchType(typ types.Type, T string) bool {
 | |
| 	if T == "anytype" {
 | |
| 		return true
 | |
| 	}
 | |
| 	if strings.Index(T, "|") > 0 {
 | |
| 		for _, t1 := range strings.Split(T, "|") {
 | |
| 			if typeStr(typ) == t1 {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 	return typeStr(typ) == T
 | |
| }
 | |
| 
 | |
| func typeStr(typ types.Type) string {
 | |
| 	return types.TypeString(typ, func(pkg *types.Package) string {
 | |
| 		if pkg.Path() == "runtime" {
 | |
| 			return ""
 | |
| 		}
 | |
| 		return pkg.Path()
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func constValue(obj types.Object) constant.Value {
 | |
| 	return obj.(*types.Const).Val()
 | |
| }
 | |
| 
 | |
| func printNode(fset *token.FileSet, n ast.Node) {
 | |
| 	ast.Fprint(os.Stderr, fset, n, nil)
 | |
| }
 | |
| 
 | |
| func exprToString(t ast.Expr) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	printer.Fprint(&buf, token.NewFileSet(), t)
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| func relative(s string) string {
 | |
| 	wd, _ := os.Getwd()
 | |
| 	r, err := filepath.Rel(wd, s)
 | |
| 	if err != nil {
 | |
| 		return s
 | |
| 	}
 | |
| 	return r
 | |
| }
 |