mirror of
https://github.com/golang/go
synced 2024-11-18 21:05:02 -07:00
go.tools/go/ssa: use bytes.Buffer instead of io.Writer.
This is (a) more efficient and (b) avoids the need for constant error handling, since buffer writes can't fail. Also: - added WriteFunction and WritePackage functions, similar to types.WriteFoo. - *Function and *Package now implement io.WriterTo. LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/57930044
This commit is contained in:
parent
83cdd9ba10
commit
744d7e68b1
@ -1141,9 +1141,9 @@ func (a *analysis) genFunc(cgn *cgnode) {
|
||||
fn2 := *cgn.fn // copy
|
||||
fn2.Locals = nil
|
||||
fn2.Blocks = nil
|
||||
fn2.DumpTo(a.log)
|
||||
fn2.WriteTo(a.log)
|
||||
} else {
|
||||
cgn.fn.DumpTo(a.log)
|
||||
cgn.fn.WriteTo(a.log)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ func optimizeBlocks(f *Function) {
|
||||
changed = false
|
||||
|
||||
if debugBlockOpt {
|
||||
f.DumpTo(os.Stderr)
|
||||
f.WriteTo(os.Stderr)
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,7 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
|
||||
}
|
||||
|
||||
if prog.mode&LogPackages != 0 {
|
||||
p.DumpTo(os.Stderr)
|
||||
p.WriteTo(os.Stderr)
|
||||
}
|
||||
|
||||
if info.Importable {
|
||||
|
@ -18,8 +18,8 @@ package ssa
|
||||
// to avoid the need for buckets of size > 1.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"sort"
|
||||
@ -310,32 +310,32 @@ func sanityCheckDomTree(f *Function) {
|
||||
// Printing functions ----------------------------------------
|
||||
|
||||
// printDomTree prints the dominator tree as text, using indentation.
|
||||
func printDomTreeText(w io.Writer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(w, "%*s%s\n", 4*indent, "", v)
|
||||
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
|
||||
for _, child := range v.dom.children {
|
||||
printDomTreeText(w, child, indent+1)
|
||||
printDomTreeText(buf, child, indent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
|
||||
// (.dot) format.
|
||||
func printDomTreeDot(w io.Writer, f *Function) {
|
||||
fmt.Fprintln(w, "//", f)
|
||||
fmt.Fprintln(w, "digraph domtree {")
|
||||
func printDomTreeDot(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintln(buf, "//", f)
|
||||
fmt.Fprintln(buf, "digraph domtree {")
|
||||
for i, b := range f.Blocks {
|
||||
v := b.dom
|
||||
fmt.Fprintf(w, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
|
||||
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
|
||||
// TODO(adonovan): improve appearance of edges
|
||||
// belonging to both dominator tree and CFG.
|
||||
|
||||
// Dominator tree edge.
|
||||
if i != 0 {
|
||||
fmt.Fprintf(w, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
|
||||
}
|
||||
// CFG edges.
|
||||
for _, pred := range b.Preds {
|
||||
fmt.Fprintf(w, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, "}")
|
||||
fmt.Fprintln(buf, "}")
|
||||
}
|
||||
|
@ -64,14 +64,14 @@ func main() {
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
|
||||
// Print out the package.
|
||||
mainPkg.DumpTo(os.Stdout)
|
||||
mainPkg.WriteTo(os.Stdout)
|
||||
|
||||
// Build SSA code for bodies of functions in mainPkg.
|
||||
mainPkg.Build()
|
||||
|
||||
// Print out the package-level functions.
|
||||
mainPkg.Func("init").DumpTo(os.Stdout)
|
||||
mainPkg.Func("main").DumpTo(os.Stdout)
|
||||
mainPkg.Func("init").WriteTo(os.Stdout)
|
||||
mainPkg.Func("main").WriteTo(os.Stdout)
|
||||
|
||||
// Output:
|
||||
//
|
||||
|
@ -330,7 +330,7 @@ func (f *Function) finishBody() {
|
||||
if f.Prog.mode&NaiveForm == 0 {
|
||||
// For debugging pre-state of lifting pass:
|
||||
// numberRegisters(f)
|
||||
// f.DumpTo(os.Stderr)
|
||||
// f.WriteTo(os.Stderr)
|
||||
lift(f)
|
||||
}
|
||||
|
||||
@ -339,7 +339,7 @@ func (f *Function) finishBody() {
|
||||
numberRegisters(f)
|
||||
|
||||
if f.Prog.mode&LogFunctions != 0 {
|
||||
f.DumpTo(os.Stderr)
|
||||
f.WriteTo(os.Stderr)
|
||||
}
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
@ -498,22 +498,20 @@ func (f *Function) RelString(from *types.Package) string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// writeSignature writes to w the signature sig in declaration syntax.
|
||||
func writeSignature(w io.Writer, pkg *types.Package, name string, sig *types.Signature, params []*Parameter) {
|
||||
io.WriteString(w, "func ")
|
||||
// writeSignature writes to buf the signature sig in declaration syntax.
|
||||
func writeSignature(buf *bytes.Buffer, pkg *types.Package, name string, sig *types.Signature, params []*Parameter) {
|
||||
buf.WriteString("func ")
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
io.WriteString(w, "(")
|
||||
buf.WriteString("(")
|
||||
if n := params[0].Name(); n != "" {
|
||||
io.WriteString(w, n)
|
||||
io.WriteString(w, " ")
|
||||
buf.WriteString(n)
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
io.WriteString(w, relType(params[0].Type(), pkg))
|
||||
io.WriteString(w, ") ")
|
||||
buf.WriteString(relType(params[0].Type(), pkg))
|
||||
buf.WriteString(") ")
|
||||
}
|
||||
io.WriteString(w, name)
|
||||
var sigbuf bytes.Buffer
|
||||
types.WriteSignature(&sigbuf, pkg, sig)
|
||||
w.Write(sigbuf.Bytes())
|
||||
buf.WriteString(name)
|
||||
types.WriteSignature(buf, pkg, sig)
|
||||
}
|
||||
|
||||
func (f *Function) pkgobj() *types.Package {
|
||||
@ -523,49 +521,56 @@ func (f *Function) pkgobj() *types.Package {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DumpTo prints to w a human readable "disassembly" of the SSA code of
|
||||
// all basic blocks of function f.
|
||||
//
|
||||
func (f *Function) DumpTo(w io.Writer) {
|
||||
fmt.Fprintf(w, "# Name: %s\n", f.String())
|
||||
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
|
||||
|
||||
func (f *Function) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WriteFunction(&buf, f)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WriteFunction writes to buf a human-readable "disassembly" of f.
|
||||
func WriteFunction(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintf(buf, "# Name: %s\n", f.String())
|
||||
if f.Pkg != nil {
|
||||
fmt.Fprintf(w, "# Package: %s\n", f.Pkg.Object.Path())
|
||||
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Object.Path())
|
||||
}
|
||||
if syn := f.Synthetic; syn != "" {
|
||||
fmt.Fprintln(w, "# Synthetic:", syn)
|
||||
fmt.Fprintln(buf, "# Synthetic:", syn)
|
||||
}
|
||||
if pos := f.Pos(); pos.IsValid() {
|
||||
fmt.Fprintf(w, "# Location: %s\n", f.Prog.Fset.Position(pos))
|
||||
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
|
||||
}
|
||||
|
||||
if f.Enclosing != nil {
|
||||
fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name())
|
||||
fmt.Fprintf(buf, "# Parent: %s\n", f.Enclosing.Name())
|
||||
}
|
||||
|
||||
if f.Recover != nil {
|
||||
fmt.Fprintf(w, "# Recover: %s\n", f.Recover)
|
||||
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
|
||||
}
|
||||
|
||||
pkgobj := f.pkgobj()
|
||||
|
||||
if f.FreeVars != nil {
|
||||
io.WriteString(w, "# Free variables:\n")
|
||||
buf.WriteString("# Free variables:\n")
|
||||
for i, fv := range f.FreeVars {
|
||||
fmt.Fprintf(w, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), pkgobj))
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), pkgobj))
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Locals) > 0 {
|
||||
io.WriteString(w, "# Locals:\n")
|
||||
buf.WriteString("# Locals:\n")
|
||||
for i, l := range f.Locals {
|
||||
fmt.Fprintf(w, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), pkgobj))
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), pkgobj))
|
||||
}
|
||||
}
|
||||
writeSignature(w, pkgobj, f.Name(), f.Signature, f.Params)
|
||||
io.WriteString(w, ":\n")
|
||||
writeSignature(buf, pkgobj, f.Name(), f.Signature, f.Params)
|
||||
buf.WriteString(":\n")
|
||||
|
||||
if f.Blocks == nil {
|
||||
io.WriteString(w, "\t(external)\n")
|
||||
buf.WriteString("\t(external)\n")
|
||||
}
|
||||
|
||||
// NB. column calculations are confused by non-ASCII characters.
|
||||
@ -573,42 +578,42 @@ func (f *Function) DumpTo(w io.Writer) {
|
||||
for _, b := range f.Blocks {
|
||||
if b == nil {
|
||||
// Corrupt CFG.
|
||||
fmt.Fprintf(w, ".nil:\n")
|
||||
fmt.Fprintf(buf, ".nil:\n")
|
||||
continue
|
||||
}
|
||||
n, _ := fmt.Fprintf(w, ".%s:", b)
|
||||
fmt.Fprintf(w, "%*sP:%d S:%d\n", punchcard-1-n-len("P:n S:n"), "", len(b.Preds), len(b.Succs))
|
||||
n, _ := fmt.Fprintf(buf, ".%s:", b)
|
||||
fmt.Fprintf(buf, "%*sP:%d S:%d\n", punchcard-1-n-len("P:n S:n"), "", len(b.Preds), len(b.Succs))
|
||||
|
||||
if false { // CFG debugging
|
||||
fmt.Fprintf(w, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
io.WriteString(w, "\t")
|
||||
buf.WriteString("\t")
|
||||
switch v := instr.(type) {
|
||||
case Value:
|
||||
l := punchcard
|
||||
// Left-align the instruction.
|
||||
if name := v.Name(); name != "" {
|
||||
n, _ := fmt.Fprintf(w, "%s = ", name)
|
||||
n, _ := fmt.Fprintf(buf, "%s = ", name)
|
||||
l -= n
|
||||
}
|
||||
// TODO(adonovan): append instructions directly to w.
|
||||
n, _ := io.WriteString(w, instr.String())
|
||||
n, _ := buf.WriteString(instr.String())
|
||||
l -= n
|
||||
// Right-align the type.
|
||||
if t := v.Type(); t != nil {
|
||||
fmt.Fprintf(w, " %*s", l-10, relType(t, pkgobj))
|
||||
fmt.Fprintf(buf, " %*s", l-10, relType(t, pkgobj))
|
||||
}
|
||||
case nil:
|
||||
// Be robust against bad transforms.
|
||||
io.WriteString(w, "<deleted>")
|
||||
buf.WriteString("<deleted>")
|
||||
default:
|
||||
io.WriteString(w, instr.String())
|
||||
buf.WriteString(instr.String())
|
||||
}
|
||||
io.WriteString(w, "\n")
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
// newBasicBlock adds to f a new basic block and returns it. It does
|
||||
|
@ -352,109 +352,109 @@ func copyVal(v value) value {
|
||||
// Prints in the style of built-in println.
|
||||
// (More or less; in gc println is actually a compiler intrinsic and
|
||||
// can distinguish println(1) from println(interface{}(1)).)
|
||||
func toWriter(w io.Writer, v value) {
|
||||
func writeValue(buf *bytes.Buffer, v value) {
|
||||
switch v := v.(type) {
|
||||
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string:
|
||||
fmt.Fprintf(w, "%v", v)
|
||||
fmt.Fprintf(buf, "%v", v)
|
||||
|
||||
case map[value]value:
|
||||
io.WriteString(w, "map[")
|
||||
buf.WriteString("map[")
|
||||
sep := ""
|
||||
for k, e := range v {
|
||||
io.WriteString(w, sep)
|
||||
buf.WriteString(sep)
|
||||
sep = " "
|
||||
toWriter(w, k)
|
||||
io.WriteString(w, ":")
|
||||
toWriter(w, e)
|
||||
writeValue(buf, k)
|
||||
buf.WriteString(":")
|
||||
writeValue(buf, e)
|
||||
}
|
||||
io.WriteString(w, "]")
|
||||
buf.WriteString("]")
|
||||
|
||||
case *hashmap:
|
||||
io.WriteString(w, "map[")
|
||||
buf.WriteString("map[")
|
||||
sep := " "
|
||||
for _, e := range v.table {
|
||||
for e != nil {
|
||||
io.WriteString(w, sep)
|
||||
buf.WriteString(sep)
|
||||
sep = " "
|
||||
toWriter(w, e.key)
|
||||
io.WriteString(w, ":")
|
||||
toWriter(w, e.value)
|
||||
writeValue(buf, e.key)
|
||||
buf.WriteString(":")
|
||||
writeValue(buf, e.value)
|
||||
e = e.next
|
||||
}
|
||||
}
|
||||
io.WriteString(w, "]")
|
||||
buf.WriteString("]")
|
||||
|
||||
case chan value:
|
||||
fmt.Fprintf(w, "%v", v) // (an address)
|
||||
fmt.Fprintf(buf, "%v", v) // (an address)
|
||||
|
||||
case *value:
|
||||
if v == nil {
|
||||
io.WriteString(w, "<nil>")
|
||||
buf.WriteString("<nil>")
|
||||
} else {
|
||||
fmt.Fprintf(w, "%p", v)
|
||||
fmt.Fprintf(buf, "%p", v)
|
||||
}
|
||||
|
||||
case iface:
|
||||
fmt.Fprintf(w, "(%s, ", v.t)
|
||||
toWriter(w, v.v)
|
||||
io.WriteString(w, ")")
|
||||
fmt.Fprintf(buf, "(%s, ", v.t)
|
||||
writeValue(buf, v.v)
|
||||
buf.WriteString(")")
|
||||
|
||||
case structure:
|
||||
io.WriteString(w, "{")
|
||||
buf.WriteString("{")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
io.WriteString(w, " ")
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
toWriter(w, e)
|
||||
writeValue(buf, e)
|
||||
}
|
||||
io.WriteString(w, "}")
|
||||
buf.WriteString("}")
|
||||
|
||||
case array:
|
||||
io.WriteString(w, "[")
|
||||
buf.WriteString("[")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
io.WriteString(w, " ")
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
toWriter(w, e)
|
||||
writeValue(buf, e)
|
||||
}
|
||||
io.WriteString(w, "]")
|
||||
buf.WriteString("]")
|
||||
|
||||
case []value:
|
||||
io.WriteString(w, "[")
|
||||
buf.WriteString("[")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
io.WriteString(w, " ")
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
toWriter(w, e)
|
||||
writeValue(buf, e)
|
||||
}
|
||||
io.WriteString(w, "]")
|
||||
buf.WriteString("]")
|
||||
|
||||
case *ssa.Function, *ssa.Builtin, *closure:
|
||||
fmt.Fprintf(w, "%p", v) // (an address)
|
||||
fmt.Fprintf(buf, "%p", v) // (an address)
|
||||
|
||||
case rtype:
|
||||
io.WriteString(w, v.t.String())
|
||||
buf.WriteString(v.t.String())
|
||||
|
||||
case tuple:
|
||||
// Unreachable in well-formed Go programs
|
||||
io.WriteString(w, "(")
|
||||
buf.WriteString("(")
|
||||
for i, e := range v {
|
||||
if i > 0 {
|
||||
io.WriteString(w, ", ")
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
toWriter(w, e)
|
||||
writeValue(buf, e)
|
||||
}
|
||||
io.WriteString(w, ")")
|
||||
buf.WriteString(")")
|
||||
|
||||
default:
|
||||
fmt.Fprintf(w, "<%T>", v)
|
||||
fmt.Fprintf(buf, "<%T>", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Implements printing of Go values in the style of built-in println.
|
||||
func toString(v value) string {
|
||||
var b bytes.Buffer
|
||||
toWriter(&b, v)
|
||||
writeValue(&b, v)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,9 @@ package ssa
|
||||
// This file implements the String() methods for all Value and
|
||||
// Instruction types.
|
||||
|
||||
// TODO(adonovan): define WriteValue(*bytes.Buffer) and avoid creation
|
||||
// of garbage.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
@ -364,8 +367,18 @@ func (p *Package) String() string {
|
||||
return "package " + p.Object.Path()
|
||||
}
|
||||
|
||||
func (p *Package) DumpTo(w io.Writer) {
|
||||
fmt.Fprintf(w, "%s:\n", p)
|
||||
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
|
||||
|
||||
func (p *Package) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WritePackage(&buf, p)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WritePackage writes to buf a human-readable summary of p.
|
||||
func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
fmt.Fprintf(buf, "%s:\n", p)
|
||||
|
||||
var names []string
|
||||
maxname := 0
|
||||
@ -380,27 +393,27 @@ func (p *Package) DumpTo(w io.Writer) {
|
||||
for _, name := range names {
|
||||
switch mem := p.Members[name].(type) {
|
||||
case *NamedConst:
|
||||
fmt.Fprintf(w, " const %-*s %s = %s\n",
|
||||
fmt.Fprintf(buf, " const %-*s %s = %s\n",
|
||||
maxname, name, mem.Name(), mem.Value.RelString(p.Object))
|
||||
|
||||
case *Function:
|
||||
fmt.Fprintf(w, " func %-*s %s\n",
|
||||
fmt.Fprintf(buf, " func %-*s %s\n",
|
||||
maxname, name, types.TypeString(p.Object, mem.Type()))
|
||||
|
||||
case *Type:
|
||||
fmt.Fprintf(w, " type %-*s %s\n",
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, types.TypeString(p.Object, mem.Type().Underlying()))
|
||||
for _, meth := range IntuitiveMethodSet(mem.Type()) {
|
||||
fmt.Fprintf(w, " %s\n", types.SelectionString(p.Object, meth))
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(p.Object, meth))
|
||||
}
|
||||
|
||||
case *Global:
|
||||
fmt.Fprintf(w, " var %-*s %s\n",
|
||||
fmt.Fprintf(buf, " var %-*s %s\n",
|
||||
maxname, name, types.TypeString(p.Object, mem.Type().(*types.Pointer).Elem()))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
// IntuitiveMethodSet returns the intuitive method set of a type, T.
|
||||
|
@ -43,7 +43,7 @@ func sanityCheck(fn *Function, reporter io.Writer) bool {
|
||||
//
|
||||
func mustSanityCheck(fn *Function, reporter io.Writer) {
|
||||
if !sanityCheck(fn, reporter) {
|
||||
fn.DumpTo(os.Stderr)
|
||||
fn.WriteTo(os.Stderr)
|
||||
panic("SanityCheck failed")
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ func TestValueForExpr(t *testing.T) {
|
||||
// debugging
|
||||
for _, mem := range mainPkg.Members {
|
||||
if fn, ok := mem.(*ssa.Function); ok {
|
||||
fn.DumpTo(os.Stderr)
|
||||
fn.WriteTo(os.Stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package {
|
||||
testmain.Members["main"] = main
|
||||
|
||||
if prog.mode&LogPackages != 0 {
|
||||
testmain.DumpTo(os.Stderr)
|
||||
testmain.WriteTo(os.Stderr)
|
||||
}
|
||||
|
||||
if prog.mode&SanityCheckFunctions != 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user