mirror of
https://github.com/golang/go
synced 2024-11-11 20:50:23 -07:00
cmd/gc: escape analysis to track flow of in to out parameters.
includes step 0: synthesize outparams, from 6600044 includes step 1,2: give outparams loopdepth 0 and verify unchanged results generate esc:$mask tags, but still tie to sink if a param has mask != 0 from 6610054 adds final steps: - have esccall generate n->escretval, a list of nodes the function results flow to - use these in esccall and ORETURN/OAS2FUNC/and f(g()) - only tie parameters to sink if tag is absent, otherwise according to mask, tie them to escretval R=rsc, bradfitz CC=dave, gobot, golang-dev, iant, rsc https://golang.org/cl/6741044
This commit is contained in:
parent
c8fe9c7606
commit
507fcf37d2
121
src/cmd/gc/esc.c
121
src/cmd/gc/esc.c
@ -209,9 +209,10 @@ struct EscState {
|
||||
int pdepth; // for debug printing in recursions.
|
||||
int dstcount, edgecount; // diagnostic
|
||||
NodeList* noesc; // list of possible non-escaping nodes, for printing
|
||||
int recursive; // recursive function or group of mutually recursive functions.
|
||||
};
|
||||
|
||||
static Strlit *tags[16] = { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil };
|
||||
static Strlit *tags[16];
|
||||
|
||||
static Strlit*
|
||||
mktag(int mask)
|
||||
@ -260,8 +261,6 @@ analyze(NodeList *all, int recursive)
|
||||
NodeList *l;
|
||||
EscState es, *e;
|
||||
|
||||
USED(recursive);
|
||||
|
||||
memset(&es, 0, sizeof es);
|
||||
e = &es;
|
||||
e->theSink.op = ONAME;
|
||||
@ -269,6 +268,7 @@ analyze(NodeList *all, int recursive)
|
||||
e->theSink.class = PEXTERN;
|
||||
e->theSink.sym = lookup(".sink");
|
||||
e->theSink.escloopdepth = -1;
|
||||
e->recursive = recursive;
|
||||
|
||||
for(l=all; l; l=l->next)
|
||||
if(l->n->op == ODCLFUNC)
|
||||
@ -308,6 +308,8 @@ escfunc(EscState *e, Node *func)
|
||||
NodeList *ll;
|
||||
int saveld;
|
||||
|
||||
// print("escfunc %N %s\n", func->nname, e->recursive?"(recursive)":"");
|
||||
|
||||
if(func->esc != 1)
|
||||
fatal("repeat escfunc %N", func->nname);
|
||||
func->esc = EscFuncStarted;
|
||||
@ -335,6 +337,12 @@ escfunc(EscState *e, Node *func)
|
||||
}
|
||||
}
|
||||
|
||||
// in a mutually recursive group we lose track of the return values
|
||||
if(e->recursive)
|
||||
for(ll=curfn->dcl; ll; ll=ll->next)
|
||||
if(ll->n->op == ONAME && ll->n->class == PPARAMOUT)
|
||||
escflows(e, &e->theSink, ll->n);
|
||||
|
||||
escloopdepthlist(e, curfn->nbody);
|
||||
esclist(e, curfn->nbody);
|
||||
curfn = savefn;
|
||||
@ -450,7 +458,7 @@ esc(EscState *e, Node *n)
|
||||
}
|
||||
// See case OLABEL in escloopdepth above
|
||||
// else if(n->left->sym->label == nil)
|
||||
// fatal("escape anaylysis missed or messed up a label: %+N", n);
|
||||
// fatal("escape analysis missed or messed up a label: %+N", n);
|
||||
|
||||
n->left->sym->label = nil;
|
||||
break;
|
||||
@ -506,13 +514,30 @@ esc(EscState *e, Node *n)
|
||||
escassign(e, &e->theSink, ll->n);
|
||||
break;
|
||||
|
||||
case OCALLMETH:
|
||||
case OCALLFUNC:
|
||||
case OCALLINTER:
|
||||
esccall(e, n);
|
||||
break;
|
||||
|
||||
case OAS2FUNC: // x,y = f()
|
||||
// esccall already done on n->rlist->n. tie it's escretval to n->list
|
||||
lr=n->rlist->n->escretval;
|
||||
for(ll=n->list; lr && ll; lr=lr->next, ll=ll->next)
|
||||
escassign(e, ll->n, lr->n);
|
||||
if(lr || ll)
|
||||
fatal("esc oas2func");
|
||||
break;
|
||||
|
||||
case ORETURN:
|
||||
ll=n->list;
|
||||
if(count(n->list) == 1 && curfn->type->outtuple > 1) {
|
||||
// OAS2FUNC in disguise
|
||||
break;
|
||||
// esccall already done on n->list->n
|
||||
// tie n->list->n->escretval to curfn->dcl PPARAMOUT's
|
||||
ll = n->list->n->escretval;
|
||||
}
|
||||
|
||||
ll=n->list;
|
||||
for(lr = curfn->dcl; lr && ll; lr=lr->next) {
|
||||
if (lr->n->op != ONAME || lr->n->class != PPARAMOUT)
|
||||
continue;
|
||||
@ -534,12 +559,6 @@ esc(EscState *e, Node *n)
|
||||
escassign(e, &e->theSink, ll->n); // lose track of assign to dereference
|
||||
break;
|
||||
|
||||
case OCALLMETH:
|
||||
case OCALLFUNC:
|
||||
case OCALLINTER:
|
||||
esccall(e, n);
|
||||
break;
|
||||
|
||||
case OCONV:
|
||||
case OCONVNOP:
|
||||
case OCONVIFACE:
|
||||
@ -693,6 +712,14 @@ escassign(EscState *e, Node *dst, Node *src)
|
||||
escflows(e, dst, src);
|
||||
break;
|
||||
|
||||
case OCALLMETH:
|
||||
case OCALLFUNC:
|
||||
case OCALLINTER:
|
||||
if(count(src->escretval) != 1)
|
||||
fatal("escassign from call %+N", src);
|
||||
escflows(e, dst, src->escretval->n);
|
||||
break;
|
||||
|
||||
case ODOT:
|
||||
// A non-pointer escaping from a struct does not concern us.
|
||||
if(src->type && !haspointers(src->type))
|
||||
@ -748,6 +775,26 @@ escassign(EscState *e, Node *dst, Node *src)
|
||||
lineno = lno;
|
||||
}
|
||||
|
||||
static void
|
||||
escassignfromtag(EscState *e, Strlit *note, NodeList *dsts, Node *src)
|
||||
{
|
||||
int em;
|
||||
|
||||
em = parsetag(note);
|
||||
|
||||
if(em == EscUnknown) {
|
||||
escassign(e, &e->theSink, src);
|
||||
return;
|
||||
}
|
||||
|
||||
for(em >>= EscBits; em && dsts; em >>= 1, dsts=dsts->next)
|
||||
if(em & 1)
|
||||
escassign(e, dsts->n, src);
|
||||
|
||||
if (em != 0 && dsts == nil)
|
||||
fatal("corrupt esc tag %Z or messed up escretval list\n", note);
|
||||
}
|
||||
|
||||
// This is a bit messier than fortunate, pulled out of esc's big
|
||||
// switch for clarity. We either have the paramnodes, which may be
|
||||
// connected to other things throug flows or we have the parameter type
|
||||
@ -760,6 +807,8 @@ esccall(EscState *e, Node *n)
|
||||
NodeList *ll, *lr;
|
||||
Node *a, *fn, *src;
|
||||
Type *t, *fntype;
|
||||
char buf[40];
|
||||
int i;
|
||||
|
||||
fn = N;
|
||||
switch(n->op) {
|
||||
@ -787,19 +836,20 @@ esccall(EscState *e, Node *n)
|
||||
ll = n->list;
|
||||
if(n->list != nil && n->list->next == nil) {
|
||||
a = n->list->n;
|
||||
if(a->type->etype == TSTRUCT && a->type->funarg) {
|
||||
// f(g()).
|
||||
// Since f's arguments are g's results and
|
||||
// all function results escape, we're done.
|
||||
ll = nil;
|
||||
}
|
||||
if(a->type->etype == TSTRUCT && a->type->funarg) // f(g()).
|
||||
ll = a->escretval;
|
||||
}
|
||||
|
||||
if(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody && fn->ntype && fn->defn->esc < EscFuncTagged) {
|
||||
// Local function in this round. Incorporate into flow graph.
|
||||
if(fn->defn->esc == EscFuncUnknown)
|
||||
// function in same mutually recursive group. Incorporate into flow graph.
|
||||
// print("esc local fn: %N\n", fn->ntype);
|
||||
if(fn->defn->esc == EscFuncUnknown || n->escretval != nil)
|
||||
fatal("graph inconsistency");
|
||||
|
||||
// set up out list on this call node
|
||||
for(lr=fn->ntype->rlist; lr; lr=lr->next)
|
||||
n->escretval = list(n->escretval, lr->n->left); // type.rlist -> dclfield -> ONAME (PPARAMOUT)
|
||||
|
||||
// Receiver.
|
||||
if(n->op != OCALLFUNC)
|
||||
escassign(e, fn->ntype->left->left, n->left->left);
|
||||
@ -823,15 +873,35 @@ esccall(EscState *e, Node *n)
|
||||
// "..." arguments are untracked
|
||||
for(; ll; ll=ll->next)
|
||||
escassign(e, &e->theSink, ll->n);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Imported or completely analyzed function. Use the escape tags.
|
||||
if(n->op != OCALLFUNC) {
|
||||
t = getthisx(fntype)->type;
|
||||
if(parsetag(t->note) != EscNone)
|
||||
escassign(e, &e->theSink, n->left->left);
|
||||
if(n->escretval != nil)
|
||||
fatal("esc already decorated call %+N\n", n);
|
||||
|
||||
// set up out list on this call node with dummy auto ONAMES in the current (calling) function.
|
||||
i = 0;
|
||||
for(t=getoutargx(fntype)->type; t; t=t->down) {
|
||||
src = nod(ONAME, N, N);
|
||||
snprint(buf, sizeof buf, ".dum%d", i++);
|
||||
src->sym = lookup(buf);
|
||||
src->type = t->type;
|
||||
src->class = PAUTO;
|
||||
src->curfn = curfn;
|
||||
src->escloopdepth = e->loopdepth;
|
||||
src->used = 1;
|
||||
src->lineno = n->lineno;
|
||||
n->escretval = list(n->escretval, src);
|
||||
}
|
||||
|
||||
// print("esc analyzed fn: %#N (%+T) returning (%+H)\n", fn, fntype, n->escretval);
|
||||
|
||||
// Receiver.
|
||||
if(n->op != OCALLFUNC)
|
||||
escassignfromtag(e, getthisx(fntype)->type->note, n->escretval, n->left->left);
|
||||
|
||||
for(t=getinargx(fntype)->type; ll; ll=ll->next) {
|
||||
src = ll->n;
|
||||
if(t->isddd && !n->isddd) {
|
||||
@ -843,8 +913,7 @@ esccall(EscState *e, Node *n)
|
||||
e->noesc = list(e->noesc, src);
|
||||
n->right = src;
|
||||
}
|
||||
if(parsetag(t->note) != EscNone)
|
||||
escassign(e, &e->theSink, src);
|
||||
escassignfromtag(e, t->note, n->escretval, src);
|
||||
if(src != ll->n)
|
||||
break;
|
||||
t = t->down;
|
||||
|
@ -306,7 +306,8 @@ struct Node
|
||||
|
||||
// Escape analysis.
|
||||
NodeList* escflowsrc; // flow(this, src)
|
||||
int escloopdepth; // -1: global, 0: not set, function top level:1, increased inside function for every loop or label to mark scopes
|
||||
NodeList* escretval; // on OCALLxxx, list of dummy return values
|
||||
int escloopdepth; // -1: global, 0: return variables, 1:function top level, increased inside function for every loop or label to mark scopes
|
||||
|
||||
Sym* sym; // various
|
||||
int32 vargen; // unique name for OTYPE/ONAME
|
||||
|
@ -561,12 +561,21 @@ func myprint1(y *int, x ...interface{}) *interface{} { // ERROR "y does not esca
|
||||
return &x[0] // ERROR "&x.0. escapes to heap"
|
||||
}
|
||||
|
||||
func foo75(z *int) { // ERROR "leaking param: z"
|
||||
func foo75(z *int) { // ERROR "z does not escape"
|
||||
myprint(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo75a(z *int) { // ERROR "z does not escape"
|
||||
myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
|
||||
myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo75esc(z *int) { // ERROR "leaking param: z"
|
||||
gxx = myprint(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo75aesc(z *int) { // ERROR "z does not escape"
|
||||
var ppi **interface{} // assignments to pointer dereferences lose track
|
||||
*ppi = myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
|
||||
}
|
||||
|
||||
func foo76(z *int) { // ERROR "leaking param: z"
|
||||
@ -574,7 +583,7 @@ func foo76(z *int) { // ERROR "leaking param: z"
|
||||
}
|
||||
|
||||
func foo76a(z *int) { // ERROR "leaking param: z"
|
||||
myprint1(nil, z) // ERROR "[.][.][.] argument escapes to heap"
|
||||
myprint1(nil, z) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo76b() {
|
||||
@ -582,7 +591,7 @@ func foo76b() {
|
||||
}
|
||||
|
||||
func foo76c() {
|
||||
myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
|
||||
myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo76d() {
|
||||
@ -590,7 +599,7 @@ func foo76d() {
|
||||
}
|
||||
|
||||
func foo76e() {
|
||||
defer myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
|
||||
defer myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
|
||||
}
|
||||
|
||||
func foo76f() {
|
||||
@ -610,10 +619,15 @@ func foo77(z []interface{}) { // ERROR "z does not escape"
|
||||
myprint(nil, z...) // z does not escape
|
||||
}
|
||||
|
||||
func foo77a(z []interface{}) { // ERROR "leaking param: z"
|
||||
func foo77a(z []interface{}) { // ERROR "z does not escape"
|
||||
myprint1(nil, z...)
|
||||
}
|
||||
|
||||
func foo77b(z []interface{}) { // ERROR "leaking param: z"
|
||||
var ppi **interface{}
|
||||
*ppi = myprint1(nil, z...)
|
||||
}
|
||||
|
||||
func foo78(z int) *int { // ERROR "moved to heap: z"
|
||||
return &z // ERROR "&z escapes to heap"
|
||||
}
|
||||
|
119
test/escape5.go
Normal file
119
test/escape5.go
Normal file
@ -0,0 +1,119 @@
|
||||
// errorcheck -0 -m -l
|
||||
|
||||
// Copyright 2012 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.
|
||||
|
||||
// Test, using compiler diagnostic flags, that the escape analysis is working.
|
||||
// Compiles but does not run. Inlining is disabled.
|
||||
|
||||
package foo
|
||||
|
||||
func noleak(p *int) int { // ERROR "p does not escape"
|
||||
return *p
|
||||
}
|
||||
|
||||
func leaktoret(p *int) *int { // ERROR "leaking param: p to result"
|
||||
return p
|
||||
}
|
||||
|
||||
func leaktoret2(p *int) (*int, *int) { // ERROR "leaking param: p to result .anon1" "leaking param: p to result .anon2"
|
||||
return p, p
|
||||
}
|
||||
|
||||
func leaktoret22(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon2" "leaking param: q to result .anon3"
|
||||
return p, q
|
||||
}
|
||||
|
||||
func leaktoret22b(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon3" "leaking param: q to result .anon2"
|
||||
return leaktoret22(q, p)
|
||||
}
|
||||
|
||||
func leaktoret22c(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon3" "leaking param: q to result .anon2"
|
||||
r, s := leaktoret22(q, p)
|
||||
return r, s
|
||||
}
|
||||
|
||||
func leaktoret22d(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
|
||||
r, s = leaktoret22(q, p)
|
||||
return
|
||||
}
|
||||
|
||||
func leaktoret22e(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
|
||||
r, s = leaktoret22(q, p)
|
||||
return r, s
|
||||
}
|
||||
|
||||
func leaktoret22f(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
|
||||
rr, ss := leaktoret22(q, p)
|
||||
return rr, ss
|
||||
}
|
||||
|
||||
var gp *int
|
||||
|
||||
func leaktosink(p *int) *int { // ERROR "leaking param: p"
|
||||
gp = p
|
||||
return p
|
||||
}
|
||||
|
||||
func f1() {
|
||||
var x int
|
||||
p := noleak(&x) // ERROR "&x does not escape"
|
||||
_ = p
|
||||
}
|
||||
|
||||
func f2() {
|
||||
var x int
|
||||
p := leaktoret(&x) // ERROR "&x does not escape"
|
||||
_ = p
|
||||
}
|
||||
|
||||
func f3() {
|
||||
var x int // ERROR "moved to heap: x"
|
||||
p := leaktoret(&x) // ERROR "&x escapes to heap"
|
||||
gp = p
|
||||
}
|
||||
|
||||
func f4() {
|
||||
var x int // ERROR "moved to heap: x"
|
||||
p, q := leaktoret2(&x) // ERROR "&x escapes to heap"
|
||||
gp = p
|
||||
gp = q
|
||||
}
|
||||
|
||||
func f5() {
|
||||
var x int
|
||||
leaktoret22(leaktoret2(&x)) // ERROR "&x does not escape"
|
||||
}
|
||||
|
||||
func f6() {
|
||||
var x int // ERROR "moved to heap: x"
|
||||
px1, px2 := leaktoret22(leaktoret2(&x)) // ERROR "&x escapes to heap"
|
||||
gp = px1
|
||||
_ = px2
|
||||
}
|
||||
|
||||
type T struct{ x int }
|
||||
|
||||
func (t *T) Foo(u int) (*T, bool) { // ERROR "leaking param: t to result"
|
||||
t.x += u
|
||||
return t, true
|
||||
}
|
||||
|
||||
func f7() *T {
|
||||
r, _ := new(T).Foo(42) // ERROR "new.T. escapes to heap"
|
||||
return r
|
||||
}
|
||||
|
||||
func leakrecursive1(p, q *int) (*int, *int) { // ERROR "leaking param: p" "leaking param: q"
|
||||
return leakrecursive2(q, p)
|
||||
}
|
||||
|
||||
func leakrecursive2(p, q *int) (*int, *int) { // ERROR "leaking param: p" "leaking param: q"
|
||||
if *p > *q {
|
||||
return leakrecursive1(q, p)
|
||||
}
|
||||
// without this, leakrecursive? are safe for p and q, b/c in fact their graph does not have leaking edges.
|
||||
return p, q
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user