package main /* Large uncovered parts are: - methods - type assignability and identity - consts - interfaces, types implementing interfaces, type assertions - ... parameters */ import ( "bufio" "fmt" "math/rand" "os" "path/filepath" "strings" ) type Smith struct { curPackage int curBlock *Block curBlockPos int curFunc *Func packages [NPackages]*Package idSeq int typeDepth int stmtCount int exprDepth int exprCount int totalExprCount int predefinedTypes []*Type stringType *Type boolType *Type intType *Type byteType *Type efaceType *Type runeType *Type float32Type *Type float64Type *Type complex64Type *Type complex128Type *Type statements []func() expressions []func(res *Type) string rng *rand.Rand } const ( NPackages = 3 NFiles = 3 NStatements = 10 NExprDepth = 4 NExprCount = 10 NTotalExprCount = 50 /* NStatements = 30 NExprDepth = 6 NExprCount = 20 NTotalExprCount = 1000 */ ) type Package struct { name string imports map[string]bool top *Block undefFuncs []*Func undefVars []*Var toplevVars []*Var toplevFuncs []*Func } type Block struct { str string parent *Block subBlock *Block extendable bool isBreakable bool isContinuable bool funcBoundary bool sub []*Block consts []*Const types []*Type funcs []*Func vars []*Var } type Func struct { name string args []*Type rets []*Type } type Var struct { id string typ *Type block *Block used bool } type Const struct { } func (smith *Smith) writeProgram(dir string) { smith.initTypes() smith.initExpressions() smith.initStatements() smith.initProgram() for pi := range smith.packages { smith.genPackage(pi) } smith.serializeProgram(dir) } func (smith *Smith) initProgram() { smith.packages[0] = smith.newPackage("main") smith.packages[0].undefFuncs = []*Func{ {name: "init", args: []*Type{}, rets: []*Type{}}, {name: "init", args: []*Type{}, rets: []*Type{}}, {name: "main", args: []*Type{}, rets: []*Type{}}, } if !*singlepkg { smith.packages[1] = smith.newPackage("a") smith.packages[2] = smith.newPackage("b") } } func (smith *Smith) newPackage(name string) *Package { return &Package{name: name, imports: make(map[string]bool), top: &Block{extendable: true}} } func (smith *Smith) genPackage(pi int) { smith.typeDepth = 0 smith.stmtCount = 0 smith.exprDepth = 0 smith.exprCount = 0 smith.totalExprCount = 0 p := smith.packages[pi] if p == nil { return } for len(p.undefFuncs) != 0 || len(p.undefVars) != 0 { if len(p.undefFuncs) != 0 { f := p.undefFuncs[len(p.undefFuncs)-1] p.undefFuncs = p.undefFuncs[:len(p.undefFuncs)-1] smith.genToplevFunction(pi, f) } if len(p.undefVars) != 0 { v := p.undefVars[len(p.undefVars)-1] p.undefVars = p.undefVars[:len(p.undefVars)-1] smith.genToplevVar(pi, v) } } } func F(f string, args ...interface{}) string { return fmt.Sprintf(f, args...) } func (smith *Smith) line(f string, args ...interface{}) { s := F(f, args...) b := &Block{parent: smith.curBlock, str: s} if smith.curBlockPos+1 == len(smith.curBlock.sub) { smith.curBlock.sub = append(smith.curBlock.sub, b) } else { smith.curBlock.sub = append(smith.curBlock.sub, nil) copy(smith.curBlock.sub[smith.curBlockPos+2:], smith.curBlock.sub[smith.curBlockPos+1:]) smith.curBlock.sub[smith.curBlockPos+1] = b } smith.curBlockPos++ } func (smith *Smith) resetContext(pi int) { smith.curPackage = pi p := smith.packages[pi] smith.curBlock = p.top smith.curBlockPos = len(smith.curBlock.sub) - 1 smith.curFunc = nil } func (smith *Smith) genToplevFunction(pi int, f *Func) { smith.resetContext(pi) smith.curFunc = f smith.enterBlock(true) smith.enterBlock(true) argIds := make([]string, len(f.args)) argStr := "" for i, a := range f.args { argIds[i] = smith.newId("Param") if i != 0 { argStr += ", " } argStr += argIds[i] + " " + a.id } smith.line("func %v(%v)%v {", f.name, argStr, fmtTypeList(f.rets, false)) for i, a := range f.args { smith.defineVar(argIds[i], a) } smith.curBlock.funcBoundary = true smith.genBlock() smith.leaveBlock() smith.stmtReturn() smith.line("}") smith.leaveBlock() if f.name != "init" { smith.packages[smith.curPackage].toplevFuncs = append(smith.packages[smith.curPackage].toplevFuncs, f) } } func (smith *Smith) genToplevVar(pi int, v *Var) { smith.resetContext(pi) smith.enterBlock(true) smith.line("var %v = %v", v.id, smith.rvalue(v.typ)) smith.leaveBlock() smith.packages[smith.curPackage].toplevVars = append(smith.packages[smith.curPackage].toplevVars, v) } func (smith *Smith) genBlock() { smith.enterBlock(false) for smith.rnd(10) != 0 { smith.genStatement() } smith.leaveBlock() } func (smith *Smith) serializeProgram(dir string) { for _, p := range smith.packages { if p == nil { continue } path := filepath.Join(dir, "src", p.name) os.MkdirAll(path, os.ModePerm) nf := NFiles if *singlefile { nf = 1 } files := make([]*bufio.Writer, nf) for i := range files { fname := filepath.Join(path, fmt.Sprintf("%v.go", i)) f, err := os.Create(fname) if err != nil { fmt.Fprintf(os.Stdout, "failed to create a file: %v\n", err) os.Exit(1) } w := bufio.NewWriter(bufio.NewWriter(f)) files[i] = w defer func() { w.Flush() f.Close() }() fmt.Fprintf(w, "package %s\n", p.name) for imp := range p.imports { fmt.Fprintf(w, "import \"%s\"\n", imp) } if i == 0 && p.name == "main" { fmt.Fprintf(w, "import \"runtime\"\n") fmt.Fprintf(w, "func init() {\n") fmt.Fprintf(w, " go func() {\n") fmt.Fprintf(w, " for {\n") fmt.Fprintf(w, " runtime.GC()\n") fmt.Fprintf(w, " runtime.Gosched()\n") fmt.Fprintf(w, " }\n") fmt.Fprintf(w, " }()\n") fmt.Fprintf(w, "}\n") } for imp := range p.imports { fmt.Fprintf(w, "var _ = %s.UsePackage\n", imp) } if i == 0 { fmt.Fprintf(w, "var UsePackage = 0\n") fmt.Fprintf(w, "var SINK interface{}\n") } } for _, decl := range p.top.sub { serializeBlock(files[smith.rnd(len(files))], decl, 0) } } path := filepath.Join(dir, "src", "a") os.MkdirAll(path, os.ModePerm) fname := filepath.Join(path, "0_test.go") f, err := os.Create(fname) if err != nil { fmt.Fprintf(os.Stdout, "failed to create a file: %v\n", err) os.Exit(1) } f.Write([]byte("package a\n")) f.Close() } func serializeBlock(w *bufio.Writer, b *Block, d int) { if true { if b.str != "" { w.WriteString(b.str) w.WriteString("\n") } } else { w.WriteString("/*" + strings.Repeat("*", d) + "*/ ") w.WriteString(b.str) w.WriteString(F(" // ext=%v vars=%v types=%v", b.extendable, len(b.vars), len(b.types))) w.WriteString("\n") } for _, b1 := range b.sub { serializeBlock(w, b1, d+1) } } func (smith *Smith) vars() []*Var { var vars []*Var vars = append(vars, smith.packages[smith.curPackage].toplevVars...) var f func(b *Block, pos int) f = func(b *Block, pos int) { for _, b1 := range b.sub[:pos+1] { vars = append(vars, b1.vars...) } if b.parent != nil { pos := len(b.parent.sub) - 1 if b.subBlock != nil { pos = -2 for i, b1 := range b.parent.sub { if b1 == b.subBlock { pos = i break } } if pos == -2 { panic("bad") } } f(b.parent, pos) } } f(smith.curBlock, smith.curBlockPos) return vars } func (smith *Smith) types() []*Type { var types []*Type types = append(types, smith.predefinedTypes...) var f func(b *Block, pos int) f = func(b *Block, pos int) { for _, b1 := range b.sub[:pos+1] { types = append(types, b1.types...) } if b.parent != nil { pos := len(b.parent.sub) - 1 if b.subBlock != nil { pos = -2 for i, b1 := range b.parent.sub { if b1 == b.subBlock { pos = i break } } if pos == -2 { panic("bad") } } f(b.parent, pos) } } f(smith.curBlock, smith.curBlockPos) return types } func (smith *Smith) defineVar(id string, t *Type) { v := &Var{id: id, typ: t, block: smith.curBlock} b := smith.curBlock.sub[smith.curBlockPos] b.vars = append(b.vars, v) } func (smith *Smith) defineType(t *Type) { b := smith.curBlock.sub[smith.curBlockPos] b.types = append(b.types, t) } func (smith *Smith) materializeVar(t *Type) string { // TODO: generate var in another package id := smith.newId("Var") curBlock0 := smith.curBlock curBlockPos0 := smith.curBlockPos curBlockLen0 := len(smith.curBlock.sub) exprDepth0 := smith.exprDepth exprCount0 := smith.exprCount smith.exprDepth = 0 smith.exprCount = 0 defer func() { if smith.curBlock == curBlock0 { curBlockPos0 += len(smith.curBlock.sub) - curBlockLen0 } smith.curBlock = curBlock0 smith.curBlockPos = curBlockPos0 smith.exprDepth = exprDepth0 smith.exprCount = exprCount0 }() loop: for { if smith.curBlock.parent == nil { break } if !smith.curBlock.extendable || smith.curBlockPos < 0 { if smith.curBlock.subBlock == nil { smith.curBlockPos = len(smith.curBlock.parent.sub) - 2 } else { smith.curBlockPos = -2 for i, b1 := range smith.curBlock.parent.sub { if b1 == smith.curBlock.subBlock { smith.curBlockPos = i break } } if smith.curBlockPos == -2 { panic("bad") } } smith.curBlock = smith.curBlock.parent continue } if smith.rnd(3) == 0 { break } if smith.curBlockPos >= 0 { b := smith.curBlock.sub[smith.curBlockPos] for _, t1 := range b.types { if dependsOn(t, t1) { break loop } } } smith.curBlockPos-- } if smith.curBlock.parent == nil { for i := smith.curPackage; i < NPackages; i++ { if smith.rndBool() || i == NPackages-1 || *singlepkg { if i == smith.curPackage { // emit global var into the current package smith.enterBlock(true) smith.line("var %v = %v", id, smith.rvalue(t)) smith.packages[smith.curPackage].toplevVars = append(smith.packages[smith.curPackage].toplevVars, &Var{id: id, typ: t}) smith.leaveBlock() } else { // emit global var into another package smith.packages[i].undefVars = append(smith.packages[i].undefVars, &Var{id: id, typ: t}) smith.packages[smith.curPackage].imports[smith.packages[i].name] = true id = smith.packages[i].name + "." + id } break } } } else { // emit local var smith.line("%v := %v", id, smith.rvalue(t)) smith.defineVar(id, t) } return id } func (smith *Smith) materializeFunc(rets []*Type) *Func { f := &Func{name: smith.newId("Func"), args: smith.atypeList(TraitGlobal), rets: rets} curBlock0 := smith.curBlock curBlockPos0 := smith.curBlockPos curFunc0 := smith.curFunc exprDepth0 := smith.exprDepth exprCount0 := smith.exprCount smith.exprDepth = 0 smith.exprCount = 0 defer func() { smith.curBlock = curBlock0 smith.curBlockPos = curBlockPos0 smith.curFunc = curFunc0 smith.exprDepth = exprDepth0 smith.exprCount = exprCount0 }() if smith.rndBool() && !*singlepkg && smith.curPackage != NPackages-1 { for _, r1 := range rets { if dependsOn(r1, nil) { goto thisPackage } } for _, t := range f.args { if dependsOn(t, nil) { goto thisPackage } } // emit global var into another package newF := new(Func) *newF = *f smith.packages[smith.curPackage+1].undefFuncs = append(smith.packages[smith.curPackage+1].undefFuncs, newF) smith.packages[smith.curPackage].imports[smith.packages[smith.curPackage+1].name] = true f.name = smith.packages[smith.curPackage+1].name + "." + f.name return f } thisPackage: smith.genToplevFunction(smith.curPackage, f) return f } func (smith *Smith) materializeGotoLabel() string { // TODO: move lavel up id := smith.newId("Label") curBlock0 := smith.curBlock curBlockPos0 := smith.curBlockPos curBlockLen0 := len(smith.curBlock.sub) defer func() { if smith.curBlock == curBlock0 { curBlockPos0 += len(smith.curBlock.sub) - curBlockLen0 } smith.curBlock = curBlock0 smith.curBlockPos = curBlockPos0 }() for { if smith.curBlock.parent.funcBoundary && smith.curBlockPos <= 0 { break } if !smith.curBlock.extendable || smith.curBlockPos < 0 { if smith.curBlock.subBlock != nil { // we should have been stopped at func boundary panic("bad") } smith.curBlock = smith.curBlock.parent smith.curBlockPos = len(smith.curBlock.sub) - 2 continue } if smith.rnd(3) == 0 { break } smith.curBlockPos-- } smith.line("%v:", id) return id } func (smith *Smith) rnd(n int) int { return smith.rng.Intn(n) } func (smith *Smith) rndBool() bool { return smith.rnd(2) == 0 } func (smith *Smith) choice(ch ...string) string { return ch[smith.rnd(len(ch))] } func (smith *Smith) newId(prefix string) string { if prefix[0] < 'A' || prefix[0] > 'Z' { panic("unexported id") } smith.idSeq++ return fmt.Sprintf("%v%v", prefix, smith.idSeq) } func (smith *Smith) enterBlock(nonextendable bool) { b := &Block{parent: smith.curBlock, extendable: !nonextendable} b.isBreakable = smith.curBlock.isBreakable b.isContinuable = smith.curBlock.isContinuable smith.curBlock.sub = append(smith.curBlock.sub, b) smith.curBlock = b smith.curBlockPos = -1 } func (smith *Smith) leaveBlock() { for _, b := range smith.curBlock.sub { for _, v := range b.vars { if !v.used { smith.line("_ = %v", v.id) } } } smith.curBlock = smith.curBlock.parent smith.curBlockPos = len(smith.curBlock.sub) - 1 }