From 507fcf37d2a5565fbe5d13b24f7082464b17dc3a Mon Sep 17 00:00:00 2001 From: Luuk van Dijk Date: Mon, 29 Oct 2012 13:38:21 +0100 Subject: [PATCH] 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 --- src/cmd/gc/esc.c | 121 +++++++++++++++++++++++++++++++++++++---------- src/cmd/gc/go.h | 3 +- test/escape2.go | 26 +++++++--- test/escape5.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 33 deletions(-) create mode 100644 test/escape5.go diff --git a/src/cmd/gc/esc.c b/src/cmd/gc/esc.c index a42027ea5f..a2bcbae8fe 100644 --- a/src/cmd/gc/esc.c +++ b/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; diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h index c1e637120c..d92dd40611 100644 --- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -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 diff --git a/test/escape2.go b/test/escape2.go index 8db12d9913..bfc90ecb41 100644 --- a/test/escape2.go +++ b/test/escape2.go @@ -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" } diff --git a/test/escape5.go b/test/escape5.go new file mode 100644 index 0000000000..22c324f902 --- /dev/null +++ b/test/escape5.go @@ -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 +} + -- 2.48.1