void visit(VarExp *e) { VarDeclaration *v = e->var->isVarDeclaration(); if (v) { Type *tb = v->type->toBasetype(); if (v->isScope()) { /* Today, scope attribute almost doesn't work for escape analysis. * Until the semantics will be completed, it should be left as-is. * See also: fail_compilation/fail_scope.d */ if (tb->ty == Tarray || tb->ty == Tsarray || tb->ty == Tclass || tb->ty == Tdelegate) { if ((!v->noscope || tb->ty == Tclass)) { error(e->loc, "escaping reference to scope local %s", v); return; } } } if (v->storage_class & STCvariadic) { if (tb->ty == Tarray || tb->ty == Tsarray) error(e->loc, "escaping reference to variadic parameter %s", v); } } }
/************************************ * Detect cases where pointers to the stack can 'escape' the * lifetime of the stack frame when throwing `e`. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check for any pointers to the stack * gag = do not print error messages * Returns: * true if pointers to the stack can escape */ bool checkThrowEscape(Scope *sc, Expression *e, bool gag) { //printf("[%s] checkThrowEscape, e = %s\n", e->loc->toChars(), e->toChars()); EscapeByResults er; escapeByValue(e, &er); if (!er.byref.dim && !er.byvalue.dim && !er.byexp.dim) return false; bool result = false; for (size_t i = 0; i < er.byvalue.dim; i++) { VarDeclaration *v = er.byvalue[i]; //printf("byvalue %s\n", v->toChars()); if (v->isDataseg()) continue; if (v->isScope()) { if (sc->_module && sc->_module->isRoot()) { // Only look for errors if in module listed on command line if (global.params.vsafe) // https://issues.dlang.org/show_bug.cgi?id=17029 { if (!gag) error(e->loc, "scope variable %s may not be thrown", v->toChars()); result = true; } continue; } } else { //printf("no infer for %s\n", v->toChars()); v->doNotInferScope = true; } } return result; }
/**************************************** * Function parameter par is being initialized to arg, * and par may escape. * Detect if scoped values can escape this way. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * par = identifier of function parameter * arg = initializer for param * gag = do not print error messages * Returns: * true if pointers to the stack can escape via assignment */ bool checkParamArgumentEscape(Scope *sc, FuncDeclaration *fdc, Identifier *par, Expression *arg, bool gag) { //printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg->toChars(), par->toChars()); //printf("type = %s, %d\n", arg->type->toChars(), arg->type->hasPointers()); if (!arg->type->hasPointers()) return false; EscapeByResults er; escapeByValue(arg, &er); if (!er.byref.dim && !er.byvalue.dim && !er.byfunc.dim && !er.byexp.dim) return false; bool result = false; for (size_t i = 0; i < er.byvalue.dim; i++) { //printf("byvalue %s\n", v->toChars()); VarDeclaration *v = er.byvalue[i]; if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); v->storage_class &= ~STCmaybescope; if (v->isScope()) { unsafeAssign(sc, fdc, par, arg, gag, result, v, "scope variable"); } else if (v->storage_class & STCvariadic && p == sc->func) { Type *tb = v->type->toBasetype(); if (tb->ty == Tarray || tb->ty == Tsarray) { unsafeAssign(sc, fdc, par, arg, gag, result, v, "variadic variable"); } } else { /* v is not 'scope', and is assigned to a parameter that may escape. * Therefore, v can never be 'scope'. */ v->doNotInferScope = true; } } for (size_t i = 0; i < er.byref.dim; i++) { VarDeclaration *v = er.byref[i]; if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); v->storage_class &= ~STCmaybescope; if ((v->storage_class & (STCref | STCout)) == 0 && p == sc->func) { unsafeAssign(sc, fdc, par, arg, gag, result, v, "reference to local variable"); continue; } } for (size_t i = 0; i < er.byfunc.dim; i++) { FuncDeclaration *fd = er.byfunc[i]; //printf("fd = %s, %d\n", fd->toChars(), fd->tookAddressOf); VarDeclarations vars; findAllOuterAccessedVariables(fd, &vars); for (size_t j = 0; j < vars.dim; j++) { VarDeclaration *v = vars[j]; //printf("v = %s\n", v->toChars()); assert(!v->isDataseg()); // these are not put in the closureVars[] Dsymbol *p = v->toParent2(); v->storage_class &= ~STCmaybescope; if ((v->storage_class & (STCref | STCout | STCscope)) && p == sc->func) { unsafeAssign(sc, fdc, par, arg, gag, result, v, "reference to local"); continue; } } } for (size_t i = 0; i < er.byexp.dim; i++) { Expression *ee = er.byexp[i]; if (sc->func->setUnsafe()) { if (!gag) error(ee->loc, "reference to stack allocated value returned by %s assigned to non-scope parameter %s", ee->toChars(), par ? par->toChars() : "unnamed"); result = true; } } return result; }
static bool checkReturnEscapeImpl(Scope *sc, Expression *e, bool refs, bool gag) { //printf("[%s] checkReturnEscapeImpl, e = %s\n", e->loc->toChars(), e->toChars()); EscapeByResults er; if (refs) escapeByRef(e, &er); else escapeByValue(e, &er); if (!er.byref.dim && !er.byvalue.dim && !er.byexp.dim) return false; bool result = false; for (size_t i = 0; i < er.byvalue.dim; i++) { VarDeclaration *v = er.byvalue[i]; //printf("byvalue %s\n", v->toChars()); if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); if ((v->isScope() || (v->storage_class & STCmaybescope)) && !(v->storage_class & STCreturn) && v->isParameter() && sc->func->flags & FUNCFLAGreturnInprocess && p == sc->func) { inferReturn(sc->func, v); // infer addition of 'return' continue; } if (v->isScope()) { if (v->storage_class & STCreturn) continue; if (sc->_module && sc->_module->isRoot() && /* This case comes up when the ReturnStatement of a __foreachbody is * checked for escapes by the caller of __foreachbody. Skip it. * * struct S { static int opApply(int delegate(S*) dg); } * S* foo() { * foreach (S* s; S) // create __foreachbody for body of foreach * return s; // s is inferred as 'scope' but incorrectly tested in foo() * return null; } */ !(!refs && p->parent == sc->func)) { // Only look for errors if in module listed on command line if (global.params.vsafe) // https://issues.dlang.org/show_bug.cgi?id=17029 { if (!gag) error(e->loc, "scope variable %s may not be returned", v->toChars()); result = true; } continue; } } else if (v->storage_class & STCvariadic && p == sc->func) { Type *tb = v->type->toBasetype(); if (tb->ty == Tarray || tb->ty == Tsarray) { if (!gag) error(e->loc, "returning `%s` escapes a reference to variadic parameter `%s`", e->toChars(), v->toChars()); result = false; } } else { //printf("no infer for %s\n", v->toChars()); v->doNotInferScope = true; } } for (size_t i = 0; i < er.byref.dim; i++) { VarDeclaration *v = er.byref[i]; //printf("byref %s\n", v->toChars()); if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); if ((v->storage_class & (STCref | STCout)) == 0) { if (p == sc->func) { escapingRef(v, e, result, gag); continue; } FuncDeclaration *fd = p->isFuncDeclaration(); if (fd && sc->func->flags & FUNCFLAGreturnInprocess) { /* Code like: * int x; * auto dg = () { return &x; } * Making it: * auto dg = () return { return &x; } * Because dg.ptr points to x, this is returning dt.ptr+offset */ if (global.params.vsafe) sc->func->storage_class |= STCreturn; } } /* Check for returning a ref variable by 'ref', but should be 'return ref' * Infer the addition of 'return', or set result to be the offending expression. */ if ( (v->storage_class & (STCref | STCout)) && !(v->storage_class & (STCreturn | STCforeach))) { if ((sc->func->flags & FUNCFLAGreturnInprocess) && p == sc->func) { inferReturn(sc->func, v); // infer addition of 'return' } else if (global.params.useDIP25 && sc->_module && sc->_module->isRoot()) { // Only look for errors if in module listed on command line if (p == sc->func) { //printf("escaping reference to local ref variable %s\n", v->toChars()); //printf("storage class = x%llx\n", v->storage_class); escapingRef(v, e, result, gag); continue; } // Don't need to be concerned if v's parent does not return a ref FuncDeclaration *fd = p->isFuncDeclaration(); if (fd && fd->type && fd->type->ty == Tfunction) { TypeFunction *tf = (TypeFunction *)fd->type; if (tf->isref) { if (!gag) error(e->loc, "escaping reference to outer local variable %s", v->toChars()); result = true; continue; } } } } } for (size_t i = 0; i < er.byexp.dim; i++) { Expression *ee = er.byexp[i]; //printf("byexp %s\n", ee->toChars()); if (!gag) error(ee->loc, "escaping reference to stack allocated value returned by %s", ee->toChars()); result = true; } return result; }
/**************************************** * Given an AssignExp, determine if the lvalue will cause * the contents of the rvalue to escape. * Print error messages when these are detected. * Infer 'scope' for the lvalue where possible, in order * to eliminate the error. * Params: * sc = used to determine current function and module * ae = AssignExp to check for any pointers to the stack * gag = do not print error messages * Returns: * true if pointers to the stack can escape via assignment */ bool checkAssignEscape(Scope *sc, Expression *e, bool gag) { //printf("checkAssignEscape(e: %s)\n", e->toChars()); if (e->op != TOKassign && e->op != TOKblit && e->op != TOKconstruct) return false; AssignExp *ae = (AssignExp *)e; Expression *e1 = ae->e1; Expression *e2 = ae->e2; //printf("type = %s, %d\n", e1->type->toChars(), e1->type->hasPointers()); if (!e1->type->hasPointers()) return false; if (e1->op == TOKslice) return false; EscapeByResults er; escapeByValue(e2, &er); if (!er.byref.dim && !er.byvalue.dim && !er.byfunc.dim && !er.byexp.dim) return false; VarDeclaration *va = NULL; while (e1->op == TOKdotvar) e1 = ((DotVarExp *)e1)->e1; if (e1->op == TOKvar) va = ((VarExp *)e1)->var->isVarDeclaration(); else if (e1->op == TOKthis) va = ((ThisExp *)e1)->var->isVarDeclaration(); else if (e1->op == TOKindex) { IndexExp *ie = (IndexExp *)e1; if (ie->e1->op == TOKvar && ie->e1->type->toBasetype()->ty == Tsarray) va = ((VarExp *)ie->e1)->var->isVarDeclaration(); } // Try to infer 'scope' for va if in a function not marked @system bool inferScope = false; if (va && sc->func && sc->func->type && sc->func->type->ty == Tfunction) inferScope = ((TypeFunction *)sc->func->type)->trust != TRUSTsystem; bool result = false; for (size_t i = 0; i < er.byvalue.dim; i++) { VarDeclaration *v = er.byvalue[i]; //printf("byvalue: %s\n", v->toChars()); if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); if (!(va && va->isScope())) v->storage_class &= ~STCmaybescope; if (v->isScope()) { if (va && va->isScope() && va->storage_class & STCreturn && !(v->storage_class & STCreturn) && sc->func->setUnsafe()) { if (!gag) error(ae->loc, "scope variable %s assigned to return scope %s", v->toChars(), va->toChars()); result = true; continue; } // If va's lifetime encloses v's, then error if (va && ((va->enclosesLifetimeOf(v) && !(v->storage_class & STCparameter)) || // va is class reference (ae->e1->op == TOKdotvar && va->type->toBasetype()->ty == Tclass && (va->enclosesLifetimeOf(v) || !va->isScope())) || va->storage_class & STCref) && sc->func->setUnsafe()) { if (!gag) error(ae->loc, "scope variable %s assigned to %s with longer lifetime", v->toChars(), va->toChars()); result = true; continue; } if (va && !va->isDataseg() && !va->doNotInferScope) { if (!va->isScope() && inferScope) { //printf("inferring scope for %s\n", va->toChars()); va->storage_class |= STCscope | STCscopeinferred; va->storage_class |= v->storage_class & STCreturn; } continue; } if (sc->func->setUnsafe()) { if (!gag) error(ae->loc, "scope variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); result = true; } } else if (v->storage_class & STCvariadic && p == sc->func) { Type *tb = v->type->toBasetype(); if (tb->ty == Tarray || tb->ty == Tsarray) { if (va && !va->isDataseg() && !va->doNotInferScope) { if (!va->isScope() && inferScope) { //printf("inferring scope for %s\n", va->toChars()); va->storage_class |= STCscope | STCscopeinferred; } continue; } if (sc->func->setUnsafe()) { if (!gag) error(ae->loc, "variadic variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); result = true; } } } else { /* v is not 'scope', and we didn't check the scope of where we assigned it to. * It may escape via that assignment, therefore, v can never be 'scope'. */ v->doNotInferScope = true; } } for (size_t i = 0; i < er.byref.dim; i++) { VarDeclaration *v = er.byref[i]; //printf("byref: %s\n", v->toChars()); if (v->isDataseg()) continue; Dsymbol *p = v->toParent2(); // If va's lifetime encloses v's, then error if (va && ((va->enclosesLifetimeOf(v) && !(v->storage_class & STCparameter)) || va->storage_class & STCref) && sc->func->setUnsafe()) { if (!gag) error(ae->loc, "address of variable %s assigned to %s with longer lifetime", v->toChars(), va->toChars()); result = true; continue; } if (!(va && va->isScope())) v->storage_class &= ~STCmaybescope; if ((v->storage_class & (STCref | STCout)) == 0 && p == sc->func) { if (va && !va->isDataseg() && !va->doNotInferScope) { if (!va->isScope() && inferScope) { //printf("inferring scope for %s\n", va->toChars()); va->storage_class |= STCscope | STCscopeinferred; } continue; } if (sc->func->setUnsafe()) { if (!gag) error(ae->loc, "reference to local variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); result = true; } continue; } } for (size_t i = 0; i < er.byfunc.dim; i++) { FuncDeclaration *fd = er.byfunc[i]; //printf("fd = %s, %d\n", fd->toChars(), fd->tookAddressOf); VarDeclarations vars; findAllOuterAccessedVariables(fd, &vars); for (size_t j = 0; j < vars.dim; j++) { VarDeclaration *v = vars[j]; //printf("v = %s\n", v->toChars()); assert(!v->isDataseg()); // these are not put in the closureVars[] Dsymbol *p = v->toParent2(); if (!(va && va->isScope())) v->storage_class &= ~STCmaybescope; if ((v->storage_class & (STCref | STCout | STCscope)) && p == sc->func) { if (va && !va->isDataseg() && !va->doNotInferScope) { /* Don't infer STCscope for va, because then a closure * won't be generated for sc->func. */ //if (!va->isScope() && inferScope) //va->storage_class |= STCscope | STCscopeinferred; continue; } if (sc->func->setUnsafe()) { if (!gag) error(ae->loc, "reference to local %s assigned to non-scope %s in @safe code", v->toChars(), e1->toChars()); result = true; } continue; } } } for (size_t i = 0; i < er.byexp.dim; i++) { Expression *ee = er.byexp[i]; if (va && !va->isDataseg() && !va->doNotInferScope) { if (!va->isScope() && inferScope) { //printf("inferring scope for %s\n", va->toChars()); va->storage_class |= STCscope | STCscopeinferred; } continue; } if (sc->func->setUnsafe()) { if (!gag) error(ee->loc, "reference to stack allocated value returned by %s assigned to non-scope %s", ee->toChars(), e1->toChars()); result = true; } } return result; }