mirror of
https://github.com/golang/go
synced 2024-11-25 03:07:56 -07:00
go/printer, gofmt: avoid exponential layout algorithm
Use memoization to avoid repeated recomputation of nested node sizes. Speeds up testdata/slow.input by several orders of magnitude. - added respective test case - added timeout to test code - deleted some unrelated unused code Fixes #1628. R=rsc, r CC=golang-dev https://golang.org/cl/4274075
This commit is contained in:
parent
708013064f
commit
4a33d440b8
@ -374,7 +374,7 @@ func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
|
||||
// with an another printer mode (which is more efficiently
|
||||
// implemented in the printer than here with another layer)
|
||||
mode := printer.TabIndent | printer.UseSpaces
|
||||
(&printer.Config{mode, *tabwidth}).Fprint(&tconv{output: w}, fset, x)
|
||||
(&printer.Config{Mode: mode, Tabwidth: *tabwidth}).Fprint(&tconv{output: w}, fset, x)
|
||||
}
|
||||
|
||||
|
||||
|
@ -127,7 +127,7 @@ func processFile(filename string, useStdin bool) os.Error {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", filename, buf.String()[1:])
|
||||
|
||||
buf.Reset()
|
||||
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||
_, err = (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string) (out
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Reset()
|
||||
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||
_, err = (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
t.Errorf("%s: printing: %v", desc, err)
|
||||
return
|
||||
@ -60,7 +60,7 @@ func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string) (out
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||
_, err = (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
t.Errorf("%s: printing: %v", desc, err)
|
||||
return
|
||||
|
@ -103,7 +103,7 @@ func processFile(f *os.File) os.Error {
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = (&printer.Config{printerMode, *tabWidth}).Fprint(&buf, fset, file)
|
||||
_, err = (&printer.Config{Mode: printerMode, Tabwidth: *tabWidth}).Fprint(&buf, fset, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,17 +108,6 @@ func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) {
|
||||
}
|
||||
|
||||
|
||||
// Compute the key size of a key:value expression.
|
||||
// Returns 0 if the expression doesn't fit onto a single line.
|
||||
func (p *printer) keySize(pair *ast.KeyValueExpr) int {
|
||||
if p.nodeSize(pair, infinity) <= infinity {
|
||||
// entire expression fits on one line - return key size
|
||||
return p.nodeSize(pair.Key, infinity)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
// Print a list of expressions. If the list spans multiple
|
||||
// source lines, the original line breaks are respected between
|
||||
// expressions. Sets multiLine to true if the list spans multiple
|
||||
@ -1324,11 +1313,21 @@ func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
|
||||
// any control chars. Otherwise, the result is > maxSize.
|
||||
//
|
||||
func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
|
||||
// nodeSize invokes the printer, which may invoke nodeSize
|
||||
// recursively. For deep composite literal nests, this can
|
||||
// lead to an exponential algorithm. Remember previous
|
||||
// results to prune the recursion (was issue 1628).
|
||||
if size, found := p.nodeSizes[n]; found {
|
||||
return size
|
||||
}
|
||||
|
||||
size = maxSize + 1 // assume n doesn't fit
|
||||
p.nodeSizes[n] = size
|
||||
|
||||
// nodeSize computation must be indendent of particular
|
||||
// style so that we always get the same decision; print
|
||||
// in RawFormat
|
||||
cfg := Config{Mode: RawFormat}
|
||||
cfg := Config{Mode: RawFormat, nodeSizes: p.nodeSizes}
|
||||
var buf bytes.Buffer
|
||||
if _, err := cfg.Fprint(&buf, p.fset, n); err != nil {
|
||||
return
|
||||
@ -1340,6 +1339,7 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
|
||||
}
|
||||
}
|
||||
size = buf.Len() // n fits
|
||||
p.nodeSizes[n] = size
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -94,9 +94,6 @@ type printer struct {
|
||||
// written using writeItem.
|
||||
last token.Position
|
||||
|
||||
// HTML support
|
||||
lastTaggedLine int // last line for which a line tag was written
|
||||
|
||||
// The list of all source comments, in order of appearance.
|
||||
comments []*ast.CommentGroup // may be nil
|
||||
cindex int // current comment index
|
||||
@ -989,8 +986,9 @@ const (
|
||||
|
||||
// A Config node controls the output of Fprint.
|
||||
type Config struct {
|
||||
Mode uint // default: 0
|
||||
Tabwidth int // default: 8
|
||||
Mode uint // default: 0
|
||||
Tabwidth int // default: 8
|
||||
nodeSizes map[ast.Node]int // memoized node sizes as computed by nodeSize
|
||||
}
|
||||
|
||||
|
||||
@ -1001,6 +999,12 @@ type Config struct {
|
||||
// ast.Decl, ast.Spec, or ast.Stmt.
|
||||
//
|
||||
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, os.Error) {
|
||||
// only if Fprint is called recursively (via nodeSize)
|
||||
// does cfg.nodeSizes exist - set it up otherwise
|
||||
if cfg.nodeSizes == nil {
|
||||
cfg.nodeSizes = make(map[ast.Node]int)
|
||||
}
|
||||
|
||||
// redirect output through a trimmer to eliminate trailing whitespace
|
||||
// (Input to a tabwriter must be untrimmed since trailing tabs provide
|
||||
// formatting information. The tabwriter could provide trimming
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
@ -45,7 +46,7 @@ const (
|
||||
)
|
||||
|
||||
|
||||
func check(t *testing.T, source, golden string, mode checkMode) {
|
||||
func runcheck(t *testing.T, source, golden string, mode checkMode) {
|
||||
// parse source
|
||||
prog, err := parser.ParseFile(fset, source, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
@ -109,6 +110,32 @@ func check(t *testing.T, source, golden string, mode checkMode) {
|
||||
}
|
||||
|
||||
|
||||
func check(t *testing.T, source, golden string, mode checkMode) {
|
||||
// start a timer to produce a time-out signal
|
||||
tc := make(chan int)
|
||||
go func() {
|
||||
time.Sleep(2e9) // plenty of a safety margin, even for very slow machines
|
||||
tc <- 0
|
||||
}()
|
||||
|
||||
// run the test
|
||||
cc := make(chan int)
|
||||
go func() {
|
||||
runcheck(t, source, golden, mode)
|
||||
cc <- 0
|
||||
}()
|
||||
|
||||
// wait for the first finisher
|
||||
select {
|
||||
case <-tc:
|
||||
// test running past time out
|
||||
t.Errorf("%s: running too slowly", source)
|
||||
case <-cc:
|
||||
// test finished within alloted time margin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type entry struct {
|
||||
source, golden string
|
||||
mode checkMode
|
||||
@ -124,6 +151,7 @@ var data = []entry{
|
||||
{"expressions.input", "expressions.raw", rawFormat},
|
||||
{"declarations.input", "declarations.golden", 0},
|
||||
{"statements.input", "statements.golden", 0},
|
||||
{"slow.input", "slow.golden", 0},
|
||||
}
|
||||
|
||||
|
||||
|
85
src/pkg/go/printer/testdata/slow.golden
vendored
Normal file
85
src/pkg/go/printer/testdata/slow.golden
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package deepequal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"google3/spam/archer/frontend/deepequal"
|
||||
)
|
||||
|
||||
func TestTwoNilValues(t *testing.T) {
|
||||
if err := deepequal.Check(nil, nil); err != nil {
|
||||
t.Errorf("expected nil, saw %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
bar *Bar
|
||||
bang *Bar
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
baz *Baz
|
||||
foo []*Foo
|
||||
}
|
||||
|
||||
type Baz struct {
|
||||
entries map[int]interface{}
|
||||
whatever string
|
||||
}
|
||||
|
||||
func newFoo() *Foo {
|
||||
return &Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
42: &Foo{},
|
||||
21: &Bar{},
|
||||
11: &Baz{whatever: "it's just a test"}}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
43: &Foo{},
|
||||
22: &Bar{},
|
||||
13: &Baz{whatever: "this is nuts"}}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
61: &Foo{},
|
||||
71: &Bar{},
|
||||
11: &Baz{whatever: "no, it's Go"}}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
0: &Foo{},
|
||||
-2: &Bar{},
|
||||
-11: &Baz{whatever: "we need to go deeper"}}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
-2: &Foo{},
|
||||
-5: &Bar{},
|
||||
-7: &Baz{whatever: "are you serious?"}}}},
|
||||
bang: &Bar{foo: []*Foo{}}},
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
-100: &Foo{},
|
||||
50: &Bar{},
|
||||
20: &Baz{whatever: "na, not really ..."}}}},
|
||||
bang: &Bar{foo: []*Foo{}}}}}}}}},
|
||||
&Foo{bar: &Bar{baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
2: &Foo{},
|
||||
1: &Bar{},
|
||||
-1: &Baz{whatever: "... it's just a test."}}}},
|
||||
bang: &Bar{foo: []*Foo{}}}}}}}}}
|
||||
}
|
||||
|
||||
func TestElaborate(t *testing.T) {
|
||||
a := newFoo()
|
||||
b := newFoo()
|
||||
|
||||
if err := deepequal.Check(a, b); err != nil {
|
||||
t.Errorf("expected nil, saw %v", err)
|
||||
}
|
||||
}
|
85
src/pkg/go/printer/testdata/slow.input
vendored
Normal file
85
src/pkg/go/printer/testdata/slow.input
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package deepequal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"google3/spam/archer/frontend/deepequal"
|
||||
)
|
||||
|
||||
func TestTwoNilValues(t *testing.T) {
|
||||
if err := deepequal.Check(nil, nil); err != nil {
|
||||
t.Errorf("expected nil, saw %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
bar *Bar
|
||||
bang *Bar
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
baz *Baz
|
||||
foo []*Foo
|
||||
}
|
||||
|
||||
type Baz struct {
|
||||
entries map[int]interface{}
|
||||
whatever string
|
||||
}
|
||||
|
||||
func newFoo() (*Foo) {
|
||||
return &Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
42: &Foo{},
|
||||
21: &Bar{},
|
||||
11: &Baz{ whatever: "it's just a test" }}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
43: &Foo{},
|
||||
22: &Bar{},
|
||||
13: &Baz{ whatever: "this is nuts" }}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
61: &Foo{},
|
||||
71: &Bar{},
|
||||
11: &Baz{ whatever: "no, it's Go" }}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
0: &Foo{},
|
||||
-2: &Bar{},
|
||||
-11: &Baz{ whatever: "we need to go deeper" }}}},
|
||||
bang: &Bar{foo: []*Foo{
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
-2: &Foo{},
|
||||
-5: &Bar{},
|
||||
-7: &Baz{ whatever: "are you serious?" }}}},
|
||||
bang: &Bar{foo: []*Foo{}}},
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
-100: &Foo{},
|
||||
50: &Bar{},
|
||||
20: &Baz{ whatever: "na, not really ..." }}}},
|
||||
bang: &Bar{foo: []*Foo{}}}}}}}}},
|
||||
&Foo{bar: &Bar{ baz: &Baz{
|
||||
entries: map[int]interface{}{
|
||||
2: &Foo{},
|
||||
1: &Bar{},
|
||||
-1: &Baz{ whatever: "... it's just a test." }}}},
|
||||
bang: &Bar{foo: []*Foo{}}}}}}}}}
|
||||
}
|
||||
|
||||
func TestElaborate(t *testing.T) {
|
||||
a := newFoo()
|
||||
b := newFoo()
|
||||
|
||||
if err := deepequal.Check(a, b); err != nil {
|
||||
t.Errorf("expected nil, saw %v", err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user