github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/internal/gosmith/context.go (about)

     1  package main
     2  
     3  /*
     4  Large uncovered parts are:
     5  - methods
     6  - type assignability and identity
     7  - consts
     8  - interfaces, types implementing interfaces, type assertions
     9  - ... parameters
    10  */
    11  
    12  import (
    13  	"bufio"
    14  	"fmt"
    15  	"math/rand"
    16  	"os"
    17  	"path/filepath"
    18  	"strings"
    19  )
    20  
    21  type Smith struct {
    22  	curPackage  int
    23  	curBlock    *Block
    24  	curBlockPos int
    25  	curFunc     *Func
    26  
    27  	packages [NPackages]*Package
    28  
    29  	idSeq          int
    30  	typeDepth      int
    31  	stmtCount      int
    32  	exprDepth      int
    33  	exprCount      int
    34  	totalExprCount int
    35  
    36  	predefinedTypes []*Type
    37  	stringType      *Type
    38  	boolType        *Type
    39  	intType         *Type
    40  	byteType        *Type
    41  	efaceType       *Type
    42  	runeType        *Type
    43  	float32Type     *Type
    44  	float64Type     *Type
    45  	complex64Type   *Type
    46  	complex128Type  *Type
    47  
    48  	statements  []func()
    49  	expressions []func(res *Type) string
    50  
    51  	rng *rand.Rand
    52  }
    53  
    54  const (
    55  	NPackages = 3
    56  	NFiles    = 3
    57  
    58  	NStatements     = 10
    59  	NExprDepth      = 4
    60  	NExprCount      = 10
    61  	NTotalExprCount = 50
    62  
    63  /*
    64  NStatements     = 30
    65  NExprDepth      = 6
    66  NExprCount      = 20
    67  NTotalExprCount = 1000
    68  */
    69  )
    70  
    71  type Package struct {
    72  	name    string
    73  	imports map[string]bool
    74  	top     *Block
    75  
    76  	undefFuncs []*Func
    77  	undefVars  []*Var
    78  
    79  	toplevVars  []*Var
    80  	toplevFuncs []*Func
    81  }
    82  
    83  type Block struct {
    84  	str           string
    85  	parent        *Block
    86  	subBlock      *Block
    87  	extendable    bool
    88  	isBreakable   bool
    89  	isContinuable bool
    90  	funcBoundary  bool
    91  	sub           []*Block
    92  	consts        []*Const
    93  	types         []*Type
    94  	funcs         []*Func
    95  	vars          []*Var
    96  }
    97  
    98  type Func struct {
    99  	name string
   100  	args []*Type
   101  	rets []*Type
   102  }
   103  
   104  type Var struct {
   105  	id    string
   106  	typ   *Type
   107  	block *Block
   108  	used  bool
   109  }
   110  
   111  type Const struct {
   112  }
   113  
   114  func (smith *Smith) writeProgram(dir string) {
   115  	smith.initTypes()
   116  	smith.initExpressions()
   117  	smith.initStatements()
   118  	smith.initProgram()
   119  	for pi := range smith.packages {
   120  		smith.genPackage(pi)
   121  	}
   122  	smith.serializeProgram(dir)
   123  }
   124  
   125  func (smith *Smith) initProgram() {
   126  	smith.packages[0] = smith.newPackage("main")
   127  	smith.packages[0].undefFuncs = []*Func{
   128  		{name: "init", args: []*Type{}, rets: []*Type{}},
   129  		{name: "init", args: []*Type{}, rets: []*Type{}},
   130  		{name: "main", args: []*Type{}, rets: []*Type{}},
   131  	}
   132  	if !*singlepkg {
   133  		smith.packages[1] = smith.newPackage("a")
   134  		smith.packages[2] = smith.newPackage("b")
   135  	}
   136  }
   137  
   138  func (smith *Smith) newPackage(name string) *Package {
   139  	return &Package{name: name, imports: make(map[string]bool), top: &Block{extendable: true}}
   140  }
   141  
   142  func (smith *Smith) genPackage(pi int) {
   143  	smith.typeDepth = 0
   144  	smith.stmtCount = 0
   145  	smith.exprDepth = 0
   146  	smith.exprCount = 0
   147  	smith.totalExprCount = 0
   148  
   149  	p := smith.packages[pi]
   150  	if p == nil {
   151  		return
   152  	}
   153  	for len(p.undefFuncs) != 0 || len(p.undefVars) != 0 {
   154  		if len(p.undefFuncs) != 0 {
   155  			f := p.undefFuncs[len(p.undefFuncs)-1]
   156  			p.undefFuncs = p.undefFuncs[:len(p.undefFuncs)-1]
   157  			smith.genToplevFunction(pi, f)
   158  		}
   159  		if len(p.undefVars) != 0 {
   160  			v := p.undefVars[len(p.undefVars)-1]
   161  			p.undefVars = p.undefVars[:len(p.undefVars)-1]
   162  			smith.genToplevVar(pi, v)
   163  		}
   164  	}
   165  }
   166  
   167  func F(f string, args ...interface{}) string {
   168  	return fmt.Sprintf(f, args...)
   169  }
   170  
   171  func (smith *Smith) line(f string, args ...interface{}) {
   172  	s := F(f, args...)
   173  	b := &Block{parent: smith.curBlock, str: s}
   174  	if smith.curBlockPos+1 == len(smith.curBlock.sub) {
   175  		smith.curBlock.sub = append(smith.curBlock.sub, b)
   176  	} else {
   177  		smith.curBlock.sub = append(smith.curBlock.sub, nil)
   178  		copy(smith.curBlock.sub[smith.curBlockPos+2:], smith.curBlock.sub[smith.curBlockPos+1:])
   179  		smith.curBlock.sub[smith.curBlockPos+1] = b
   180  	}
   181  	smith.curBlockPos++
   182  }
   183  
   184  func (smith *Smith) resetContext(pi int) {
   185  	smith.curPackage = pi
   186  	p := smith.packages[pi]
   187  	smith.curBlock = p.top
   188  	smith.curBlockPos = len(smith.curBlock.sub) - 1
   189  	smith.curFunc = nil
   190  }
   191  
   192  func (smith *Smith) genToplevFunction(pi int, f *Func) {
   193  	smith.resetContext(pi)
   194  	smith.curFunc = f
   195  	smith.enterBlock(true)
   196  	smith.enterBlock(true)
   197  	argIds := make([]string, len(f.args))
   198  	argStr := ""
   199  	for i, a := range f.args {
   200  		argIds[i] = smith.newId("Param")
   201  		if i != 0 {
   202  			argStr += ", "
   203  		}
   204  		argStr += argIds[i] + " " + a.id
   205  	}
   206  	smith.line("func %v(%v)%v {", f.name, argStr, fmtTypeList(f.rets, false))
   207  	for i, a := range f.args {
   208  		smith.defineVar(argIds[i], a)
   209  	}
   210  	smith.curBlock.funcBoundary = true
   211  	smith.genBlock()
   212  	smith.leaveBlock()
   213  	smith.stmtReturn()
   214  	smith.line("}")
   215  	smith.leaveBlock()
   216  	if f.name != "init" {
   217  		smith.packages[smith.curPackage].toplevFuncs = append(smith.packages[smith.curPackage].toplevFuncs, f)
   218  	}
   219  }
   220  
   221  func (smith *Smith) genToplevVar(pi int, v *Var) {
   222  	smith.resetContext(pi)
   223  	smith.enterBlock(true)
   224  	smith.line("var %v = %v", v.id, smith.rvalue(v.typ))
   225  	smith.leaveBlock()
   226  	smith.packages[smith.curPackage].toplevVars = append(smith.packages[smith.curPackage].toplevVars, v)
   227  }
   228  
   229  func (smith *Smith) genBlock() {
   230  	smith.enterBlock(false)
   231  	for smith.rnd(10) != 0 {
   232  		smith.genStatement()
   233  	}
   234  	smith.leaveBlock()
   235  }
   236  
   237  func (smith *Smith) serializeProgram(dir string) {
   238  	for _, p := range smith.packages {
   239  		if p == nil {
   240  			continue
   241  		}
   242  		path := filepath.Join(dir, "src", p.name)
   243  		os.MkdirAll(path, os.ModePerm)
   244  		nf := NFiles
   245  		if *singlefile {
   246  			nf = 1
   247  		}
   248  		files := make([]*bufio.Writer, nf)
   249  		for i := range files {
   250  			fname := filepath.Join(path, fmt.Sprintf("%v.go", i))
   251  			f, err := os.Create(fname)
   252  			if err != nil {
   253  				fmt.Fprintf(os.Stdout, "failed to create a file: %v\n", err)
   254  				os.Exit(1)
   255  			}
   256  			w := bufio.NewWriter(bufio.NewWriter(f))
   257  			files[i] = w
   258  			defer func() {
   259  				w.Flush()
   260  				f.Close()
   261  			}()
   262  			fmt.Fprintf(w, "package %s\n", p.name)
   263  			for imp := range p.imports {
   264  				fmt.Fprintf(w, "import \"%s\"\n", imp)
   265  			}
   266  			if i == 0 && p.name == "main" {
   267  				fmt.Fprintf(w, "import \"runtime\"\n")
   268  				fmt.Fprintf(w, "func init() {\n")
   269  				fmt.Fprintf(w, "	go func() {\n")
   270  				fmt.Fprintf(w, "		for {\n")
   271  				fmt.Fprintf(w, "			runtime.GC()\n")
   272  				fmt.Fprintf(w, "			runtime.Gosched()\n")
   273  				fmt.Fprintf(w, "		}\n")
   274  				fmt.Fprintf(w, "	}()\n")
   275  				fmt.Fprintf(w, "}\n")
   276  			}
   277  			for imp := range p.imports {
   278  				fmt.Fprintf(w, "var _ = %s.UsePackage\n", imp)
   279  			}
   280  			if i == 0 {
   281  				fmt.Fprintf(w, "var UsePackage = 0\n")
   282  				fmt.Fprintf(w, "var SINK interface{}\n")
   283  			}
   284  		}
   285  		for _, decl := range p.top.sub {
   286  			serializeBlock(files[smith.rnd(len(files))], decl, 0)
   287  		}
   288  	}
   289  
   290  	path := filepath.Join(dir, "src", "a")
   291  	os.MkdirAll(path, os.ModePerm)
   292  	fname := filepath.Join(path, "0_test.go")
   293  	f, err := os.Create(fname)
   294  	if err != nil {
   295  		fmt.Fprintf(os.Stdout, "failed to create a file: %v\n", err)
   296  		os.Exit(1)
   297  	}
   298  	f.Write([]byte("package a\n"))
   299  	f.Close()
   300  }
   301  
   302  func serializeBlock(w *bufio.Writer, b *Block, d int) {
   303  	if true {
   304  		if b.str != "" {
   305  			w.WriteString(b.str)
   306  			w.WriteString("\n")
   307  		}
   308  	} else {
   309  		w.WriteString("/*" + strings.Repeat("*", d) + "*/ ")
   310  		w.WriteString(b.str)
   311  		w.WriteString(F(" // ext=%v vars=%v types=%v", b.extendable, len(b.vars), len(b.types)))
   312  		w.WriteString("\n")
   313  	}
   314  	for _, b1 := range b.sub {
   315  		serializeBlock(w, b1, d+1)
   316  	}
   317  }
   318  
   319  func (smith *Smith) vars() []*Var {
   320  	var vars []*Var
   321  	vars = append(vars, smith.packages[smith.curPackage].toplevVars...)
   322  	var f func(b *Block, pos int)
   323  	f = func(b *Block, pos int) {
   324  		for _, b1 := range b.sub[:pos+1] {
   325  			vars = append(vars, b1.vars...)
   326  		}
   327  		if b.parent != nil {
   328  			pos := len(b.parent.sub) - 1
   329  			if b.subBlock != nil {
   330  				pos = -2
   331  				for i, b1 := range b.parent.sub {
   332  					if b1 == b.subBlock {
   333  						pos = i
   334  						break
   335  					}
   336  				}
   337  				if pos == -2 {
   338  					panic("bad")
   339  				}
   340  			}
   341  			f(b.parent, pos)
   342  		}
   343  	}
   344  	f(smith.curBlock, smith.curBlockPos)
   345  	return vars
   346  }
   347  
   348  func (smith *Smith) types() []*Type {
   349  	var types []*Type
   350  	types = append(types, smith.predefinedTypes...)
   351  	var f func(b *Block, pos int)
   352  	f = func(b *Block, pos int) {
   353  		for _, b1 := range b.sub[:pos+1] {
   354  			types = append(types, b1.types...)
   355  		}
   356  		if b.parent != nil {
   357  			pos := len(b.parent.sub) - 1
   358  			if b.subBlock != nil {
   359  				pos = -2
   360  				for i, b1 := range b.parent.sub {
   361  					if b1 == b.subBlock {
   362  						pos = i
   363  						break
   364  					}
   365  				}
   366  				if pos == -2 {
   367  					panic("bad")
   368  				}
   369  			}
   370  			f(b.parent, pos)
   371  		}
   372  	}
   373  	f(smith.curBlock, smith.curBlockPos)
   374  	return types
   375  }
   376  
   377  func (smith *Smith) defineVar(id string, t *Type) {
   378  	v := &Var{id: id, typ: t, block: smith.curBlock}
   379  	b := smith.curBlock.sub[smith.curBlockPos]
   380  	b.vars = append(b.vars, v)
   381  }
   382  
   383  func (smith *Smith) defineType(t *Type) {
   384  	b := smith.curBlock.sub[smith.curBlockPos]
   385  	b.types = append(b.types, t)
   386  }
   387  
   388  func (smith *Smith) materializeVar(t *Type) string {
   389  	// TODO: generate var in another package
   390  	id := smith.newId("Var")
   391  	curBlock0 := smith.curBlock
   392  	curBlockPos0 := smith.curBlockPos
   393  	curBlockLen0 := len(smith.curBlock.sub)
   394  	exprDepth0 := smith.exprDepth
   395  	exprCount0 := smith.exprCount
   396  	smith.exprDepth = 0
   397  	smith.exprCount = 0
   398  	defer func() {
   399  		if smith.curBlock == curBlock0 {
   400  			curBlockPos0 += len(smith.curBlock.sub) - curBlockLen0
   401  		}
   402  		smith.curBlock = curBlock0
   403  		smith.curBlockPos = curBlockPos0
   404  		smith.exprDepth = exprDepth0
   405  		smith.exprCount = exprCount0
   406  	}()
   407  loop:
   408  	for {
   409  		if smith.curBlock.parent == nil {
   410  			break
   411  		}
   412  		if !smith.curBlock.extendable || smith.curBlockPos < 0 {
   413  			if smith.curBlock.subBlock == nil {
   414  				smith.curBlockPos = len(smith.curBlock.parent.sub) - 2
   415  			} else {
   416  				smith.curBlockPos = -2
   417  				for i, b1 := range smith.curBlock.parent.sub {
   418  					if b1 == smith.curBlock.subBlock {
   419  						smith.curBlockPos = i
   420  						break
   421  					}
   422  				}
   423  				if smith.curBlockPos == -2 {
   424  					panic("bad")
   425  				}
   426  			}
   427  			smith.curBlock = smith.curBlock.parent
   428  			continue
   429  		}
   430  		if smith.rnd(3) == 0 {
   431  			break
   432  		}
   433  		if smith.curBlockPos >= 0 {
   434  			b := smith.curBlock.sub[smith.curBlockPos]
   435  			for _, t1 := range b.types {
   436  				if dependsOn(t, t1) {
   437  					break loop
   438  				}
   439  			}
   440  		}
   441  		smith.curBlockPos--
   442  	}
   443  	if smith.curBlock.parent == nil {
   444  		for i := smith.curPackage; i < NPackages; i++ {
   445  			if smith.rndBool() || i == NPackages-1 || *singlepkg {
   446  				if i == smith.curPackage {
   447  					// emit global var into the current package
   448  					smith.enterBlock(true)
   449  					smith.line("var %v = %v", id, smith.rvalue(t))
   450  					smith.packages[smith.curPackage].toplevVars = append(smith.packages[smith.curPackage].toplevVars, &Var{id: id, typ: t})
   451  					smith.leaveBlock()
   452  				} else {
   453  					// emit global var into another package
   454  					smith.packages[i].undefVars = append(smith.packages[i].undefVars, &Var{id: id, typ: t})
   455  					smith.packages[smith.curPackage].imports[smith.packages[i].name] = true
   456  					id = smith.packages[i].name + "." + id
   457  				}
   458  				break
   459  			}
   460  		}
   461  	} else {
   462  		// emit local var
   463  		smith.line("%v := %v", id, smith.rvalue(t))
   464  		smith.defineVar(id, t)
   465  	}
   466  	return id
   467  }
   468  
   469  func (smith *Smith) materializeFunc(rets []*Type) *Func {
   470  	f := &Func{name: smith.newId("Func"), args: smith.atypeList(TraitGlobal), rets: rets}
   471  
   472  	curBlock0 := smith.curBlock
   473  	curBlockPos0 := smith.curBlockPos
   474  	curFunc0 := smith.curFunc
   475  	exprDepth0 := smith.exprDepth
   476  	exprCount0 := smith.exprCount
   477  	smith.exprDepth = 0
   478  	smith.exprCount = 0
   479  	defer func() {
   480  		smith.curBlock = curBlock0
   481  		smith.curBlockPos = curBlockPos0
   482  		smith.curFunc = curFunc0
   483  		smith.exprDepth = exprDepth0
   484  		smith.exprCount = exprCount0
   485  	}()
   486  
   487  	if smith.rndBool() && !*singlepkg && smith.curPackage != NPackages-1 {
   488  		for _, r1 := range rets {
   489  			if dependsOn(r1, nil) {
   490  				goto thisPackage
   491  			}
   492  		}
   493  		for _, t := range f.args {
   494  			if dependsOn(t, nil) {
   495  				goto thisPackage
   496  			}
   497  		}
   498  		// emit global var into another package
   499  		newF := new(Func)
   500  		*newF = *f
   501  		smith.packages[smith.curPackage+1].undefFuncs = append(smith.packages[smith.curPackage+1].undefFuncs, newF)
   502  		smith.packages[smith.curPackage].imports[smith.packages[smith.curPackage+1].name] = true
   503  		f.name = smith.packages[smith.curPackage+1].name + "." + f.name
   504  		return f
   505  	}
   506  thisPackage:
   507  	smith.genToplevFunction(smith.curPackage, f)
   508  	return f
   509  }
   510  
   511  func (smith *Smith) materializeGotoLabel() string {
   512  	// TODO: move label up
   513  	id := smith.newId("Label")
   514  
   515  	curBlock0 := smith.curBlock
   516  	curBlockPos0 := smith.curBlockPos
   517  	curBlockLen0 := len(smith.curBlock.sub)
   518  	defer func() {
   519  		if smith.curBlock == curBlock0 {
   520  			curBlockPos0 += len(smith.curBlock.sub) - curBlockLen0
   521  		}
   522  		smith.curBlock = curBlock0
   523  		smith.curBlockPos = curBlockPos0
   524  	}()
   525  
   526  	for {
   527  		if smith.curBlock.parent.funcBoundary && smith.curBlockPos <= 0 {
   528  			break
   529  		}
   530  		if !smith.curBlock.extendable || smith.curBlockPos < 0 {
   531  			if smith.curBlock.subBlock != nil {
   532  				// we should have been stopped at func boundary
   533  				panic("bad")
   534  			}
   535  			smith.curBlock = smith.curBlock.parent
   536  			smith.curBlockPos = len(smith.curBlock.sub) - 2
   537  			continue
   538  		}
   539  		if smith.rnd(3) == 0 {
   540  			break
   541  		}
   542  		smith.curBlockPos--
   543  	}
   544  
   545  	smith.line("%v:", id)
   546  	return id
   547  }
   548  
   549  func (smith *Smith) rnd(n int) int {
   550  	return smith.rng.Intn(n)
   551  }
   552  
   553  func (smith *Smith) rndBool() bool {
   554  	return smith.rnd(2) == 0
   555  }
   556  
   557  func (smith *Smith) choice(ch ...string) string {
   558  	return ch[smith.rnd(len(ch))]
   559  }
   560  
   561  func (smith *Smith) newId(prefix string) string {
   562  	if prefix[0] < 'A' || prefix[0] > 'Z' {
   563  		panic("unexported id")
   564  	}
   565  	smith.idSeq++
   566  	return fmt.Sprintf("%v%v", prefix, smith.idSeq)
   567  }
   568  
   569  func (smith *Smith) enterBlock(nonextendable bool) {
   570  	b := &Block{parent: smith.curBlock, extendable: !nonextendable}
   571  	b.isBreakable = smith.curBlock.isBreakable
   572  	b.isContinuable = smith.curBlock.isContinuable
   573  	smith.curBlock.sub = append(smith.curBlock.sub, b)
   574  	smith.curBlock = b
   575  	smith.curBlockPos = -1
   576  }
   577  
   578  func (smith *Smith) leaveBlock() {
   579  	for _, b := range smith.curBlock.sub {
   580  		for _, v := range b.vars {
   581  			if !v.used {
   582  				smith.line("_ = %v", v.id)
   583  			}
   584  		}
   585  	}
   586  
   587  	smith.curBlock = smith.curBlock.parent
   588  	smith.curBlockPos = len(smith.curBlock.sub) - 1
   589  }