From: Matthew Dempsky Date: Mon, 23 Sep 2019 17:57:00 +0000 (-0700) Subject: cmd/compile: restore -m=2 diagnostics X-Git-Tag: go1.14beta1~429 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=063d0f11e535edf61d1e0b4ba16cfeae0f312bcf;p=gostls13.git cmd/compile: restore -m=2 diagnostics This is a rough attempt at restoring -m=2 escape analysis diagnostics on par with those that were available with esc.go. It's meant to be simple and non-invasive. For example, given this random example from bytes/reader.go: 138 func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { ... 143 b := r.s[r.i:] 144 m, err := w.Write(b) esc.go used to report: bytes/reader.go:138:7: leaking param content: r bytes/reader.go:138:7: from r.s (dot of pointer) at bytes/reader.go:143:8 bytes/reader.go:138:7: from b (assigned) at bytes/reader.go:143:4 bytes/reader.go:138:7: from w.Write(b) (parameter to indirect call) at bytes/reader.go:144:19 With this CL, escape.go now reports: bytes/reader.go:138:7: parameter r leaks to {heap} with derefs=1: bytes/reader.go:138:7: flow: b = *r: bytes/reader.go:138:7: from r.s (dot of pointer) at bytes/reader.go:143:8 bytes/reader.go:138:7: from r.s[r.i:] (slice) at bytes/reader.go:143:10 bytes/reader.go:138:7: from b := r.s[r.i:] (assign) at bytes/reader.go:143:4 bytes/reader.go:138:7: flow: {heap} = b: bytes/reader.go:138:7: from w.Write(b) (call parameter) at bytes/reader.go:144:19 Updates #31489. Change-Id: I0c2b943a0f9ce6345bfff61e1c635172a9290cbb Reviewed-on: https://go-review.googlesource.com/c/go/+/196959 Run-TryBot: Matthew Dempsky Reviewed-by: David Chase --- diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go index fdf327d715..dc078c54c4 100644 --- a/src/cmd/compile/internal/gc/escape.go +++ b/src/cmd/compile/internal/gc/escape.go @@ -107,6 +107,12 @@ type EscLocation struct { derefs int // >= -1 walkgen uint32 + // dst and dstEdgeindex track the next immediate assignment + // destination location during walkone, along with the index + // of the edge pointing back to this location. + dst *EscLocation + dstEdgeIdx int + // queued is used by walkAll to track whether this location is // in the walk queue. queued bool @@ -129,6 +135,7 @@ type EscLocation struct { type EscEdge struct { src *EscLocation derefs int // >= -1 + notes *EscNote } // escapeFuncs performs escape analysis on a minimal batch of @@ -318,7 +325,7 @@ func (e *Escape) stmt(n *Node) { cv := cas.Rlist.First() k := e.dcl(cv) // type switch variables have no ODCL. if types.Haspointers(cv.Type) { - ks = append(ks, k.dotType(cv.Type, n, "switch case")) + ks = append(ks, k.dotType(cv.Type, cas, "switch case")) } } @@ -692,12 +699,12 @@ func (e *Escape) assign(dst, src *Node, why string, where *Node) { k := e.addr(dst) if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) { - e.unsafeValue(e.heapHole(), src) + e.unsafeValue(e.heapHole().note(where, why), src) } else { if ignore { k = e.discardHole() } - e.expr(k, src) + e.expr(k.note(where, why), src) } } @@ -815,18 +822,18 @@ func (e *Escape) call(ks []EscHole, call, where *Node) { if call.Op == OCALLFUNC { // Evaluate callee function expression. - e.expr(e.augmentParamHole(e.discardHole(), where), call.Left) + e.expr(e.augmentParamHole(e.discardHole(), call, where), call.Left) } if recv != nil { // TODO(mdempsky): Handle go:uintptrescapes here too? - e.expr(e.augmentParamHole(recvK, where), recv) + e.expr(e.augmentParamHole(recvK, call, where), recv) } // Apply augmentParamHole before ODDDARG so that it affects // the implicit slice allocation for variadic calls, if any. for i, paramK := range paramKs { - paramKs[i] = e.augmentParamHole(paramK, where) + paramKs[i] = e.augmentParamHole(paramK, call, where) } // TODO(mdempsky): Remove after early ddd-ification. @@ -870,7 +877,8 @@ func (e *Escape) call(ks []EscHole, call, where *Node) { // augmentParamHole augments parameter holes as necessary for use in // go/defer statements. -func (e *Escape) augmentParamHole(k EscHole, where *Node) EscHole { +func (e *Escape) augmentParamHole(k EscHole, call, where *Node) EscHole { + k = k.note(call, "call parameter") if where == nil { return k } @@ -886,7 +894,7 @@ func (e *Escape) augmentParamHole(k EscHole, where *Node) EscHole { return e.later(k) } - return e.heapHole() + return e.heapHole().note(where, "call parameter") } // tagHole returns a hole for evaluating an argument passed to param. @@ -923,10 +931,26 @@ func (e *Escape) tagHole(ks []EscHole, param *types.Field, static bool) EscHole type EscHole struct { dst *EscLocation derefs int // >= -1 + notes *EscNote +} + +type EscNote struct { + next *EscNote + where *Node + why string } func (k EscHole) note(where *Node, why string) EscHole { - // TODO(mdempsky): Keep a record of where/why for diagnostics. + if where == nil || why == "" { + Fatalf("note: missing where/why") + } + if Debug['m'] >= 2 { + k.notes = &EscNote{ + next: k.notes, + where: where, + why: why, + } + } return k } @@ -1068,7 +1092,7 @@ func (e *Escape) flow(k EscHole, src *EscLocation) { } // TODO(mdempsky): Deduplicate edges? - dst.edges = append(dst.edges, EscEdge{src: src, derefs: k.derefs}) + dst.edges = append(dst.edges, EscEdge{src: src, derefs: k.derefs, notes: k.notes}) } func (e *Escape) heapHole() EscHole { return e.heapLoc.asHole() } @@ -1119,6 +1143,7 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc root.walkgen = walkgen root.derefs = 0 + root.dst = nil todo := []*EscLocation{root} // LIFO queue for len(todo) > 0 { @@ -1152,6 +1177,10 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc // that value flow for tagging the function // later. if l.isName(PPARAM) { + if Debug['m'] >= 2 && !l.escapes { + fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", linestr(l.n.Pos), l.n, e.explainLoc(root), base) + e.explainPath(root, l) + } l.leakTo(root, base) } @@ -1159,13 +1188,17 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc // outlives it, then l needs to be heap // allocated. if addressOf && !l.escapes { + if Debug['m'] >= 2 { + fmt.Printf("%s: %v escapes to heap:\n", linestr(l.n.Pos), l.n) + e.explainPath(root, l) + } l.escapes = true enqueue(l) continue } } - for _, edge := range l.edges { + for i, edge := range l.edges { if edge.src.escapes { continue } @@ -1173,12 +1206,55 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc if edge.src.walkgen != walkgen || edge.src.derefs > derefs { edge.src.walkgen = walkgen edge.src.derefs = derefs + edge.src.dst = l + edge.src.dstEdgeIdx = i todo = append(todo, edge.src) } } } } +// explainPath prints an explanation of how src flows to the walk root. +func (e *Escape) explainPath(root, src *EscLocation) { + pos := linestr(src.n.Pos) + for { + dst := src.dst + edge := &dst.edges[src.dstEdgeIdx] + if edge.src != src { + Fatalf("path inconsistency: %v != %v", edge.src, src) + } + + derefs := "&" + if edge.derefs >= 0 { + derefs = strings.Repeat("*", edge.derefs) + } + + fmt.Printf("%s: flow: %s = %s%v:\n", pos, e.explainLoc(dst), derefs, e.explainLoc(src)) + for notes := edge.notes; notes != nil; notes = notes.next { + fmt.Printf("%s: from %v (%v) at %s\n", pos, notes.where, notes.why, linestr(notes.where.Pos)) + } + + if dst == root { + break + } + src = dst + } +} + +func (e *Escape) explainLoc(l *EscLocation) string { + if l == &e.heapLoc { + return "{heap}" + } + if l.n == nil { + // TODO(mdempsky): Omit entirely. + return "{temp}" + } + if l.n.Op == ONAME { + return fmt.Sprintf("%v", l.n) + } + return fmt.Sprintf("{storage for %v}", l.n) +} + // outlives reports whether values stored in l may survive beyond // other's lifetime if stack allocated. func (e *Escape) outlives(l, other *EscLocation) bool {