2019-04-02 10:40:12 -07:00
|
|
|
// Copyright 2018 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 gc
|
|
|
|
|
|
|
|
|
|
import (
|
2019-10-29 14:44:54 -04:00
|
|
|
"cmd/compile/internal/logopt"
|
2019-04-02 10:40:12 -07:00
|
|
|
"cmd/compile/internal/types"
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
"cmd/internal/src"
|
2019-04-02 10:40:12 -07:00
|
|
|
"fmt"
|
2019-09-26 17:21:50 -07:00
|
|
|
"math"
|
|
|
|
|
"strings"
|
2019-04-02 10:40:12 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Escape analysis.
|
|
|
|
|
//
|
|
|
|
|
// Here we analyze functions to determine which Go variables
|
|
|
|
|
// (including implicit allocations such as calls to "new" or "make",
|
|
|
|
|
// composite literals, etc.) can be allocated on the stack. The two
|
|
|
|
|
// key invariants we have to ensure are: (1) pointers to stack objects
|
|
|
|
|
// cannot be stored in the heap, and (2) pointers to a stack object
|
|
|
|
|
// cannot outlive that object (e.g., because the declaring function
|
|
|
|
|
// returned and destroyed the object's stack frame, or its space is
|
|
|
|
|
// reused across loop iterations for logically distinct variables).
|
|
|
|
|
//
|
|
|
|
|
// We implement this with a static data-flow analysis of the AST.
|
|
|
|
|
// First, we construct a directed weighted graph where vertices
|
|
|
|
|
// (termed "locations") represent variables allocated by statements
|
|
|
|
|
// and expressions, and edges represent assignments between variables
|
2019-09-08 19:36:13 +03:00
|
|
|
// (with weights representing addressing/dereference counts).
|
2019-04-02 10:40:12 -07:00
|
|
|
//
|
|
|
|
|
// Next we walk the graph looking for assignment paths that might
|
|
|
|
|
// violate the invariants stated above. If a variable v's address is
|
|
|
|
|
// stored in the heap or elsewhere that may outlive it, then v is
|
|
|
|
|
// marked as requiring heap allocation.
|
|
|
|
|
//
|
|
|
|
|
// To support interprocedural analysis, we also record data-flow from
|
|
|
|
|
// each function's parameters to the heap and to its result
|
2019-09-08 19:36:13 +03:00
|
|
|
// parameters. This information is summarized as "parameter tags",
|
2019-04-02 10:40:12 -07:00
|
|
|
// which are used at static call sites to improve escape analysis of
|
|
|
|
|
// function arguments.
|
|
|
|
|
|
|
|
|
|
// Constructing the location graph.
|
|
|
|
|
//
|
|
|
|
|
// Every allocating statement (e.g., variable declaration) or
|
|
|
|
|
// expression (e.g., "new" or "make") is first mapped to a unique
|
|
|
|
|
// "location."
|
|
|
|
|
//
|
|
|
|
|
// We also model every Go assignment as a directed edges between
|
2019-09-08 19:36:13 +03:00
|
|
|
// locations. The number of dereference operations minus the number of
|
2019-04-02 10:40:12 -07:00
|
|
|
// addressing operations is recorded as the edge's weight (termed
|
|
|
|
|
// "derefs"). For example:
|
|
|
|
|
//
|
|
|
|
|
// p = &q // -1
|
|
|
|
|
// p = q // 0
|
|
|
|
|
// p = *q // 1
|
|
|
|
|
// p = **q // 2
|
|
|
|
|
//
|
|
|
|
|
// p = **&**&q // 2
|
|
|
|
|
//
|
|
|
|
|
// Note that the & operator can only be applied to addressable
|
|
|
|
|
// expressions, and the expression &x itself is not addressable, so
|
|
|
|
|
// derefs cannot go below -1.
|
|
|
|
|
//
|
|
|
|
|
// Every Go language construct is lowered into this representation,
|
|
|
|
|
// generally without sensitivity to flow, path, or context; and
|
|
|
|
|
// without distinguishing elements within a compound variable. For
|
|
|
|
|
// example:
|
|
|
|
|
//
|
|
|
|
|
// var x struct { f, g *int }
|
|
|
|
|
// var u []*int
|
|
|
|
|
//
|
|
|
|
|
// x.f = u[0]
|
|
|
|
|
//
|
|
|
|
|
// is modeled simply as
|
|
|
|
|
//
|
|
|
|
|
// x = *u
|
|
|
|
|
//
|
|
|
|
|
// That is, we don't distinguish x.f from x.g, or u[0] from u[1],
|
|
|
|
|
// u[2], etc. However, we do record the implicit dereference involved
|
|
|
|
|
// in indexing a slice.
|
|
|
|
|
|
|
|
|
|
type Escape struct {
|
|
|
|
|
allLocs []*EscLocation
|
|
|
|
|
|
|
|
|
|
curfn *Node
|
|
|
|
|
|
|
|
|
|
// loopDepth counts the current loop nesting depth within
|
|
|
|
|
// curfn. It increments within each "for" loop and at each
|
|
|
|
|
// label with a corresponding backwards "goto" (i.e.,
|
|
|
|
|
// unstructured loop).
|
|
|
|
|
loopDepth int
|
|
|
|
|
|
|
|
|
|
heapLoc EscLocation
|
|
|
|
|
blankLoc EscLocation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An EscLocation represents an abstract location that stores a Go
|
|
|
|
|
// variable.
|
|
|
|
|
type EscLocation struct {
|
|
|
|
|
n *Node // represented variable or expression, if any
|
|
|
|
|
curfn *Node // enclosing function
|
|
|
|
|
edges []EscEdge // incoming edges
|
|
|
|
|
loopDepth int // loopDepth at declaration
|
|
|
|
|
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
// derefs and walkgen are used during walkOne to track the
|
2019-04-02 10:40:12 -07:00
|
|
|
// minimal dereferences from the walk root.
|
|
|
|
|
derefs int // >= -1
|
|
|
|
|
walkgen uint32
|
|
|
|
|
|
2019-09-23 10:57:00 -07:00
|
|
|
// 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
|
|
|
|
|
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
// queued is used by walkAll to track whether this location is
|
|
|
|
|
// in the walk queue.
|
|
|
|
|
queued bool
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// escapes reports whether the represented variable's address
|
|
|
|
|
// escapes; that is, whether the variable must be heap
|
|
|
|
|
// allocated.
|
|
|
|
|
escapes bool
|
|
|
|
|
|
|
|
|
|
// transient reports whether the represented expression's
|
|
|
|
|
// address does not outlive the statement; that is, whether
|
|
|
|
|
// its storage can be immediately reused.
|
|
|
|
|
transient bool
|
|
|
|
|
|
2019-09-26 15:55:58 -07:00
|
|
|
// paramEsc records the represented parameter's leak set.
|
|
|
|
|
paramEsc EscLeaks
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An EscEdge represents an assignment edge between two Go variables.
|
|
|
|
|
type EscEdge struct {
|
|
|
|
|
src *EscLocation
|
|
|
|
|
derefs int // >= -1
|
2019-09-23 10:57:00 -07:00
|
|
|
notes *EscNote
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// escapeFuncs performs escape analysis on a minimal batch of
|
|
|
|
|
// functions.
|
|
|
|
|
func escapeFuncs(fns []*Node, recursive bool) {
|
|
|
|
|
for _, fn := range fns {
|
|
|
|
|
if fn.Op != ODCLFUNC {
|
|
|
|
|
Fatalf("unexpected node: %v", fn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var e Escape
|
2019-09-20 15:31:13 -07:00
|
|
|
e.heapLoc.escapes = true
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
// Construct data-flow graph from syntax trees.
|
|
|
|
|
for _, fn := range fns {
|
|
|
|
|
e.initFunc(fn)
|
|
|
|
|
}
|
|
|
|
|
for _, fn := range fns {
|
|
|
|
|
e.walkFunc(fn)
|
|
|
|
|
}
|
|
|
|
|
e.curfn = nil
|
|
|
|
|
|
|
|
|
|
e.walkAll()
|
2019-09-04 15:16:25 -07:00
|
|
|
e.finish(fns)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) initFunc(fn *Node) {
|
|
|
|
|
if fn.Op != ODCLFUNC || fn.Esc != EscFuncUnknown {
|
|
|
|
|
Fatalf("unexpected node: %v", fn)
|
|
|
|
|
}
|
|
|
|
|
fn.Esc = EscFuncPlanned
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m > 3 {
|
2019-04-02 10:40:12 -07:00
|
|
|
Dump("escAnalyze", fn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.curfn = fn
|
|
|
|
|
e.loopDepth = 1
|
|
|
|
|
|
|
|
|
|
// Allocate locations for local variables.
|
|
|
|
|
for _, dcl := range fn.Func.Dcl {
|
|
|
|
|
if dcl.Op == ONAME {
|
2019-09-26 17:21:50 -07:00
|
|
|
e.newLoc(dcl, false)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) walkFunc(fn *Node) {
|
|
|
|
|
fn.Esc = EscFuncStarted
|
|
|
|
|
|
|
|
|
|
// Identify labels that mark the head of an unstructured loop.
|
|
|
|
|
inspectList(fn.Nbody, func(n *Node) bool {
|
|
|
|
|
switch n.Op {
|
|
|
|
|
case OLABEL:
|
|
|
|
|
n.Sym.Label = asTypesNode(&nonlooping)
|
|
|
|
|
|
|
|
|
|
case OGOTO:
|
|
|
|
|
// If we visited the label before the goto,
|
|
|
|
|
// then this is a looping label.
|
|
|
|
|
if n.Sym.Label == asTypesNode(&nonlooping) {
|
|
|
|
|
n.Sym.Label = asTypesNode(&looping)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
e.curfn = fn
|
|
|
|
|
e.loopDepth = 1
|
2019-09-05 11:15:59 +07:00
|
|
|
e.block(fn.Nbody)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Below we implement the methods for walking the AST and recording
|
|
|
|
|
// data flow edges. Note that because a sub-expression might have
|
|
|
|
|
// side-effects, it's important to always visit the entire AST.
|
|
|
|
|
//
|
|
|
|
|
// For example, write either:
|
|
|
|
|
//
|
|
|
|
|
// if x {
|
|
|
|
|
// e.discard(n.Left)
|
|
|
|
|
// } else {
|
|
|
|
|
// e.value(k, n.Left)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// or
|
|
|
|
|
//
|
|
|
|
|
// if x {
|
|
|
|
|
// k = e.discardHole()
|
|
|
|
|
// }
|
|
|
|
|
// e.value(k, n.Left)
|
|
|
|
|
//
|
|
|
|
|
// Do NOT write:
|
|
|
|
|
//
|
|
|
|
|
// // BAD: possibly loses side-effects within n.Left
|
|
|
|
|
// if !x {
|
|
|
|
|
// e.value(k, n.Left)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// stmt evaluates a single Go statement.
|
|
|
|
|
func (e *Escape) stmt(n *Node) {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lno := setlineno(n)
|
|
|
|
|
defer func() {
|
|
|
|
|
lineno = lno
|
|
|
|
|
}()
|
|
|
|
|
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m > 2 {
|
2019-04-02 10:40:12 -07:00
|
|
|
fmt.Printf("%v:[%d] %v stmt: %v\n", linestr(lineno), e.loopDepth, funcSym(e.curfn), n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.stmts(n.Ninit)
|
|
|
|
|
|
|
|
|
|
switch n.Op {
|
|
|
|
|
default:
|
|
|
|
|
Fatalf("unexpected stmt: %v", n)
|
|
|
|
|
|
|
|
|
|
case ODCLCONST, ODCLTYPE, OEMPTY, OFALL, OINLMARK:
|
|
|
|
|
// nop
|
|
|
|
|
|
|
|
|
|
case OBREAK, OCONTINUE, OGOTO:
|
|
|
|
|
// TODO(mdempsky): Handle dead code?
|
|
|
|
|
|
|
|
|
|
case OBLOCK:
|
|
|
|
|
e.stmts(n.List)
|
|
|
|
|
|
|
|
|
|
case ODCL:
|
|
|
|
|
// Record loop depth at declaration.
|
|
|
|
|
if !n.Left.isBlank() {
|
|
|
|
|
e.dcl(n.Left)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OLABEL:
|
|
|
|
|
switch asNode(n.Sym.Label) {
|
|
|
|
|
case &nonlooping:
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m > 2 {
|
2019-04-02 10:40:12 -07:00
|
|
|
fmt.Printf("%v:%v non-looping label\n", linestr(lineno), n)
|
|
|
|
|
}
|
|
|
|
|
case &looping:
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m > 2 {
|
2019-04-02 10:40:12 -07:00
|
|
|
fmt.Printf("%v: %v looping label\n", linestr(lineno), n)
|
|
|
|
|
}
|
|
|
|
|
e.loopDepth++
|
|
|
|
|
default:
|
|
|
|
|
Fatalf("label missing tag")
|
|
|
|
|
}
|
|
|
|
|
n.Sym.Label = nil
|
|
|
|
|
|
|
|
|
|
case OIF:
|
|
|
|
|
e.discard(n.Left)
|
2019-09-05 11:15:59 +07:00
|
|
|
e.block(n.Nbody)
|
|
|
|
|
e.block(n.Rlist)
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
case OFOR, OFORUNTIL:
|
|
|
|
|
e.loopDepth++
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.stmt(n.Right)
|
2019-09-05 11:15:59 +07:00
|
|
|
e.block(n.Nbody)
|
2019-04-02 10:40:12 -07:00
|
|
|
e.loopDepth--
|
|
|
|
|
|
|
|
|
|
case ORANGE:
|
|
|
|
|
// for List = range Right { Nbody }
|
|
|
|
|
e.loopDepth++
|
|
|
|
|
ks := e.addrs(n.List)
|
2019-09-19 16:55:56 -07:00
|
|
|
e.block(n.Nbody)
|
|
|
|
|
e.loopDepth--
|
|
|
|
|
|
|
|
|
|
// Right is evaluated outside the loop.
|
|
|
|
|
k := e.discardHole()
|
2019-04-02 10:40:12 -07:00
|
|
|
if len(ks) >= 2 {
|
|
|
|
|
if n.Right.Type.IsArray() {
|
2019-09-19 16:55:56 -07:00
|
|
|
k = ks[1].note(n, "range")
|
2019-04-02 10:40:12 -07:00
|
|
|
} else {
|
2019-09-19 16:55:56 -07:00
|
|
|
k = ks[1].deref(n, "range-deref")
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-19 16:55:56 -07:00
|
|
|
e.expr(e.later(k), n.Right)
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
case OSWITCH:
|
2019-09-19 16:55:56 -07:00
|
|
|
typesw := n.Left != nil && n.Left.Op == OTYPESW
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2019-09-19 16:55:56 -07:00
|
|
|
var ks []EscHole
|
2019-04-02 10:40:12 -07:00
|
|
|
for _, cas := range n.List.Slice() { // cases
|
2019-09-19 16:55:56 -07:00
|
|
|
if typesw && n.Left.Left != nil {
|
2019-04-02 10:40:12 -07:00
|
|
|
cv := cas.Rlist.First()
|
2019-09-19 16:55:56 -07:00
|
|
|
k := e.dcl(cv) // type switch variables have no ODCL.
|
2020-08-22 14:07:30 -07:00
|
|
|
if cv.Type.HasPointers() {
|
2019-09-23 10:57:00 -07:00
|
|
|
ks = append(ks, k.dotType(cv.Type, cas, "switch case"))
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.discards(cas.List)
|
2019-09-05 11:15:59 +07:00
|
|
|
e.block(cas.Nbody)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-19 16:55:56 -07:00
|
|
|
if typesw {
|
|
|
|
|
e.expr(e.teeHole(ks...), n.Left.Right)
|
|
|
|
|
} else {
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
case OSELECT:
|
|
|
|
|
for _, cas := range n.List.Slice() {
|
|
|
|
|
e.stmt(cas.Left)
|
2019-09-05 11:15:59 +07:00
|
|
|
e.block(cas.Nbody)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
case OSELRECV:
|
|
|
|
|
e.assign(n.Left, n.Right, "selrecv", n)
|
|
|
|
|
case OSELRECV2:
|
|
|
|
|
e.assign(n.Left, n.Right, "selrecv", n)
|
|
|
|
|
e.assign(n.List.First(), nil, "selrecv", n)
|
|
|
|
|
case ORECV:
|
|
|
|
|
// TODO(mdempsky): Consider e.discard(n.Left).
|
|
|
|
|
e.exprSkipInit(e.discardHole(), n) // already visited n.Ninit
|
|
|
|
|
case OSEND:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.assignHeap(n.Right, "send", n)
|
|
|
|
|
|
|
|
|
|
case OAS, OASOP:
|
|
|
|
|
e.assign(n.Left, n.Right, "assign", n)
|
|
|
|
|
|
|
|
|
|
case OAS2:
|
|
|
|
|
for i, nl := range n.List.Slice() {
|
|
|
|
|
e.assign(nl, n.Rlist.Index(i), "assign-pair", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OAS2DOTTYPE: // v, ok = x.(type)
|
2019-09-27 15:59:03 -07:00
|
|
|
e.assign(n.List.First(), n.Right, "assign-pair-dot-type", n)
|
2019-04-02 10:40:12 -07:00
|
|
|
e.assign(n.List.Second(), nil, "assign-pair-dot-type", n)
|
|
|
|
|
case OAS2MAPR: // v, ok = m[k]
|
2019-09-27 15:59:03 -07:00
|
|
|
e.assign(n.List.First(), n.Right, "assign-pair-mapr", n)
|
2019-04-02 10:40:12 -07:00
|
|
|
e.assign(n.List.Second(), nil, "assign-pair-mapr", n)
|
|
|
|
|
case OAS2RECV: // v, ok = <-ch
|
2019-09-27 15:59:03 -07:00
|
|
|
e.assign(n.List.First(), n.Right, "assign-pair-receive", n)
|
2019-04-02 10:40:12 -07:00
|
|
|
e.assign(n.List.Second(), nil, "assign-pair-receive", n)
|
|
|
|
|
|
|
|
|
|
case OAS2FUNC:
|
2019-09-27 15:59:03 -07:00
|
|
|
e.stmts(n.Right.Ninit)
|
|
|
|
|
e.call(e.addrs(n.List), n.Right, nil)
|
2019-04-02 10:40:12 -07:00
|
|
|
case ORETURN:
|
|
|
|
|
results := e.curfn.Type.Results().FieldSlice()
|
|
|
|
|
for i, v := range n.List.Slice() {
|
|
|
|
|
e.assign(asNode(results[i].Nname), v, "return", n)
|
|
|
|
|
}
|
|
|
|
|
case OCALLFUNC, OCALLMETH, OCALLINTER, OCLOSE, OCOPY, ODELETE, OPANIC, OPRINT, OPRINTN, ORECOVER:
|
|
|
|
|
e.call(nil, n, nil)
|
|
|
|
|
case OGO, ODEFER:
|
|
|
|
|
e.stmts(n.Left.Ninit)
|
|
|
|
|
e.call(nil, n.Left, n)
|
|
|
|
|
|
|
|
|
|
case ORETJMP:
|
|
|
|
|
// TODO(mdempsky): What do? esc.go just ignores it.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) stmts(l Nodes) {
|
|
|
|
|
for _, n := range l.Slice() {
|
|
|
|
|
e.stmt(n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 11:15:59 +07:00
|
|
|
// block is like stmts, but preserves loopDepth.
|
|
|
|
|
func (e *Escape) block(l Nodes) {
|
|
|
|
|
old := e.loopDepth
|
|
|
|
|
e.stmts(l)
|
|
|
|
|
e.loopDepth = old
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// expr models evaluating an expression n and flowing the result into
|
|
|
|
|
// hole k.
|
|
|
|
|
func (e *Escape) expr(k EscHole, n *Node) {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
e.stmts(n.Ninit)
|
|
|
|
|
e.exprSkipInit(k, n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) exprSkipInit(k EscHole, n *Node) {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lno := setlineno(n)
|
|
|
|
|
defer func() {
|
|
|
|
|
lineno = lno
|
|
|
|
|
}()
|
|
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
uintptrEscapesHack := k.uintptrEscapesHack
|
|
|
|
|
k.uintptrEscapesHack = false
|
|
|
|
|
|
|
|
|
|
if uintptrEscapesHack && n.Op == OCONVNOP && n.Left.Type.IsUnsafePtr() {
|
|
|
|
|
// nop
|
2020-08-22 14:07:30 -07:00
|
|
|
} else if k.derefs >= 0 && !n.Type.HasPointers() {
|
2019-04-02 10:40:12 -07:00
|
|
|
k = e.discardHole()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch n.Op {
|
|
|
|
|
default:
|
|
|
|
|
Fatalf("unexpected expr: %v", n)
|
|
|
|
|
|
|
|
|
|
case OLITERAL, OGETG, OCLOSUREVAR, OTYPE:
|
|
|
|
|
// nop
|
|
|
|
|
|
|
|
|
|
case ONAME:
|
|
|
|
|
if n.Class() == PFUNC || n.Class() == PEXTERN {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
e.flow(k, e.oldLoc(n))
|
|
|
|
|
|
|
|
|
|
case OPLUS, ONEG, OBITNOT, ONOT:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OLSH, ORSH, OAND, OANDNOT, OEQ, ONE, OLT, OLE, OGT, OGE, OANDAND, OOROR:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.discard(n.Right)
|
|
|
|
|
|
|
|
|
|
case OADDR:
|
|
|
|
|
e.expr(k.addr(n, "address-of"), n.Left) // "address-of"
|
|
|
|
|
case ODEREF:
|
|
|
|
|
e.expr(k.deref(n, "indirection"), n.Left) // "indirection"
|
|
|
|
|
case ODOT, ODOTMETH, ODOTINTER:
|
|
|
|
|
e.expr(k.note(n, "dot"), n.Left)
|
|
|
|
|
case ODOTPTR:
|
|
|
|
|
e.expr(k.deref(n, "dot of pointer"), n.Left) // "dot of pointer"
|
|
|
|
|
case ODOTTYPE, ODOTTYPE2:
|
|
|
|
|
e.expr(k.dotType(n.Type, n, "dot"), n.Left)
|
|
|
|
|
case OINDEX:
|
|
|
|
|
if n.Left.Type.IsArray() {
|
|
|
|
|
e.expr(k.note(n, "fixed-array-index-of"), n.Left)
|
|
|
|
|
} else {
|
|
|
|
|
// TODO(mdempsky): Fix why reason text.
|
|
|
|
|
e.expr(k.deref(n, "dot of pointer"), n.Left)
|
|
|
|
|
}
|
|
|
|
|
e.discard(n.Right)
|
|
|
|
|
case OINDEXMAP:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.discard(n.Right)
|
|
|
|
|
case OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR, OSLICESTR:
|
|
|
|
|
e.expr(k.note(n, "slice"), n.Left)
|
|
|
|
|
low, high, max := n.SliceBounds()
|
|
|
|
|
e.discard(low)
|
|
|
|
|
e.discard(high)
|
|
|
|
|
e.discard(max)
|
|
|
|
|
|
|
|
|
|
case OCONV, OCONVNOP:
|
2020-09-09 12:06:18 +07:00
|
|
|
if checkPtr(e.curfn, 2) && n.Type.IsUnsafePtr() && n.Left.Type.IsPtr() {
|
2019-10-17 16:31:19 -07:00
|
|
|
// When -d=checkptr=2 is enabled, treat
|
2019-10-17 12:31:07 -07:00
|
|
|
// conversions to unsafe.Pointer as an
|
|
|
|
|
// escaping operation. This allows better
|
|
|
|
|
// runtime instrumentation, since we can more
|
|
|
|
|
// easily detect object boundaries on the heap
|
|
|
|
|
// than the stack.
|
|
|
|
|
e.assignHeap(n.Left, "conversion to unsafe.Pointer", n)
|
2020-09-09 12:09:26 +07:00
|
|
|
} else if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() {
|
2019-04-02 10:40:12 -07:00
|
|
|
e.unsafeValue(k, n.Left)
|
|
|
|
|
} else {
|
|
|
|
|
e.expr(k, n.Left)
|
|
|
|
|
}
|
|
|
|
|
case OCONVIFACE:
|
|
|
|
|
if !n.Left.Type.IsInterface() && !isdirectiface(n.Left.Type) {
|
|
|
|
|
k = e.spill(k, n)
|
|
|
|
|
}
|
|
|
|
|
e.expr(k.note(n, "interface-converted"), n.Left)
|
|
|
|
|
|
|
|
|
|
case ORECV:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
|
|
|
|
|
case OCALLMETH, OCALLFUNC, OCALLINTER, OLEN, OCAP, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCOPY:
|
|
|
|
|
e.call([]EscHole{k}, n, nil)
|
|
|
|
|
|
|
|
|
|
case ONEW:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
|
|
|
|
|
case OMAKESLICE:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.discard(n.Right)
|
|
|
|
|
case OMAKECHAN:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
case OMAKEMAP:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
|
|
|
|
|
case ORECOVER:
|
|
|
|
|
// nop
|
|
|
|
|
|
|
|
|
|
case OCALLPART:
|
2020-04-20 11:14:36 -07:00
|
|
|
// Flow the receiver argument to both the closure and
|
|
|
|
|
// to the receiver parameter.
|
|
|
|
|
|
|
|
|
|
closureK := e.spill(k, n)
|
|
|
|
|
|
|
|
|
|
m := callpartMethod(n)
|
|
|
|
|
|
|
|
|
|
// We don't know how the method value will be called
|
|
|
|
|
// later, so conservatively assume the result
|
|
|
|
|
// parameters all flow to the heap.
|
|
|
|
|
//
|
|
|
|
|
// TODO(mdempsky): Change ks into a callback, so that
|
|
|
|
|
// we don't have to create this dummy slice?
|
|
|
|
|
var ks []EscHole
|
|
|
|
|
for i := m.Type.NumResults(); i > 0; i-- {
|
|
|
|
|
ks = append(ks, e.heapHole())
|
|
|
|
|
}
|
2020-11-22 20:43:16 -08:00
|
|
|
paramK := e.tagHole(ks, asNode(m.Nname), m.Type.Recv())
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2020-04-20 11:14:36 -07:00
|
|
|
e.expr(e.teeHole(paramK, closureK), n.Left)
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
case OPTRLIT:
|
|
|
|
|
e.expr(e.spill(k, n), n.Left)
|
|
|
|
|
|
|
|
|
|
case OARRAYLIT:
|
|
|
|
|
for _, elt := range n.List.Slice() {
|
|
|
|
|
if elt.Op == OKEY {
|
|
|
|
|
elt = elt.Right
|
|
|
|
|
}
|
|
|
|
|
e.expr(k.note(n, "array literal element"), elt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OSLICELIT:
|
|
|
|
|
k = e.spill(k, n)
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
for _, elt := range n.List.Slice() {
|
|
|
|
|
if elt.Op == OKEY {
|
|
|
|
|
elt = elt.Right
|
|
|
|
|
}
|
|
|
|
|
e.expr(k.note(n, "slice-literal-element"), elt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OSTRUCTLIT:
|
|
|
|
|
for _, elt := range n.List.Slice() {
|
|
|
|
|
e.expr(k.note(n, "struct literal element"), elt.Left)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OMAPLIT:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
|
|
|
|
|
// Map keys and values are always stored in the heap.
|
|
|
|
|
for _, elt := range n.List.Slice() {
|
|
|
|
|
e.assignHeap(elt.Left, "map literal key", n)
|
|
|
|
|
e.assignHeap(elt.Right, "map literal value", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OCLOSURE:
|
|
|
|
|
k = e.spill(k, n)
|
|
|
|
|
|
|
|
|
|
// Link addresses of captured variables to closure.
|
|
|
|
|
for _, v := range n.Func.Closure.Func.Cvars.Slice() {
|
|
|
|
|
if v.Op == OXXX { // unnamed out argument; see dcl.go:/^funcargs
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k := k
|
|
|
|
|
if !v.Name.Byval() {
|
|
|
|
|
k = k.addr(v, "reference")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.expr(k.note(n, "captured by a closure"), v.Name.Defn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case ORUNES2STR, OBYTES2STR, OSTR2RUNES, OSTR2BYTES, ORUNESTR:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
|
|
|
|
|
case OADDSTR:
|
|
|
|
|
e.spill(k, n)
|
|
|
|
|
|
|
|
|
|
// Arguments of OADDSTR never escape;
|
|
|
|
|
// runtime.concatstrings makes sure of that.
|
|
|
|
|
e.discards(n.List)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unsafeValue evaluates a uintptr-typed arithmetic expression looking
|
|
|
|
|
// for conversions from an unsafe.Pointer.
|
|
|
|
|
func (e *Escape) unsafeValue(k EscHole, n *Node) {
|
|
|
|
|
if n.Type.Etype != TUINTPTR {
|
|
|
|
|
Fatalf("unexpected type %v for %v", n.Type, n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.stmts(n.Ninit)
|
|
|
|
|
|
|
|
|
|
switch n.Op {
|
|
|
|
|
case OCONV, OCONVNOP:
|
2020-09-09 12:06:18 +07:00
|
|
|
if n.Left.Type.IsUnsafePtr() {
|
2019-04-02 10:40:12 -07:00
|
|
|
e.expr(k, n.Left)
|
|
|
|
|
} else {
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
}
|
|
|
|
|
case ODOTPTR:
|
|
|
|
|
if isReflectHeaderDataField(n) {
|
|
|
|
|
e.expr(k.deref(n, "reflect.Header.Data"), n.Left)
|
|
|
|
|
} else {
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
}
|
|
|
|
|
case OPLUS, ONEG, OBITNOT:
|
|
|
|
|
e.unsafeValue(k, n.Left)
|
2019-07-07 23:08:20 +07:00
|
|
|
case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OAND, OANDNOT:
|
2019-04-02 10:40:12 -07:00
|
|
|
e.unsafeValue(k, n.Left)
|
|
|
|
|
e.unsafeValue(k, n.Right)
|
2019-07-07 23:08:20 +07:00
|
|
|
case OLSH, ORSH:
|
|
|
|
|
e.unsafeValue(k, n.Left)
|
|
|
|
|
// RHS need not be uintptr-typed (#32959) and can't meaningfully
|
|
|
|
|
// flow pointers anyway.
|
|
|
|
|
e.discard(n.Right)
|
2019-04-02 10:40:12 -07:00
|
|
|
default:
|
|
|
|
|
e.exprSkipInit(e.discardHole(), n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// discard evaluates an expression n for side-effects, but discards
|
|
|
|
|
// its value.
|
|
|
|
|
func (e *Escape) discard(n *Node) {
|
|
|
|
|
e.expr(e.discardHole(), n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) discards(l Nodes) {
|
|
|
|
|
for _, n := range l.Slice() {
|
|
|
|
|
e.discard(n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addr evaluates an addressable expression n and returns an EscHole
|
|
|
|
|
// that represents storing into the represented location.
|
|
|
|
|
func (e *Escape) addr(n *Node) EscHole {
|
|
|
|
|
if n == nil || n.isBlank() {
|
|
|
|
|
// Can happen at least in OSELRECV.
|
|
|
|
|
// TODO(mdempsky): Anywhere else?
|
|
|
|
|
return e.discardHole()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k := e.heapHole()
|
|
|
|
|
|
|
|
|
|
switch n.Op {
|
|
|
|
|
default:
|
|
|
|
|
Fatalf("unexpected addr: %v", n)
|
|
|
|
|
case ONAME:
|
|
|
|
|
if n.Class() == PEXTERN {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
k = e.oldLoc(n).asHole()
|
|
|
|
|
case ODOT:
|
|
|
|
|
k = e.addr(n.Left)
|
|
|
|
|
case OINDEX:
|
|
|
|
|
e.discard(n.Right)
|
|
|
|
|
if n.Left.Type.IsArray() {
|
|
|
|
|
k = e.addr(n.Left)
|
|
|
|
|
} else {
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
}
|
|
|
|
|
case ODEREF, ODOTPTR:
|
|
|
|
|
e.discard(n)
|
|
|
|
|
case OINDEXMAP:
|
|
|
|
|
e.discard(n.Left)
|
|
|
|
|
e.assignHeap(n.Right, "key of map put", n)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 14:07:30 -07:00
|
|
|
if !n.Type.HasPointers() {
|
2019-04-02 10:40:12 -07:00
|
|
|
k = e.discardHole()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) addrs(l Nodes) []EscHole {
|
|
|
|
|
var ks []EscHole
|
|
|
|
|
for _, n := range l.Slice() {
|
|
|
|
|
ks = append(ks, e.addr(n))
|
|
|
|
|
}
|
|
|
|
|
return ks
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// assign evaluates the assignment dst = src.
|
|
|
|
|
func (e *Escape) assign(dst, src *Node, why string, where *Node) {
|
|
|
|
|
// Filter out some no-op assignments for escape analysis.
|
|
|
|
|
ignore := dst != nil && src != nil && isSelfAssign(dst, src)
|
2020-10-19 11:31:10 +02:00
|
|
|
if ignore && Debug.m != 0 {
|
2019-04-02 10:40:12 -07:00
|
|
|
Warnl(where.Pos, "%v ignoring self-assignment in %S", funcSym(e.curfn), where)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k := e.addr(dst)
|
|
|
|
|
if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) {
|
2019-09-23 10:57:00 -07:00
|
|
|
e.unsafeValue(e.heapHole().note(where, why), src)
|
2019-04-02 10:40:12 -07:00
|
|
|
} else {
|
|
|
|
|
if ignore {
|
|
|
|
|
k = e.discardHole()
|
|
|
|
|
}
|
2019-09-23 10:57:00 -07:00
|
|
|
e.expr(k.note(where, why), src)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) assignHeap(src *Node, why string, where *Node) {
|
|
|
|
|
e.expr(e.heapHole().note(where, why), src)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// call evaluates a call expressions, including builtin calls. ks
|
|
|
|
|
// should contain the holes representing where the function callee's
|
|
|
|
|
// results flows; where is the OGO/ODEFER context of the call, if any.
|
|
|
|
|
func (e *Escape) call(ks []EscHole, call, where *Node) {
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
topLevelDefer := where != nil && where.Op == ODEFER && e.loopDepth == 1
|
|
|
|
|
if topLevelDefer {
|
|
|
|
|
// force stack allocation of defer record, unless
|
|
|
|
|
// open-coded defers are used (see ssa.go)
|
|
|
|
|
where.Esc = EscNever
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
argument := func(k EscHole, arg *Node) {
|
|
|
|
|
if topLevelDefer {
|
|
|
|
|
// Top level defers arguments don't escape to
|
|
|
|
|
// heap, but they do need to last until end of
|
|
|
|
|
// function.
|
|
|
|
|
k = e.later(k)
|
|
|
|
|
} else if where != nil {
|
|
|
|
|
k = e.heapHole()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.expr(k.note(call, "call parameter"), arg)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
switch call.Op {
|
|
|
|
|
default:
|
|
|
|
|
Fatalf("unexpected call op: %v", call.Op)
|
|
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
case OCALLFUNC, OCALLMETH, OCALLINTER:
|
|
|
|
|
fixVariadicCall(call)
|
|
|
|
|
|
|
|
|
|
// Pick out the function callee, if statically known.
|
|
|
|
|
var fn *Node
|
|
|
|
|
switch call.Op {
|
|
|
|
|
case OCALLFUNC:
|
2020-09-22 02:12:03 -07:00
|
|
|
switch v := staticValue(call.Left); {
|
|
|
|
|
case v.Op == ONAME && v.Class() == PFUNC:
|
|
|
|
|
fn = v
|
|
|
|
|
case v.Op == OCLOSURE:
|
|
|
|
|
fn = v.Func.Closure.Func.Nname
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
}
|
|
|
|
|
case OCALLMETH:
|
2020-11-22 20:43:16 -08:00
|
|
|
fn = call.Left.MethodName()
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fntype := call.Left.Type
|
|
|
|
|
if fn != nil {
|
|
|
|
|
fntype = fn.Type
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2020-04-20 11:14:15 -07:00
|
|
|
if ks != nil && fn != nil && e.inMutualBatch(fn) {
|
|
|
|
|
for i, result := range fn.Type.Results().FieldSlice() {
|
2019-04-02 10:40:12 -07:00
|
|
|
e.expr(ks[i], asNode(result.Nname))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r := fntype.Recv(); r != nil {
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
argument(e.tagHole(ks, fn, r), call.Left.Left)
|
|
|
|
|
} else {
|
|
|
|
|
// Evaluate callee function expression.
|
|
|
|
|
argument(e.discardHole(), call.Left)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
args := call.List.Slice()
|
|
|
|
|
for i, param := range fntype.Params().FieldSlice() {
|
|
|
|
|
argument(e.tagHole(ks, fn, param), args[i])
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
2019-04-19 12:20:56 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
case OAPPEND:
|
|
|
|
|
args := call.List.Slice()
|
2019-04-02 10:40:12 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
// Appendee slice may flow directly to the result, if
|
|
|
|
|
// it has enough capacity. Alternatively, a new heap
|
|
|
|
|
// slice might be allocated, and all slice elements
|
|
|
|
|
// might flow to heap.
|
|
|
|
|
appendeeK := ks[0]
|
2020-08-22 14:07:30 -07:00
|
|
|
if args[0].Type.Elem().HasPointers() {
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice"))
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
argument(appendeeK, args[0])
|
2019-04-02 10:40:12 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
if call.IsDDD() {
|
|
|
|
|
appendedK := e.discardHole()
|
2020-08-22 14:07:30 -07:00
|
|
|
if args[1].Type.IsSlice() && args[1].Type.Elem().HasPointers() {
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
appendedK = e.heapHole().deref(call, "appended slice...")
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
argument(appendedK, args[1])
|
|
|
|
|
} else {
|
|
|
|
|
for _, arg := range args[1:] {
|
|
|
|
|
argument(e.heapHole(), arg)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
case OCOPY:
|
|
|
|
|
argument(e.discardHole(), call.Left)
|
2019-04-02 10:40:12 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
copiedK := e.discardHole()
|
2020-08-22 14:07:30 -07:00
|
|
|
if call.Right.Type.IsSlice() && call.Right.Type.Elem().HasPointers() {
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
copiedK = e.heapHole().deref(call, "copied slice")
|
|
|
|
|
}
|
|
|
|
|
argument(copiedK, call.Right)
|
2019-04-19 12:20:56 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
case OPANIC:
|
|
|
|
|
argument(e.heapHole(), call.Left)
|
2019-04-19 12:20:56 -07:00
|
|
|
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
case OCOMPLEX:
|
|
|
|
|
argument(e.discardHole(), call.Left)
|
|
|
|
|
argument(e.discardHole(), call.Right)
|
|
|
|
|
case ODELETE, OPRINT, OPRINTN, ORECOVER:
|
|
|
|
|
for _, arg := range call.List.Slice() {
|
|
|
|
|
argument(e.discardHole(), arg)
|
|
|
|
|
}
|
|
|
|
|
case OLEN, OCAP, OREAL, OIMAG, OCLOSE:
|
|
|
|
|
argument(e.discardHole(), call.Left)
|
|
|
|
|
}
|
2019-04-19 12:20:56 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// tagHole returns a hole for evaluating an argument passed to param.
|
|
|
|
|
// ks should contain the holes representing where the function
|
2020-04-20 11:14:15 -07:00
|
|
|
// callee's results flows. fn is the statically-known callee function,
|
|
|
|
|
// if any.
|
|
|
|
|
func (e *Escape) tagHole(ks []EscHole, fn *Node, param *types.Field) EscHole {
|
2019-04-02 10:40:12 -07:00
|
|
|
// If this is a dynamic call, we can't rely on param.Note.
|
2020-04-20 11:14:15 -07:00
|
|
|
if fn == nil {
|
2019-04-02 10:40:12 -07:00
|
|
|
return e.heapHole()
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-20 11:14:15 -07:00
|
|
|
if e.inMutualBatch(fn) {
|
|
|
|
|
return e.addr(asNode(param.Nname))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call to previously tagged function.
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
|
|
|
|
|
if param.Note == uintptrEscapesTag {
|
|
|
|
|
k := e.heapHole()
|
|
|
|
|
k.uintptrEscapesHack = true
|
|
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
var tagKs []EscHole
|
2019-09-26 15:55:58 -07:00
|
|
|
|
|
|
|
|
esc := ParseLeaks(param.Note)
|
|
|
|
|
if x := esc.Heap(); x >= 0 {
|
|
|
|
|
tagKs = append(tagKs, e.heapHole().shift(x))
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ks != nil {
|
2019-09-26 15:55:58 -07:00
|
|
|
for i := 0; i < numEscResults; i++ {
|
|
|
|
|
if x := esc.Result(i); x >= 0 {
|
2019-04-02 10:40:12 -07:00
|
|
|
tagKs = append(tagKs, ks[i].shift(x))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return e.teeHole(tagKs...)
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-20 11:14:15 -07:00
|
|
|
// inMutualBatch reports whether function fn is in the batch of
|
|
|
|
|
// mutually recursive functions being analyzed. When this is true,
|
|
|
|
|
// fn has not yet been analyzed, so its parameters and results
|
|
|
|
|
// should be incorporated directly into the flow graph instead of
|
|
|
|
|
// relying on its escape analysis tagging.
|
|
|
|
|
func (e *Escape) inMutualBatch(fn *Node) bool {
|
|
|
|
|
if fn.Name.Defn != nil && fn.Name.Defn.Esc < EscFuncTagged {
|
|
|
|
|
if fn.Name.Defn.Esc == EscFuncUnknown {
|
|
|
|
|
Fatalf("graph inconsistency")
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// An EscHole represents a context for evaluation a Go
|
|
|
|
|
// expression. E.g., when evaluating p in "x = **p", we'd have a hole
|
|
|
|
|
// with dst==x and derefs==2.
|
|
|
|
|
type EscHole struct {
|
|
|
|
|
dst *EscLocation
|
|
|
|
|
derefs int // >= -1
|
2019-09-23 10:57:00 -07:00
|
|
|
notes *EscNote
|
cmd/compile: use fixVariadicCall in escape analysis
This CL uses fixVariadicCall before escape analyzing function calls.
This has a number of benefits, though also some minor obstacles:
Most notably, it allows us to remove ODDDARG along with the logic
involved in setting it up, manipulating EscHoles, and later copying
its escape analysis flags to the actual slice argument. Instead, we
uniformly handle all variadic calls the same way. (E.g., issue31573.go
is updated because now f() and f(nil...) are handled identically.)
It also allows us to simplify handling of builtins and generic
function calls. Previously handling of calls was hairy enough to
require multiple dispatches on n.Op, whereas now the logic is uniform
enough that we can easily handle it with a single dispatch.
The downside is handling //go:uintptrescapes is now somewhat clumsy.
(It used to be clumsy, but it still is, too.) The proper fix here is
probably to stop using escape analysis tags for //go:uintptrescapes
and unsafe-uintptr, and have an earlier pass responsible for them.
Finally, note that while we now call fixVariadicCall in Escape, we
still have to call it in Order, because we don't (yet) run Escape on
all compiler-generated functions. In particular, the generated "init"
function for initializing package-level variables can contain calls to
variadic functions and isn't escape analyzed.
Passes toolstash-check -race.
Change-Id: I4cdb92a393ac487910aeee58a5cb8c1500eef881
Reviewed-on: https://go-review.googlesource.com/c/go/+/229759
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
2020-04-21 19:48:02 -07:00
|
|
|
|
|
|
|
|
// uintptrEscapesHack indicates this context is evaluating an
|
|
|
|
|
// argument for a //go:uintptrescapes function.
|
|
|
|
|
uintptrEscapesHack bool
|
2019-09-23 10:57:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type EscNote struct {
|
|
|
|
|
next *EscNote
|
|
|
|
|
where *Node
|
|
|
|
|
why string
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (k EscHole) note(where *Node, why string) EscHole {
|
2019-09-23 10:57:00 -07:00
|
|
|
if where == nil || why == "" {
|
|
|
|
|
Fatalf("note: missing where/why")
|
|
|
|
|
}
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m >= 2 || logopt.Enabled() {
|
2019-09-23 10:57:00 -07:00
|
|
|
k.notes = &EscNote{
|
|
|
|
|
next: k.notes,
|
|
|
|
|
where: where,
|
|
|
|
|
why: why,
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (k EscHole) shift(delta int) EscHole {
|
|
|
|
|
k.derefs += delta
|
|
|
|
|
if k.derefs < -1 {
|
|
|
|
|
Fatalf("derefs underflow: %v", k.derefs)
|
|
|
|
|
}
|
|
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (k EscHole) deref(where *Node, why string) EscHole { return k.shift(1).note(where, why) }
|
|
|
|
|
func (k EscHole) addr(where *Node, why string) EscHole { return k.shift(-1).note(where, why) }
|
|
|
|
|
|
|
|
|
|
func (k EscHole) dotType(t *types.Type, where *Node, why string) EscHole {
|
|
|
|
|
if !t.IsInterface() && !isdirectiface(t) {
|
|
|
|
|
k = k.shift(1)
|
|
|
|
|
}
|
|
|
|
|
return k.note(where, why)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// teeHole returns a new hole that flows into each hole of ks,
|
|
|
|
|
// similar to the Unix tee(1) command.
|
|
|
|
|
func (e *Escape) teeHole(ks ...EscHole) EscHole {
|
|
|
|
|
if len(ks) == 0 {
|
|
|
|
|
return e.discardHole()
|
|
|
|
|
}
|
|
|
|
|
if len(ks) == 1 {
|
|
|
|
|
return ks[0]
|
|
|
|
|
}
|
|
|
|
|
// TODO(mdempsky): Optimize if there's only one non-discard hole?
|
|
|
|
|
|
|
|
|
|
// Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a
|
|
|
|
|
// new temporary location ltmp, wire it into place, and return
|
|
|
|
|
// a hole for "ltmp = _".
|
|
|
|
|
loc := e.newLoc(nil, true)
|
|
|
|
|
for _, k := range ks {
|
|
|
|
|
// N.B., "p = &q" and "p = &tmp; tmp = q" are not
|
|
|
|
|
// semantically equivalent. To combine holes like "l1
|
|
|
|
|
// = _" and "l2 = &_", we'd need to wire them as "l1 =
|
|
|
|
|
// *ltmp" and "l2 = ltmp" and return "ltmp = &_"
|
|
|
|
|
// instead.
|
|
|
|
|
if k.derefs < 0 {
|
|
|
|
|
Fatalf("teeHole: negative derefs")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.flow(k, loc)
|
|
|
|
|
}
|
|
|
|
|
return loc.asHole()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) dcl(n *Node) EscHole {
|
|
|
|
|
loc := e.oldLoc(n)
|
|
|
|
|
loc.loopDepth = e.loopDepth
|
|
|
|
|
return loc.asHole()
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 16:55:56 -07:00
|
|
|
// spill allocates a new location associated with expression n, flows
|
|
|
|
|
// its address to k, and returns a hole that flows values to it. It's
|
|
|
|
|
// intended for use with most expressions that allocate storage.
|
2019-04-02 10:40:12 -07:00
|
|
|
func (e *Escape) spill(k EscHole, n *Node) EscHole {
|
|
|
|
|
loc := e.newLoc(n, true)
|
|
|
|
|
e.flow(k.addr(n, "spill"), loc)
|
|
|
|
|
return loc.asHole()
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 16:55:56 -07:00
|
|
|
// later returns a new hole that flows into k, but some time later.
|
|
|
|
|
// Its main effect is to prevent immediate reuse of temporary
|
|
|
|
|
// variables introduced during Order.
|
|
|
|
|
func (e *Escape) later(k EscHole) EscHole {
|
|
|
|
|
loc := e.newLoc(nil, false)
|
|
|
|
|
e.flow(k, loc)
|
|
|
|
|
return loc.asHole()
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// canonicalNode returns the canonical *Node that n logically
|
|
|
|
|
// represents.
|
|
|
|
|
func canonicalNode(n *Node) *Node {
|
cmd/compile: move some ONAME-specific flags from Node to Name
The IsClosureVar, IsOutputParamHeapAddr, Assigned, Addrtaken,
InlFormal, and InlLocal flags are only interesting for ONAME nodes, so
it's better to set these flags on Name.flags instead of Node.flags.
Two caveats though:
1. Previously, we would set Assigned and Addrtaken on the entire
expression tree involved in an assignment or addressing operation.
However, the rest of the compiler only actually cares about knowing
whether the underlying ONAME (if any) was assigned/addressed.
2. This actually requires bumping Name.flags from bitset8 to bitset16,
whereas it doesn't allow shrinking Node.flags any. However, Name has
some trailing padding bytes, so expanding Name.flags doesn't cost any
memory.
Passes toolstash-check.
Change-Id: I7775d713566a38d5b9723360b1659b79391744c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/200898
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-10-12 16:21:55 -07:00
|
|
|
if n != nil && n.Op == ONAME && n.Name.IsClosureVar() {
|
2019-04-02 10:40:12 -07:00
|
|
|
n = n.Name.Defn
|
cmd/compile: move some ONAME-specific flags from Node to Name
The IsClosureVar, IsOutputParamHeapAddr, Assigned, Addrtaken,
InlFormal, and InlLocal flags are only interesting for ONAME nodes, so
it's better to set these flags on Name.flags instead of Node.flags.
Two caveats though:
1. Previously, we would set Assigned and Addrtaken on the entire
expression tree involved in an assignment or addressing operation.
However, the rest of the compiler only actually cares about knowing
whether the underlying ONAME (if any) was assigned/addressed.
2. This actually requires bumping Name.flags from bitset8 to bitset16,
whereas it doesn't allow shrinking Node.flags any. However, Name has
some trailing padding bytes, so expanding Name.flags doesn't cost any
memory.
Passes toolstash-check.
Change-Id: I7775d713566a38d5b9723360b1659b79391744c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/200898
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-10-12 16:21:55 -07:00
|
|
|
if n.Name.IsClosureVar() {
|
2019-04-02 10:40:12 -07:00
|
|
|
Fatalf("still closure var")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) newLoc(n *Node, transient bool) *EscLocation {
|
|
|
|
|
if e.curfn == nil {
|
|
|
|
|
Fatalf("e.curfn isn't set")
|
|
|
|
|
}
|
2020-08-21 20:20:12 -07:00
|
|
|
if n != nil && n.Type != nil && n.Type.NotInHeap() {
|
2020-08-27 14:05:52 -07:00
|
|
|
yyerrorl(n.Pos, "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type)
|
2020-08-21 20:20:12 -07:00
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
n = canonicalNode(n)
|
|
|
|
|
loc := &EscLocation{
|
|
|
|
|
n: n,
|
|
|
|
|
curfn: e.curfn,
|
|
|
|
|
loopDepth: e.loopDepth,
|
|
|
|
|
transient: transient,
|
|
|
|
|
}
|
|
|
|
|
e.allLocs = append(e.allLocs, loc)
|
|
|
|
|
if n != nil {
|
|
|
|
|
if n.Op == ONAME && n.Name.Curfn != e.curfn {
|
|
|
|
|
Fatalf("curfn mismatch: %v != %v", n.Name.Curfn, e.curfn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.HasOpt() {
|
|
|
|
|
Fatalf("%v already has a location", n)
|
|
|
|
|
}
|
|
|
|
|
n.SetOpt(loc)
|
|
|
|
|
|
2020-10-01 12:03:27 +02:00
|
|
|
if why := heapAllocReason(n); why != "" {
|
2019-11-07 12:32:30 -08:00
|
|
|
e.flow(e.heapHole().addr(n, why), loc)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return loc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) oldLoc(n *Node) *EscLocation {
|
|
|
|
|
n = canonicalNode(n)
|
|
|
|
|
return n.Opt().(*EscLocation)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *EscLocation) asHole() EscHole {
|
|
|
|
|
return EscHole{dst: l}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) flow(k EscHole, src *EscLocation) {
|
|
|
|
|
dst := k.dst
|
|
|
|
|
if dst == &e.blankLoc {
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-09-20 15:31:13 -07:00
|
|
|
if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ...
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if dst.escapes && k.derefs < 0 { // dst = &src
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m >= 2 || logopt.Enabled() {
|
2019-11-07 12:32:30 -08:00
|
|
|
pos := linestr(src.n.Pos)
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m >= 2 {
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
fmt.Printf("%s: %v escapes to heap:\n", pos, src.n)
|
|
|
|
|
}
|
|
|
|
|
explanation := e.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{})
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
logopt.LogOpt(src.n.Pos, "escapes", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", src.n), explanation)
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
2019-09-20 15:31:13 -07:00
|
|
|
src.escapes = true
|
2019-04-02 10:40:12 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(mdempsky): Deduplicate edges?
|
2019-09-23 10:57:00 -07:00
|
|
|
dst.edges = append(dst.edges, EscEdge{src: src, derefs: k.derefs, notes: k.notes})
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Escape) heapHole() EscHole { return e.heapLoc.asHole() }
|
|
|
|
|
func (e *Escape) discardHole() EscHole { return e.blankLoc.asHole() }
|
|
|
|
|
|
|
|
|
|
// walkAll computes the minimal dereferences between all pairs of
|
|
|
|
|
// locations.
|
|
|
|
|
func (e *Escape) walkAll() {
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
// We use a work queue to keep track of locations that we need
|
|
|
|
|
// to visit, and repeatedly walk until we reach a fixed point.
|
2019-09-20 15:31:13 -07:00
|
|
|
//
|
|
|
|
|
// We walk once from each location (including the heap), and
|
|
|
|
|
// then re-enqueue each location on its transition from
|
|
|
|
|
// transient->!transient and !escapes->escapes, which can each
|
|
|
|
|
// happen at most once. So we take Θ(len(e.allLocs)) walks.
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
|
2020-04-23 02:21:59 +07:00
|
|
|
// LIFO queue, has enough room for e.allLocs and e.heapLoc.
|
|
|
|
|
todo := make([]*EscLocation, 0, len(e.allLocs)+1)
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
enqueue := func(loc *EscLocation) {
|
|
|
|
|
if !loc.queued {
|
|
|
|
|
todo = append(todo, loc)
|
|
|
|
|
loc.queued = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
|
|
|
|
|
for _, loc := range e.allLocs {
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
enqueue(loc)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
2019-09-20 15:31:13 -07:00
|
|
|
enqueue(&e.heapLoc)
|
2019-04-02 10:40:12 -07:00
|
|
|
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
var walkgen uint32
|
|
|
|
|
for len(todo) > 0 {
|
|
|
|
|
root := todo[len(todo)-1]
|
|
|
|
|
todo = todo[:len(todo)-1]
|
|
|
|
|
root.queued = false
|
|
|
|
|
|
|
|
|
|
walkgen++
|
|
|
|
|
e.walkOne(root, walkgen, enqueue)
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// walkOne computes the minimal number of dereferences from root to
|
|
|
|
|
// all other locations.
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLocation)) {
|
2019-04-02 10:40:12 -07:00
|
|
|
// The data flow graph has negative edges (from addressing
|
|
|
|
|
// operations), so we use the Bellman-Ford algorithm. However,
|
|
|
|
|
// we don't have to worry about infinite negative cycles since
|
|
|
|
|
// we bound intermediate dereference counts to 0.
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
root.walkgen = walkgen
|
|
|
|
|
root.derefs = 0
|
2019-09-23 10:57:00 -07:00
|
|
|
root.dst = nil
|
2019-04-02 10:40:12 -07:00
|
|
|
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
todo := []*EscLocation{root} // LIFO queue
|
2019-04-02 10:40:12 -07:00
|
|
|
for len(todo) > 0 {
|
|
|
|
|
l := todo[len(todo)-1]
|
|
|
|
|
todo = todo[:len(todo)-1]
|
|
|
|
|
|
|
|
|
|
base := l.derefs
|
|
|
|
|
|
|
|
|
|
// If l.derefs < 0, then l's address flows to root.
|
|
|
|
|
addressOf := base < 0
|
|
|
|
|
if addressOf {
|
|
|
|
|
// For a flow path like "root = &l; l = x",
|
|
|
|
|
// l's address flows to root, but x's does
|
|
|
|
|
// not. We recognize this by lower bounding
|
|
|
|
|
// base at 0.
|
|
|
|
|
base = 0
|
|
|
|
|
|
|
|
|
|
// If l's address flows to a non-transient
|
|
|
|
|
// location, then l can't be transiently
|
|
|
|
|
// allocated.
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
if !root.transient && l.transient {
|
2019-04-02 10:40:12 -07:00
|
|
|
l.transient = false
|
cmd/compile: use proper work queue for escape graph walking
The old escape analysis code used to repeatedly walk the entire flow
graph until it reached a fixed point. With escape.go, I wanted to
avoid this if possible, so I structured the walking code with two
constraints:
1. Always walk from the heap location last.
2. If an object escapes, ensure it has flow edge to the heap location.
This works, but it precludes some graph construction
optimizations. E.g., if there's an assignment "heap = &x", then we can
immediately tell that 'x' escapes without needing to visit it during
the graph walk. Similarly, if there's a later assignment "x = &y", we
could immediately tell that 'y' escapes too. However, the natural way
to implement this optimization ends up violating the constraints
above.
Further, the constraints above don't guarantee that the 'transient'
flag is handled correctly. Today I think that's handled correctly
because of the order that locations happen to be constructed and
visited based on the AST, but I've felt uneasy about it for a little
while.
This CL changes walkAll to use a proper work queue (technically a work
stack) to track locations that need to be visited, and allows walkOne
to request that a location be re-visited.
Passes toolstash-check.
Change-Id: Iaa6f4d3fe4719c04d67009fb9a2a3e4930b3d7c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/196958
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-09-20 15:27:14 -07:00
|
|
|
enqueue(l)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if e.outlives(root, l) {
|
|
|
|
|
// l's value flows to root. If l is a function
|
|
|
|
|
// parameter and root is the heap or a
|
|
|
|
|
// corresponding result parameter, then record
|
|
|
|
|
// that value flow for tagging the function
|
|
|
|
|
// later.
|
|
|
|
|
if l.isName(PPARAM) {
|
2020-10-19 11:31:10 +02:00
|
|
|
if (logopt.Enabled() || Debug.m >= 2) && !l.escapes {
|
|
|
|
|
if Debug.m >= 2 {
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", linestr(l.n.Pos), l.n, e.explainLoc(root), base)
|
|
|
|
|
}
|
|
|
|
|
explanation := e.explainPath(root, l)
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
logopt.LogOpt(l.n.Pos, "leak", "escape", e.curfn.funcname(),
|
|
|
|
|
fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, e.explainLoc(root), base), explanation)
|
|
|
|
|
}
|
2019-09-23 10:57:00 -07:00
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
l.leakTo(root, base)
|
|
|
|
|
}
|
2019-09-20 15:31:13 -07:00
|
|
|
|
|
|
|
|
// If l's address flows somewhere that
|
|
|
|
|
// outlives it, then l needs to be heap
|
|
|
|
|
// allocated.
|
|
|
|
|
if addressOf && !l.escapes {
|
2020-10-19 11:31:10 +02:00
|
|
|
if logopt.Enabled() || Debug.m >= 2 {
|
|
|
|
|
if Debug.m >= 2 {
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
fmt.Printf("%s: %v escapes to heap:\n", linestr(l.n.Pos), l.n)
|
|
|
|
|
}
|
|
|
|
|
explanation := e.explainPath(root, l)
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
logopt.LogOpt(l.n.Pos, "escape", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", l.n), explanation)
|
|
|
|
|
}
|
2019-09-23 10:57:00 -07:00
|
|
|
}
|
2019-09-20 15:31:13 -07:00
|
|
|
l.escapes = true
|
|
|
|
|
enqueue(l)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-23 10:57:00 -07:00
|
|
|
for i, edge := range l.edges {
|
2019-09-20 15:31:13 -07:00
|
|
|
if edge.src.escapes {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
derefs := base + edge.derefs
|
|
|
|
|
if edge.src.walkgen != walkgen || edge.src.derefs > derefs {
|
|
|
|
|
edge.src.walkgen = walkgen
|
|
|
|
|
edge.src.derefs = derefs
|
2019-09-23 10:57:00 -07:00
|
|
|
edge.src.dst = l
|
|
|
|
|
edge.src.dstEdgeIdx = i
|
2019-04-02 10:40:12 -07:00
|
|
|
todo = append(todo, edge.src)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-23 10:57:00 -07:00
|
|
|
// explainPath prints an explanation of how src flows to the walk root.
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
func (e *Escape) explainPath(root, src *EscLocation) []*logopt.LoggedOpt {
|
2019-11-11 16:45:34 -08:00
|
|
|
visited := make(map[*EscLocation]bool)
|
2019-09-23 10:57:00 -07:00
|
|
|
pos := linestr(src.n.Pos)
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
var explanation []*logopt.LoggedOpt
|
2019-09-23 10:57:00 -07:00
|
|
|
for {
|
2019-11-11 16:45:34 -08:00
|
|
|
// Prevent infinite loop.
|
|
|
|
|
if visited[src] {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m >= 2 {
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos)
|
|
|
|
|
}
|
2019-11-11 16:45:34 -08:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
visited[src] = true
|
2019-09-23 10:57:00 -07:00
|
|
|
dst := src.dst
|
|
|
|
|
edge := &dst.edges[src.dstEdgeIdx]
|
|
|
|
|
if edge.src != src {
|
|
|
|
|
Fatalf("path inconsistency: %v != %v", edge.src, src)
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
explanation = e.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation)
|
2019-09-23 10:57:00 -07:00
|
|
|
|
|
|
|
|
if dst == root {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
src = dst
|
|
|
|
|
}
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
|
|
|
|
|
return explanation
|
2019-09-23 10:57:00 -07:00
|
|
|
}
|
|
|
|
|
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
func (e *Escape) explainFlow(pos string, dst, srcloc *EscLocation, derefs int, notes *EscNote, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt {
|
2019-11-07 12:32:30 -08:00
|
|
|
ops := "&"
|
|
|
|
|
if derefs >= 0 {
|
|
|
|
|
ops = strings.Repeat("*", derefs)
|
|
|
|
|
}
|
2020-10-19 11:31:10 +02:00
|
|
|
print := Debug.m >= 2
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
|
|
|
|
|
flow := fmt.Sprintf(" flow: %s = %s%v:", e.explainLoc(dst), ops, e.explainLoc(srcloc))
|
|
|
|
|
if print {
|
|
|
|
|
fmt.Printf("%s:%s\n", pos, flow)
|
|
|
|
|
}
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
var epos src.XPos
|
|
|
|
|
if notes != nil {
|
|
|
|
|
epos = notes.where.Pos
|
|
|
|
|
} else if srcloc != nil && srcloc.n != nil {
|
|
|
|
|
epos = srcloc.n.Pos
|
|
|
|
|
}
|
|
|
|
|
explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", e.curfn.funcname(), flow))
|
|
|
|
|
}
|
2019-11-07 12:32:30 -08:00
|
|
|
|
|
|
|
|
for note := notes; note != nil; note = note.next {
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
if print {
|
|
|
|
|
fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, linestr(note.where.Pos))
|
|
|
|
|
}
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos, "escflow", "escape", e.curfn.funcname(),
|
|
|
|
|
fmt.Sprintf(" from %v (%v)", note.where, note.why)))
|
|
|
|
|
}
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
cmd/compile: add explanations to escape-analysis JSON/LSP logging
For 1.15.
From the test:
{"range":{"start":{"line":7,"character":13},"end":{...},"severity":3,"code":"leaks","source":"go compiler","message":"parameter z leaks to ~r2 with derefs=0","relatedInformation":[
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: y = z:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: flow: ~r1 = y:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from y.b (dot of pointer)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":11},"end":{...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from \u0026y.b (address-of)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":4,"character":9},"end":...}},"message":"inlineLoc"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":13},"end":{...}},"message":"escflow: from ~r1 = \u003cN\u003e (assign-pair)"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: flow: ~r2 = ~r1:"},
{"location":{"uri":"file://T/file.go","range":{"start":{"line":9,"character":3},"end":...}},"message":"escflow: from return (*int)(~r1) (return)"}]}
Change-Id: Idf02438801f63e487c35a928cf5a0b6d3cc48674
Reviewed-on: https://go-review.googlesource.com/c/go/+/206658
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-11-11 13:18:38 -05:00
|
|
|
return explanation
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
2019-09-23 10:57:00 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
// outlives reports whether values stored in l may survive beyond
|
|
|
|
|
// other's lifetime if stack allocated.
|
|
|
|
|
func (e *Escape) outlives(l, other *EscLocation) bool {
|
|
|
|
|
// The heap outlives everything.
|
2019-09-20 15:31:13 -07:00
|
|
|
if l.escapes {
|
2019-04-02 10:40:12 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We don't know what callers do with returned values, so
|
|
|
|
|
// pessimistically we need to assume they flow to the heap and
|
|
|
|
|
// outlive everything too.
|
|
|
|
|
if l.isName(PPARAMOUT) {
|
|
|
|
|
// Exception: Directly called closures can return
|
|
|
|
|
// locations allocated outside of them without forcing
|
|
|
|
|
// them to the heap. For example:
|
|
|
|
|
//
|
|
|
|
|
// var u int // okay to stack allocate
|
|
|
|
|
// *(func() *int { return &u }()) = 42
|
|
|
|
|
if containsClosure(other.curfn, l.curfn) && l.curfn.Func.Closure.Func.Top&ctxCallee != 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If l and other are within the same function, then l
|
|
|
|
|
// outlives other if it was declared outside other's loop
|
|
|
|
|
// scope. For example:
|
|
|
|
|
//
|
|
|
|
|
// var l *int
|
|
|
|
|
// for {
|
|
|
|
|
// l = new(int)
|
|
|
|
|
// }
|
|
|
|
|
if l.curfn == other.curfn && l.loopDepth < other.loopDepth {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If other is declared within a child closure of where l is
|
|
|
|
|
// declared, then l outlives it. For example:
|
|
|
|
|
//
|
|
|
|
|
// var l *int
|
|
|
|
|
// func() {
|
|
|
|
|
// l = new(int)
|
|
|
|
|
// }
|
|
|
|
|
if containsClosure(l.curfn, other.curfn) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// containsClosure reports whether c is a closure contained within f.
|
|
|
|
|
func containsClosure(f, c *Node) bool {
|
|
|
|
|
if f.Op != ODCLFUNC || c.Op != ODCLFUNC {
|
|
|
|
|
Fatalf("bad containsClosure: %v, %v", f, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Common case.
|
|
|
|
|
if f == c {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Closures within function Foo are named like "Foo.funcN..."
|
|
|
|
|
// TODO(mdempsky): Better way to recognize this.
|
|
|
|
|
fn := f.Func.Nname.Sym.Name
|
|
|
|
|
cn := c.Func.Nname.Sym.Name
|
|
|
|
|
return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// leak records that parameter l leaks to sink.
|
|
|
|
|
func (l *EscLocation) leakTo(sink *EscLocation, derefs int) {
|
|
|
|
|
// If sink is a result parameter and we can fit return bits
|
|
|
|
|
// into the escape analysis tag, then record a return leak.
|
|
|
|
|
if sink.isName(PPARAMOUT) && sink.curfn == l.curfn {
|
|
|
|
|
// TODO(mdempsky): Eliminate dependency on Vargen here.
|
|
|
|
|
ri := int(sink.n.Name.Vargen) - 1
|
2019-09-26 15:55:58 -07:00
|
|
|
if ri < numEscResults {
|
2019-04-02 10:40:12 -07:00
|
|
|
// Leak to result parameter.
|
2019-09-26 15:55:58 -07:00
|
|
|
l.paramEsc.AddResult(ri, derefs)
|
2019-04-02 10:40:12 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, record as heap leak.
|
2019-09-26 15:55:58 -07:00
|
|
|
l.paramEsc.AddHeap(derefs)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-04 15:16:25 -07:00
|
|
|
func (e *Escape) finish(fns []*Node) {
|
|
|
|
|
// Record parameter tags for package export data.
|
|
|
|
|
for _, fn := range fns {
|
|
|
|
|
fn.Esc = EscFuncTagged
|
|
|
|
|
|
|
|
|
|
narg := 0
|
2019-11-01 18:12:27 +07:00
|
|
|
for _, fs := range &types.RecvsParams {
|
2019-09-04 15:16:25 -07:00
|
|
|
for _, f := range fs(fn.Type).Fields().Slice() {
|
|
|
|
|
narg++
|
|
|
|
|
f.Note = e.paramTag(fn, narg, f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
for _, loc := range e.allLocs {
|
|
|
|
|
n := loc.n
|
|
|
|
|
if n == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
n.SetOpt(nil)
|
|
|
|
|
|
|
|
|
|
// Update n.Esc based on escape analysis results.
|
|
|
|
|
|
|
|
|
|
if loc.escapes {
|
2019-10-29 14:44:54 -04:00
|
|
|
if n.Op != ONAME {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 {
|
2019-10-29 14:44:54 -04:00
|
|
|
Warnl(n.Pos, "%S escapes to heap", n)
|
|
|
|
|
}
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
logopt.LogOpt(n.Pos, "escape", "escape", e.curfn.funcname())
|
|
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
n.Esc = EscHeap
|
|
|
|
|
addrescapes(n)
|
|
|
|
|
} else {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 && n.Op != ONAME {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(n.Pos, "%S does not escape", n)
|
2019-09-04 15:16:25 -07:00
|
|
|
}
|
2019-04-02 10:40:12 -07:00
|
|
|
n.Esc = EscNone
|
|
|
|
|
if loc.transient {
|
2019-10-08 14:21:17 -07:00
|
|
|
n.SetTransient(true)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *EscLocation) isName(c Class) bool {
|
|
|
|
|
return l.n != nil && l.n.Op == ONAME && l.n.Class() == c
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
const numEscResults = 7
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// An EscLeaks represents a set of assignment flows from a parameter
|
|
|
|
|
// to the heap or to any of its function's (first numEscResults)
|
|
|
|
|
// result parameters.
|
|
|
|
|
type EscLeaks [1 + numEscResults]uint8
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// Empty reports whether l is an empty set (i.e., no assignment flows).
|
|
|
|
|
func (l EscLeaks) Empty() bool { return l == EscLeaks{} }
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// Heap returns the minimum deref count of any assignment flow from l
|
|
|
|
|
// to the heap. If no such flows exist, Heap returns -1.
|
|
|
|
|
func (l EscLeaks) Heap() int { return l.get(0) }
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// Result returns the minimum deref count of any assignment flow from
|
|
|
|
|
// l to its function's i'th result parameter. If no such flows exist,
|
|
|
|
|
// Result returns -1.
|
|
|
|
|
func (l EscLeaks) Result(i int) int { return l.get(1 + i) }
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// AddHeap adds an assignment flow from l to the heap.
|
|
|
|
|
func (l *EscLeaks) AddHeap(derefs int) { l.add(0, derefs) }
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// AddResult adds an assignment flow from l to its function's i'th
|
|
|
|
|
// result parameter.
|
|
|
|
|
func (l *EscLeaks) AddResult(i, derefs int) { l.add(1+i, derefs) }
|
2019-04-02 10:40:12 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
func (l *EscLeaks) setResult(i, derefs int) { l.set(1+i, derefs) }
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
func (l EscLeaks) get(i int) int { return int(l[i]) - 1 }
|
|
|
|
|
|
|
|
|
|
func (l *EscLeaks) add(i, derefs int) {
|
|
|
|
|
if old := l.get(i); old < 0 || derefs < old {
|
|
|
|
|
l.set(i, derefs)
|
2019-09-26 15:55:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
func (l *EscLeaks) set(i, derefs int) {
|
|
|
|
|
v := derefs + 1
|
|
|
|
|
if v < 0 {
|
2019-09-26 15:55:58 -07:00
|
|
|
Fatalf("invalid derefs count: %v", derefs)
|
|
|
|
|
}
|
2019-09-26 17:21:50 -07:00
|
|
|
if v > math.MaxUint8 {
|
|
|
|
|
v = math.MaxUint8
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
l[i] = uint8(v)
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
2019-09-26 15:55:58 -07:00
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// Optimize removes result flow paths that are equal in length or
|
|
|
|
|
// longer than the shortest heap flow path.
|
2019-09-26 15:55:58 -07:00
|
|
|
func (l *EscLeaks) Optimize() {
|
|
|
|
|
// If we have a path to the heap, then there's no use in
|
|
|
|
|
// keeping equal or longer paths elsewhere.
|
|
|
|
|
if x := l.Heap(); x >= 0 {
|
|
|
|
|
for i := 0; i < numEscResults; i++ {
|
|
|
|
|
if l.Result(i) >= x {
|
|
|
|
|
l.setResult(i, -1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
var leakTagCache = map[EscLeaks]string{}
|
|
|
|
|
|
|
|
|
|
// Encode converts l into a binary string for export data.
|
2019-09-26 15:55:58 -07:00
|
|
|
func (l EscLeaks) Encode() string {
|
2019-09-26 17:21:50 -07:00
|
|
|
if l.Heap() == 0 {
|
|
|
|
|
// Space optimization: empty string encodes more
|
|
|
|
|
// efficiently in export data.
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
if s, ok := leakTagCache[l]; ok {
|
|
|
|
|
return s
|
2019-09-26 15:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
n := len(l)
|
|
|
|
|
for n > 0 && l[n-1] == 0 {
|
|
|
|
|
n--
|
|
|
|
|
}
|
|
|
|
|
s := "esc:" + string(l[:n])
|
|
|
|
|
leakTagCache[l] = s
|
|
|
|
|
return s
|
2019-09-26 15:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-26 17:21:50 -07:00
|
|
|
// ParseLeaks parses a binary string representing an EscLeaks.
|
2019-09-26 15:55:58 -07:00
|
|
|
func ParseLeaks(s string) EscLeaks {
|
2019-09-26 17:21:50 -07:00
|
|
|
var l EscLeaks
|
|
|
|
|
if !strings.HasPrefix(s, "esc:") {
|
|
|
|
|
l.AddHeap(0)
|
|
|
|
|
return l
|
2019-09-26 15:55:58 -07:00
|
|
|
}
|
2019-09-26 17:21:50 -07:00
|
|
|
copy(l[:], s[4:])
|
2019-09-26 15:55:58 -07:00
|
|
|
return l
|
|
|
|
|
}
|