mirror of
https://github.com/golang/go
synced 2024-09-15 22:20:06 +00:00
cmd/compile: escape analysis explanations added to -m -m output
This should probably be considered "experimental" at this stage, but what it needs is feedback from adventurous adopters. I think the data structure used for describing escape reasons might be extendable to allow a cleanup of the underlying algorithms, which suffers from insufficiently separated concerns (the graph does not deal well with escape level adjustments, so it is augmented by a second custom-walk portion of the "flood" phase. It would be better to put it all, including level adjustments, in a single graph structure, and then simply flood the graph. Tweaked to avoid allocations in the no-logging case. Modified run.go to ignore lines with leading "#" in the output (since it can never match a line), and in -update_errors to ignore leading tabs in output lines and to normalize embedded filenames. Currently requires -m -m because otherwise the noise/update burden for the other escape tests is considerable. There is a partial test. Existing escape analysis tests seem to cover all except the panic case and what looks like it might be unreachable code in escape analysis. Fixes #10526. Change-Id: I2524fdec54facae48b00b2548e25d9e46fcaf832 Reviewed-on: https://go-review.googlesource.com/18041 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
50bc546d43
commit
2d56dee61b
|
@ -297,9 +297,19 @@ func (l Level) guaranteedDereference() int {
|
||||||
return int(l.suffixValue)
|
return int(l.suffixValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An EscStep documents one step in the path from memory
|
||||||
|
// that is heap allocated to the (alleged) reason for the
|
||||||
|
// heap allocation.
|
||||||
|
type EscStep struct {
|
||||||
|
src, dst *Node // the endpoints of this edge in the escape-to-heap chain.
|
||||||
|
parent *EscStep // used in flood to record path
|
||||||
|
why string // explanation for this step in the escape-to-heap chain
|
||||||
|
busy bool // used in prevent to snip cycles.
|
||||||
|
}
|
||||||
|
|
||||||
type NodeEscState struct {
|
type NodeEscState struct {
|
||||||
Curfn *Node
|
Curfn *Node
|
||||||
Escflowsrc []*Node // flow(this, src)
|
Escflowsrc []EscStep // flow(this, src)
|
||||||
Escretval Nodes // on OCALLxxx, list of dummy return values
|
Escretval Nodes // on OCALLxxx, list of dummy return values
|
||||||
Escloopdepth int32 // -1: global, 0: return variables, 1:function top level, increased inside function for every loop or label to mark scopes
|
Escloopdepth int32 // -1: global, 0: return variables, 1:function top level, increased inside function for every loop or label to mark scopes
|
||||||
Esclevel Level
|
Esclevel Level
|
||||||
|
@ -399,6 +409,28 @@ type EscState struct {
|
||||||
walkgen uint32
|
walkgen uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EscState) stepWalk(dst, src *Node, why string, parent *EscStep) *EscStep {
|
||||||
|
// TODO: keep a cache of these, mark entry/exit in escwalk to avoid allocation
|
||||||
|
// Or perhaps never mind, since it is disabled unless printing is on.
|
||||||
|
// We may want to revisit this, since the EscStep nodes would make
|
||||||
|
// an excellent replacement for the poorly-separated graph-build/graph-flood
|
||||||
|
// stages.
|
||||||
|
if Debug['m'] == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &EscStep{src: src, dst: dst, why: why, parent: parent}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EscState) stepAssign(step *EscStep, dst, src *Node, why string) *EscStep {
|
||||||
|
if Debug['m'] == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if step != nil { // Caller may have known better.
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
return &EscStep{src: src, dst: dst, why: why}
|
||||||
|
}
|
||||||
|
|
||||||
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
|
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
|
||||||
func funcSym(fn *Node) *Sym {
|
func funcSym(fn *Node) *Sym {
|
||||||
if fn == nil || fn.Func.Nname == nil {
|
if fn == nil || fn.Func.Nname == nil {
|
||||||
|
@ -503,7 +535,7 @@ func escfunc(e *EscState, func_ *Node) {
|
||||||
if e.recursive {
|
if e.recursive {
|
||||||
for _, ln := range Curfn.Func.Dcl {
|
for _, ln := range Curfn.Func.Dcl {
|
||||||
if ln.Op == ONAME && ln.Class == PPARAMOUT {
|
if ln.Op == ONAME && ln.Class == PPARAMOUT {
|
||||||
escflows(e, &e.theSink, ln)
|
escflows(e, &e.theSink, ln, e.stepAssign(nil, ln, ln, "returned from recursive function"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,12 +642,12 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
(n.Type.Width > MaxStackVarSize ||
|
(n.Type.Width > MaxStackVarSize ||
|
||||||
n.Op == ONEW && n.Type.Type.Width >= 1<<16 ||
|
n.Op == ONEW && n.Type.Type.Width >= 1<<16 ||
|
||||||
n.Op == OMAKESLICE && !isSmallMakeSlice(n)) {
|
n.Op == OMAKESLICE && !isSmallMakeSlice(n)) {
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
Warnl(n.Lineno, "%v is too large for stack", n)
|
Warnl(n.Lineno, "%v is too large for stack", n)
|
||||||
}
|
}
|
||||||
n.Esc = EscHeap
|
n.Esc = EscHeap
|
||||||
addrescapes(n)
|
addrescapes(n)
|
||||||
escassign(e, &e.theSink, n)
|
escassignSinkNilWhy(e, n, n, "too large for stack") // TODO category: tooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
esc(e, n.Left, n)
|
esc(e, n.Left, n)
|
||||||
|
@ -628,7 +660,7 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
e.loopdepth--
|
e.loopdepth--
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("%v:[%d] %v esc: %v\n", linestr(lineno), e.loopdepth, funcSym(Curfn), n)
|
fmt.Printf("%v:[%d] %v esc: %v\n", linestr(lineno), e.loopdepth, funcSym(Curfn), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,11 +673,11 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
|
|
||||||
case OLABEL:
|
case OLABEL:
|
||||||
if n.Left.Sym.Label == &nonlooping {
|
if n.Left.Sym.Label == &nonlooping {
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("%v:%v non-looping label\n", linestr(lineno), n)
|
fmt.Printf("%v:%v non-looping label\n", linestr(lineno), n)
|
||||||
}
|
}
|
||||||
} else if n.Left.Sym.Label == &looping {
|
} else if n.Left.Sym.Label == &looping {
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("%v: %v looping label\n", linestr(lineno), n)
|
fmt.Printf("%v: %v looping label\n", linestr(lineno), n)
|
||||||
}
|
}
|
||||||
e.loopdepth++
|
e.loopdepth++
|
||||||
|
@ -666,9 +698,9 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
// dereferenced (see #12588)
|
// dereferenced (see #12588)
|
||||||
if Isfixedarray(n.Type) &&
|
if Isfixedarray(n.Type) &&
|
||||||
!(Isptr[n.Right.Type.Etype] && Eqtype(n.Right.Type.Type, n.Type)) {
|
!(Isptr[n.Right.Type.Etype] && Eqtype(n.Right.Type.Type, n.Type)) {
|
||||||
escassign(e, n.List.Second(), n.Right)
|
escassignNilWhy(e, n.List.Second(), n.Right, "range")
|
||||||
} else {
|
} else {
|
||||||
escassignDereference(e, n.List.Second(), n.Right)
|
escassignDereference(e, n.List.Second(), n.Right, e.stepAssign(nil, n.List.Second(), n.Right, "range-deref"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +711,7 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
// n.Left.Right is the argument of the .(type),
|
// n.Left.Right is the argument of the .(type),
|
||||||
// it.N().Rlist is the variable per case
|
// it.N().Rlist is the variable per case
|
||||||
if n2.Rlist.Len() != 0 {
|
if n2.Rlist.Len() != 0 {
|
||||||
escassign(e, n2.Rlist.First(), n.Left.Right)
|
escassignNilWhy(e, n2.Rlist.First(), n.Left.Right, "switch case")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -717,23 +749,25 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
escassign(e, n.Left, n.Right)
|
escassign(e, n.Left, n.Right, nil)
|
||||||
|
|
||||||
case OAS2: // x,y = a,b
|
case OAS2: // x,y = a,b
|
||||||
if n.List.Len() == n.Rlist.Len() {
|
if n.List.Len() == n.Rlist.Len() {
|
||||||
rs := n.Rlist.Slice()
|
rs := n.Rlist.Slice()
|
||||||
for i, n := range n.List.Slice() {
|
for i, n := range n.List.Slice() {
|
||||||
escassign(e, n, rs[i])
|
escassignNilWhy(e, n, rs[i], "assign-pair")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case OAS2RECV, // v, ok = <-ch
|
case OAS2RECV: // v, ok = <-ch
|
||||||
OAS2MAPR, // v, ok = m[k]
|
escassignNilWhy(e, n.List.First(), n.Rlist.First(), "assign-pair-receive")
|
||||||
OAS2DOTTYPE: // v, ok = x.(type)
|
case OAS2MAPR: // v, ok = m[k]
|
||||||
escassign(e, n.List.First(), n.Rlist.First())
|
escassignNilWhy(e, n.List.First(), n.Rlist.First(), "assign-pair-mapr")
|
||||||
|
case OAS2DOTTYPE: // v, ok = x.(type)
|
||||||
|
escassignNilWhy(e, n.List.First(), n.Rlist.First(), "assign-pair-dot-type")
|
||||||
|
|
||||||
case OSEND: // ch <- x
|
case OSEND: // ch <- x
|
||||||
escassign(e, &e.theSink, n.Right)
|
escassignSinkNilWhy(e, n, n.Right, "send")
|
||||||
|
|
||||||
case ODEFER:
|
case ODEFER:
|
||||||
if e.loopdepth == 1 { // top level
|
if e.loopdepth == 1 { // top level
|
||||||
|
@ -741,15 +775,21 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
}
|
}
|
||||||
// arguments leak out of scope
|
// arguments leak out of scope
|
||||||
// TODO: leak to a dummy node instead
|
// TODO: leak to a dummy node instead
|
||||||
fallthrough
|
// go f(x) - f and x escape
|
||||||
|
escassignSinkNilWhy(e, n, n.Left.Left, "defer func")
|
||||||
|
|
||||||
|
escassignSinkNilWhy(e, n, n.Left.Right, "defer func ...") // ODDDARG for call
|
||||||
|
for _, n4 := range n.Left.List.Slice() {
|
||||||
|
escassignSinkNilWhy(e, n, n4, "defer func arg")
|
||||||
|
}
|
||||||
|
|
||||||
case OPROC:
|
case OPROC:
|
||||||
// go f(x) - f and x escape
|
// go f(x) - f and x escape
|
||||||
escassign(e, &e.theSink, n.Left.Left)
|
escassignSinkNilWhy(e, n, n.Left.Left, "go func")
|
||||||
|
|
||||||
escassign(e, &e.theSink, n.Left.Right) // ODDDARG for call
|
escassignSinkNilWhy(e, n, n.Left.Right, "go func ...") // ODDDARG for call
|
||||||
for _, n4 := range n.Left.List.Slice() {
|
for _, n4 := range n.Left.List.Slice() {
|
||||||
escassign(e, &e.theSink, n4)
|
escassignSinkNilWhy(e, n, n4, "go func arg")
|
||||||
}
|
}
|
||||||
|
|
||||||
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
||||||
|
@ -762,7 +802,7 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
if i >= len(rs) {
|
if i >= len(rs) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
escassign(e, n, rs[i])
|
escassignNilWhy(e, n, rs[i], "assign-pair-func-call")
|
||||||
}
|
}
|
||||||
if n.List.Len() != len(rs) {
|
if n.List.Len() != len(rs) {
|
||||||
Fatalf("esc oas2func")
|
Fatalf("esc oas2func")
|
||||||
|
@ -785,7 +825,7 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
if lrn.Op != ONAME || lrn.Class != PPARAMOUT {
|
if lrn.Op != ONAME || lrn.Class != PPARAMOUT {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
escassign(e, lrn, ll.Index(i))
|
escassignNilWhy(e, lrn, ll.Index(i), "return")
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -795,67 +835,69 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
|
|
||||||
// Argument could leak through recover.
|
// Argument could leak through recover.
|
||||||
case OPANIC:
|
case OPANIC:
|
||||||
escassign(e, &e.theSink, n.Left)
|
escassignSinkNilWhy(e, n, n.Left, "panic")
|
||||||
|
|
||||||
case OAPPEND:
|
case OAPPEND:
|
||||||
if !n.Isddd {
|
if !n.Isddd {
|
||||||
for _, n := range n.List.Slice()[1:] {
|
for _, nn := range n.List.Slice()[1:] {
|
||||||
escassign(e, &e.theSink, n) // lose track of assign to dereference
|
escassignSinkNilWhy(e, n, nn, "appended to slice") // lose track of assign to dereference
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// append(slice1, slice2...) -- slice2 itself does not escape, but contents do.
|
// append(slice1, slice2...) -- slice2 itself does not escape, but contents do.
|
||||||
slice2 := n.List.Second()
|
slice2 := n.List.Second()
|
||||||
escassignDereference(e, &e.theSink, slice2) // lose track of assign of dereference
|
escassignDereference(e, &e.theSink, slice2, e.stepAssign(nil, n, slice2, "appended slice...")) // lose track of assign of dereference
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
Warnl(n.Lineno, "%v special treatment of append(slice1, slice2...) %v", e.curfnSym(n), Nconv(n, FmtShort))
|
Warnl(n.Lineno, "%v special treatment of append(slice1, slice2...) %v", e.curfnSym(n), Nconv(n, FmtShort))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
escassignDereference(e, &e.theSink, n.List.First()) // The original elements are now leaked, too
|
escassignDereference(e, &e.theSink, n.List.First(), e.stepAssign(nil, n, n.List.First(), "appendee slice")) // The original elements are now leaked, too
|
||||||
|
|
||||||
case OCOPY:
|
case OCOPY:
|
||||||
escassignDereference(e, &e.theSink, n.Right) // lose track of assign of dereference
|
escassignDereference(e, &e.theSink, n.Right, e.stepAssign(nil, n, n.Right, "copied slice")) // lose track of assign of dereference
|
||||||
|
|
||||||
case OCONV, OCONVNOP:
|
case OCONV, OCONVNOP:
|
||||||
escassign(e, n, n.Left)
|
escassignNilWhy(e, n, n.Left, "converted")
|
||||||
|
|
||||||
case OCONVIFACE:
|
case OCONVIFACE:
|
||||||
e.track(n)
|
e.track(n)
|
||||||
escassign(e, n, n.Left)
|
escassignNilWhy(e, n, n.Left, "interface-converted")
|
||||||
|
|
||||||
case OARRAYLIT:
|
case OARRAYLIT:
|
||||||
|
why := "array literal element"
|
||||||
if Isslice(n.Type) {
|
if Isslice(n.Type) {
|
||||||
// Slice itself is not leaked until proven otherwise
|
// Slice itself is not leaked until proven otherwise
|
||||||
e.track(n)
|
e.track(n)
|
||||||
|
why = "slice literal element"
|
||||||
}
|
}
|
||||||
// Link values to array/slice
|
// Link values to array/slice
|
||||||
for _, n5 := range n.List.Slice() {
|
for _, n5 := range n.List.Slice() {
|
||||||
escassign(e, n, n5.Right)
|
escassign(e, n, n5.Right, e.stepAssign(nil, n, n5.Right, why))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link values to struct.
|
// Link values to struct.
|
||||||
case OSTRUCTLIT:
|
case OSTRUCTLIT:
|
||||||
for _, n6 := range n.List.Slice() {
|
for _, n6 := range n.List.Slice() {
|
||||||
escassign(e, n, n6.Right)
|
escassignNilWhy(e, n, n6.Right, "struct literal element")
|
||||||
}
|
}
|
||||||
|
|
||||||
case OPTRLIT:
|
case OPTRLIT:
|
||||||
e.track(n)
|
e.track(n)
|
||||||
|
|
||||||
// Link OSTRUCTLIT to OPTRLIT; if OPTRLIT escapes, OSTRUCTLIT elements do too.
|
// Link OSTRUCTLIT to OPTRLIT; if OPTRLIT escapes, OSTRUCTLIT elements do too.
|
||||||
escassign(e, n, n.Left)
|
escassignNilWhy(e, n, n.Left, "pointer literal [assign]")
|
||||||
|
|
||||||
case OCALLPART:
|
case OCALLPART:
|
||||||
e.track(n)
|
e.track(n)
|
||||||
|
|
||||||
// Contents make it to memory, lose track.
|
// Contents make it to memory, lose track.
|
||||||
escassign(e, &e.theSink, n.Left)
|
escassignSinkNilWhy(e, n, n.Left, "call part")
|
||||||
|
|
||||||
case OMAPLIT:
|
case OMAPLIT:
|
||||||
e.track(n)
|
e.track(n)
|
||||||
// Keys and values make it to memory, lose track.
|
// Keys and values make it to memory, lose track.
|
||||||
for _, n7 := range n.List.Slice() {
|
for _, n7 := range n.List.Slice() {
|
||||||
escassign(e, &e.theSink, n7.Left)
|
escassignSinkNilWhy(e, n, n7.Left, "map literal key")
|
||||||
escassign(e, &e.theSink, n7.Right)
|
escassignSinkNilWhy(e, n, n7.Right, "map literal value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link addresses of captured variables to closure.
|
// Link addresses of captured variables to closure.
|
||||||
|
@ -873,7 +915,7 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
typecheck(&a, Erv)
|
typecheck(&a, Erv)
|
||||||
}
|
}
|
||||||
|
|
||||||
escassign(e, n, a)
|
escassignNilWhy(e, n, a, "captured by a closure")
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
|
@ -925,16 +967,36 @@ func esc(e *EscState, n *Node, up *Node) {
|
||||||
lineno = lno
|
lineno = lno
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// escassignNilWhy bundles a common case of
|
||||||
|
// escassign(e, dst, src, e.stepAssign(nil, dst, src, reason))
|
||||||
|
func escassignNilWhy(e *EscState, dst, src *Node, reason string) {
|
||||||
|
var step *EscStep
|
||||||
|
if Debug['m'] != 0 {
|
||||||
|
step = e.stepAssign(nil, dst, src, reason)
|
||||||
|
}
|
||||||
|
escassign(e, dst, src, step)
|
||||||
|
}
|
||||||
|
|
||||||
|
// escassignSinkNilWhy bundles a common case of
|
||||||
|
// escassign(e, &e.theSink, src, e.stepAssign(nil, dst, src, reason))
|
||||||
|
func escassignSinkNilWhy(e *EscState, dst, src *Node, reason string) {
|
||||||
|
var step *EscStep
|
||||||
|
if Debug['m'] != 0 {
|
||||||
|
step = e.stepAssign(nil, dst, src, reason)
|
||||||
|
}
|
||||||
|
escassign(e, &e.theSink, src, step)
|
||||||
|
}
|
||||||
|
|
||||||
// Assert that expr somehow gets assigned to dst, if non nil. for
|
// Assert that expr somehow gets assigned to dst, if non nil. for
|
||||||
// dst==nil, any name node expr still must be marked as being
|
// dst==nil, any name node expr still must be marked as being
|
||||||
// evaluated in curfn. For expr==nil, dst must still be examined for
|
// evaluated in curfn. For expr==nil, dst must still be examined for
|
||||||
// evaluations inside it (e.g *f(x) = y)
|
// evaluations inside it (e.g *f(x) = y)
|
||||||
func escassign(e *EscState, dst *Node, src *Node) {
|
func escassign(e *EscState, dst, src *Node, step *EscStep) {
|
||||||
if isblank(dst) || dst == nil || src == nil || src.Op == ONONAME || src.Op == OXXX {
|
if isblank(dst) || dst == nil || src == nil || src.Op == ONONAME || src.Op == OXXX {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("%v:[%d] %v escassign: %v(%v)[%v] = %v(%v)[%v]\n",
|
fmt.Printf("%v:[%d] %v escassign: %v(%v)[%v] = %v(%v)[%v]\n",
|
||||||
linestr(lineno), e.loopdepth, funcSym(Curfn),
|
linestr(lineno), e.loopdepth, funcSym(Curfn),
|
||||||
Nconv(dst, FmtShort), Jconv(dst, FmtShort), Oconv(dst.Op, 0),
|
Nconv(dst, FmtShort), Jconv(dst, FmtShort), Oconv(dst.Op, 0),
|
||||||
|
@ -943,6 +1005,9 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
|
|
||||||
setlineno(dst)
|
setlineno(dst)
|
||||||
|
|
||||||
|
originalDst := dst
|
||||||
|
dstwhy := "assigned"
|
||||||
|
|
||||||
// Analyze lhs of assignment.
|
// Analyze lhs of assignment.
|
||||||
// Replace dst with e->theSink if we can't track it.
|
// Replace dst with e->theSink if we can't track it.
|
||||||
switch dst.Op {
|
switch dst.Op {
|
||||||
|
@ -964,29 +1029,35 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
|
|
||||||
case ONAME:
|
case ONAME:
|
||||||
if dst.Class == PEXTERN {
|
if dst.Class == PEXTERN {
|
||||||
|
dstwhy = "assigned to top level variable"
|
||||||
dst = &e.theSink
|
dst = &e.theSink
|
||||||
}
|
}
|
||||||
|
|
||||||
case ODOT: // treat "dst.x = src" as "dst = src"
|
case ODOT: // treat "dst.x = src" as "dst = src"
|
||||||
escassign(e, dst.Left, src)
|
escassign(e, dst.Left, src, e.stepAssign(step, originalDst, src, "dot-equals"))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
case OINDEX:
|
case OINDEX:
|
||||||
if Isfixedarray(dst.Left.Type) {
|
if Isfixedarray(dst.Left.Type) {
|
||||||
escassign(e, dst.Left, src)
|
escassign(e, dst.Left, src, e.stepAssign(step, originalDst, src, "array-element-equals"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dstwhy = "slice-element-equals"
|
||||||
dst = &e.theSink // lose track of dereference
|
dst = &e.theSink // lose track of dereference
|
||||||
|
|
||||||
case OIND, ODOTPTR:
|
case OIND:
|
||||||
|
dstwhy = "star-equals"
|
||||||
|
dst = &e.theSink // lose track of dereference
|
||||||
|
|
||||||
|
case ODOTPTR:
|
||||||
|
dstwhy = "star-dot-equals"
|
||||||
dst = &e.theSink // lose track of dereference
|
dst = &e.theSink // lose track of dereference
|
||||||
|
|
||||||
// lose track of key and value
|
// lose track of key and value
|
||||||
case OINDEXMAP:
|
case OINDEXMAP:
|
||||||
escassign(e, &e.theSink, dst.Right)
|
escassign(e, &e.theSink, dst.Right, e.stepAssign(nil, originalDst, src, "key of map put"))
|
||||||
|
dstwhy = "value of map put"
|
||||||
dst = &e.theSink
|
dst = &e.theSink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1016,7 +1087,7 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
OCALLPART,
|
OCALLPART,
|
||||||
ORUNESTR,
|
ORUNESTR,
|
||||||
OCONVIFACE:
|
OCONVIFACE:
|
||||||
escflows(e, dst, src)
|
escflows(e, dst, src, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
|
|
||||||
case OCLOSURE:
|
case OCLOSURE:
|
||||||
// OCLOSURE is lowered to OPTRLIT,
|
// OCLOSURE is lowered to OPTRLIT,
|
||||||
|
@ -1025,13 +1096,13 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
a.Lineno = src.Lineno
|
a.Lineno = src.Lineno
|
||||||
e.nodeEscState(a).Escloopdepth = e.nodeEscState(src).Escloopdepth
|
e.nodeEscState(a).Escloopdepth = e.nodeEscState(src).Escloopdepth
|
||||||
a.Type = Ptrto(src.Type)
|
a.Type = Ptrto(src.Type)
|
||||||
escflows(e, dst, a)
|
escflows(e, dst, a, e.stepAssign(nil, originalDst, src, dstwhy))
|
||||||
|
|
||||||
// Flowing multiple returns to a single dst happens when
|
// Flowing multiple returns to a single dst happens when
|
||||||
// analyzing "go f(g())": here g() flows to sink (issue 4529).
|
// analyzing "go f(g())": here g() flows to sink (issue 4529).
|
||||||
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
||||||
for _, n := range e.nodeEscState(src).Escretval.Slice() {
|
for _, n := range e.nodeEscState(src).Escretval.Slice() {
|
||||||
escflows(e, dst, n)
|
escflows(e, dst, n, e.stepAssign(nil, originalDst, n, dstwhy))
|
||||||
}
|
}
|
||||||
|
|
||||||
// A non-pointer escaping from a struct does not concern us.
|
// A non-pointer escaping from a struct does not concern us.
|
||||||
|
@ -1054,25 +1125,25 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
OSLICE3ARR,
|
OSLICE3ARR,
|
||||||
OSLICESTR:
|
OSLICESTR:
|
||||||
// Conversions, field access, slice all preserve the input value.
|
// Conversions, field access, slice all preserve the input value.
|
||||||
escassign(e, dst, src.Left)
|
escassign(e, dst, src.Left, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
|
|
||||||
case ODOTTYPE:
|
case ODOTTYPE:
|
||||||
if src.Type != nil && !haspointers(src.Type) {
|
if src.Type != nil && !haspointers(src.Type) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
escassign(e, dst, src.Left)
|
escassign(e, dst, src.Left, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
|
|
||||||
case OAPPEND:
|
case OAPPEND:
|
||||||
// Append returns first argument.
|
// Append returns first argument.
|
||||||
// Subsequent arguments are already leaked because they are operands to append.
|
// Subsequent arguments are already leaked because they are operands to append.
|
||||||
escassign(e, dst, src.List.First())
|
escassign(e, dst, src.List.First(), e.stepAssign(step, dst, src.List.First(), dstwhy))
|
||||||
|
|
||||||
case OINDEX:
|
case OINDEX:
|
||||||
// Index of array preserves input value.
|
// Index of array preserves input value.
|
||||||
if Isfixedarray(src.Left.Type) {
|
if Isfixedarray(src.Left.Type) {
|
||||||
escassign(e, dst, src.Left)
|
escassign(e, dst, src.Left, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
} else {
|
} else {
|
||||||
escflows(e, dst, src)
|
escflows(e, dst, src, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Might be pointer arithmetic, in which case
|
// Might be pointer arithmetic, in which case
|
||||||
|
@ -1092,9 +1163,9 @@ func escassign(e *EscState, dst *Node, src *Node) {
|
||||||
OPLUS,
|
OPLUS,
|
||||||
OMINUS,
|
OMINUS,
|
||||||
OCOM:
|
OCOM:
|
||||||
escassign(e, dst, src.Left)
|
escassign(e, dst, src.Left, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
|
|
||||||
escassign(e, dst, src.Right)
|
escassign(e, dst, src.Right, e.stepAssign(step, originalDst, src, dstwhy))
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pdepth--
|
e.pdepth--
|
||||||
|
@ -1204,13 +1275,13 @@ func escassignfromtag(e *EscState, note *string, dsts Nodes, src *Node) uint16 {
|
||||||
return em
|
return em
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::assignfromtag:: src=%v, em=%s\n",
|
fmt.Printf("%v::assignfromtag:: src=%v, em=%s\n",
|
||||||
linestr(lineno), Nconv(src, FmtShort), describeEscape(em))
|
linestr(lineno), Nconv(src, FmtShort), describeEscape(em))
|
||||||
}
|
}
|
||||||
|
|
||||||
if em == EscUnknown {
|
if em == EscUnknown {
|
||||||
escassign(e, &e.theSink, src)
|
escassignSinkNilWhy(e, src, src, "passed to function[unknown]")
|
||||||
return em
|
return em
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1221,7 +1292,7 @@ func escassignfromtag(e *EscState, note *string, dsts Nodes, src *Node) uint16 {
|
||||||
// If content inside parameter (reached via indirection)
|
// If content inside parameter (reached via indirection)
|
||||||
// escapes to heap, mark as such.
|
// escapes to heap, mark as such.
|
||||||
if em&EscContentEscapes != 0 {
|
if em&EscContentEscapes != 0 {
|
||||||
escassign(e, &e.theSink, e.addDereference(src))
|
escassign(e, &e.theSink, e.addDereference(src), e.stepAssign(nil, src, src, "passed to function[content escapes]"))
|
||||||
}
|
}
|
||||||
|
|
||||||
em0 := em
|
em0 := em
|
||||||
|
@ -1238,7 +1309,7 @@ func escassignfromtag(e *EscState, note *string, dsts Nodes, src *Node) uint16 {
|
||||||
for i := uint16(0); i < embits-1; i++ {
|
for i := uint16(0); i < embits-1; i++ {
|
||||||
n = e.addDereference(n) // encode level>0 as indirections
|
n = e.addDereference(n) // encode level>0 as indirections
|
||||||
}
|
}
|
||||||
escassign(e, dsts.Index(dstsi), n)
|
escassign(e, dsts.Index(dstsi), n, e.stepAssign(nil, dsts.Index(dstsi), src, "passed-to-and-returned-from-function"))
|
||||||
}
|
}
|
||||||
dstsi++
|
dstsi++
|
||||||
}
|
}
|
||||||
|
@ -1252,11 +1323,11 @@ func escassignfromtag(e *EscState, note *string, dsts Nodes, src *Node) uint16 {
|
||||||
return em0
|
return em0
|
||||||
}
|
}
|
||||||
|
|
||||||
func escassignDereference(e *EscState, dst *Node, src *Node) {
|
func escassignDereference(e *EscState, dst *Node, src *Node, step *EscStep) {
|
||||||
if src.Op == OLITERAL {
|
if src.Op == OLITERAL {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
escassign(e, dst, e.addDereference(src))
|
escassign(e, dst, e.addDereference(src), step)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addDereference constructs a suitable OIND note applied to src.
|
// addDereference constructs a suitable OIND note applied to src.
|
||||||
|
@ -1374,8 +1445,8 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
// We know nothing!
|
// We know nothing!
|
||||||
// Leak all the parameters
|
// Leak all the parameters
|
||||||
for _, n1 := range ll.Slice() {
|
for _, n1 := range ll.Slice() {
|
||||||
escassign(e, &e.theSink, n1)
|
escassignSinkNilWhy(e, n, n1, "parameter to indirect call")
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::esccall:: indirect call <- %v, untracked\n", linestr(lineno), Nconv(n1, FmtShort))
|
fmt.Printf("%v::esccall:: indirect call <- %v, untracked\n", linestr(lineno), Nconv(n1, FmtShort))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1386,7 +1457,7 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
t := fntype.Recv()
|
t := fntype.Recv()
|
||||||
src := n.Left.Left
|
src := n.Left.Left
|
||||||
if haspointers(t.Type) {
|
if haspointers(t.Type) {
|
||||||
escassign(e, &e.theSink, src)
|
escassignSinkNilWhy(e, n, src, "receiver in indirect call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1395,7 +1466,7 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
nE := e.nodeEscState(n)
|
nE := e.nodeEscState(n)
|
||||||
if fn != nil && fn.Op == ONAME && fn.Class == PFUNC &&
|
if fn != nil && fn.Op == ONAME && fn.Class == PFUNC &&
|
||||||
fn.Name.Defn != nil && len(fn.Name.Defn.Nbody.Slice()) != 0 && fn.Name.Param.Ntype != nil && fn.Name.Defn.Esc < EscFuncTagged {
|
fn.Name.Defn != nil && len(fn.Name.Defn.Nbody.Slice()) != 0 && fn.Name.Param.Ntype != nil && fn.Name.Defn.Esc < EscFuncTagged {
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::esccall:: %v in recursive group\n", linestr(lineno), Nconv(n, FmtShort))
|
fmt.Printf("%v::esccall:: %v in recursive group\n", linestr(lineno), Nconv(n, FmtShort))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1411,7 +1482,7 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
|
|
||||||
// Receiver.
|
// Receiver.
|
||||||
if n.Op != OCALLFUNC {
|
if n.Op != OCALLFUNC {
|
||||||
escassign(e, fn.Name.Param.Ntype.Left.Left, n.Left.Left)
|
escassignNilWhy(e, fn.Name.Param.Ntype.Left.Left, n.Left.Left, "call receiver")
|
||||||
}
|
}
|
||||||
|
|
||||||
var src *Node
|
var src *Node
|
||||||
|
@ -1433,7 +1504,7 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if lrs[i].Left != nil {
|
if lrs[i].Left != nil {
|
||||||
escassign(e, lrs[i].Left, src)
|
escassignNilWhy(e, lrs[i].Left, src, "arg to recursive call")
|
||||||
}
|
}
|
||||||
if src != lls[i] {
|
if src != lls[i] {
|
||||||
break
|
break
|
||||||
|
@ -1442,10 +1513,10 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
|
|
||||||
// "..." arguments are untracked
|
// "..." arguments are untracked
|
||||||
for ; i < len(lls); i++ {
|
for ; i < len(lls); i++ {
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::esccall:: ... <- %v, untracked\n", linestr(lineno), Nconv(lls[i], FmtShort))
|
fmt.Printf("%v::esccall:: ... <- %v, untracked\n", linestr(lineno), Nconv(lls[i], FmtShort))
|
||||||
}
|
}
|
||||||
escassign(e, &e.theSink, lls[i])
|
escassignSinkNilWhy(e, src, lls[i], "... arg to recursive call")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -1456,7 +1527,7 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
Fatalf("esc already decorated call %v\n", Nconv(n, FmtSign))
|
Fatalf("esc already decorated call %v\n", Nconv(n, FmtSign))
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::esccall:: %v not recursive\n", linestr(lineno), Nconv(n, FmtShort))
|
fmt.Printf("%v::esccall:: %v not recursive\n", linestr(lineno), Nconv(n, FmtShort))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1526,16 +1597,16 @@ func esccall(e *EscState, n *Node, up *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for ; i < len(lls); i++ {
|
for ; i < len(lls); i++ {
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::esccall:: ... <- %v\n", linestr(lineno), Nconv(lls[i], FmtShort))
|
fmt.Printf("%v::esccall:: ... <- %v\n", linestr(lineno), Nconv(lls[i], FmtShort))
|
||||||
}
|
}
|
||||||
escassign(e, src, lls[i]) // args to slice
|
escassignNilWhy(e, src, lls[i], "arg to ...") // args to slice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// escflows records the link src->dst in dst, throwing out some quick wins,
|
// escflows records the link src->dst in dst, throwing out some quick wins,
|
||||||
// and also ensuring that dst is noted as a flow destination.
|
// and also ensuring that dst is noted as a flow destination.
|
||||||
func escflows(e *EscState, dst *Node, src *Node) {
|
func escflows(e *EscState, dst, src *Node, why *EscStep) {
|
||||||
if dst == nil || src == nil || dst == src {
|
if dst == nil || src == nil || dst == src {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1545,7 +1616,7 @@ func escflows(e *EscState, dst *Node, src *Node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 2 {
|
if Debug['m'] > 3 {
|
||||||
fmt.Printf("%v::flows:: %v <- %v\n", linestr(lineno), Nconv(dst, FmtShort), Nconv(src, FmtShort))
|
fmt.Printf("%v::flows:: %v <- %v\n", linestr(lineno), Nconv(dst, FmtShort), Nconv(src, FmtShort))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1557,7 +1628,13 @@ func escflows(e *EscState, dst *Node, src *Node) {
|
||||||
|
|
||||||
e.edgecount++
|
e.edgecount++
|
||||||
|
|
||||||
dstE.Escflowsrc = append(dstE.Escflowsrc, src)
|
if why == nil {
|
||||||
|
dstE.Escflowsrc = append(dstE.Escflowsrc, EscStep{src: src})
|
||||||
|
} else {
|
||||||
|
starwhy := *why
|
||||||
|
starwhy.src = src // TODO: need to reconcile this w/ needs of explanations.
|
||||||
|
dstE.Escflowsrc = append(dstE.Escflowsrc, starwhy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whenever we hit a reference node, the level goes up by one, and whenever
|
// Whenever we hit a reference node, the level goes up by one, and whenever
|
||||||
|
@ -1579,13 +1656,14 @@ func escflood(e *EscState, dst *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dstE := e.nodeEscState(dst)
|
dstE := e.nodeEscState(dst)
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("\nescflood:%d: dst %v scope:%v[%d]\n", e.walkgen, Nconv(dst, FmtShort), e.curfnSym(dst), dstE.Escloopdepth)
|
fmt.Printf("\nescflood:%d: dst %v scope:%v[%d]\n", e.walkgen, Nconv(dst, FmtShort), e.curfnSym(dst), dstE.Escloopdepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range dstE.Escflowsrc {
|
for i, l := range dstE.Escflowsrc {
|
||||||
e.walkgen++
|
e.walkgen++
|
||||||
escwalk(e, levelFrom(0), dst, n)
|
dstE.Escflowsrc[i].parent = nil
|
||||||
|
escwalk(e, levelFrom(0), dst, l.src, &dstE.Escflowsrc[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1596,13 +1674,37 @@ func funcOutputAndInput(dst, src *Node) bool {
|
||||||
src.Op == ONAME && src.Class == PPARAM && src.Name.Curfn == dst.Name.Curfn
|
src.Op == ONAME && src.Class == PPARAM && src.Name.Curfn == dst.Name.Curfn
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTALOOPDEPTH = -1
|
func (es *EscStep) describe(src *Node) {
|
||||||
|
if Debug['m'] < 2 {
|
||||||
func escwalk(e *EscState, level Level, dst *Node, src *Node) {
|
return
|
||||||
escwalkBody(e, level, dst, src, NOTALOOPDEPTH)
|
}
|
||||||
|
step0 := es
|
||||||
|
for step := step0; step != nil && !step.busy; step = step.parent {
|
||||||
|
// TODO: We get cycles. Trigger is i = &i (where var i interface{})
|
||||||
|
step.busy = true
|
||||||
|
// The trail is a little odd because of how the
|
||||||
|
// graph is constructed. The link to the current
|
||||||
|
// Node is parent.src unless parent is nil in which
|
||||||
|
// case it is step.dst.
|
||||||
|
nextDest := step.parent
|
||||||
|
dst := step.dst
|
||||||
|
if nextDest != nil {
|
||||||
|
dst = nextDest.src
|
||||||
|
}
|
||||||
|
Warnl(src.Lineno, "\tfrom %s (%s) at %s", dst, step.why, dst.Line())
|
||||||
|
}
|
||||||
|
for step := step0; step != nil && step.busy; step = step.parent {
|
||||||
|
step.busy = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth int32) {
|
const NOTALOOPDEPTH = -1
|
||||||
|
|
||||||
|
func escwalk(e *EscState, level Level, dst *Node, src *Node, step *EscStep) {
|
||||||
|
escwalkBody(e, level, dst, src, step, NOTALOOPDEPTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escwalkBody(e *EscState, level Level, dst *Node, src *Node, step *EscStep, extraloopdepth int32) {
|
||||||
if src.Op == OLITERAL {
|
if src.Op == OLITERAL {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1633,7 +1735,7 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
modSrcLoopdepth = extraloopdepth
|
modSrcLoopdepth = extraloopdepth
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("escwalk: level:%d depth:%d %.*s op=%v %v(%v) scope:%v[%d] extraloopdepth=%v\n",
|
fmt.Printf("escwalk: level:%d depth:%d %.*s op=%v %v(%v) scope:%v[%d] extraloopdepth=%v\n",
|
||||||
level, e.pdepth, e.pdepth, "\t\t\t\t\t\t\t\t\t\t", Oconv(src.Op, 0), Nconv(src, FmtShort), Jconv(src, FmtShort), e.curfnSym(src), srcE.Escloopdepth, extraloopdepth)
|
level, e.pdepth, e.pdepth, "\t\t\t\t\t\t\t\t\t\t", Oconv(src.Op, 0), Nconv(src, FmtShort), Jconv(src, FmtShort), e.curfnSym(src), srcE.Escloopdepth, extraloopdepth)
|
||||||
}
|
}
|
||||||
|
@ -1642,6 +1744,8 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
|
|
||||||
// Input parameter flowing to output parameter?
|
// Input parameter flowing to output parameter?
|
||||||
var leaks bool
|
var leaks bool
|
||||||
|
var osrcesc uint16 // used to prevent duplicate error messages
|
||||||
|
|
||||||
dstE := e.nodeEscState(dst)
|
dstE := e.nodeEscState(dst)
|
||||||
if funcOutputAndInput(dst, src) && src.Esc&EscMask < EscScope && dst.Esc != EscHeap {
|
if funcOutputAndInput(dst, src) && src.Esc&EscMask < EscScope && dst.Esc != EscHeap {
|
||||||
// This case handles:
|
// This case handles:
|
||||||
|
@ -1650,8 +1754,9 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
// 3. tmp := in; return &tmp
|
// 3. tmp := in; return &tmp
|
||||||
// 4. return *in
|
// 4. return *in
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 {
|
||||||
if Debug['m'] == 1 {
|
if Debug['m'] <= 2 {
|
||||||
Warnl(src.Lineno, "leaking param: %v to result %v level=%v", Nconv(src, FmtShort), dst.Sym, level.int())
|
Warnl(src.Lineno, "leaking param: %v to result %v level=%v", Nconv(src, FmtShort), dst.Sym, level.int())
|
||||||
|
step.describe(src)
|
||||||
} else {
|
} else {
|
||||||
Warnl(src.Lineno, "leaking param: %v to result %v level=%v", Nconv(src, FmtShort), dst.Sym, level)
|
Warnl(src.Lineno, "leaking param: %v to result %v level=%v", Nconv(src, FmtShort), dst.Sym, level)
|
||||||
}
|
}
|
||||||
|
@ -1671,19 +1776,24 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
src.Esc = escMax(EscContentEscapes|src.Esc, EscNone)
|
src.Esc = escMax(EscContentEscapes|src.Esc, EscNone)
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 {
|
||||||
Warnl(src.Lineno, "mark escaped content: %v", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "mark escaped content: %v", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leaks = level.int() <= 0 && level.guaranteedDereference() <= 0 && dstE.Escloopdepth < modSrcLoopdepth
|
leaks = level.int() <= 0 && level.guaranteedDereference() <= 0 && dstE.Escloopdepth < modSrcLoopdepth
|
||||||
|
|
||||||
|
osrcesc = src.Esc
|
||||||
switch src.Op {
|
switch src.Op {
|
||||||
case ONAME:
|
case ONAME:
|
||||||
if src.Class == PPARAM && (leaks || dstE.Escloopdepth < 0) && src.Esc&EscMask < EscScope {
|
if src.Class == PPARAM && (leaks || dstE.Escloopdepth < 0) && src.Esc&EscMask < EscScope {
|
||||||
if level.guaranteedDereference() > 0 {
|
if level.guaranteedDereference() > 0 {
|
||||||
src.Esc = escMax(EscContentEscapes|src.Esc, EscNone)
|
src.Esc = escMax(EscContentEscapes|src.Esc, EscNone)
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 {
|
||||||
if Debug['m'] == 1 {
|
if Debug['m'] <= 2 {
|
||||||
|
if osrcesc != src.Esc {
|
||||||
Warnl(src.Lineno, "leaking param content: %v", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "leaking param content: %v", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Warnl(src.Lineno, "leaking param content: %v level=%v dst.eld=%v src.eld=%v dst=%v",
|
Warnl(src.Lineno, "leaking param content: %v level=%v dst.eld=%v src.eld=%v dst=%v",
|
||||||
Nconv(src, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth, Nconv(dst, FmtShort))
|
Nconv(src, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth, Nconv(dst, FmtShort))
|
||||||
|
@ -1692,8 +1802,10 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
} else {
|
} else {
|
||||||
src.Esc = EscScope
|
src.Esc = EscScope
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 {
|
||||||
if Debug['m'] == 1 {
|
|
||||||
|
if Debug['m'] <= 2 {
|
||||||
Warnl(src.Lineno, "leaking param: %v", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "leaking param: %v", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
} else {
|
} else {
|
||||||
Warnl(src.Lineno, "leaking param: %v level=%v dst.eld=%v src.eld=%v dst=%v",
|
Warnl(src.Lineno, "leaking param: %v level=%v dst.eld=%v src.eld=%v dst=%v",
|
||||||
Nconv(src, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth, Nconv(dst, FmtShort))
|
Nconv(src, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth, Nconv(dst, FmtShort))
|
||||||
|
@ -1707,40 +1819,47 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
if src.Class == PPARAMREF {
|
if src.Class == PPARAMREF {
|
||||||
if leaks && Debug['m'] != 0 {
|
if leaks && Debug['m'] != 0 {
|
||||||
Warnl(src.Lineno, "leaking closure reference %v", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "leaking closure reference %v", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
}
|
}
|
||||||
escwalk(e, level, dst, src.Name.Param.Closure)
|
escwalk(e, level, dst, src.Name.Param.Closure, e.stepWalk(dst, src.Name.Param.Closure, "closure-var", step))
|
||||||
}
|
}
|
||||||
|
|
||||||
case OPTRLIT, OADDR:
|
case OPTRLIT, OADDR:
|
||||||
|
why := "pointer literal"
|
||||||
|
if src.Op == OADDR {
|
||||||
|
why = "address-of"
|
||||||
|
}
|
||||||
if leaks {
|
if leaks {
|
||||||
src.Esc = EscHeap
|
src.Esc = EscHeap
|
||||||
addrescapes(src.Left)
|
addrescapes(src.Left)
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 && osrcesc != src.Esc {
|
||||||
p := src
|
p := src
|
||||||
if p.Left.Op == OCLOSURE {
|
if p.Left.Op == OCLOSURE {
|
||||||
p = p.Left // merely to satisfy error messages in tests
|
p = p.Left // merely to satisfy error messages in tests
|
||||||
}
|
}
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
Warnl(src.Lineno, "%v escapes to heap, level=%v, dst.eld=%v, src.eld=%v",
|
Warnl(src.Lineno, "%v escapes to heap, level=%v, dst.eld=%v, src.eld=%v",
|
||||||
Nconv(p, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth)
|
Nconv(p, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth)
|
||||||
} else {
|
} else {
|
||||||
Warnl(src.Lineno, "%v escapes to heap", Nconv(p, FmtShort))
|
Warnl(src.Lineno, "%v escapes to heap", Nconv(p, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
escwalkBody(e, level.dec(), dst, src.Left, modSrcLoopdepth)
|
escwalkBody(e, level.dec(), dst, src.Left, e.stepWalk(dst, src.Left, why, step), modSrcLoopdepth)
|
||||||
extraloopdepth = modSrcLoopdepth // passes to recursive case, seems likely a no-op
|
extraloopdepth = modSrcLoopdepth // passes to recursive case, seems likely a no-op
|
||||||
} else {
|
} else {
|
||||||
escwalk(e, level.dec(), dst, src.Left)
|
escwalk(e, level.dec(), dst, src.Left, e.stepWalk(dst, src.Left, why, step))
|
||||||
}
|
}
|
||||||
|
|
||||||
case OAPPEND:
|
case OAPPEND:
|
||||||
escwalk(e, level, dst, src.List.First())
|
escwalk(e, level, dst, src.List.First(), e.stepWalk(dst, src.List.First(), "append-first-arg", step))
|
||||||
|
|
||||||
case ODDDARG:
|
case ODDDARG:
|
||||||
if leaks {
|
if leaks {
|
||||||
src.Esc = EscHeap
|
src.Esc = EscHeap
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 && osrcesc != src.Esc {
|
||||||
Warnl(src.Lineno, "%v escapes to heap", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "%v escapes to heap", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
}
|
}
|
||||||
extraloopdepth = modSrcLoopdepth
|
extraloopdepth = modSrcLoopdepth
|
||||||
}
|
}
|
||||||
|
@ -1752,7 +1871,7 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, n1 := range src.List.Slice() {
|
for _, n1 := range src.List.Slice() {
|
||||||
escwalk(e, level.dec(), dst, n1.Right)
|
escwalk(e, level.dec(), dst, n1.Right, e.stepWalk(dst, n1.Right, "slice-literal-element", step))
|
||||||
}
|
}
|
||||||
|
|
||||||
fallthrough
|
fallthrough
|
||||||
|
@ -1773,30 +1892,38 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
OCONVIFACE:
|
OCONVIFACE:
|
||||||
if leaks {
|
if leaks {
|
||||||
src.Esc = EscHeap
|
src.Esc = EscHeap
|
||||||
if Debug['m'] != 0 {
|
if Debug['m'] != 0 && osrcesc != src.Esc {
|
||||||
Warnl(src.Lineno, "%v escapes to heap", Nconv(src, FmtShort))
|
Warnl(src.Lineno, "%v escapes to heap", Nconv(src, FmtShort))
|
||||||
|
step.describe(src)
|
||||||
}
|
}
|
||||||
extraloopdepth = modSrcLoopdepth
|
extraloopdepth = modSrcLoopdepth
|
||||||
}
|
}
|
||||||
|
|
||||||
case ODOT,
|
case ODOT,
|
||||||
ODOTTYPE,
|
ODOTTYPE:
|
||||||
|
escwalk(e, level, dst, src.Left, e.stepWalk(dst, src.Left, "dot", step))
|
||||||
|
|
||||||
|
case
|
||||||
OSLICE,
|
OSLICE,
|
||||||
OSLICEARR,
|
OSLICEARR,
|
||||||
OSLICE3,
|
OSLICE3,
|
||||||
OSLICE3ARR,
|
OSLICE3ARR,
|
||||||
OSLICESTR:
|
OSLICESTR:
|
||||||
escwalk(e, level, dst, src.Left)
|
escwalk(e, level, dst, src.Left, e.stepWalk(dst, src.Left, "slice", step))
|
||||||
|
|
||||||
case OINDEX:
|
case OINDEX:
|
||||||
if Isfixedarray(src.Left.Type) {
|
if Isfixedarray(src.Left.Type) {
|
||||||
escwalk(e, level, dst, src.Left)
|
escwalk(e, level, dst, src.Left, e.stepWalk(dst, src.Left, "fixed-array-index-of", step))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case ODOTPTR, OINDEXMAP, OIND:
|
case ODOTPTR:
|
||||||
escwalk(e, level.inc(), dst, src.Left)
|
escwalk(e, level.inc(), dst, src.Left, e.stepWalk(dst, src.Left, "dot of pointer", step))
|
||||||
|
case OINDEXMAP:
|
||||||
|
escwalk(e, level.inc(), dst, src.Left, e.stepWalk(dst, src.Left, "map index", step))
|
||||||
|
case OIND:
|
||||||
|
escwalk(e, level.inc(), dst, src.Left, e.stepWalk(dst, src.Left, "indirection", step))
|
||||||
|
|
||||||
// In this case a link went directly to a call, but should really go
|
// In this case a link went directly to a call, but should really go
|
||||||
// to the dummy .outN outputs that were created for the call that
|
// to the dummy .outN outputs that were created for the call that
|
||||||
|
@ -1805,7 +1932,7 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
// This can only happen with functions returning a single result.
|
// This can only happen with functions returning a single result.
|
||||||
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
case OCALLMETH, OCALLFUNC, OCALLINTER:
|
||||||
if srcE.Escretval.Len() != 0 {
|
if srcE.Escretval.Len() != 0 {
|
||||||
if Debug['m'] > 1 {
|
if Debug['m'] > 2 {
|
||||||
fmt.Printf("%v:[%d] dst %v escwalk replace src: %v with %v\n",
|
fmt.Printf("%v:[%d] dst %v escwalk replace src: %v with %v\n",
|
||||||
linestr(lineno), e.loopdepth,
|
linestr(lineno), e.loopdepth,
|
||||||
Nconv(dst, FmtShort), Nconv(src, FmtShort), Nconv(srcE.Escretval.First(), FmtShort))
|
Nconv(dst, FmtShort), Nconv(src, FmtShort), Nconv(srcE.Escretval.First(), FmtShort))
|
||||||
|
@ -1817,8 +1944,10 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, extraloopdepth
|
||||||
|
|
||||||
recurse:
|
recurse:
|
||||||
level = level.copy()
|
level = level.copy()
|
||||||
for _, n := range srcE.Escflowsrc {
|
for i, ll := range srcE.Escflowsrc {
|
||||||
escwalkBody(e, level, dst, n, extraloopdepth)
|
srcE.Escflowsrc[i].parent = step
|
||||||
|
escwalkBody(e, level, dst, ll.src, &srcE.Escflowsrc[i], extraloopdepth)
|
||||||
|
srcE.Escflowsrc[i].parent = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pdepth--
|
e.pdepth--
|
||||||
|
|
|
@ -45,7 +45,7 @@ func Compile(f *Func) {
|
||||||
}
|
}
|
||||||
const logMemStats = false
|
const logMemStats = false
|
||||||
for _, p := range passes {
|
for _, p := range passes {
|
||||||
if !f.Config.optimize && !p.required {
|
if !f.Config.optimize && !p.required || p.disabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.pass = &p
|
f.pass = &p
|
||||||
|
|
177
test/escape_because.go
Normal file
177
test/escape_because.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
// errorcheck -0 -m -m -l
|
||||||
|
|
||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// Note the doubled -m; this tests the "because" explanations for escapes,
|
||||||
|
// and is likely to be annoyingly fragile under compiler change.
|
||||||
|
// As long as the explanations look reasonably sane, meaning eyeball verify output of
|
||||||
|
// go build -gcflags '-l -m -m' escape_because.go
|
||||||
|
// and investigate changes, feel free to update with
|
||||||
|
// go run run.go -update_errors -- escape_because.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
|
|
||||||
|
var sink interface{}
|
||||||
|
|
||||||
|
type pair struct {
|
||||||
|
x, y *int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pairy interface {
|
||||||
|
EqualParts() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pair) EqualParts() bool { // ERROR "\(\*pair\).EqualParts p does not escape$"
|
||||||
|
return p != nil && (p.x == p.y || *p.x == *p.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func f1(p *int) { // ERROR "from \[3\]\*int literal \(array literal element\) at escape_because.go:34$" "from a \(assigned\) at escape_because.go:34$" "from a \(interface-converted\) at escape_because.go:35$" "from sink \(assigned to top level variable\) at escape_because.go:19$" "leaking param: p$"
|
||||||
|
a := [3]*int{p, nil, nil}
|
||||||
|
sink = a // ERROR "a escapes to heap$" "from sink \(assigned to top level variable\) at escape_because.go:19$"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func f2(q *int) { // ERROR "from &u \(address-of\) at escape_because.go:43$" "from &u \(interface-converted\) at escape_because.go:43$" "from pair literal \(struct literal element\) at escape_because.go:41$" "from s \(assigned\) at escape_because.go:40$" "from sink \(assigned to top level variable\) at escape_because.go:19$" "from t \(assigned\) at escape_because.go:41$" "from u \(assigned\) at escape_because.go:42$" "leaking param: q$"
|
||||||
|
s := q
|
||||||
|
t := pair{s, nil}
|
||||||
|
u := t // ERROR "moved to heap: u$"
|
||||||
|
sink = &u // ERROR "&u escapes to heap$" "from &u \(interface-converted\) at escape_because.go:43$" "from sink \(assigned to top level variable\) at escape_because.go:19$"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f3(r *int) interface{} { // ERROR "from \[\]\*int literal \(slice-literal-element\) at escape_because.go:47$" "from c \(assigned\) at escape_because.go:47$" "from c \(interface-converted\) at escape_because.go:48$" "from ~r1 \(return\) at escape_because.go:46$" "leaking param: r to result ~r1 level=-1$"
|
||||||
|
c := []*int{r} // ERROR "\[\]\*int literal escapes to heap$" "from c \(assigned\) at escape_because.go:47$" "from c \(interface-converted\) at escape_because.go:48$" "from ~r1 \(return\) at escape_because.go:46$"
|
||||||
|
return c // "return" // ERROR "c escapes to heap$" "from ~r1 \(return\) at escape_because.go:46$"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f4(a *int, s []*int) int { // ERROR "from \*s \(indirection\) at escape_because.go:51$" "from append\(s, a\) \(appended to slice\) at escape_because.go:52$" "from append\(s, a\) \(appendee slice\) at escape_because.go:52$" "leaking param content: s$" "leaking param: a$"
|
||||||
|
s = append(s, a)
|
||||||
|
return *(s[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func f5(s1, s2 []*int) int { // ERROR "from \*s1 \(indirection\) at escape_because.go:56$" "from \*s2 \(indirection\) at escape_because.go:56$" "from append\(s1, s2...\) \(appended slice...\) at escape_because.go:57$" "from append\(s1, s2...\) \(appendee slice\) at escape_because.go:57$" "leaking param content: s1$" "leaking param content: s2$"
|
||||||
|
s1 = append(s1, s2...)
|
||||||
|
return *(s1[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func f6(x, y *int) bool { // ERROR "f6 x does not escape$" "f6 y does not escape$"
|
||||||
|
p := pair{x, y}
|
||||||
|
var P Pairy = &p // ERROR "f6 &p does not escape$"
|
||||||
|
pp := P.(*pair)
|
||||||
|
return pp.EqualParts()
|
||||||
|
}
|
||||||
|
|
||||||
|
func f7(x map[int]*int, y int) *int { // ERROR "f7 x does not escape$"
|
||||||
|
z, ok := x[y]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
func f8(x int, y *int) *int { // ERROR "from ~r2 \(return\) at escape_because.go:76$" "from ~r2 \(returned from recursive function\) at escape_because.go:76$" "leaking param: y$" "moved to heap: x$"
|
||||||
|
if x <= 0 {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
x--
|
||||||
|
return f8(*y, &x) // ERROR "&x escapes to heap$" "from y \(arg to recursive call\) at escape_because.go:76$" "from ~r2 \(return\) at escape_because.go:76$" "from ~r2 \(returned from recursive function\) at escape_because.go:76$"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f9(x int, y ...*int) *int { // ERROR "from y\[0\] \(dot of pointer\) at escape_because.go:86$" "from ~r2 \(return\) at escape_because.go:84$" "from ~r2 \(returned from recursive function\) at escape_because.go:84$" "leaking param content: y$" "leaking param: y to result ~r2 level=1$" "moved to heap: x$"
|
||||||
|
if x <= 0 {
|
||||||
|
return y[0]
|
||||||
|
}
|
||||||
|
x--
|
||||||
|
return f9(*y[0], &x) // ERROR "&x escapes to heap$" "f9 ... argument does not escape$" "from ... argument \(... arg to recursive call\) at escape_because.go:89$"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f10(x map[*int]*int, y, z *int) *int { // ERROR "f10 x does not escape$" "from x\[y\] \(key of map put\) at escape_because.go:93$" "from x\[y\] \(value of map put\) at escape_because.go:93$" "leaking param: y$" "leaking param: z$"
|
||||||
|
x[y] = z
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
func f11(x map[*int]*int, y, z *int) map[*int]*int { // ERROR "f11 x does not escape$" "from map\[\*int\]\*int literal \(map literal key\) at escape_because.go:98$" "from map\[\*int\]\*int literal \(map literal value\) at escape_because.go:98$" "leaking param: y$" "leaking param: z$"
|
||||||
|
return map[*int]*int{y: z} // ERROR "from ~r3 \(return\) at escape_because.go:97$" "map\[\*int\]\*int literal escapes to heap$"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list below is all of the why-escapes messages seen building the escape analysis tests.
|
||||||
|
/*
|
||||||
|
for i in escape*go ; do echo compile $i; go build -gcflags '-l -m -m' $i >& `basename $i .go`.log ; done
|
||||||
|
grep 'from .* at ' escape*.log | sed -e 's/^.*(\([^()]*\))[^()]*$/\1/' | sort -u
|
||||||
|
*/
|
||||||
|
// sed RE above assumes that (reason) is the last parenthesized phrase in the line,
|
||||||
|
// and that none of the reasons contains any parentheses
|
||||||
|
|
||||||
|
/*
|
||||||
|
... arg to recursive call
|
||||||
|
address-of
|
||||||
|
appended slice...
|
||||||
|
appended to slice
|
||||||
|
appendee slice
|
||||||
|
arg to ...
|
||||||
|
arg to recursive call
|
||||||
|
array literal element
|
||||||
|
array-element-equals
|
||||||
|
assign-pair
|
||||||
|
assign-pair-dot-type
|
||||||
|
assign-pair-func-call
|
||||||
|
assigned
|
||||||
|
assigned to top level variable
|
||||||
|
call part
|
||||||
|
captured by a closure
|
||||||
|
closure-var
|
||||||
|
converted
|
||||||
|
copied slice
|
||||||
|
defer func
|
||||||
|
defer func ...
|
||||||
|
defer func arg
|
||||||
|
dot
|
||||||
|
dot of pointer
|
||||||
|
dot-equals
|
||||||
|
fixed-array-index-of
|
||||||
|
go func
|
||||||
|
go func ...
|
||||||
|
go func arg
|
||||||
|
indirection
|
||||||
|
interface-converted
|
||||||
|
key of map put
|
||||||
|
map literal key
|
||||||
|
map literal value
|
||||||
|
parameter to indirect call
|
||||||
|
passed to function[content escapes]
|
||||||
|
passed to function[unknown]
|
||||||
|
passed-to-and-returned-from-function
|
||||||
|
pointer literal
|
||||||
|
range
|
||||||
|
range-deref
|
||||||
|
receiver in indirect call
|
||||||
|
return
|
||||||
|
returned from recursive function
|
||||||
|
send
|
||||||
|
slice
|
||||||
|
slice-element-equals
|
||||||
|
slice-literal-element
|
||||||
|
star-dot-equals
|
||||||
|
star-equals
|
||||||
|
struct literal element
|
||||||
|
switch case
|
||||||
|
too large for stack
|
||||||
|
value of map put
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Expected, but not yet seen (they may be unreachable):
|
||||||
|
|
||||||
|
/*
|
||||||
|
append-first-arg
|
||||||
|
assign-pair-mapr
|
||||||
|
assign-pair-receive
|
||||||
|
call receiver
|
||||||
|
map index
|
||||||
|
panic
|
||||||
|
pointer literal [assign]
|
||||||
|
slice literal element
|
||||||
|
*/
|
|
@ -795,7 +795,7 @@ func splitOutput(out string) []string {
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "\t") {
|
if strings.HasPrefix(line, "\t") {
|
||||||
res[len(res)-1] += "\n" + line
|
res[len(res)-1] += "\n" + line
|
||||||
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "<autogenerated>") {
|
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "<autogenerated>") || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
} else if strings.TrimSpace(line) != "" {
|
} else if strings.TrimSpace(line) != "" {
|
||||||
res = append(res, line)
|
res = append(res, line)
|
||||||
|
@ -870,7 +870,8 @@ func (t *test) errorCheck(outStr string, fullshort ...string) (err error) {
|
||||||
return errors.New(buf.String())
|
return errors.New(buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *test) updateErrors(out string, file string) {
|
func (t *test) updateErrors(out, file string) {
|
||||||
|
base := path.Base(file)
|
||||||
// Read in source file.
|
// Read in source file.
|
||||||
src, err := ioutil.ReadFile(file)
|
src, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -904,6 +905,8 @@ func (t *test) updateErrors(out string, file string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msg := errStr[colon2+2:]
|
msg := errStr[colon2+2:]
|
||||||
|
msg = strings.Replace(msg, file, base, -1) // normalize file mentions in error itself
|
||||||
|
msg = strings.TrimLeft(msg, " \t")
|
||||||
for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} {
|
for _, r := range []string{`\`, `*`, `+`, `[`, `]`, `(`, `)`} {
|
||||||
msg = strings.Replace(msg, r, `\`+r, -1)
|
msg = strings.Replace(msg, r, `\`+r, -1)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue