// heap allocation.
type EscStep struct {
src, dst *Node // the endpoints of this edge in the escape-to-heap chain.
+ where *Node // sometimes the endpoints don't match source locations; set 'where' to make that right
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.
return nil
}
if step != nil { // Caller may have known better.
+ if step.why == "" {
+ step.why = why
+ }
+ if step.dst == nil {
+ step.dst = dst
+ }
+ if step.src == nil {
+ step.src = src
+ }
return step
}
return &EscStep{src: src, dst: dst, why: why}
}
+func (e *EscState) stepAssignWhere(dst, src *Node, why string, where *Node) *EscStep {
+ if Debug['m'] == 0 {
+ return nil
+ }
+ return &EscStep{src: src, dst: dst, why: why, where: where}
+}
+
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
func funcSym(fn *Node) *Sym {
if fn == nil || fn.Func.Nname == nil {
}
n.Esc = EscHeap
addrescapes(n)
- e.escassignSinkNilWhy(n, n, "too large for stack") // TODO category: tooLarge
+ e.escassignSinkWhy(n, n, "too large for stack") // TODO category: tooLarge
}
e.esc(n.Left, n)
// dereferenced (see #12588)
if n.Type.IsArray() &&
!(n.Right.Type.IsPtr() && eqtype(n.Right.Type.Elem(), n.Type)) {
- e.escassignNilWhy(n.List.Second(), n.Right, "range")
+ e.escassignWhyWhere(n.List.Second(), n.Right, "range", n)
} else {
- e.escassignDereference(n.List.Second(), n.Right, e.stepAssign(nil, n.List.Second(), n.Right, "range-deref"))
+ e.escassignDereference(n.List.Second(), n.Right, e.stepAssignWhere(n.List.Second(), n.Right, "range-deref", n))
}
}
// n.Left.Right is the argument of the .(type),
// it.N().Rlist is the variable per case
if n2.Rlist.Len() != 0 {
- e.escassignNilWhy(n2.Rlist.First(), n.Left.Right, "switch case")
+ e.escassignWhyWhere(n2.Rlist.First(), n.Left.Right, "switch case", n)
}
}
}
break
}
- e.escassign(n.Left, n.Right, nil)
+ e.escassign(n.Left, n.Right, e.stepAssignWhere(nil, nil, "", n))
case OAS2: // x,y = a,b
if n.List.Len() == n.Rlist.Len() {
rs := n.Rlist.Slice()
for i, n := range n.List.Slice() {
- e.escassignNilWhy(n, rs[i], "assign-pair")
+ e.escassignWhyWhere(n, rs[i], "assign-pair", n)
}
}
case OAS2RECV: // v, ok = <-ch
- e.escassignNilWhy(n.List.First(), n.Rlist.First(), "assign-pair-receive")
+ e.escassignWhyWhere(n.List.First(), n.Rlist.First(), "assign-pair-receive", n)
case OAS2MAPR: // v, ok = m[k]
- e.escassignNilWhy(n.List.First(), n.Rlist.First(), "assign-pair-mapr")
+ e.escassignWhyWhere(n.List.First(), n.Rlist.First(), "assign-pair-mapr", n)
case OAS2DOTTYPE: // v, ok = x.(type)
- e.escassignNilWhy(n.List.First(), n.Rlist.First(), "assign-pair-dot-type")
+ e.escassignWhyWhere(n.List.First(), n.Rlist.First(), "assign-pair-dot-type", n)
case OSEND: // ch <- x
- e.escassignSinkNilWhy(n, n.Right, "send")
+ e.escassignSinkWhy(n, n.Right, "send")
case ODEFER:
if e.loopdepth == 1 { // top level
// arguments leak out of scope
// TODO: leak to a dummy node instead
// defer f(x) - f and x escape
- e.escassignSinkNilWhy(n, n.Left.Left, "defer func")
+ e.escassignSinkWhy(n, n.Left.Left, "defer func")
- e.escassignSinkNilWhy(n, n.Left.Right, "defer func ...") // ODDDARG for call
+ e.escassignSinkWhy(n, n.Left.Right, "defer func ...") // ODDDARG for call
for _, n4 := range n.Left.List.Slice() {
- e.escassignSinkNilWhy(n, n4, "defer func arg")
+ e.escassignSinkWhy(n, n4, "defer func arg")
}
case OPROC:
// go f(x) - f and x escape
- e.escassignSinkNilWhy(n, n.Left.Left, "go func")
+ e.escassignSinkWhy(n, n.Left.Left, "go func")
- e.escassignSinkNilWhy(n, n.Left.Right, "go func ...") // ODDDARG for call
+ e.escassignSinkWhy(n, n.Left.Right, "go func ...") // ODDDARG for call
for _, n4 := range n.Left.List.Slice() {
- e.escassignSinkNilWhy(n, n4, "go func arg")
+ e.escassignSinkWhy(n, n4, "go func arg")
}
case OCALLMETH, OCALLFUNC, OCALLINTER:
if i >= len(rs) {
break
}
- e.escassignNilWhy(n, rs[i], "assign-pair-func-call")
+ e.escassignWhyWhere(n, rs[i], "assign-pair-func-call", n)
}
if n.List.Len() != len(rs) {
Fatalf("esc oas2func")
if lrn.Op != ONAME || lrn.Class != PPARAMOUT {
continue
}
- e.escassignNilWhy(lrn, retList.Index(i), "return")
+ e.escassignWhyWhere(lrn, retList.Index(i), "return", n)
i++
}
// Argument could leak through recover.
case OPANIC:
- e.escassignSinkNilWhy(n, n.Left, "panic")
+ e.escassignSinkWhy(n, n.Left, "panic")
case OAPPEND:
if !n.Isddd {
for _, nn := range n.List.Slice()[1:] {
- e.escassignSinkNilWhy(n, nn, "appended to slice") // lose track of assign to dereference
+ e.escassignSinkWhy(n, nn, "appended to slice") // lose track of assign to dereference
}
} else {
// append(slice1, slice2...) -- slice2 itself does not escape, but contents do.
slice2 := n.List.Second()
- e.escassignDereference(&e.theSink, slice2, e.stepAssign(nil, n, slice2, "appended slice...")) // lose track of assign of dereference
+ e.escassignDereference(&e.theSink, slice2, e.stepAssignWhere(n, slice2, "appended slice...", n)) // lose track of assign of dereference
if Debug['m'] > 3 {
Warnl(n.Lineno, "%v special treatment of append(slice1, slice2...) %S", e.curfnSym(n), n)
}
}
- e.escassignDereference(&e.theSink, n.List.First(), e.stepAssign(nil, n, n.List.First(), "appendee slice")) // The original elements are now leaked, too
+ e.escassignDereference(&e.theSink, n.List.First(), e.stepAssignWhere(n, n.List.First(), "appendee slice", n)) // The original elements are now leaked, too
case OCOPY:
- e.escassignDereference(&e.theSink, n.Right, e.stepAssign(nil, n, n.Right, "copied slice")) // lose track of assign of dereference
+ e.escassignDereference(&e.theSink, n.Right, e.stepAssignWhere(n, n.Right, "copied slice", n)) // lose track of assign of dereference
case OCONV, OCONVNOP:
- e.escassignNilWhy(n, n.Left, "converted")
+ e.escassignWhyWhere(n, n.Left, "converted", n)
case OCONVIFACE:
e.track(n)
- e.escassignNilWhy(n, n.Left, "interface-converted")
+ e.escassignWhyWhere(n, n.Left, "interface-converted", n)
case OARRAYLIT:
// Link values to array
for _, n5 := range n.List.Slice() {
- e.escassign(n, n5.Right, e.stepAssign(nil, n, n5.Right, "array literal element"))
+ e.escassign(n, n5.Right, e.stepAssignWhere(n, n5.Right, "array literal element", n))
}
case OSLICELIT:
e.track(n)
// Link values to slice
for _, n5 := range n.List.Slice() {
- e.escassign(n, n5.Right, e.stepAssign(nil, n, n5.Right, "slice literal element"))
+ e.escassign(n, n5.Right, e.stepAssignWhere(n, n5.Right, "slice literal element", n))
}
// Link values to struct.
case OSTRUCTLIT:
for _, n6 := range n.List.Slice() {
- e.escassignNilWhy(n, n6.Left, "struct literal element")
+ e.escassignWhyWhere(n, n6.Left, "struct literal element", n)
}
case OPTRLIT:
e.track(n)
// Link OSTRUCTLIT to OPTRLIT; if OPTRLIT escapes, OSTRUCTLIT elements do too.
- e.escassignNilWhy(n, n.Left, "pointer literal [assign]")
+ e.escassignWhyWhere(n, n.Left, "pointer literal [assign]", n)
case OCALLPART:
e.track(n)
// Contents make it to memory, lose track.
- e.escassignSinkNilWhy(n, n.Left, "call part")
+ e.escassignSinkWhy(n, n.Left, "call part")
case OMAPLIT:
e.track(n)
// Keys and values make it to memory, lose track.
for _, n7 := range n.List.Slice() {
- e.escassignSinkNilWhy(n, n7.Left, "map literal key")
- e.escassignSinkNilWhy(n, n7.Right, "map literal value")
+ e.escassignSinkWhy(n, n7.Left, "map literal key")
+ e.escassignSinkWhy(n, n7.Right, "map literal value")
}
case OCLOSURE:
a = typecheck(a, Erv)
}
- e.escassignNilWhy(n, a, "captured by a closure")
+ e.escassignWhyWhere(n, a, "captured by a closure", n)
}
fallthrough
lineno = lno
}
-// escassignNilWhy bundles a common case of
-// escassign(e, dst, src, e.stepAssign(nil, dst, src, reason))
-func (e *EscState) escassignNilWhy(dst, src *Node, reason string) {
+// escassignWhyWhere bundles a common case of
+// escassign(e, dst, src, e.stepAssignWhere(dst, src, reason, where))
+func (e *EscState) escassignWhyWhere(dst, src *Node, reason string, where *Node) {
var step *EscStep
if Debug['m'] != 0 {
- step = e.stepAssign(nil, dst, src, reason)
+ step = e.stepAssignWhere(dst, src, reason, where)
}
e.escassign(dst, src, step)
}
-// escassignSinkNilWhy bundles a common case of
+// escassignSinkWhy bundles a common case of
// escassign(e, &e.theSink, src, e.stepAssign(nil, dst, src, reason))
-func (e *EscState) escassignSinkNilWhy(dst, src *Node, reason string) {
+func (e *EscState) escassignSinkWhy(dst, src *Node, reason string) {
var step *EscStep
if Debug['m'] != 0 {
step = e.stepAssign(nil, dst, src, reason)
e.escassign(&e.theSink, src, step)
}
+// escassignSinkWhyWhere is escassignSinkWhy but includes a call site
+// for accurate location reporting.
+func (e *EscState) escassignSinkWhyWhere(dst, src *Node, reason string, call *Node) {
+ var step *EscStep
+ if Debug['m'] != 0 {
+ step = e.stepAssignWhere(dst, src, reason, call)
+ }
+ e.escassign(&e.theSink, src, step)
+}
+
// Assert that expr somehow gets assigned to dst, if non nil. for
// dst==nil, any name node expr still must be marked as being
// evaluated in curfn. For expr==nil, dst must still be examined for
// escassignfromtag models the input-to-output assignment flow of one of a function
// calls arguments, where the flow is encoded in "note".
-func (e *EscState) escassignfromtag(note string, dsts Nodes, src *Node) uint16 {
+func (e *EscState) escassignfromtag(note string, dsts Nodes, src, call *Node) uint16 {
em := parsetag(note)
if src.Op == OLITERAL {
return em
}
if em == EscUnknown {
- e.escassignSinkNilWhy(src, src, "passed to function[unknown]")
+ e.escassignSinkWhyWhere(src, src, "passed to call[argument escapes]", call)
return em
}
// If content inside parameter (reached via indirection)
// escapes to heap, mark as such.
if em&EscContentEscapes != 0 {
- e.escassign(&e.theSink, e.addDereference(src), e.stepAssign(nil, src, src, "passed to function[content escapes]"))
+ e.escassign(&e.theSink, e.addDereference(src), e.stepAssignWhere(src, src, "passed to call[argument content escapes]", call))
}
em0 := em
for i := uint16(0); i < embits-1; i++ {
n = e.addDereference(n) // encode level>0 as indirections
}
- e.escassign(dsts.Index(dstsi), n, e.stepAssign(nil, dsts.Index(dstsi), src, "passed-to-and-returned-from-function"))
+ e.escassign(dsts.Index(dstsi), n, e.stepAssignWhere(dsts.Index(dstsi), src, "passed-to-and-returned-from-call", call))
}
dstsi++
}
// We know nothing!
// Leak all the parameters
for _, arg := range args {
- e.escassignSinkNilWhy(call, arg, "parameter to indirect call")
+ e.escassignSinkWhy(call, arg, "parameter to indirect call")
if Debug['m'] > 3 {
fmt.Printf("%v::esccall:: indirect call <- %S, untracked\n", linestr(lineno), arg)
}
rf := fntype.Recv()
r := call.Left.Left
if haspointers(rf.Type) {
- e.escassignSinkNilWhy(call, r, "receiver in indirect call")
+ e.escassignSinkWhy(call, r, "receiver in indirect call")
}
} else { // indirect and OCALLFUNC = could be captured variables, too. (#14409)
rets := e.nodeEscState(call).Retval.Slice()
for _, ret := range rets {
- e.escassignDereference(ret, fn, e.stepAssign(nil, ret, fn, "captured by called closure"))
+ e.escassignDereference(ret, fn, e.stepAssignWhere(ret, fn, "captured by called closure", call))
}
}
return
switch n.Class {
case PPARAM:
if call.Op != OCALLFUNC && !sawRcvr {
- e.escassignNilWhy(n, call.Left.Left, "call receiver")
+ e.escassignWhyWhere(n, call.Left.Left, "call receiver", call)
sawRcvr = true
continue
}
e.track(arg)
call.Right = arg
}
- e.escassignNilWhy(n, arg, "arg to recursive call")
+ e.escassignWhyWhere(n, arg, "arg to recursive call", call) // TODO this message needs help.
if arg != args[0] {
// "..." arguments are untracked
for _, a := range args {
if Debug['m'] > 3 {
fmt.Printf("%v::esccall:: ... <- %S, untracked\n", linestr(lineno), a)
}
- e.escassignSinkNilWhy(arg, a, "... arg to recursive call")
+ e.escassignSinkWhyWhere(arg, a, "... arg to recursive call", call)
}
// No more PPARAM processing, but keep
// going for PPARAMOUT.
rf := fntype.Recv()
r := call.Left.Left
if haspointers(rf.Type) {
- e.escassignfromtag(rf.Note, cE.Retval, r)
+ e.escassignfromtag(rf.Note, cE.Retval, r, call)
}
}
}
if haspointers(param.Type) {
- if e.escassignfromtag(note, cE.Retval, arg)&EscMask == EscNone && parent.Op != ODEFER && parent.Op != OPROC {
+ if e.escassignfromtag(note, cE.Retval, arg, call)&EscMask == EscNone && parent.Op != ODEFER && parent.Op != OPROC {
a := arg
for a.Op == OCONVNOP {
a = a.Left
}
if note == uintptrEscapesTag {
- e.escassignSinkNilWhy(arg, arg, "escaping uintptr")
+ e.escassignSinkWhy(arg, arg, "escaping uintptr")
}
param = it.Next()
fmt.Printf("%v::esccall:: ... <- %S\n", linestr(lineno), args[i])
}
if note == uintptrEscapesTag {
- e.escassignSinkNilWhy(arg, args[i], "arg to uintptrescapes ...")
+ e.escassignSinkWhyWhere(arg, args[i], "arg to uintptrescapes ...", call)
} else {
- e.escassignNilWhy(arg, args[i], "arg to ...")
+ e.escassignWhyWhere(arg, args[i], "arg to ...", call)
}
}
}
// case it is step.dst.
nextDest := step.parent
dst := step.dst
+ where := step.where
if nextDest != nil {
dst = nextDest.src
}
- Warnl(src.Lineno, "\tfrom %v (%s) at %s", dst, step.why, dst.Line())
+ if where == nil {
+ where = dst
+ }
+ Warnl(src.Lineno, "\tfrom %v (%s) at %s", dst, step.why, where.Line())
}
for step := step0; step != nil && step.busy; step = step.parent {
step.busy = false