Skip to content

Commit 3d6b368

Browse files
committed
go/printer, gofmt: don't print incorrect programs
Be careful when printing line comments with incorrect position information. Maintain additional state impliedSemi: when set, a comment containing a newline would imply a semicolon and thus placement must be delayed. Precompute state information pertaining to the next comment for faster checks (the printer is marginally faster now despite additional checks for each comment). No effect on existing src, misc sources. Fixes #1505. R=rsc CC=golang-dev https://golang.org/cl/5598054
1 parent e375543 commit 3d6b368

File tree

4 files changed

+209
-46
lines changed

4 files changed

+209
-46
lines changed

src/cmd/fix/timefileinfo_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,32 @@ func main() {
156156
t2 := time.Now()
157157
dt := t2.Sub(t1)
158158
}
159+
`,
160+
},
161+
{
162+
Name: "timefileinfo.5", // test for issues 1505, 2636
163+
In: `package main
164+
165+
import (
166+
"fmt"
167+
"time"
168+
)
169+
170+
func main() {
171+
fmt.Println(time.SecondsToUTC(now)) // this comment must not introduce an illegal linebreak
172+
}
173+
`,
174+
Out: `package main
175+
176+
import (
177+
"fmt"
178+
"time"
179+
)
180+
181+
func main() {
182+
fmt.Println(time.Unix(now, 0).UTC( // this comment must not introduce an illegal linebreak
183+
))
184+
}
159185
`,
160186
},
161187
}

src/pkg/go/printer/nodes.go

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func (p *printer) setComment(g *ast.CommentGroup) {
7676
}
7777
p.comments[0] = g
7878
p.cindex = 0
79+
p.nextComment() // get comment ready for use
7980
}
8081

8182
type exprListMode uint

src/pkg/go/printer/printer.go

+114-39
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"go/token"
1313
"io"
1414
"os"
15-
"path/filepath"
1615
"strconv"
1716
"strings"
1817
"text/tabwriter"
@@ -52,11 +51,12 @@ type printer struct {
5251
fset *token.FileSet
5352

5453
// Current state
55-
output bytes.Buffer // raw printer result
56-
indent int // current indentation
57-
mode pmode // current printer mode
58-
lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
59-
wsbuf []whiteSpace // delayed white space
54+
output bytes.Buffer // raw printer result
55+
indent int // current indentation
56+
mode pmode // current printer mode
57+
impliedSemi bool // if set, a linebreak implies a semicolon
58+
lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
59+
wsbuf []whiteSpace // delayed white space
6060

6161
// The (possibly estimated) position in the generated output;
6262
// in AST space (i.e., pos is set whenever a token position is
@@ -73,6 +73,11 @@ type printer struct {
7373
cindex int // current comment index
7474
useNodeComments bool // if not set, ignore lead and line comments of nodes
7575

76+
// Information about p.comments[p.cindex]; set up by nextComment.
77+
comment *ast.CommentGroup // = p.comments[p.cindex]; or nil
78+
commentOffset int // = p.posFor(p.comments[p.cindex].List[0].Pos()).Offset; or infinity
79+
commentNewline bool // true if the comment group contains newlines
80+
7681
// Cache of already computed node sizes.
7782
nodeSizes map[ast.Node]int
7883

@@ -89,6 +94,42 @@ func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]
8994
p.cachedPos = -1
9095
}
9196

97+
// commentsHaveNewline reports whether a list of comments belonging to
98+
// an *ast.CommentGroup contains newlines. Because the position information
99+
// may only be partially correct, we also have to read the comment text.
100+
func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
101+
// len(list) > 0
102+
line := p.lineFor(list[0].Pos())
103+
for i, c := range list {
104+
if i > 0 && p.lineFor(list[i].Pos()) != line {
105+
// not all comments on the same line
106+
return true
107+
}
108+
if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
109+
return true
110+
}
111+
}
112+
_ = line
113+
return false
114+
}
115+
116+
func (p *printer) nextComment() {
117+
for p.cindex < len(p.comments) {
118+
c := p.comments[p.cindex]
119+
p.cindex++
120+
if list := c.List; len(list) > 0 {
121+
p.comment = c
122+
p.commentOffset = p.posFor(list[0].Pos()).Offset
123+
p.commentNewline = p.commentsHaveNewline(list)
124+
return
125+
}
126+
// we should not reach here (correct ASTs don't have empty
127+
// ast.CommentGroup nodes), but be conservative and try again
128+
}
129+
// no more comments
130+
p.commentOffset = infinity
131+
}
132+
92133
func (p *printer) internalError(msg ...interface{}) {
93134
if debug {
94135
fmt.Print(p.pos.String() + ": ")
@@ -204,8 +245,7 @@ func (p *printer) writeItem(pos token.Position, data string, isLit bool) {
204245
}
205246
if debug {
206247
// do not update p.pos - use write0
207-
_, filename := filepath.Split(pos.Filename)
208-
fmt.Fprintf(&p.output, "[%s:%d:%d]", filename, pos.Line, pos.Column)
248+
fmt.Fprintf(&p.output, "/*%s*/", pos)
209249
}
210250
p.writeString(data, isLit)
211251
p.last = p.pos
@@ -618,12 +658,13 @@ func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, dropped
618658
//
619659
func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
620660
var last *ast.Comment
621-
for ; p.commentBefore(next); p.cindex++ {
622-
for _, c := range p.comments[p.cindex].List {
661+
for p.commentBefore(next) {
662+
for _, c := range p.comment.List {
623663
p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok.IsKeyword())
624664
p.writeComment(c)
625665
last = c
626666
}
667+
p.nextComment()
627668
}
628669

629670
if last != nil {
@@ -735,22 +776,24 @@ func mayCombine(prev token.Token, next byte) (b bool) {
735776
// printed, followed by the actual token.
736777
//
737778
func (p *printer) print(args ...interface{}) {
738-
for _, f := range args {
739-
next := p.pos // estimated position of next item
740-
data := ""
741-
isLit := false
742-
var tok token.Token
779+
for _, arg := range args {
780+
// information about the current arg
781+
var data string
782+
var isLit bool
783+
var impliedSemi bool // value for p.impliedSemi after this arg
743784

744-
switch x := f.(type) {
785+
switch x := arg.(type) {
745786
case pmode:
746787
// toggle printer mode
747788
p.mode ^= x
789+
continue
790+
748791
case whiteSpace:
749792
if x == ignore {
750793
// don't add ignore's to the buffer; they
751794
// may screw up "correcting" unindents (see
752795
// LabeledStmt)
753-
break
796+
continue
754797
}
755798
i := len(p.wsbuf)
756799
if i == cap(p.wsbuf) {
@@ -762,13 +805,27 @@ func (p *printer) print(args ...interface{}) {
762805
}
763806
p.wsbuf = p.wsbuf[0 : i+1]
764807
p.wsbuf[i] = x
808+
if x == newline || x == formfeed {
809+
// newlines affect the current state (p.impliedSemi)
810+
// and not the state after printing arg (impliedSemi)
811+
// because comments can be interspersed before the arg
812+
// in this case
813+
p.impliedSemi = false
814+
}
815+
p.lastTok = token.ILLEGAL
816+
continue
817+
765818
case *ast.Ident:
766819
data = x.Name
767-
tok = token.IDENT
820+
impliedSemi = true
821+
p.lastTok = token.IDENT
822+
768823
case *ast.BasicLit:
769824
data = x.Value
770825
isLit = true
771-
tok = x.Kind
826+
impliedSemi = true
827+
p.lastTok = x.Kind
828+
772829
case token.Token:
773830
s := x.String()
774831
if mayCombine(p.lastTok, s[0]) {
@@ -785,30 +842,40 @@ func (p *printer) print(args ...interface{}) {
785842
p.wsbuf[0] = ' '
786843
}
787844
data = s
788-
tok = x
845+
// some keywords followed by a newline imply a semicolon
846+
switch x {
847+
case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
848+
token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
849+
impliedSemi = true
850+
}
851+
p.lastTok = x
852+
789853
case token.Pos:
790854
if x.IsValid() {
791-
next = p.posFor(x) // accurate position of next item
855+
p.pos = p.posFor(x) // accurate position of next item
792856
}
793-
tok = p.lastTok
857+
continue
858+
794859
case string:
795860
// incorrect AST - print error message
796861
data = x
797862
isLit = true
798-
tok = token.STRING
863+
impliedSemi = true
864+
p.lastTok = token.STRING
865+
799866
default:
800-
fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", f, f)
867+
fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
801868
panic("go/printer type")
802869
}
803-
p.lastTok = tok
804-
p.pos = next
870+
// data != ""
805871

806-
if data != "" {
807-
wroteNewline, droppedFF := p.flush(next, tok)
872+
next := p.pos // estimated/accurate position of next item
873+
wroteNewline, droppedFF := p.flush(next, p.lastTok)
808874

809-
// intersperse extra newlines if present in the source
810-
// (don't do this in flush as it will cause extra newlines
811-
// at the end of a file)
875+
// intersperse extra newlines if present in the source and
876+
// if they don't cause extra semicolons (don't do this in
877+
// flush as it will cause extra newlines at the end of a file)
878+
if !p.impliedSemi {
812879
n := nlimit(next.Line - p.pos.Line)
813880
// don't exceed maxNewlines if we already wrote one
814881
if wroteNewline && n == maxNewlines {
@@ -820,22 +887,25 @@ func (p *printer) print(args ...interface{}) {
820887
ch = '\f' // use formfeed since we dropped one before
821888
}
822889
p.writeByteN(ch, n)
890+
impliedSemi = false
823891
}
824-
825-
p.writeItem(next, data, isLit)
826892
}
893+
894+
p.writeItem(next, data, isLit)
895+
p.impliedSemi = impliedSemi
827896
}
828897
}
829898

830-
// commentBefore returns true iff the current comment occurs
831-
// before the next position in the source code.
899+
// commentBefore returns true iff the current comment group occurs
900+
// before the next position in the source code and printing it does
901+
// not introduce implicit semicolons.
832902
//
833-
func (p *printer) commentBefore(next token.Position) bool {
834-
return p.cindex < len(p.comments) && p.posFor(p.comments[p.cindex].List[0].Pos()).Offset < next.Offset
903+
func (p *printer) commentBefore(next token.Position) (result bool) {
904+
return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
835905
}
836906

837-
// Flush prints any pending comments and whitespace occurring textually
838-
// before the position of the next token tok. The Flush result indicates
907+
// flush prints any pending comments and whitespace occurring textually
908+
// before the position of the next token tok. The flush result indicates
839909
// if a newline was written or if a formfeed was dropped from the whitespace
840910
// buffer.
841911
//
@@ -915,6 +985,9 @@ func (p *printer) printNode(node interface{}) error {
915985
// if there are no comments, use node comments
916986
p.useNodeComments = p.comments == nil
917987

988+
// get comments ready for use
989+
p.nextComment()
990+
918991
// format node
919992
switch n := node.(type) {
920993
case ast.Expr:
@@ -1068,6 +1141,8 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{
10681141
if err = p.printNode(node); err != nil {
10691142
return
10701143
}
1144+
// print outstanding comments
1145+
p.impliedSemi = false // EOF acts like a newline
10711146
p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
10721147

10731148
// redirect output through a trimmer to eliminate trailing whitespace

0 commit comments

Comments
 (0)