// Copyright 2011 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. // // Escape analysis. // // First escfunc, esc and escassign recurse over the ast of each // function to dig out flow(dst,src) edges between any // pointer-containing nodes and store them in dst->escflowsrc. For // variables assigned to a variable in an outer scope or used as a // return value, they store a flow(theSink, src) edge to a fake node // 'the Sink'. For variables referenced in closures, an edge // flow(closure, &var) is recorded and the flow of a closure itself to // an outer scope is tracked the same way as other variables. // // Then escflood walks the graph starting at theSink and tags all // variables of it can reach an & node as escaping and all function // parameters it can reach as leaking. // // If a value's address is taken but the address does not escape, // then the value can stay on the stack. If the value new(T) does // not escape, then new(T) can be rewritten into a stack allocation. // The same is true of slice literals. // // If escape analysis is disabled (-s), this code is not used. // Instead, the compiler assumes that any value whose address // is taken without being immediately dereferenced // needs to be moved to the heap, and new(T) and slice // literals are always real allocations. #include #include #include "go.h" static void escfunc(Node *func); static void esclist(NodeList *l); static void esc(Node *n); static void escassign(Node *dst, Node *src); static void esccall(Node*); static void escflows(Node *dst, Node *src); static void escflood(Node *dst); static void escwalk(int level, Node *dst, Node *src); static void esctag(Node *func); // Fake node that all // - return values and output variables // - parameters on imported functions not marked 'safe' // - assignments to global variables // flow to. static Node theSink; static NodeList* dsts; // all dst nodes static int loopdepth; // for detecting nested loop scopes static int pdepth; // for debug printing in recursions. static Strlit* safetag; // gets slapped on safe parameters' field types for export static int dstcount, edgecount; // diagnostic static NodeList* noesc; // list of possible non-escaping nodes, for printing void escapes(void) { NodeList *l; theSink.op = ONAME; theSink.class = PEXTERN; theSink.sym = lookup(".sink"); theSink.escloopdepth = -1; safetag = strlit("noescape"); // flow-analyze top level functions for(l=xtop; l; l=l->next) if(l->n->op == ODCLFUNC || l->n->op == OCLOSURE) escfunc(l->n); // print("escapes: %d dsts, %d edges\n", dstcount, edgecount); // visit the updstream of each dst, mark address nodes with // addrescapes, mark parameters unsafe for(l = dsts; l; l=l->next) escflood(l->n); // for all top level functions, tag the typenodes corresponding to the param nodes for(l=xtop; l; l=l->next) if(l->n->op == ODCLFUNC) esctag(l->n); if(debug['m']) { for(l=noesc; l; l=l->next) if(l->n->esc == EscNone) warnl(l->n->lineno, "%S %hN does not escape", (l->n->curfn && l->n->curfn->nname) ? l->n->curfn->nname->sym : S, l->n); } } static void escfunc(Node *func) { Node *savefn, *n; NodeList *ll; int saveld; saveld = loopdepth; loopdepth = 1; savefn = curfn; curfn = func; for(ll=curfn->dcl; ll; ll=ll->next) { if(ll->n->op != ONAME) continue; switch (ll->n->class) { case PPARAMOUT: // output parameters flow to the sink escflows(&theSink, ll->n); ll->n->escloopdepth = loopdepth; break; case PPARAM: if(ll->n->type && !haspointers(ll->n->type)) break; ll->n->esc = EscNone; // prime for escflood later noesc = list(noesc, ll->n); ll->n->escloopdepth = loopdepth; break; } } // walk will take the address of cvar->closure later and assign it to cvar. // handle that here by linking a fake oaddr node directly to the closure. for(ll=curfn->cvars; ll; ll=ll->next) { if(ll->n->op == OXXX) // see dcl.c:398 continue; n = nod(OADDR, ll->n->closure, N); n->lineno = ll->n->lineno; typecheck(&n, Erv); escassign(curfn, n); } esclist(curfn->nbody); curfn = savefn; loopdepth = saveld; } static void esclist(NodeList *l) { for(; l; l=l->next) esc(l->n); } static void esc(Node *n) { int lno; NodeList *ll, *lr; if(n == N) return; lno = setlineno(n); if(n->op == OFOR || n->op == ORANGE) loopdepth++; esc(n->left); esc(n->right); esc(n->ntest); esc(n->nincr); esclist(n->ninit); esclist(n->nbody); esclist(n->nelse); esclist(n->list); esclist(n->rlist); if(n->op == OFOR || n->op == ORANGE) loopdepth--; if(debug['m'] > 1) print("%L:[%d] %S esc: %N\n", lineno, loopdepth, (curfn && curfn->nname) ? curfn->nname->sym : S, n); switch(n->op) { case ODCL: // Record loop depth at declaration. if(n->left) n->left->escloopdepth = loopdepth; break; case OLABEL: // TODO: new loop/scope only if there are backjumps to it. loopdepth++; break; case ORANGE: // Everything but fixed array is a dereference. if(isfixedarray(n->type) && n->list->next) escassign(n->list->next->n, n->right); break; case OSWITCH: if(n->ntest && n->ntest->op == OTYPESW) { for(ll=n->list; ll; ll=ll->next) { // cases // ntest->right is the argument of the .(type), // ll->n->nname is the variable per case escassign(ll->n->nname, n->ntest->right); } } break; case OAS: case OASOP: escassign(n->left, n->right); break; case OAS2: // x,y = a,b if(count(n->list) == count(n->rlist)) for(ll=n->list, lr=n->rlist; ll; ll=ll->next, lr=lr->next) escassign(ll->n, lr->n); break; case OAS2RECV: // v, ok = <-ch case OAS2MAPR: // v, ok = m[k] case OAS2DOTTYPE: // v, ok = x.(type) escassign(n->list->n, n->rlist->n); break; case OSEND: // ch <- x escassign(&theSink, n->right); break; case ODEFER: if(loopdepth == 1) // top level break; // arguments leak out of scope // TODO: leak to a dummy node instead // fallthrough case OPROC: // go f(x) - f and x escape escassign(&theSink, n->left->left); escassign(&theSink, n->left->right); // ODDDARG for call for(ll=n->left->list; ll; ll=ll->next) escassign(&theSink, ll->n); break; case ORETURN: for(ll=n->list; ll; ll=ll->next) escassign(&theSink, ll->n); break; case OPANIC: // Argument could leak through recover. escassign(&theSink, n->left); break; case OAPPEND: if(!n->isddd) for(ll=n->list->next; ll; ll=ll->next) escassign(&theSink, ll->n); // lose track of assign to dereference break; case OCALLMETH: case OCALLFUNC: case OCALLINTER: esccall(n); break; case OCONV: case OCONVNOP: case OCONVIFACE: escassign(n, n->left); break; case OARRAYLIT: if(isslice(n->type)) { n->esc = EscNone; // until proven otherwise noesc = list(noesc, n); n->escloopdepth = loopdepth; // Values make it to memory, lose track. for(ll=n->list; ll; ll=ll->next) escassign(&theSink, ll->n->right); } else { // Link values to array. for(ll=n->list; ll; ll=ll->next) escassign(n, ll->n->right); } break; case OSTRUCTLIT: // Link values to struct. for(ll=n->list; ll; ll=ll->next) escassign(n, ll->n->right); break; case OMAPLIT: n->esc = EscNone; // until proven otherwise noesc = list(noesc, n); n->escloopdepth = loopdepth; // Keys and values make it to memory, lose track. for(ll=n->list; ll; ll=ll->next) { escassign(&theSink, ll->n->left); escassign(&theSink, ll->n->right); } break; case OADDR: case OCLOSURE: case OMAKECHAN: case OMAKEMAP: case OMAKESLICE: case ONEW: n->escloopdepth = loopdepth; n->esc = EscNone; // until proven otherwise noesc = list(noesc, n); break; } lineno = lno; } // 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 // evaluations inside it (e.g *f(x) = y) static void escassign(Node *dst, Node *src) { int lno; if(isblank(dst) || dst == N || src == N || src->op == ONONAME || src->op == OXXX) return; if(debug['m'] > 1) print("%L:[%d] %S escassign: %hN = %hN\n", lineno, loopdepth, (curfn && curfn->nname) ? curfn->nname->sym : S, dst, src); setlineno(dst); // Analyze lhs of assignment. // Replace dst with theSink if we can't track it. switch(dst->op) { default: dump("dst", dst); fatal("escassign: unexpected dst"); case OARRAYLIT: case OCLOSURE: case OCONV: case OCONVIFACE: case OCONVNOP: case OMAPLIT: case OSTRUCTLIT: break; case ONAME: if(dst->class == PEXTERN) dst = &theSink; break; case ODOT: // treat "dst.x = src" as "dst = src" escassign(dst->left, src); return; case OINDEX: if(isfixedarray(dst->left->type)) { escassign(dst->left, src); return; } dst = &theSink; // lose track of dereference break; case OIND: case ODOTPTR: dst = &theSink; // lose track of dereference break; case OINDEXMAP: // lose track of key and value escassign(&theSink, dst->right); dst = &theSink; break; } lno = setlineno(src); pdepth++; switch(src->op) { case OADDR: // dst = &x case OIND: // dst = *x case ODOTPTR: // dst = (*x).f case ONAME: case OPARAM: case ODDDARG: case OARRAYLIT: case OMAPLIT: case OSTRUCTLIT: // loopdepth was set in the defining statement or function header escflows(dst, src); break; case OCONV: case OCONVIFACE: case OCONVNOP: case ODOT: case ODOTMETH: // treat recv.meth as a value with recv in it, only happens in ODEFER and OPROC // iface.method already leaks iface in esccall, no need to put in extra ODOTINTER edge here case ODOTTYPE: case ODOTTYPE2: case OSLICE: case OSLICEARR: // Conversions, field access, slice all preserve the input value. escassign(dst, src->left); break; case OAPPEND: // Append returns first argument. escassign(dst, src->list->n); break; case OINDEX: // Index of array preserves input value. if(isfixedarray(src->left->type)) escassign(dst, src->left); break; case OMAKECHAN: case OMAKEMAP: case OMAKESLICE: case ONEW: escflows(dst, src); break; case OCLOSURE: escflows(dst, src); escfunc(src); break; case OADD: case OSUB: case OOR: case OXOR: case OMUL: case ODIV: case OMOD: case OLSH: case ORSH: case OAND: case OANDNOT: case OPLUS: case OMINUS: case OCOM: // Might be pointer arithmetic, in which case // the operands flow into the result. // TODO(rsc): Decide what the story is here. This is unsettling. escassign(dst, src->left); escassign(dst, src->right); break; } pdepth--; lineno = lno; } // This is a bit messier than fortunate, pulled out of escassign'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 // nodes, which may be marked 'n(ofloworescape)'. Navigating the ast is slightly // different for methods vs plain functions and for imported vs // this-package static void esccall(Node *n) { NodeList *ll, *lr; Node *a, *fn, *src; Type *t, *fntype; fn = N; switch(n->op) { default: fatal("esccall"); case OCALLFUNC: fn = n->left; fntype = fn->type; break; case OCALLMETH: fn = n->left->right->sym->def; if(fn) fntype = fn->type; else fntype = n->left->type; break; case OCALLINTER: fntype = n->left->type; break; } 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(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody && fn->ntype) { // Local function. Incorporate into flow graph. // Receiver. if(n->op != OCALLFUNC) escassign(fn->ntype->left->left, n->left->left); for(lr=fn->ntype->list; ll && lr; ll=ll->next, lr=lr->next) { src = ll->n; if(lr->n->isddd && !n->isddd) { // Introduce ODDDARG node to represent ... allocation. src = nod(ODDDARG, N, N); src->escloopdepth = loopdepth; src->lineno = n->lineno; src->esc = EscNone; // until we find otherwise noesc = list(noesc, src); n->right = src; } if(lr->n->left != N) escassign(lr->n->left, src); if(src != ll->n) break; } // "..." arguments are untracked for(; ll; ll=ll->next) escassign(&theSink, ll->n); return; } // Imported function. Use the escape tags. if(n->op != OCALLFUNC) { t = getthisx(fntype)->type; if(!t->note || strcmp(t->note->s, safetag->s) != 0) escassign(&theSink, n->left->left); } for(t=getinargx(fntype)->type; ll; ll=ll->next) { src = ll->n; if(t->isddd && !n->isddd) { // Introduce ODDDARG node to represent ... allocation. src = nod(ODDDARG, N, N); src->escloopdepth = loopdepth; src->lineno = n->lineno; src->esc = EscNone; // until we find otherwise noesc = list(noesc, src); n->right = src; } if(!t->note || strcmp(t->note->s, safetag->s) != 0) escassign(&theSink, src); if(src != ll->n) break; t = t->down; } // "..." arguments are untracked for(; ll; ll=ll->next) escassign(&theSink, ll->n); } // Store the link src->dst in dst, throwing out some quick wins. static void escflows(Node *dst, Node *src) { if(dst == nil || src == nil || dst == src) return; // Don't bother building a graph for scalars. if(src->type && !haspointers(src->type)) return; if(debug['m']>2) print("%L::flows:: %hN <- %hN\n", lineno, dst, src); if(dst->escflowsrc == nil) { dsts = list(dsts, dst); dstcount++; } edgecount++; dst->escflowsrc = list(dst->escflowsrc, src); } // Whenever we hit a reference node, the level goes up by one, and whenever // we hit an OADDR, the level goes down by one. as long as we're on a level > 0 // finding an OADDR just means we're following the upstream of a dereference, // so this address doesn't leak (yet). // If level == 0, it means the /value/ of this node can reach the root of this flood. // so if this node is an OADDR, it's argument should be marked as escaping iff // it's currfn/loopdepth are different from the flood's root. // Once an object has been moved to the heap, all of it's upstream should be considered // escaping to the global scope. static void escflood(Node *dst) { NodeList *l; switch(dst->op) { case ONAME: case OCLOSURE: break; default: return; } if(debug['m']>1) print("\nescflood:%d: dst %hN scope:%S[%d]\n", walkgen, dst, (dst->curfn && dst->curfn->nname) ? dst->curfn->nname->sym : S, dst->escloopdepth); for(l = dst->escflowsrc; l; l=l->next) { walkgen++; escwalk(0, dst, l->n); } } static void escwalk(int level, Node *dst, Node *src) { NodeList *ll; int leaks; if(src->walkgen == walkgen) return; src->walkgen = walkgen; if(debug['m']>1) print("escwalk: level:%d depth:%d %.*s %hN scope:%S[%d]\n", level, pdepth, pdepth, "\t\t\t\t\t\t\t\t\t\t", src, (src->curfn && src->curfn->nname) ? src->curfn->nname->sym : S, src->escloopdepth); pdepth++; leaks = (level <= 0) && (dst->escloopdepth < src->escloopdepth); switch(src->op) { case ONAME: if(src->class == PPARAM && leaks && src->esc == EscNone) { src->esc = EscScope; if(debug['m']) warnl(src->lineno, "leaking param: %hN", src); } break; case OADDR: if(leaks) { src->esc = EscHeap; addrescapes(src->left); if(debug['m']) warnl(src->lineno, "%hN escapes to heap", src); } escwalk(level-1, dst, src->left); break; case OARRAYLIT: if(isfixedarray(src->type)) break; // fall through case ODDDARG: case OMAKECHAN: case OMAKEMAP: case OMAKESLICE: case OMAPLIT: case ONEW: case OCLOSURE: if(leaks) { src->esc = EscHeap; if(debug['m']) warnl(src->lineno, "%hN escapes to heap", src); } break; case OINDEX: if(isfixedarray(src->type)) break; // fall through case OSLICE: case ODOTPTR: case OINDEXMAP: case OIND: escwalk(level+1, dst, src->left); } for(ll=src->escflowsrc; ll; ll=ll->next) escwalk(level, dst, ll->n); pdepth--; } static void esctag(Node *func) { Node *savefn; NodeList *ll; // External functions must be assumed unsafe. if(func->nbody == nil) return; savefn = curfn; curfn = func; for(ll=curfn->dcl; ll; ll=ll->next) { if(ll->n->op != ONAME || ll->n->class != PPARAM) continue; switch (ll->n->esc) { case EscNone: // not touched by escflood if(haspointers(ll->n->type)) // don't bother tagging for scalars ll->n->paramfld->note = safetag; case EscHeap: // touched by escflood, moved to heap case EscScope: // touched by escflood, value leaves scope break; } } curfn = savefn; }