github.com/goplus/gossa@v0.3.25/cmd/qexp/api.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"hash/fnv"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  )
    13  
    14  func apipath(base string) string {
    15  	return filepath.Join(os.Getenv("GOROOT"), "api", base)
    16  }
    17  
    18  //pkg syscall (windows-386), const CERT_E_CN_NO_MATCH = 2148204815
    19  var reSym = regexp.MustCompile(`^pkg (\S+)\s?(.*)?, (?:(var|func|type|const|method))\s?(\((\*?[A-Z]\w*)\))? ([A-Z]\w*)`)
    20  
    21  // pkg reflect, type Type interface, Align
    22  var reInterface = regexp.MustCompile(`^pkg (\S+)\s?(.*)?, type ([A-Z]\w*) interface, ([A-Z]\w*)`)
    23  
    24  // var reMethod = regexp.MustCompile(`^pkg (\S+)\s?(.*)?, method (\((\*?[A-Z]\w*)\))? ([A-Z]\w*)`) // (\(\*?[A-Z]\w*\)) ([A-Z]\w*)`)
    25  // var reNum = regexp.MustCompile(`^\-?[0-9]+$`)
    26  
    27  type ApiInfo struct {
    28  	Name       string   // name key
    29  	Pkg        string   // pkg path
    30  	Kind       string   // var|const|func|type|method|
    31  	MethodType string   // type or empty
    32  	MethodName string   // name or empty
    33  	MethodPtr  string   // * or empty
    34  	Tags       []string // (os-arch-cgo)
    35  	Ver        string   // go1.15 | go1.16 or empty
    36  }
    37  
    38  func (i ApiInfo) String() string {
    39  	info := fmt.Sprintf("%v %v.%v", i.Kind, i.Pkg, i.Name)
    40  	if i.Kind == "method" {
    41  		info = fmt.Sprintf("%v (%v%v.%v).%v", i.Kind, i.MethodPtr, i.Pkg, i.MethodType, i.MethodName)
    42  	}
    43  	if i.Ver != "" {
    44  		info += " (" + i.Ver + ")"
    45  	}
    46  	if len(i.Tags) > 1 {
    47  		info += " " + strings.Join(i.Tags[1:], ",")
    48  	}
    49  	return info
    50  }
    51  
    52  type GoApi struct {
    53  	Keys map[string]*ApiInfo // name - os-arch-cgo
    54  	Ver  string
    55  }
    56  
    57  func (c *GoApi) List() []string {
    58  	var list []string
    59  	for k, _ := range c.Keys {
    60  		list = append(list, k)
    61  	}
    62  	sort.Strings(list)
    63  	return list
    64  }
    65  
    66  func LoadApi(ver string, tagVer bool) (*GoApi, error) {
    67  	f, err := os.Open(apipath(ver + ".txt"))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	var verInfo string
    72  	if tagVer {
    73  		verInfo = ver
    74  	}
    75  	sc := bufio.NewScanner(f)
    76  	keys := make(map[string]*ApiInfo)
    77  	toTag := func(tag string) string {
    78  		if tag == "" {
    79  			return "-"
    80  		}
    81  		return tag
    82  	}
    83  	for sc.Scan() {
    84  		l := sc.Text()
    85  		has := func(v string) bool { return strings.Contains(l, v) }
    86  		if has("struct, ") {
    87  			continue
    88  		}
    89  		if has("interface, ") {
    90  			if m := reInterface.FindStringSubmatch(l); m != nil {
    91  				// m[1] pkg
    92  				// m[2] os-arch-cgo
    93  				// m[3] name
    94  				// m[4] method
    95  				pkg := m[1]
    96  				tag := m[2]
    97  				mtype := m[3]
    98  				mname := m[4]
    99  				key := pkg + "." + mtype
   100  				if _, ok := keys[key]; !ok {
   101  					keys[key] = &ApiInfo{
   102  						Pkg:  pkg,
   103  						Name: mtype,
   104  						Kind: "type",
   105  						Tags: []string{toTag(tag)},
   106  						Ver:  verInfo,
   107  					}
   108  				}
   109  				key = pkg + "." + mtype + "." + mname
   110  				keys[key] = &ApiInfo{
   111  					Pkg:        pkg,
   112  					Name:       mtype + "." + mname,
   113  					Kind:       "method",
   114  					MethodType: mtype,
   115  					MethodName: mname,
   116  					Tags:       []string{toTag(tag)},
   117  					Ver:        verInfo,
   118  				}
   119  			}
   120  			continue
   121  		}
   122  		if m := reSym.FindStringSubmatch(l); m != nil {
   123  			// m[1] pkg
   124  			// m[2] os-arch-cgo
   125  			// m[3] var|func|type|const|method
   126  			// m[4] (*?Type)
   127  			// m[5] *?Type
   128  			// m[6] name
   129  			pkg := m[1]
   130  			tag := m[2]
   131  			if tag != "" {
   132  				tag = tag[1 : len(tag)-1]
   133  			}
   134  			kind := m[3]
   135  			mtype := m[5]
   136  			name := m[6]
   137  			var mptr string
   138  			var mname string
   139  			if kind == "method" {
   140  				mname = name
   141  				if mtype[0] == '*' {
   142  					mtype = mtype[1:]
   143  					mptr = "*"
   144  				}
   145  				name = mtype + "." + name
   146  			}
   147  			key := pkg + "." + name
   148  			if api, ok := keys[key]; ok {
   149  				if api.Tags[0] != "-" {
   150  					api.Tags = append(api.Tags, toTag(tag))
   151  				}
   152  				continue
   153  			}
   154  			keys[key] = &ApiInfo{
   155  				Pkg:        pkg,
   156  				Name:       name,
   157  				Kind:       kind,
   158  				MethodType: mtype,
   159  				MethodName: mname,
   160  				MethodPtr:  mptr,
   161  				Tags:       []string{toTag(tag)},
   162  				Ver:        verInfo,
   163  			}
   164  		}
   165  	}
   166  	return &GoApi{Ver: ver, Keys: keys}, nil
   167  }
   168  
   169  type ApiCheck struct {
   170  	Base map[string]*ApiInfo
   171  	Apis []*GoApi
   172  }
   173  
   174  func NewApiCheck() *ApiCheck {
   175  	ac := &ApiCheck{}
   176  	ac.Base = make(map[string]*ApiInfo)
   177  	return ac
   178  }
   179  
   180  func (ac *ApiCheck) LoadBase(vers ...string) error {
   181  	for _, ver := range vers {
   182  		api, err := LoadApi(ver, false)
   183  		if err != nil {
   184  			return err
   185  		}
   186  		for k, v := range api.Keys {
   187  			if info, ok := ac.Base[k]; ok {
   188  				if info.Tags[0] != "-" {
   189  					info.Tags = append(info.Tags, v.Tags...)
   190  				}
   191  			} else {
   192  				ac.Base[k] = v
   193  			}
   194  		}
   195  	}
   196  	return nil
   197  }
   198  
   199  func (ac *ApiCheck) LoadApi(vers ...string) error {
   200  	for _, ver := range vers {
   201  		api, err := LoadApi(ver, true)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		for k, v := range api.Keys {
   206  			if info, ok := ac.Base[k]; ok {
   207  				if info.Tags[0] != "-" {
   208  					if info.Ver != v.Ver {
   209  						continue
   210  					}
   211  					info.Tags = append(info.Tags, v.Tags...)
   212  				}
   213  				delete(api.Keys, k)
   214  			}
   215  		}
   216  		ac.Apis = append(ac.Apis, api)
   217  	}
   218  	return nil
   219  }
   220  
   221  func (ac *ApiCheck) FindApis(name string) (vers string, info *ApiInfo) {
   222  	for _, api := range ac.Apis {
   223  		if info, ok := api.Keys[name]; ok {
   224  			return api.Ver, info
   225  		}
   226  	}
   227  	return
   228  }
   229  
   230  func (ac *ApiCheck) ApiVers() (vers []string) {
   231  	for _, api := range ac.Apis {
   232  		vers = append(vers, api.Ver)
   233  	}
   234  	return
   235  }
   236  
   237  func (ac *ApiCheck) LoadPkg(pkg string) (infos []*ApiInfo) {
   238  	for _, api := range ac.Apis {
   239  		for _, v := range api.Keys {
   240  			if v.Pkg == pkg {
   241  				infos = append(infos, v)
   242  			}
   243  		}
   244  	}
   245  	for _, v := range ac.Base {
   246  		if v.Pkg == pkg {
   247  			infos = append(infos, v)
   248  		}
   249  	}
   250  	sort.Slice(infos, func(i, j int) bool {
   251  		n := strings.Compare(infos[i].Kind, infos[j].Kind)
   252  		if n == 0 {
   253  			return infos[i].Name < infos[j].Name
   254  		}
   255  		return n < 0
   256  	})
   257  	return
   258  }
   259  
   260  type Style struct {
   261  	Ver string
   262  	Tag string
   263  }
   264  
   265  type LineData struct {
   266  	Ver  string
   267  	Tag  []string
   268  	Info string
   269  }
   270  
   271  var (
   272  	osOldNews = []string{
   273  		"darwin-386 darwin-386-cgo darwin-386-cgo darwin-amd64 darwin-amd64 darwin-amd64-cgo darwin-amd64-cgo", "darwin",
   274  		"darwin-386-cgo darwin-amd64 darwin-amd64-cgo", "darwin",
   275  		"darwin-386 darwin", "darwin",
   276  		"linux-386 linux-386-cgo linux-amd64 linux-amd64-cgo linux-arm linux-arm-cgo", "linux",
   277  		"linux-386-cgo linux-amd64 linux-amd64-cgo linux-arm linux-arm-cgo", "linux",
   278  		"linux-386 linux-386-cgo linux-amd64 linux-amd64-cgo linux-arm", "linux",
   279  		"linux-386-cgo linux-amd64 linux-amd64-cgo", "linux",
   280  		"freebsd-386 freebsd-386-cgo freebsd-amd64 freebsd-amd64-cgo freebsd-arm freebsd-arm-cgo", "freebsd",
   281  		"freebsd-386-cgo freebsd-amd64 freebsd-amd64-cgo freebsd-arm freebsd-arm-cgo", "freebsd",
   282  		"freebsd-386 freebsd-amd64", "freebsd",
   283  		"netbsd-386 netbsd-386-cgo netbsd-amd64 netbsd-amd64-cgo netbsd-arm netbsd-arm-cgo netbsd-arm64 netbsd-arm64-cgo", "netbsd",
   284  		"netbsd-386-cgo netbsd-amd64 netbsd-amd64-cgo netbsd-arm netbsd-arm-cgo netbsd-arm64 netbsd-arm64-cgo", "netbsd",
   285  		"netbsd-386 netbsd-386-cgo netbsd-amd64 netbsd-amd64-cgo netbsd-arm netbsd-arm-cgo", "netbsd",
   286  		"openbsd-386 openbsd-386-cgo openbsd-amd64 openbsd-amd64-cgo", "openbsd",
   287  		"windows-386 windows-amd64", "windows",
   288  	}
   289  	osReplacer = strings.NewReplacer(osOldNews...)
   290  )
   291  
   292  func osReplace(s string) string {
   293  	for i := 0; i < len(osOldNews); i += 2 {
   294  		s = strings.ReplaceAll(s, osOldNews[i], osOldNews[i+1])
   295  	}
   296  	return s
   297  }
   298  
   299  var (
   300  	tagsName = make(map[string]string)
   301  	tagIndex int
   302  )
   303  
   304  func (d LineData) TagName() (name string, tags []string) {
   305  	if d.Ver != "" {
   306  		name += "_" + strings.Replace(d.Ver, ".", "", -1)
   307  		tags = append(tags, "// +build "+d.Ver)
   308  	}
   309  	switch len(d.Tag) {
   310  	case 0:
   311  	case 1:
   312  		name += "_" + strings.Replace(d.Tag[0], "-", "_", -1)
   313  	default:
   314  		otags := strings.Join(d.Tag, " ")
   315  		ntags := osReplace(otags)
   316  		if strings.Index(ntags, " ") == -1 {
   317  			name += "_" + ntags
   318  		} else {
   319  			tags = append(tags, "// +build "+strings.Replace(ntags, "-", ",", -1))
   320  			if tag, ok := tagsName[ntags]; ok {
   321  				name += "_" + tag
   322  			} else {
   323  				h := fnv.New32()
   324  				h.Write([]byte(ntags))
   325  				name += fmt.Sprintf("_%v", h.Sum32())
   326  			}
   327  		}
   328  	}
   329  	return
   330  }
   331  
   332  type File struct {
   333  	Id      string
   334  	Name    string
   335  	Tags    []string
   336  	ExtList []string
   337  	TypList []string
   338  }
   339  
   340  func (ac *ApiCheck) Export(pkgPath string) (map[string]*File, error) {
   341  	extList, typList, ok := ac.ExportData(pkgPath)
   342  	if !ok {
   343  		return nil, nil
   344  	}
   345  	fileMap := make(map[string]*File)
   346  	for _, v := range extList {
   347  		name, tags := v.TagName()
   348  		f, ok := fileMap[name]
   349  		if !ok {
   350  			f = &File{Name: name, Tags: tags}
   351  			fileMap[name] = f
   352  		}
   353  		f.ExtList = append(f.ExtList, v.Info)
   354  	}
   355  	for _, v := range typList {
   356  		name, tags := v.TagName()
   357  		f, ok := fileMap[name]
   358  		if !ok {
   359  			f = &File{Name: name, Tags: tags}
   360  			fileMap[name] = f
   361  		}
   362  		f.TypList = append(f.TypList, v.Info)
   363  	}
   364  	return fileMap, nil
   365  }
   366  
   367  func cleanTags(tags []string) []string {
   368  	sort.Strings(tags)
   369  	var i, j int
   370  	for {
   371  		if i >= len(tags)-1 {
   372  			break
   373  		}
   374  		for j = i + 1; j < len(tags) && tags[i] == tags[j]; j++ {
   375  		}
   376  		tags = append(tags[:i+1], tags[j:]...)
   377  		i++
   378  	}
   379  	return tags
   380  }
   381  
   382  // linux-386-cgo linux-amd64 linux-amd64-cgo linux-arm linux-arm-cgo
   383  // darwin-386-cgo darwin-amd64 darwin-amd64-cgo
   384  // freebsd-386 freebsd-386-cgo freebsd-amd64 freebsd-amd64-cgo freebsd-arm freebsd-arm-cgo
   385  // netbsd-386 netbsd-386-cgo netbsd-amd64 netbsd-amd64-cgo netbsd-arm netbsd-arm-cgo netbsd-arm64 netbsd-arm64-cgo
   386  // openbsd-386 openbsd-386-cgo openbsd-amd64 openbsd-amd64-cgo
   387  // windows-386 windows-amd64
   388  func (ac *ApiCheck) ExportData(pkgPath string) (extList []LineData, typList []LineData, ok bool) {
   389  	infos := ac.LoadPkg(pkgPath)
   390  	if len(infos) == 0 {
   391  		return
   392  	}
   393  	pkgList := strings.Split(pkgPath, "/")
   394  	pkgName := pkgList[len(pkgList)-1]
   395  	for _, t := range infos {
   396  		tags := cleanTags(t.Tags)[1:]
   397  		switch t.Kind {
   398  		case "var":
   399  			extList = append(extList, LineData{
   400  				t.Ver,
   401  				tags,
   402  				fmt.Sprintf("%q : &%v", pkgPath+"."+t.Name, pkgName+"."+t.Name),
   403  			})
   404  		case "func":
   405  			extList = append(extList, LineData{
   406  				t.Ver,
   407  				tags,
   408  				fmt.Sprintf("%q : %v", pkgPath+"."+t.Name, pkgName+"."+t.Name),
   409  			})
   410  		case "type":
   411  			typList = append(typList, LineData{
   412  				t.Ver,
   413  				tags,
   414  				fmt.Sprintf("(*%v.%v)(nil)", pkgName, t.Name),
   415  			})
   416  			for _, v := range infos {
   417  				if v.Kind == "method" {
   418  					if v.MethodType == t.Name {
   419  						tags := v.Tags[1:]
   420  						sort.Strings(tags)
   421  						extList = append(extList, LineData{
   422  							v.Ver,
   423  							tags,
   424  							fmt.Sprintf("\"(%v%v.%v).%v\" : (%v%v.%v).%v",
   425  								v.MethodPtr, pkgPath, v.MethodType, v.MethodName,
   426  								v.MethodPtr, pkgName, v.MethodType, v.MethodName),
   427  						})
   428  					}
   429  				}
   430  			}
   431  		}
   432  	}
   433  	ok = len(extList) > 0 || len(typList) > 0
   434  	return
   435  }