closure.$O\
const.$O\
dcl.$O\
+ esc.$O\
export.$O\
gen.$O\
init.$O\
f->width = BADWIDTH;
f->isddd = n->isddd;
+ // esc.c needs to find f given a PPARAM to add the tag.
+ if(funarg && n->left && n->left->class == PPARAM)
+ n->left->paramfld = f;
+
if(left != N && left->op == ONAME) {
f->nname = left;
f->embedded = n->embedded;
--- /dev/null
+// 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.
+//
+// The base version before this file existed, active with debug['s']
+// == 0, assumes any node that has a reference to it created at some
+// point, may flow to the global scope except
+// - if its address is dereferenced immediately with only CONVNOPs in
+// between the * and the &
+// - if it is for a closure variable and the closure executed at the
+// place it's defined
+//
+// Flag -s disables the old codepaths and switches on the code here:
+//
+// First escfunc, escstmt and escexpr 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.
+//
+// Watch the variables moved to the heap and parameters tagged as
+// unsafe with -m, more detailed analysis output with -mm
+//
+
+#include "go.h"
+
+static void escfunc(Node *func);
+static void escstmtlist(NodeList *stmts);
+static void escstmt(Node *stmt);
+static void escexpr(Node *dst, Node *expr);
+static void escexprcall(Node *dst, Node *callexpr);
+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 int floodgen; // loop prevention in flood/walk
+static Strlit* safetag; // gets slapped on safe parameters' field types for export
+static int dstcount, edgecount; // diagnostic
+
+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);
+}
+
+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:
+ ll->n->esc = EscNone; // prime for escflood later
+ 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);
+ escexpr(curfn, n);
+ }
+
+ escstmtlist(curfn->nbody);
+ curfn = savefn;
+ loopdepth = saveld;
+}
+
+static void
+escstmtlist(NodeList* stmts)
+{
+ for(; stmts; stmts=stmts->next)
+ escstmt(stmts->n);
+}
+
+static void
+escstmt(Node *stmt)
+{
+ int cl, cr, lno;
+ NodeList *ll, *lr;
+ Node *dst;
+
+ if(stmt == N)
+ return;
+
+ lno = setlineno(stmt);
+
+ if(stmt->typecheck == 0 && stmt->op != ODCL) { // TODO something with OAS2
+ dump("escstmt missing typecheck", stmt);
+ fatal("missing typecheck.");
+ }
+
+ // Common to almost all statements, and nil if n/a.
+ escstmtlist(stmt->ninit);
+
+ if(debug['m'] > 1)
+ print("%L:[%d] %#S statement: %#N\n", lineno, loopdepth,
+ (curfn && curfn->nname) ? curfn->nname->sym : S, stmt);
+
+ switch(stmt->op) {
+ case ODCL:
+ case ODCLFIELD:
+ // a declaration ties the node to the current
+ // function, but we already have that edge in
+ // curfn->dcl and will follow it explicitly in
+ // escflood to avoid storing redundant information
+ // What does have to happen here is note if the name
+ // is declared inside a looping scope.
+ stmt->left->escloopdepth = loopdepth;
+ break;
+
+ case OLABEL: // TODO: new loop/scope only if there are backjumps to it.
+ loopdepth++;
+ break;
+
+ case OBLOCK:
+ escstmtlist(stmt->list);
+ break;
+
+ case OFOR:
+ if(stmt->ntest != N) {
+ escstmtlist(stmt->ntest->ninit);
+ escexpr(N, stmt->ntest);
+ }
+ escstmt(stmt->nincr);
+ loopdepth++;
+ escstmtlist(stmt->nbody);
+ loopdepth--;
+ break;
+
+ case ORANGE: // for <list> = range <right> { <nbody> }
+ switch(stmt->type->etype) {
+ case TSTRING: // never flows
+ escexpr(stmt->list->n, N);
+ if(stmt->list->next)
+ escexpr(stmt->list->next->n, N);
+ escexpr(N, stmt->right);
+ break;
+ case TARRAY: // i, v = range sliceorarray
+ escexpr(stmt->list->n, N);
+ if(stmt->list->next)
+ escexpr(stmt->list->next->n, stmt->right);
+ break;
+ case TMAP: // k [, v] = range map
+ escexpr(stmt->list->n, stmt->right);
+ if(stmt->list->next)
+ escexpr(stmt->list->next->n, stmt->right);
+ break;
+ case TCHAN: // v = range chan
+ escexpr(stmt->list->n, stmt->right);
+ break;
+ }
+ loopdepth++;
+ escstmtlist(stmt->nbody);
+ loopdepth--;
+ break;
+
+ case OIF:
+ escexpr(N, stmt->ntest);
+ escstmtlist(stmt->nbody);
+ escstmtlist(stmt->nelse);
+ break;
+
+ case OSELECT:
+ for(ll=stmt->list; ll; ll=ll->next) { // cases
+ escstmt(ll->n->left);
+ escstmtlist(ll->n->nbody);
+ }
+ break;
+
+ case OSELRECV2: // v, ok := <-ch ntest:ok
+ escexpr(N, stmt->ntest);
+ // fallthrough
+ case OSELRECV: // v := <-ch left: v right->op = ORECV
+ escexpr(N, stmt->left);
+ escexpr(stmt->left, stmt->right);
+ break;
+
+ case OSWITCH:
+ if(stmt->ntest && stmt->ntest->op == OTYPESW) {
+ for(ll=stmt->list; ll; ll=ll->next) { // cases
+ // ntest->right is the argument of the .(type),
+ // ll->n->nname is the variable per case
+ escexpr(ll->n->nname, stmt->ntest->right);
+ escstmtlist(ll->n->nbody);
+ }
+ } else {
+ escexpr(N, stmt->ntest);
+ for(ll=stmt->list; ll; ll=ll->next) { // cases
+ for(lr=ll->n->list; lr; lr=lr->next)
+ escexpr(N, lr->n);
+ escstmtlist(ll->n->nbody);
+ }
+ }
+ break;
+
+ case OAS:
+ case OASOP:
+ escexpr(stmt->left, stmt->right);
+ break;
+
+ // escape analysis happens after typecheck, so the
+ // OAS2xxx have already been substituted.
+ case OAS2: // x,y = a,b
+ cl = count(stmt->list);
+ cr = count(stmt->rlist);
+ if(cl > 1 && cr == 1) {
+ for(ll=stmt->list; ll; ll=ll->next)
+ escexpr(ll->n, stmt->rlist->n);
+ } else {
+ if(cl != cr)
+ fatal("escstmt: bad OAS2: %N", stmt);
+ for(ll=stmt->list, lr=stmt->rlist; ll; ll=ll->next, lr=lr->next)
+ escexpr(ll->n, lr->n);
+ }
+ break;
+
+ case OAS2RECV: // v, ok = <-ch
+ case OAS2MAPR: // v, ok = m[k]
+ case OAS2DOTTYPE: // v, ok = x.(type)
+ escexpr(stmt->list->n, stmt->rlist->n);
+ escexpr(stmt->list->next->n, N);
+ break;
+
+ case OAS2MAPW: // m[k] = x, ok.. stmt->list->n is the INDEXMAP, k is handled in escexpr(dst...)
+ escexpr(stmt->list->n, stmt->rlist->n);
+ escexpr(N, stmt->rlist->next->n);
+ break;
+
+ case ORECV: // unary <-ch as statement
+ escexpr(N, stmt->left);
+ break;
+
+ case OSEND: // ch <- x
+ escexpr(&theSink, stmt->right); // for now. TODO escexpr(stmt->left, stmt->right);
+ break;
+
+ case OCOPY: // todo: treat as *dst=*src instead of as dst=src
+ escexpr(stmt->left, stmt->right);
+ break;
+
+ case OAS2FUNC: // x,y,z = f()
+ for(ll = stmt->list; ll; ll=ll->next)
+ escexpr(ll->n, N);
+ escexpr(N, stmt->rlist->n);
+ break;
+
+ case OCALLINTER:
+ case OCALLFUNC:
+ case OCALLMETH:
+ escexpr(N, stmt);
+ break;
+
+ case OPROC:
+ case ODEFER:
+ // stmt->left is a (pseud)ocall, stmt->left->left is
+ // the function being called. if this defer is at
+ // loopdepth >1, everything leaks. TODO this is
+ // overly conservative, it's enough if it leaks to a
+ // fake node at the function's top level
+ dst = &theSink;
+ if (stmt->op == ODEFER && loopdepth <= 1)
+ dst = nil;
+ escexpr(dst, stmt->left->left);
+ for(ll=stmt->left->list; ll; ll=ll->next)
+ escexpr(dst, ll->n);
+ break;
+
+ case ORETURN:
+ for(ll=stmt->list; ll; ll=ll->next)
+ escexpr(&theSink, ll->n);
+ break;
+
+ case OCLOSE:
+ case OPRINT:
+ case OPRINTN:
+ escexpr(N, stmt->left);
+ for(ll=stmt->list; ll; ll=ll->next)
+ escexpr(N, ll->n);
+ break;
+
+ case OPANIC:
+ // Argument could leak through recover.
+ escexpr(&theSink, stmt->left);
+ 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
+escexpr(Node *dst, Node *expr)
+{
+ int lno;
+ NodeList *ll;
+
+ if(isblank(dst)) dst = N;
+
+ // the lhs of an assignment needs recursive analysis too
+ // these are the only interesting cases
+ // todo:check channel case
+ if(dst) {
+ setlineno(dst);
+
+ switch(dst->op) {
+ case OINDEX:
+ case OSLICE:
+ escexpr(N, dst->right);
+
+ // slice: "dst[x] = src" is like *(underlying array)[x] = src
+ // TODO maybe this never occurs b/c of OSLICEARR and it's inserted OADDR
+ if(!isfixedarray(dst->left->type))
+ goto doref;
+
+ // fallthrough; treat "dst[x] = src" as "dst = src"
+ case ODOT: // treat "dst.x = src" as "dst = src"
+ escexpr(dst->left, expr);
+ return;
+
+ case OINDEXMAP:
+ escexpr(&theSink, dst->right); // map key is put in map
+ // fallthrough
+ case OIND:
+ case ODOTPTR:
+ case OSLICEARR: // ->left is the OADDR of the array
+ doref:
+ escexpr(N, dst->left);
+ // assignment to dereferences: for now we lose track
+ escexpr(&theSink, expr);
+ return;
+ }
+
+ }
+
+ if(expr == N || expr->op == ONONAME || expr->op == OXXX)
+ return;
+
+ if(expr->typecheck == 0 && expr->op != OKEY) {
+ dump("escexpr missing typecheck", expr);
+ fatal("Missing typecheck.");
+ }
+
+ lno = setlineno(expr);
+ pdepth++;
+
+ if(debug['m'] > 1)
+ print("%L:[%d] %#S \t%hN %.*s<= %hN\n", lineno, loopdepth,
+ (curfn && curfn->nname) ? curfn->nname->sym : S, dst,
+ 2*pdepth, ".\t.\t.\t.\t.\t", expr);
+
+
+ switch(expr->op) {
+ case OADDR: // dst = &x
+ case OIND: // dst = *x
+ case ODOTPTR: // dst = (*x).f
+ // restart the recursion at x to figure out where it came from
+ escexpr(expr->left, expr->left);
+ // fallthrough
+ case ONAME:
+ case OPARAM:
+ // loopdepth was set in the defining statement or function header
+ escflows(dst, expr);
+ break;
+
+ case OARRAYLIT:
+ case OSTRUCTLIT:
+ case OMAPLIT:
+ expr->escloopdepth = loopdepth;
+ escflows(dst, expr);
+ for(ll=expr->list; ll; ll=ll->next) {
+ escexpr(expr, ll->n->left);
+ escexpr(expr, ll->n->right);
+ }
+ break;
+
+ case OMAKECHAN:
+ case OMAKEMAP:
+ case OMAKESLICE:
+ case ONEW:
+ expr->curfn = curfn; // should have been done in parse, but patch it up here.
+ expr->escloopdepth = loopdepth;
+ escflows(dst, expr);
+ // first arg is type, all others need checking
+ for(ll=expr->list->next; ll; ll=ll->next)
+ escexpr(N, ll->n);
+ break;
+
+ case OCLOSURE:
+ expr->curfn = curfn; // should have been done in parse, but patch it up here.
+ expr->escloopdepth = loopdepth;
+ escflows(dst, expr);
+ escfunc(expr);
+ break;
+
+ // end of the leaf cases. no calls to escflows() in the cases below.
+
+
+ case OCONV: // unaries that pass the value through
+ case OCONVIFACE:
+ case OCONVNOP:
+ case ODOTTYPE:
+ case ODOTTYPE2:
+ case ORECV: // leaks the whole channel
+ case ODOTMETH: // expr->right is just the field or method name
+ case ODOTINTER:
+ case ODOT:
+ escexpr(dst, expr->left);
+ break;
+
+ case OCOPY:
+ // left leaks to right, but the return value is harmless
+ // TODO: treat as *dst = *src, rather than as dst = src
+ escexpr(expr->left, expr->right);
+ break;
+
+ case OAPPEND:
+ // See TODO for OCOPY
+ escexpr(dst, expr->list->n);
+ for(ll=expr->list->next; ll; ll=ll->next)
+ escexpr(expr->list->n, ll->n);
+ break;
+
+ case OCALLMETH:
+ case OCALLFUNC:
+ case OCALLINTER:
+ // Moved to separate function to isolate the hair.
+ escexprcall(dst, expr);
+ break;
+
+ case OSLICEARR: // like an implicit OIND to the underlying buffer, but typecheck has inserted an OADDR
+ case OSLICESTR:
+ case OSLICE:
+ case OINDEX:
+ case OINDEXMAP:
+ // the big thing flows, the keys just need checking
+ escexpr(dst, expr->left);
+ escexpr(N, expr->right); // expr->right is the OKEY
+ break;
+
+ default: // all other harmless leaf, unary or binary cases end up here
+ escexpr(N, expr->left);
+ escexpr(N, expr->right);
+ break;
+ }
+
+ pdepth--;
+ lineno = lno;
+}
+
+
+// This is a bit messier than fortunate, pulled out of escexpr'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
+escexprcall(Node *dst, Node *expr)
+{
+ NodeList *ll, *lr;
+ Node *fn;
+ Type *t, *fntype, *thisarg, *inargs;
+
+ fn = nil;
+ fntype = nil;
+
+ switch(expr->op) {
+ case OCALLFUNC:
+ fn = expr->left;
+ escexpr(N, fn);
+ fntype = fn->type;
+ break;
+
+ case OCALLMETH:
+ fn = expr->left->right; // ODOTxx name
+ fn = fn->sym->def; // resolve to definition if we have it
+ if(fn)
+ fntype = fn->type;
+ else
+ fntype = expr->left->type;
+ break;
+
+ case OCALLINTER:
+ break;
+
+ default:
+ fatal("escexprcall called with non-call expression");
+ }
+
+ if(fn && fn->ntype) {
+ if(debug['m'] > 2)
+ print("escexprcall: have param nodes: %N\n", fn->ntype);
+
+ if(expr->op == OCALLMETH) {
+ if(debug['m'] > 2)
+ print("escexprcall: this: %N\n",fn->ntype->left->left);
+ escexpr(fn->ntype->left->left, expr->left->left);
+ }
+
+ // lr->n is the dclfield, ->left is the ONAME param node
+ for(ll=expr->list, lr=fn->ntype->list; ll && lr; ll=ll->next) {
+ if(debug['m'] > 2)
+ print("escexprcall: field param: %N\n", lr->n->left);
+ if (lr->n->left)
+ escexpr(lr->n->left, ll->n);
+ else
+ escexpr(&theSink, ll->n);
+ if(lr->n->left && !lr->n->left->isddd)
+ lr=lr->next;
+ }
+ return;
+ }
+
+ if(fntype) {
+ if(debug['m'] > 2)
+ print("escexprcall: have param types: %T\n", fntype);
+
+ if(expr->op == OCALLMETH) {
+ thisarg = getthisx(fntype);
+ t = thisarg->type;
+ if(debug['m'] > 2)
+ print("escexprcall: this: %T\n", t);
+ if(!t->note || strcmp(t->note->s, safetag->s) != 0)
+ escexpr(&theSink, expr->left->left);
+ else
+ escexpr(N, expr->left->left);
+ }
+
+ inargs = getinargx(fntype);
+ for(ll=expr->list, t=inargs->type; ll; ll=ll->next) {
+ if(debug['m'] > 2)
+ print("escexprcall: field type: %T\n", t);
+ if(!t->note || strcmp(t->note->s, safetag->s))
+ escexpr(&theSink, ll->n);
+ else
+ escexpr(N, ll->n);
+ if(t->down)
+ t=t->down;
+ }
+
+ return;
+ }
+
+ // fallthrough if we don't have enough information:
+ // can only assume all parameters are unsafe
+ // OCALLINTER always ends up here
+
+ if(debug['m']>1 && expr->op != OCALLINTER) {
+ // dump("escexprcall", expr);
+ print("escexprcall: %O, no nodes, no types: %N\n", expr->op, fn);
+ }
+
+ escexpr(&theSink, expr->left->left); // the this argument
+ for(ll=expr->list; ll; ll=ll->next)
+ escexpr(&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);
+
+ // Assignments to global variables get lumped into theSink.
+ if (dst->op == ONAME && dst->class == PEXTERN)
+ dst = &theSink;
+
+ 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", floodgen, dst,
+ (dst->curfn && dst->curfn->nname) ? dst->curfn->nname->sym : S,
+ dst->escloopdepth);
+
+ for (l = dst->escflowsrc; l; l=l->next) {
+ floodgen++;
+ escwalk(0, dst, l->n);
+ }
+}
+
+static void
+escwalk(int level, Node *dst, Node *src)
+{
+ NodeList* ll;
+ int leaks;
+
+ if (src->escfloodgen == floodgen)
+ return;
+ src->escfloodgen = floodgen;
+
+ 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'])
+ print("%L:leaking param: %hN\n", src->lineno, src);
+ }
+ break;
+
+ case OADDR:
+ if (leaks)
+ addrescapes(src->left);
+ escwalk(level-1, dst, src->left);
+ break;
+
+ case OINDEX:
+ if(isfixedarray(src->type))
+ break;
+ 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;
+
+ 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;
+ default:
+ fatal("messed up escape tagging: %N::%N", curfn, ll->n);
+ }
+ }
+
+ curfn = savefn;
+}
static void cgen_dcl(Node *n);
static void cgen_proc(Node *n, int proc);
-static void checkgoto(Node*, Node*);
+static void checkgoto(Node*, Node*);
static Label *labellist;
static Label *lastlabel;
}
if(n->op != ONAME || n->class != PAUTO)
continue;
- if (n->xoffset != BADWIDTH)
+ if(n->xoffset != BADWIDTH)
continue;
if(n->type == T)
continue;
lineno = lno;
}
+/*
+ * the address of n has been taken and might be used after
+ * the current function returns. mark any local vars
+ * as needing to move to the heap.
+ */
+void
+addrescapes(Node *n)
+{
+ char buf[100];
+ switch(n->op) {
+ default:
+ // probably a type error already.
+ // dump("addrescapes", n);
+ break;
+
+ case ONAME:
+ if(n == nodfp)
+ break;
+
+ // if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping.
+ // on PPARAM it means something different.
+ if(n->class == PAUTO && n->esc == EscNever)
+ break;
+
+ if(!debug['s'] && n->esc != EscUnknown)
+ fatal("without escape analysis, only PAUTO's should have esc: %N", n);
+
+ switch(n->class) {
+ case PPARAMREF:
+ addrescapes(n->defn);
+ break;
+ case PPARAM:
+ case PPARAMOUT:
+ // if func param, need separate temporary
+ // to hold heap pointer.
+ // the function type has already been checked
+ // (we're in the function body)
+ // so the param already has a valid xoffset.
+
+ // expression to refer to stack copy
+ n->stackparam = nod(OPARAM, n, N);
+ n->stackparam->type = n->type;
+ n->stackparam->addable = 1;
+ if(n->xoffset == BADWIDTH)
+ fatal("addrescapes before param assignment");
+ n->stackparam->xoffset = n->xoffset;
+ // fallthrough
+ case PAUTO:
+
+ n->class |= PHEAP;
+ n->addable = 0;
+ n->ullman = 2;
+ n->xoffset = 0;
+
+ // create stack variable to hold pointer to heap
+ n->heapaddr = nod(ONAME, N, N);
+ n->heapaddr->type = ptrto(n->type);
+ snprint(buf, sizeof buf, "&%S", n->sym);
+ n->heapaddr->sym = lookup(buf);
+ n->heapaddr->class = PHEAP-1; // defer tempname to allocparams
+ n->heapaddr->ullman = 1;
+ n->curfn->dcl = list(n->curfn->dcl, n->heapaddr);
+
+ if(debug['s'])
+ n->esc = EscHeap;
+
+ if(debug['m'])
+ print("%L: moved to heap: %hN\n", n->lineno, n);
+
+ break;
+ }
+ break;
+
+ case OIND:
+ case ODOTPTR:
+ break;
+
+ case ODOT:
+ case OINDEX:
+ // ODOTPTR has already been introduced,
+ // so these are the non-pointer ODOT and OINDEX.
+ // In &x[0], if x is a slice, then x does not
+ // escape--the pointer inside x does, but that
+ // is always a heap pointer anyway.
+ if(!isslice(n->left->type))
+ addrescapes(n->left);
+ break;
+ }
+}
+
void
clearlabels(void)
{
if(stksize < 0)
fatal("tempname not during code generation");
- if (curfn == N)
+ if(curfn == N)
fatal("no curfn for tempname");
if(t == T) {
n->class = PAUTO;
n->addable = 1;
n->ullman = 1;
- n->noescape = 1;
+ n->esc = EscNever;
n->curfn = curfn;
curfn->dcl = list(curfn->dcl, n);
{
uchar etype;
uchar chan;
- uchar recur; // to detect loops
uchar trecur; // to detect loops
uchar printed;
uchar embedded; // TFIELD embedded type
};
#define T ((Type*)0)
+enum
+{
+ EscUnknown,
+ EscHeap,
+ EscScope,
+ EscNone,
+ EscNever,
+};
+
struct Node
{
uchar op;
uchar embedded; // ODCLFIELD embedded type
uchar colas; // OAS resulting from :=
uchar diag; // already printed error about this
- uchar noescape; // ONAME never move to heap
+ uchar esc; // EscXXX
uchar funcdepth;
uchar builtin; // built-in name, like len or close
uchar walkdef;
Node* defn;
Node* pack; // real package for import . names
Node* curfn; // function for local variables
+ Type* paramfld; // TFIELD for this PPARAM
// ONAME func param with PHEAP
Node* heapaddr; // temp holding heap address of param
// OPACK
Pkg* pkg;
+ // 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
+ int escfloodgen; // increased for every flood to detect loops
+
Sym* sym; // various
int32 vargen; // unique name for OTYPE/ONAME
int32 lineno;
OADDR,
OANDAND,
OAPPEND,
- OARRAY,
OARRAYBYTESTR, OARRAYRUNESTR,
OSTRARRAYBYTE, OSTRARRAYRUNE,
OAS, OAS2, OAS2MAPW, OAS2FUNC, OAS2RECV, OAS2MAPR, OAS2DOTTYPE, OASOP,
// misc
ODDD,
+ ODDDARG,
// for back ends
OCMP, ODEC, OEXTEND, OINC, OREGISTER, OINDREG,
Node* typenod(Type *t);
NodeList* variter(NodeList *vl, Node *t, NodeList *el);
+/*
+ * esc.c
+ */
+void escapes(void);
+
/*
* export.c
*/
/*
* gen.c
*/
+void addrescapes(Node *n);
void allocparams(void);
void cgen_as(Node *nl, Node *nr);
void cgen_callmeth(Node *n, int proc);
Type* methodfunc(Type *f, Type*);
Node* typename(Type *t);
Sym* typesym(Type *t);
+int haspointers(Type *t);
/*
* select.c
if(debug['f'])
frame(1);
- // Process top-level declarations in four phases.
+ // Process top-level declarations in phases.
// Phase 1: const, type, and names and types of funcs.
// This will gather all the information about types
// and methods but doesn't depend on any of it.
- // Phase 2: Variable assignments.
- // To check interface assignments, depends on phase 1.
- // Phase 3: Type check function bodies.
- // Phase 4: Compile function bodies.
defercheckwidth();
for(l=xtop; l; l=l->next)
if(l->n->op != ODCL && l->n->op != OAS)
typecheck(&l->n, Etop);
+
+ // Phase 2: Variable assignments.
+ // To check interface assignments, depends on phase 1.
for(l=xtop; l; l=l->next)
if(l->n->op == ODCL || l->n->op == OAS)
typecheck(&l->n, Etop);
resumetypecopy();
resumecheckwidth();
+ // Phase 3: Type check function bodies.
for(l=xtop; l; l=l->next) {
if(l->n->op == ODCLFUNC || l->n->op == OCLOSURE) {
curfn = l->n;
if(nsavederrors+nerrors)
errorexit();
+ // Phase 3b: escape analysis.
+ if(debug['s'])
+ escapes();
+
+ // Phase 4: Compile function bodies.
for(l=xtop; l; l=l->next)
if(l->n->op == ODCLFUNC)
funccompile(l->n, 0);
if(nsavederrors+nerrors == 0)
fninit(xtop);
+ // Phase 4b: Compile all closures.
while(closures) {
l = closures;
closures = nil;
}
}
+ // Phase 5: check external declarations.
for(l=externdcl; l; l=l->next)
if(l->n->op == ONAME)
typecheck(&l->n, Erv);
}
nodfp = nod(ONAME, N, N);
- nodfp->noescape = 1;
nodfp->type = types[TINT32];
nodfp->xoffset = 0;
nodfp->class = PPARAM;
fmtprint(f, "(%#N)", n->left);
break;
+ case ODDDARG:
+ fmtprint(f, "... argument");
+ break;
+
case OREGISTER:
fmtprint(f, "%R", n->val.u.reg);
break;
return pkglookup(name, typepkg);
}
-static int
+int
haspointers(Type *t)
{
Type *t1;
break;
case OAS2RECV:
- // convert x, ok = <-c into OSELRECV(x, <-c) with ntest=ok
+ // convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok
if(n->right->op != ORECV) {
yyerror("select assignment must have receive on right hand side");
break;
case ORECV:
// convert <-c into OSELRECV(N, <-c)
n = nod(OSELRECV, N, n);
+ n->typecheck = 1;
ncase->left = n;
break;
if(n->class != 0) {
s = "";
- if (n->class & PHEAP) s = ",heap";
- if ((n->class & ~PHEAP) < nelem(classnames))
+ if(n->class & PHEAP) s = ",heap";
+ if((n->class & ~PHEAP) < nelem(classnames))
fmtprint(fp, " class(%s%s)", classnames[n->class&~PHEAP], s);
else
fmtprint(fp, " class(%d?%s)", n->class&~PHEAP, s);
if(n->funcdepth != 0)
fmtprint(fp, " f(%d)", n->funcdepth);
- if(n->noescape != 0)
- fmtprint(fp, " ne(%d)", n->noescape);
+ switch(n->esc) {
+ case EscUnknown:
+ break;
+ case EscHeap:
+ fmtprint(fp, " esc(h)");
+ break;
+ case EscScope:
+ fmtprint(fp, " esc(s)");
+ break;
+ case EscNone:
+ fmtprint(fp, " esc(no)");
+ break;
+ case EscNever:
+ if(!c)
+ fmtprint(fp, " esc(N)");
+ break;
+ default:
+ fmtprint(fp, " esc(%d)", n->esc);
+ break;
+ }
+
+ if(n->escloopdepth)
+ fmtprint(fp, " ld(%d)", n->escloopdepth);
if(!c && n->typecheck != 0)
fmtprint(fp, " tc(%d)", n->typecheck);
switch(n->op) {
default:
- if (fp->flags & FmtShort)
+ if(fp->flags & FmtShort)
fmtprint(fp, "%O%hJ", n->op, n);
else
fmtprint(fp, "%O%J", n->op, n);
case ONAME:
case ONONAME:
if(n->sym == S) {
- if (fp->flags & FmtShort)
+ if(fp->flags & FmtShort)
fmtprint(fp, "%O%hJ", n->op, n);
else
fmtprint(fp, "%O%J", n->op, n);
break;
}
- if (fp->flags & FmtShort)
+ if(fp->flags & FmtShort)
fmtprint(fp, "%O-%S%hJ", n->op, n->sym, n);
else
fmtprint(fp, "%O-%S%J", n->op, n->sym, n);
int isddd;
Val v;
- if(debug['r'])
+ if(0 && debug['r'])
print("genwrapper rcvrtype=%T method=%T newnam=%S\n",
rcvr, method, newnam);
listsort(&l1, f);
listsort(&l2, f);
- if ((*f)(l1->n, l2->n) < 0) {
+ if((*f)(l1->n, l2->n) < 0) {
*l = l1;
} else {
*l = l2;
// l1 is last one from l1 that is < l2
le = l1->next; // le is the rest of l1, first one that is >= l2
- if (le != nil)
+ if(le != nil)
le->end = (*l)->end;
(*l)->end = l1; // cut *l at l1
static Type* lookdot1(Sym *s, Type *t, Type *f, int);
static int nokeys(NodeList*);
static void typecheckcomplit(Node**);
-static void addrescapes(Node*);
static void typecheckas2(Node*);
static void typecheckas(Node*);
static void typecheckfunc(Node*);
*/
case OIND:
ntop = Erv | Etype;
- if(!(top & Eaddr))
+ if(!(top & Eaddr)) // The *x in &*x is not an indirect.
ntop |= Eindir;
l = typecheck(&n->left, ntop);
if((t = l->type) == T)
l = n->left;
if((t = l->type) == T)
goto error;
- if(!(top & Eindir) && !n->etype)
+ // top&Eindir means this is &x in *&x. (or the arg to built-in print)
+ // n->etype means code generator flagged it as non-escaping.
+ if(!(top & Eindir) && !n->etype && !debug['s'])
addrescapes(n->left);
n->type = ptrto(t);
goto ret;
}
n->left = args->n;
n->right = args->next->n;
+ args = nil;
+ n->list = nil;
n->type = types[TINT];
typecheck(&n->left, Erv);
typecheck(&n->right, Erv);
// copy([]byte, string)
if(isslice(n->left->type) && n->right->type->etype == TSTRING) {
- if (n->left->type->type == types[TUINT8])
+ if(n->left->type->type == types[TUINT8])
goto ret;
yyerror("arguments to copy have different element types: %lT and string", n->left->type);
goto error;
if(!eqtype(rcvr, tt)) {
if(rcvr->etype == tptr && eqtype(rcvr->type, tt)) {
checklvalue(n->left, "call pointer method on");
- addrescapes(n->left);
+ if(!debug['s'])
+ addrescapes(n->left);
n->left = nod(OADDR, n->left, N);
n->left->implicit = 1;
typecheck(&n->left, Etype|Erv);
lineno = lno;
}
-/*
- * the address of n has been taken and might be used after
- * the current function returns. mark any local vars
- * as needing to move to the heap.
- */
-static void
-addrescapes(Node *n)
-{
- char buf[100];
- switch(n->op) {
- default:
- // probably a type error already.
- // dump("addrescapes", n);
- break;
-
- case ONAME:
- if(n->noescape)
- break;
- switch(n->class) {
- case PPARAMREF:
- addrescapes(n->defn);
- break;
- case PPARAM:
- case PPARAMOUT:
- // if func param, need separate temporary
- // to hold heap pointer.
- // the function type has already been checked
- // (we're in the function body)
- // so the param already has a valid xoffset.
-
- // expression to refer to stack copy
- n->stackparam = nod(OPARAM, n, N);
- n->stackparam->type = n->type;
- n->stackparam->addable = 1;
- if(n->xoffset == BADWIDTH)
- fatal("addrescapes before param assignment");
- n->stackparam->xoffset = n->xoffset;
- n->xoffset = 0;
- // fallthrough
- case PAUTO:
-
- n->class |= PHEAP;
- n->addable = 0;
- n->ullman = 2;
- n->xoffset = 0;
-
- // create stack variable to hold pointer to heap
- n->heapaddr = nod(ONAME, N, N);
- n->heapaddr->type = ptrto(n->type);
- snprint(buf, sizeof buf, "&%S", n->sym);
- n->heapaddr->sym = lookup(buf);
- n->heapaddr->class = PHEAP-1; // defer tempname to allocparams
- n->heapaddr->ullman = 1;
- n->curfn->dcl = list(n->curfn->dcl, n->heapaddr);
-
- break;
- }
- break;
-
- case OIND:
- case ODOTPTR:
- break;
-
- case ODOT:
- case OINDEX:
- // ODOTPTR has already been introduced,
- // so these are the non-pointer ODOT and OINDEX.
- // In &x[0], if x is a slice, then x does not
- // escape--the pointer inside x does, but that
- // is always a heap pointer anyway.
- if(!isslice(n->left->type))
- addrescapes(n->left);
- break;
- }
-}
-
/*
* lvalue etc
*/
{
Type *t, *rcvr;
-//dump("nname", n->nname);
typecheck(&n->nname, Erv | Easgn);
if((t = n->nname->type) == T)
return;
if(n->ntype != N) {
typecheck(&n->ntype, Etype);
n->type = n->ntype->type;
+
if(n->type == T) {
n->diag = 1;
goto ret;
--- /dev/null
+// errchk -0 $G -sm $D/$F.go
+
+// Copyright 2010 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.
+
+package foo
+
+import "unsafe"
+
+var gxx *int
+
+func foo1(x int) { // ERROR "moved to heap: NAME-x"
+ gxx = &x
+}
+
+func foo2(yy *int) { // ERROR "leaking param: NAME-yy"
+ gxx = yy
+}
+
+func foo3(x int) *int { // ERROR "moved to heap: NAME-x"
+ return &x
+}
+
+type T *T
+func foo3b(t T) { // ERROR "leaking param: NAME-t"
+ *t = t
+}
+
+// xx isn't going anywhere, so use of yy is ok
+func foo4(xx, yy *int) {
+ xx = yy
+}
+
+// xx isn't going anywhere, so taking address of yy is ok
+func foo5(xx **int, yy *int) {
+ xx = &yy
+}
+
+func foo6(xx **int, yy *int) { // ERROR "leaking param: NAME-yy"
+ *xx = yy
+}
+
+func foo7(xx **int, yy *int) {
+ **xx = *yy
+}
+
+func foo8(xx, yy *int) int {
+ xx = yy
+ return *xx
+}
+
+func foo9(xx, yy *int) *int { // ERROR "leaking param: NAME-xx" "leaking param: NAME-yy"
+ xx = yy
+ return xx
+}
+
+func foo10(xx, yy *int) {
+ *xx = *yy
+}
+
+func foo11() int {
+ x, y := 0, 42
+ xx := &x
+ yy := &y
+ *xx = *yy
+ return x
+}
+
+
+var xxx **int
+
+func foo12(yyy **int) { // ERROR "leaking param: NAME-yyy"
+ xxx = yyy
+}
+
+func foo13(yyy **int) {
+ *xxx = *yyy
+}
+
+func foo14(yyy **int) {
+ **xxx = **yyy
+}
+
+func foo15(yy *int) { // ERROR "moved to heap: NAME-yy"
+ xxx = &yy
+}
+
+func foo16(yy *int) { // ERROR "leaking param: NAME-yy"
+ *xxx = yy
+}
+
+func foo17(yy *int) {
+ **xxx = *yy
+}
+
+func foo18(y int) { // ERROR "moved to heap: "NAME-y"
+ *xxx = &y
+}
+
+func foo19(y int) {
+ **xxx = y
+}
+
+type Bar struct {
+ i int
+ ii *int
+}
+
+func NewBar() *Bar {
+ return &Bar{ 42, nil }
+}
+
+func NewBarp(x *int) *Bar { // ERROR "leaking param: NAME-x"
+ return &Bar{ 42, x }
+}
+
+func NewBarp2(x *int) *Bar {
+ return &Bar{ *x, nil }
+}
+
+func (b *Bar) NoLeak() int {
+ return *(b.ii)
+}
+
+func (b *Bar) AlsoNoLeak() *int {
+ return b.ii
+}
+
+type Bar2 struct {
+ i [12]int
+ ii []int
+}
+
+func NewBar2() *Bar2 {
+ return &Bar2{ [12]int{ 42 }, nil }
+}
+
+func (b *Bar2) NoLeak() int {
+ return b.i[0]
+}
+
+func (b *Bar2) Leak() []int { // ERROR "leaking param: NAME-b"
+ return b.i[:]
+}
+
+func (b *Bar2) AlsoNoLeak() []int {
+ return b.ii[0:1]
+}
+
+func (b *Bar2) LeakSelf() { // ERROR "leaking param: NAME-b"
+ b.ii = b.i[0:4]
+}
+
+func (b *Bar2) LeakSelf2() { // ERROR "leaking param: NAME-b"
+ var buf []int
+ buf = b.i[0:]
+ b.ii = buf
+}
+
+func foo21() func() int {
+ x := 42 // ERROR "moved to heap: NAME-x"
+ return func() int {
+ return x
+ }
+}
+
+func foo22() int {
+ x := 42
+ return func() int {
+ return x
+ }()
+}
+
+func foo23(x int) func() int { // ERROR "moved to heap: NAME-x"
+ return func() int {
+ return x
+ }
+}
+
+func foo23a(x int) (func() int) { // ERROR "moved to heap: NAME-x"
+ f := func() int {
+ return x
+ }
+ return f
+}
+
+func foo23b(x int) *(func() int) { // ERROR "moved to heap: NAME-x"
+ f := func() int { return x } // ERROR "moved to heap: NAME-f"
+ return &f
+}
+
+func foo24(x int) int {
+ return func() int {
+ return x
+ }()
+}
+
+
+var x *int
+
+func fooleak(xx *int) int { // ERROR "leaking param: NAME-xx"
+ x = xx
+ return *x
+}
+
+func foonoleak(xx *int) int {
+ return *x + *xx
+}
+
+func foo31(x int) int { // ERROR "moved to heap: NAME-x"
+ return fooleak(&x)
+}
+
+func foo32(x int) int {
+ return foonoleak(&x)
+}
+
+type Foo struct {
+ xx *int
+ x int
+}
+
+var F Foo
+var pf *Foo
+
+func (f *Foo) fooleak() { // ERROR "leaking param: NAME-f"
+ pf = f
+}
+
+func (f *Foo) foonoleak() {
+ F.x = f.x
+}
+
+func (f *Foo) Leak() { // ERROR "leaking param: NAME-f"
+ f.fooleak()
+}
+
+func (f *Foo) NoLeak() {
+ f.foonoleak()
+}
+
+
+func foo41(x int) { // ERROR "moved to heap: NAME-x"
+ F.xx = &x
+}
+
+func (f *Foo) foo42(x int) { // ERROR "moved to heap: NAME-x"
+ f.xx = &x
+}
+
+func foo43(f *Foo, x int) { // ERROR "moved to heap: NAME-x"
+ f.xx = &x
+}
+
+func foo44(yy *int) { // ERROR "leaking param: NAME-yy"
+ F.xx = yy
+}
+
+func (f *Foo) foo45() {
+ F.x = f.x
+}
+
+func (f *Foo) foo46() {
+ F.xx = f.xx
+}
+
+func (f *Foo) foo47() { // ERROR "leaking param: NAME-f"
+ f.xx = &f.x
+}
+
+
+var ptrSlice []*int
+
+func foo50(i *int) { // ERROR "leaking param: NAME-i"
+ ptrSlice[0] = i
+}
+
+
+var ptrMap map[*int]*int
+
+func foo51(i *int) { // ERROR "leaking param: NAME-i"
+ ptrMap[i] = i
+}
+
+
+func indaddr1(x int) *int { // ERROR "moved to heap: NAME-x"
+ return &x
+}
+
+func indaddr2(x *int) *int { // ERROR "leaking param: NAME-x"
+ return *&x
+}
+
+func indaddr3(x *int32) *int { // ERROR "leaking param: NAME-x"
+ return *(**int)(unsafe.Pointer(&x))
+}
+
+// From package math:
+
+func Float32bits(f float32) uint32 {
+ return *(*uint32)(unsafe.Pointer(&f))
+}
+
+func Float32frombits(b uint32) float32 {
+ return *(*float32)(unsafe.Pointer(&b))
+}
+
+func Float64bits(f float64) uint64 {
+ return *(*uint64)(unsafe.Pointer(&f))
+}
+
+func Float64frombits(b uint64) float64 {
+ return *(*float64)(unsafe.Pointer(&b))
+}
+
+// contrast with
+func float64bitsptr(f float64) *uint64 { // ERROR "moved to heap: NAME-f"
+ return (*uint64)(unsafe.Pointer(&f))
+}
+
+func float64ptrbitsptr(f *float64) *uint64 { // ERROR "leaking param: NAME-f"
+ return (*uint64)(unsafe.Pointer(f))
+}
+
+func typesw(i interface{}) *int { // ERROR "leaking param: NAME-i"
+ switch val := i.(type) {
+ case *int:
+ return val
+ case *int8:
+ v := int(*val) // ERROR "moved to heap: NAME-v"
+ return &v
+ }
+ return nil
+}
+
+func exprsw(i *int) *int { // ERROR "leaking param: NAME-i"
+ switch j := i; *j + 110 {
+ case 12:
+ return j
+ case 42:
+ return nil
+ }
+ return nil
+
+}
+
+// assigning to an array element is like assigning to the array
+func foo60(i *int) *int { // ERROR "leaking param: NAME-i"
+ var a [12]*int
+ a[0] = i
+ return a[1]
+}
+
+func foo60a(i *int) *int {
+ var a [12]*int
+ a[0] = i
+ return nil
+}
+
+// assigning to a struct field is like assigning to the struct
+func foo61(i *int) *int { // ERROR "leaking param: NAME-i"
+ type S struct {
+ a,b *int
+ }
+ var s S
+ s.a = i
+ return s.b
+}
+
+func foo61a(i *int) *int {
+ type S struct {
+ a,b *int
+ }
+ var s S
+ s.a = i
+ return nil
+}
+
+// assigning to a struct field is like assigning to the struct but
+// here this subtlety is lost, since s.a counts as an assignment to a
+// track-losing dereference.
+func foo62(i *int) *int { // ERROR "leaking param: NAME-i"
+ type S struct {
+ a,b *int
+ }
+ s := new(S)
+ s.a = i
+ return nil // s.b
+}
+
+
+type M interface { M() }
+
+func foo63(m M) {
+}
+
+func foo64(m M) { // ERROR "leaking param: NAME-m"
+ m.M()
+}
+
+type MV int
+func (MV) M() {}
+
+func foo65() {
+ var mv MV
+ foo63(&mv)
+}
+
+func foo66() {
+ var mv MV // ERROR "moved to heap: NAME-mv"
+ foo64(&mv)
+}
+
+func foo67() {
+ var mv MV
+ foo63(mv)
+}
+
+func foo68() {
+ var mv MV
+ foo64(mv) // escapes but it's an int so irrelevant
+}
+
+func foo69(m M) { // ERROR "leaking param: NAME-m"
+ foo64(m)
+}
+
+func foo70(mv1 *MV, m M) { // ERROR "leaking param: NAME-mv1" "leaking param: NAME-m"
+ m = mv1
+ foo64(m)
+}
+
+func foo71(x *int) []*int { // ERROR "leaking param: NAME-x"
+ var y []*int
+ y = append(y, x)
+ return y
+}
+
+func foo71a(x int) []*int { // ERROR "moved to heap: NAME-x"
+ var y []*int
+ y = append(y, &x)
+ return y
+}
+
+func foo72() {
+ var x int
+ var y [1]*int
+ y[0] = &x
+}
+
+func foo72aa() [10]*int {
+ var x int // ERROR "moved to heap: NAME-x"
+ var y [10]*int
+ y[0] = &x
+ return y
+}
+
+func foo72a() {
+ var y [10]*int
+ for i := 0; i < 10; i++ {
+ x := i // not moved to heap b/c y goes nowhere
+ y[i] = &x
+ }
+ return
+}
+
+func foo72b() [10]*int {
+ var y [10]*int
+ for i := 0; i < 10; i++ {
+ x := i // ERROR "moved to heap: NAME-x"
+ y[i] = &x
+ }
+ return y
+}
+
+
+// issue 2145
+func foo73() {
+ s := []int{3,2,1}
+ for _, v := range s {
+ vv := v // ERROR "moved to heap: NAME-vv"
+ defer func() { // "func literal escapes its scope" "&vv escapes its scope"
+ println(vv)
+ }()
+ }
+}
+
+func foo74() {
+ s := []int{3,2,1}
+ for _, v := range s {
+ vv := v // ERROR "moved to heap: NAME-vv"
+ fn := func() { // "func literal escapes its scope" "&vv escapes its scope"
+ println(vv)
+ }
+ defer fn()
+ }
+}
+
+func myprint(y *int, x ...interface{}) *int { // ERROR "leaking param: NAME-y"
+ return y
+}
+
+func myprint1(y *int, x ...interface{}) *interface{} { // ERROR "leaking param: NAME-x"
+ return &x[0]
+}
+
+func foo75(z *int) { // ERROR "leaking param: NAME-z"
+ myprint(z, 1, 2, 3)
+}
+
+func foo75a(z *int) {
+ myprint1(z, 1, 2, 3) // "[.][.][.] argument escapes to heap"
+}
+
+func foo76(z *int) {
+ myprint(nil, z)
+}
+
+func foo76a(z *int) { // ERROR "leaking param: NAME-z"
+ myprint1(nil, z) // "[.][.][.] argument escapes to heap"
+}
+
+func foo76b() {
+ myprint(nil, 1, 2, 3)
+}
+
+func foo76c() {
+ myprint1(nil, 1, 2, 3) // "[.][.][.] argument escapes to heap"
+}
+
+func foo76d() {
+ defer myprint(nil, 1, 2, 3)
+}
+
+func foo76e() {
+ defer myprint1(nil, 1, 2, 3) // "[.][.][.] argument escapes to heap"
+}
+
+func foo76f() {
+ for {
+ defer myprint(nil, 1, 2, 3) // "[.][.][.] argument escapes its scope"
+ }
+}
+
+func foo76g() {
+ for {
+ defer myprint1(nil, 1, 2, 3) // "[.][.][.] argument escapes to heap"
+ }
+}
+
+func foo77(z []interface{}) {
+ myprint(nil, z...) // z does not escape
+}
+
+func foo77a(z []interface{}) { // ERROR "leaking param: NAME-z"
+ myprint1(nil, z...)
+}
+
+func foo78(z int) *int { // ERROR "moved to heap: NAME-z"
+ return &z // "&z escapes"
+}
+
+func foo78a(z int) *int { // ERROR "moved to heap: NAME-z"
+ y := &z
+ x := &y
+ return *x // really return y
+}
+
+func foo79() *int {
+ return new(int) // "moved to heap: new[(]int[)]"
+}
+
+func foo80() *int {
+ var z *int
+ for {
+ z = new(int) // "new[(]int[)] escapes its scope"
+ }
+ _ = z
+ return nil
+}
+
+func foo81() *int {
+ for {
+ z := new(int)
+ _ = z
+ }
+ return nil
+}
+
+type Fooer interface {
+ Foo()
+}
+
+type LimitedFooer struct {
+ Fooer
+ N int64
+}
+
+func LimitFooer(r Fooer, n int64) Fooer { // ERROR "leaking param: NAME-r"
+ return &LimitedFooer{r, n}
+}
+
+func foo90(x *int) map[*int]*int { // ERROR "leaking param: NAME-x"
+ return map[*int]*int{ nil: x }
+}
+
+func foo91(x *int) map[*int]*int { // ERROR "leaking param: NAME-x"
+ return map[*int]*int{ x:nil }
+}
+
+func foo92(x *int) [2]*int { // ERROR "leaking param: NAME-x"
+ return [2]*int{ x, nil }
+}
+