/**************************************** * 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; }