Commit 063d0f11 authored by Matthew Dempsky's avatar Matthew Dempsky

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 <mdempsky@google.com>
Reviewed-by: default avatarDavid Chase <drchase@google.com>
parent b6245cef
......@@ -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 {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment