github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/go_generics/main.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // go_generics reads a Go source file and writes a new version of that file with
    16  // a few transformations applied to each. Namely:
    17  //
    18  // 1. Global types can be explicitly renamed with the -t option. For example,
    19  //    if -t=A=B is passed in, all references to A will be replaced with
    20  //    references to B; a function declaration like:
    21  //
    22  //    func f(arg *A)
    23  //
    24  //    would be renamed to:
    25  //
    26  //    func f(arg *B)
    27  //
    28  // 2. Global type definitions and their method sets will be removed when they're
    29  //    being renamed with -t. For example, if -t=A=B is passed in, the following
    30  //    definition and methods that existed in the input file wouldn't exist at
    31  //    all in the output file:
    32  //
    33  //    type A struct{}
    34  //
    35  //    func (*A) f() {}
    36  //
    37  // 3. All global types, variables, constants and functions (not methods) are
    38  //    prefixed and suffixed based on the option -prefix and -suffix arguments.
    39  //    For example, if -suffix=A is passed in, the following globals:
    40  //
    41  //    func f()
    42  //    type t struct{}
    43  //
    44  //    would be renamed to:
    45  //
    46  //    func fA()
    47  //    type tA struct{}
    48  //
    49  //    Some special tags are also modified. For example:
    50  //
    51  //    "state:.(t)"
    52  //
    53  //    would become:
    54  //
    55  //    "state:.(tA)"
    56  //
    57  // 4. The package is renamed to the value via the -p argument.
    58  // 5. Value of constants can be modified with -c argument.
    59  //
    60  // Note that not just the top-level declarations are renamed, all references to
    61  // them are also properly renamed as well, taking into account visibility rules
    62  // and shadowing. For example, if -suffix=A is passed in, the following:
    63  //
    64  // var b = 100
    65  //
    66  // func f() {
    67  //   g(b)
    68  //   b := 0
    69  //   g(b)
    70  // }
    71  //
    72  // Would be replaced with:
    73  //
    74  // var bA = 100
    75  //
    76  // func f() {
    77  //   g(bA)
    78  //   b := 0
    79  //   g(b)
    80  // }
    81  //
    82  // Note that the second call to g() kept "b" as an argument because it refers to
    83  // the local variable "b".
    84  //
    85  // Note that go_generics can handle anonymous fields with renamed types if
    86  // -anon is passed in, however it does not perform strict checking on parameter
    87  // types that share the same name as the global type and therefore will rename
    88  // them as well.
    89  //
    90  // You can see an example in the tools/go_generics/generics_tests/interface test.
    91  package main
    92  
    93  import (
    94  	"bytes"
    95  	"flag"
    96  	"fmt"
    97  	"go/ast"
    98  	"go/format"
    99  	"go/parser"
   100  	"go/token"
   101  	"io/ioutil"
   102  	"os"
   103  	"regexp"
   104  	"strings"
   105  
   106  	"github.com/SagerNet/gvisor/tools/go_generics/globals"
   107  )
   108  
   109  var (
   110  	input       = flag.String("i", "", "input `file`")
   111  	output      = flag.String("o", "", "output `file`")
   112  	suffix      = flag.String("suffix", "", "`suffix` to add to each global symbol")
   113  	prefix      = flag.String("prefix", "", "`prefix` to add to each global symbol")
   114  	packageName = flag.String("p", "main", "output package `name`")
   115  	printAST    = flag.Bool("ast", false, "prints the AST")
   116  	processAnon = flag.Bool("anon", false, "process anonymous fields")
   117  	types       = make(mapValue)
   118  	consts      = make(mapValue)
   119  	imports     = make(mapValue)
   120  )
   121  
   122  // mapValue implements flag.Value. We use a mapValue flag instead of a regular
   123  // string flag when we want to allow more than one instance of the flag. For
   124  // example, we allow several "-t A=B" arguments, and will rename them all.
   125  type mapValue map[string]string
   126  
   127  func (m mapValue) String() string {
   128  	var b bytes.Buffer
   129  	first := true
   130  	for k, v := range m {
   131  		if !first {
   132  			b.WriteRune(',')
   133  		} else {
   134  			first = false
   135  		}
   136  		b.WriteString(k)
   137  		b.WriteRune('=')
   138  		b.WriteString(v)
   139  	}
   140  	return b.String()
   141  }
   142  
   143  func (m mapValue) Set(s string) error {
   144  	sep := strings.Index(s, "=")
   145  	if sep == -1 {
   146  		return fmt.Errorf("missing '=' from '%s'", s)
   147  	}
   148  
   149  	m[s[:sep]] = s[sep+1:]
   150  
   151  	return nil
   152  }
   153  
   154  // stateTagRegexp matches against the 'typed' state tags.
   155  var stateTagRegexp = regexp.MustCompile(`^(.*[^a-z0-9_])state:"\.\(([^\)]*)\)"(.*)$`)
   156  
   157  var identifierRegexp = regexp.MustCompile(`^(.*[^a-zA-Z_])([a-zA-Z_][a-zA-Z0-9_]*)(.*)$`)
   158  
   159  func main() {
   160  	flag.Usage = func() {
   161  		fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
   162  		flag.PrintDefaults()
   163  	}
   164  
   165  	flag.Var(types, "t", "rename type A to B when `A=B` is passed in. Multiple such mappings are allowed.")
   166  	flag.Var(consts, "c", "reassign constant A to value B when `A=B` is passed in. Multiple such mappings are allowed.")
   167  	flag.Var(imports, "import", "specifies the import libraries to use when types are not local. `name=path` specifies that 'name', used in types as name.type, refers to the package living in 'path'.")
   168  	flag.Parse()
   169  
   170  	if *input == "" || *output == "" {
   171  		flag.Usage()
   172  		os.Exit(1)
   173  	}
   174  
   175  	// Parse the input file.
   176  	fset := token.NewFileSet()
   177  	f, err := parser.ParseFile(fset, *input, nil, parser.ParseComments|parser.DeclarationErrors|parser.SpuriousErrors)
   178  	if err != nil {
   179  		fmt.Fprintf(os.Stderr, "%v\n", err)
   180  		os.Exit(1)
   181  	}
   182  
   183  	// Print the AST if requested.
   184  	if *printAST {
   185  		ast.Print(fset, f)
   186  	}
   187  
   188  	cmap := ast.NewCommentMap(fset, f, f.Comments)
   189  
   190  	// Update imports based on what's used in types and consts.
   191  	maps := []mapValue{types, consts}
   192  	importDecl, err := updateImports(maps, imports)
   193  	if err != nil {
   194  		fmt.Fprintf(os.Stderr, "%v\n", err)
   195  		os.Exit(1)
   196  	}
   197  	types = maps[0]
   198  	consts = maps[1]
   199  
   200  	// Reassign all specified constants.
   201  	for _, decl := range f.Decls {
   202  		d, ok := decl.(*ast.GenDecl)
   203  		if !ok || d.Tok != token.CONST {
   204  			continue
   205  		}
   206  
   207  		for _, gs := range d.Specs {
   208  			s := gs.(*ast.ValueSpec)
   209  			for i, id := range s.Names {
   210  				if n, ok := consts[id.Name]; ok {
   211  					s.Values[i] = &ast.BasicLit{Value: n}
   212  				}
   213  			}
   214  		}
   215  	}
   216  
   217  	// Go through all globals and their uses in the AST and rename the types
   218  	// with explicitly provided names, and rename all types, variables,
   219  	// consts and functions with the provided prefix and suffix.
   220  	globals.Visit(fset, f, func(ident *ast.Ident, kind globals.SymKind) {
   221  		if n, ok := types[ident.Name]; ok && kind == globals.KindType {
   222  			ident.Name = n
   223  		} else {
   224  			switch kind {
   225  			case globals.KindType, globals.KindVar, globals.KindConst, globals.KindFunction:
   226  				if ident.Name != "_" && !(ident.Name == "init" && kind == globals.KindFunction) {
   227  					ident.Name = *prefix + ident.Name + *suffix
   228  				}
   229  			case globals.KindTag:
   230  				// Modify the state tag appropriately.
   231  				if m := stateTagRegexp.FindStringSubmatch(ident.Name); m != nil {
   232  					if t := identifierRegexp.FindStringSubmatch(m[2]); t != nil {
   233  						typeName := *prefix + t[2] + *suffix
   234  						if n, ok := types[t[2]]; ok {
   235  							typeName = n
   236  						}
   237  						ident.Name = m[1] + `state:".(` + t[1] + typeName + t[3] + `)"` + m[3]
   238  					}
   239  				}
   240  			}
   241  		}
   242  	}, *processAnon)
   243  
   244  	// Remove the definition of all types that are being remapped.
   245  	set := make(typeSet)
   246  	for _, v := range types {
   247  		set[v] = struct{}{}
   248  	}
   249  	removeTypes(set, f)
   250  
   251  	// Add the new imports, if any, to the top.
   252  	if importDecl != nil {
   253  		newDecls := make([]ast.Decl, 0, len(f.Decls)+1)
   254  		newDecls = append(newDecls, importDecl)
   255  		newDecls = append(newDecls, f.Decls...)
   256  		f.Decls = newDecls
   257  	}
   258  
   259  	// Update comments to remove the ones potentially associated with the
   260  	// type T that we removed.
   261  	f.Comments = cmap.Filter(f).Comments()
   262  
   263  	// If there are file (package) comments, delete them.
   264  	if f.Doc != nil {
   265  		for i, cg := range f.Comments {
   266  			if cg == f.Doc {
   267  				f.Comments = append(f.Comments[:i], f.Comments[i+1:]...)
   268  				break
   269  			}
   270  		}
   271  	}
   272  
   273  	// Write the output file.
   274  	f.Name.Name = *packageName
   275  
   276  	var buf bytes.Buffer
   277  	if err := format.Node(&buf, fset, f); err != nil {
   278  		fmt.Fprintf(os.Stderr, "%v\n", err)
   279  		os.Exit(1)
   280  	}
   281  
   282  	if err := ioutil.WriteFile(*output, buf.Bytes(), 0644); err != nil {
   283  		fmt.Fprintf(os.Stderr, "%v\n", err)
   284  		os.Exit(1)
   285  	}
   286  }