mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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: David Chase <drchase@google.com>
This commit is contained in:
parent
b6245cef3c
commit
063d0f11e5
1 changed files with 87 additions and 11 deletions
|
|
@ -107,6 +107,12 @@ type EscLocation struct {
|
||||||
derefs int // >= -1
|
derefs int // >= -1
|
||||||
walkgen uint32
|
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
|
// queued is used by walkAll to track whether this location is
|
||||||
// in the walk queue.
|
// in the walk queue.
|
||||||
queued bool
|
queued bool
|
||||||
|
|
@ -129,6 +135,7 @@ type EscLocation struct {
|
||||||
type EscEdge struct {
|
type EscEdge struct {
|
||||||
src *EscLocation
|
src *EscLocation
|
||||||
derefs int // >= -1
|
derefs int // >= -1
|
||||||
|
notes *EscNote
|
||||||
}
|
}
|
||||||
|
|
||||||
// escapeFuncs performs escape analysis on a minimal batch of
|
// escapeFuncs performs escape analysis on a minimal batch of
|
||||||
|
|
@ -318,7 +325,7 @@ func (e *Escape) stmt(n *Node) {
|
||||||
cv := cas.Rlist.First()
|
cv := cas.Rlist.First()
|
||||||
k := e.dcl(cv) // type switch variables have no ODCL.
|
k := e.dcl(cv) // type switch variables have no ODCL.
|
||||||
if types.Haspointers(cv.Type) {
|
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)
|
k := e.addr(dst)
|
||||||
if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) {
|
if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) {
|
||||||
e.unsafeValue(e.heapHole(), src)
|
e.unsafeValue(e.heapHole().note(where, why), src)
|
||||||
} else {
|
} else {
|
||||||
if ignore {
|
if ignore {
|
||||||
k = e.discardHole()
|
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 {
|
if call.Op == OCALLFUNC {
|
||||||
// Evaluate callee function expression.
|
// 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 {
|
if recv != nil {
|
||||||
// TODO(mdempsky): Handle go:uintptrescapes here too?
|
// 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
|
// Apply augmentParamHole before ODDDARG so that it affects
|
||||||
// the implicit slice allocation for variadic calls, if any.
|
// the implicit slice allocation for variadic calls, if any.
|
||||||
for i, paramK := range paramKs {
|
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.
|
// 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
|
// augmentParamHole augments parameter holes as necessary for use in
|
||||||
// go/defer statements.
|
// 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 {
|
if where == nil {
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
@ -886,7 +894,7 @@ func (e *Escape) augmentParamHole(k EscHole, where *Node) EscHole {
|
||||||
return e.later(k)
|
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.
|
// 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 {
|
type EscHole struct {
|
||||||
dst *EscLocation
|
dst *EscLocation
|
||||||
derefs int // >= -1
|
derefs int // >= -1
|
||||||
|
notes *EscNote
|
||||||
|
}
|
||||||
|
|
||||||
|
type EscNote struct {
|
||||||
|
next *EscNote
|
||||||
|
where *Node
|
||||||
|
why string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k EscHole) note(where *Node, why string) EscHole {
|
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
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1068,7 +1092,7 @@ func (e *Escape) flow(k EscHole, src *EscLocation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mdempsky): Deduplicate edges?
|
// 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() }
|
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.walkgen = walkgen
|
||||||
root.derefs = 0
|
root.derefs = 0
|
||||||
|
root.dst = nil
|
||||||
|
|
||||||
todo := []*EscLocation{root} // LIFO queue
|
todo := []*EscLocation{root} // LIFO queue
|
||||||
for len(todo) > 0 {
|
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
|
// that value flow for tagging the function
|
||||||
// later.
|
// later.
|
||||||
if l.isName(PPARAM) {
|
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)
|
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
|
// outlives it, then l needs to be heap
|
||||||
// allocated.
|
// allocated.
|
||||||
if addressOf && !l.escapes {
|
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
|
l.escapes = true
|
||||||
enqueue(l)
|
enqueue(l)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, edge := range l.edges {
|
for i, edge := range l.edges {
|
||||||
if edge.src.escapes {
|
if edge.src.escapes {
|
||||||
continue
|
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 {
|
if edge.src.walkgen != walkgen || edge.src.derefs > derefs {
|
||||||
edge.src.walkgen = walkgen
|
edge.src.walkgen = walkgen
|
||||||
edge.src.derefs = derefs
|
edge.src.derefs = derefs
|
||||||
|
edge.src.dst = l
|
||||||
|
edge.src.dstEdgeIdx = i
|
||||||
todo = append(todo, edge.src)
|
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
|
// outlives reports whether values stored in l may survive beyond
|
||||||
// other's lifetime if stack allocated.
|
// other's lifetime if stack allocated.
|
||||||
func (e *Escape) outlives(l, other *EscLocation) bool {
|
func (e *Escape) outlives(l, other *EscLocation) bool {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue