/* Compiler implementation of the D programming language * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved * written by Walter Bright * http://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * http://www.boost.org/LICENSE_1_0.txt * https://github.com/D-Programming-Language/dmd/blob/master/src/escape.c */ #include "mars.h" #include "init.h" #include "expression.h" #include "scope.h" #include "aggregate.h" #include "declaration.h" #include "module.h" /************************************ * Aggregate the data collected by the escapeBy??() functions. */ struct EscapeByResults { VarDeclarations byref; // array into which variables being returned by ref are inserted VarDeclarations byvalue; // array into which variables with values containing pointers are inserted FuncDeclarations byfunc; // nested functions that are turned into delegates Expressions byexp; // array into which temporaries being returned by ref are inserted }; static bool checkReturnEscapeImpl(Scope *sc, Expression *e, bool refs, bool gag); static void inferReturn(FuncDeclaration *fd, VarDeclaration *v); static void escapeByValue(Expression *e, EscapeByResults *er); static void escapeByRef(Expression *e, EscapeByResults *er); static void findAllOuterAccessedVariables(FuncDeclaration *fd, VarDeclarations *vars); /* 'v' is assigned unsafely to 'par' */ static void unsafeAssign(Scope *sc, FuncDeclaration *fdc, Identifier *par, Expression *arg, bool gag, bool &result, VarDeclaration *v, const char *desc) { if (global.params.vsafe && sc->func->setUnsafe()) { if (!gag) error(arg->loc, "%s %s assigned to non-scope parameter %s calling %s", desc, v->toChars(), par ? par->toChars() : "unnamed", fdc ? fdc->toPrettyChars() : "indirectly"); result = true; } } /**************************************** * 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; } /**************************************** * 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; } /************************************ * 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; } /************************************ * Detect cases where pointers to the stack can 'escape' the * lifetime of the stack frame by returning 'e' by value. * 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 checkReturnEscape(Scope *sc, Expression *e, bool gag) { //printf("[%s] checkReturnEscape, e = %s\n", e->loc->toChars(), e->toChars()); return checkReturnEscapeImpl(sc, e, false, gag); } /************************************ * Detect cases where returning 'e' by ref can result in a reference to the stack * being returned. * Print error messages when these are detected. * Params: * sc = used to determine current function and module * e = expression to check * gag = do not print error messages * Returns: * true if references to the stack can escape */ bool checkReturnEscapeRef(Scope *sc, Expression *e, bool gag) { //printf("[%s] checkReturnEscapeRef, e = %s\n", e->loc.toChars(), e->toChars()); //printf("current function %s\n", sc->func->toChars()); //printf("parent2 function %s\n", sc->func->toParent2()->toChars()); return checkReturnEscapeImpl(sc, e, true, gag); } static void escapingRef(VarDeclaration *v, Expression *e, bool &result, bool gag) { if (!gag) { const char *msg; if (v->storage_class & STCparameter) msg = "returning `%s` escapes a reference to parameter `%s`, perhaps annotate with `return`"; else msg = "returning `%s` escapes a reference to local variable `%s`"; error(e->loc, msg, e->toChars(), v->toChars()); } result = true; } 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; } /************************************* * Variable v needs to have 'return' inferred for it. * Params: * fd = function that v is a parameter to * v = parameter that needs to be STCreturn */ static void inferReturn(FuncDeclaration *fd, VarDeclaration *v) { // v is a local in the current function //printf("for function '%s' inferring 'return' for variable '%s'\n", fd->toChars(), v->toChars()); v->storage_class |= STCreturn; TypeFunction *tf = (TypeFunction *)fd->type; if (v == fd->vthis) { /* v is the 'this' reference, so mark the function */ fd->storage_class |= STCreturn; if (tf->ty == Tfunction) { //printf("'this' too %p %s\n", tf, sc->func->toChars()); tf->isreturn = true; } } else { // Perform 'return' inference on parameter if (tf->ty == Tfunction && tf->parameters) { const size_t dim = Parameter::dim(tf->parameters); for (size_t i = 0; i < dim; i++) { Parameter *p = Parameter::getNth(tf->parameters, i); if (p->ident == v->ident) { p->storageClass |= STCreturn; break; // there can be only one } } } } } /**************************************** * e is an expression to be returned by value, and that value contains pointers. * Walk e to determine which variables are possibly being * returned by value, such as: * int* function(int* p) { return p; } * If e is a form of &p, determine which variables have content * which is being returned as ref, such as: * int* function(int i) { return &i; } * Multiple variables can be inserted, because of expressions like this: * int function(bool b, int i, int* p) { return b ? &i : p; } * * No side effects. * * Params: * e = expression to be returned by value * er = where to place collected data */ static void escapeByValue(Expression *e, EscapeByResults *er) { //printf("[%s] escapeByValue, e: %s\n", e->loc.toChars(), e->toChars()); class EscapeVisitor : public Visitor { public: EscapeByResults *er; EscapeVisitor(EscapeByResults *er) : er(er) { } void visit(Expression *) { } void visit(AddrExp *e) { escapeByRef(e->e1, er); } void visit(SymOffExp *e) { VarDeclaration *v = e->var->isVarDeclaration(); if (v) er->byref.push(v); } void visit(VarExp *e) { VarDeclaration *v = e->var->isVarDeclaration(); if (v) er->byvalue.push(v); } void visit(ThisExp *e) { if (e->var) er->byvalue.push(e->var); } void visit(DotVarExp *e) { Type *t = e->e1->type->toBasetype(); if (t->ty == Tstruct) e->e1->accept(this); } void visit(DelegateExp *e) { Type *t = e->e1->type->toBasetype(); if (t->ty == Tclass || t->ty == Tpointer) escapeByValue(e->e1, er); else escapeByRef(e->e1, er); er->byfunc.push(e->func); } void visit(FuncExp *e) { if (e->fd->tok == TOKdelegate) er->byfunc.push(e->fd); } void visit(TupleExp *) { assert(0); // should have been lowered by now } void visit(ArrayLiteralExp *e) { Type *tb = e->type->toBasetype(); if (tb->ty == Tsarray || tb->ty == Tarray) { if (e->basis) e->basis->accept(this); for (size_t i = 0; i < e->elements->dim; i++) { Expression *el = (*e->elements)[i]; if (el) el->accept(this); } } } void visit(StructLiteralExp *e) { if (e->elements) { for (size_t i = 0; i < e->elements->dim; i++) { Expression *ex = (*e->elements)[i]; if (ex) ex->accept(this); } } } void visit(NewExp *e) { Type *tb = e->newtype->toBasetype(); if (tb->ty == Tstruct && !e->member && e->arguments) { for (size_t i = 0; i < e->arguments->dim; i++) { Expression *ex = (*e->arguments)[i]; if (ex) ex->accept(this); } } } void visit(CastExp *e) { Type *tb = e->type->toBasetype(); if (tb->ty == Tarray && e->e1->type->toBasetype()->ty == Tsarray) { escapeByRef(e->e1, er); } else e->e1->accept(this); } void visit(SliceExp *e) { if (e->e1->op == TOKvar) { VarDeclaration *v = ((VarExp *)e->e1)->var->isVarDeclaration(); Type *tb = e->type->toBasetype(); if (v) { if (tb->ty == Tsarray) return; if (v->storage_class & STCvariadic) { er->byvalue.push(v); return; } } } Type *t1b = e->e1->type->toBasetype(); if (t1b->ty == Tsarray) { Type *tb = e->type->toBasetype(); if (tb->ty != Tsarray) escapeByRef(e->e1, er); } else e->e1->accept(this); } void visit(BinExp *e) { Type *tb = e->type->toBasetype(); if (tb->ty == Tpointer) { e->e1->accept(this); e->e2->accept(this); } } void visit(BinAssignExp *e) { e->e1->accept(this); } void visit(AssignExp *e) { e->e1->accept(this); } void visit(CommaExp *e) { e->e2->accept(this); } void visit(CondExp *e) { e->e1->accept(this); e->e2->accept(this); } void visit(CallExp *e) { //printf("CallExp(): %s\n", e->toChars()); /* Check each argument that is * passed as 'return scope'. */ Type *t1 = e->e1->type->toBasetype(); TypeFunction *tf = NULL; TypeDelegate *dg = NULL; if (t1->ty == Tdelegate) { dg = (TypeDelegate *)t1; tf = (TypeFunction *)dg->next; } else if (t1->ty == Tfunction) tf = (TypeFunction *)t1; else return; if (e->arguments && e->arguments->dim) { /* j=1 if _arguments[] is first argument, * skip it because it is not passed by ref */ size_t j = (tf->linkage == LINKd && tf->varargs == 1); for (size_t i = j; i < e->arguments->dim; ++i) { Expression *arg = (*e->arguments)[i]; size_t nparams = Parameter::dim(tf->parameters); if (i - j < nparams && i >= j) { Parameter *p = Parameter::getNth(tf->parameters, i - j); const StorageClass stc = tf->parameterStorageClass(p); if ((stc & (STCscope)) && (stc & STCreturn)) arg->accept(this); else if ((stc & (STCref)) && (stc & STCreturn)) escapeByRef(arg, er); } } } // If 'this' is returned, check it too if (e->e1->op == TOKdotvar && t1->ty == Tfunction) { DotVarExp *dve = (DotVarExp *)e->e1; FuncDeclaration *fd = dve->var->isFuncDeclaration(); AggregateDeclaration *ad = NULL; if (global.params.vsafe && tf->isreturn && fd && (ad = fd->isThis()) != NULL) { if (ad->isClassDeclaration() || tf->isscope) // this is 'return scope' dve->e1->accept(this); else if (ad->isStructDeclaration()) // this is 'return ref' escapeByRef(dve->e1, er); } else if (dve->var->storage_class & STCreturn || tf->isreturn) { if (dve->var->storage_class & STCscope) dve->e1->accept(this); else if (dve->var->storage_class & STCref) escapeByRef(dve->e1, er); } } /* If returning the result of a delegate call, the .ptr * field of the delegate must be checked. */ if (dg) { if (tf->isreturn) e->e1->accept(this); } } }; EscapeVisitor v(er); e->accept(&v); } /**************************************** * e is an expression to be returned by 'ref'. * Walk e to determine which variables are possibly being * returned by ref, such as: * ref int function(int i) { return i; } * If e is a form of *p, determine which variables have content * which is being returned as ref, such as: * ref int function(int* p) { return *p; } * Multiple variables can be inserted, because of expressions like this: * ref int function(bool b, int i, int* p) { return b ? i : *p; } * * No side effects. * * Params: * e = expression to be returned by 'ref' * er = where to place collected data */ static void escapeByRef(Expression *e, EscapeByResults *er) { //printf("[%s] escapeByRef, e: %s\n", e->loc->toChars(), e->toChars()); class EscapeRefVisitor : public Visitor { public: EscapeByResults *er; EscapeRefVisitor(EscapeByResults *er) : er(er) { } void visit(Expression *) { } void visit(VarExp *e) { VarDeclaration *v = e->var->isVarDeclaration(); if (v) { if (v->storage_class & STCref && v->storage_class & (STCforeach | STCtemp) && v->_init) { /* If compiler generated ref temporary * (ref v = ex; ex) * look at the initializer instead */ if (ExpInitializer *ez = v->_init->isExpInitializer()) { assert(ez->exp && ez->exp->op == TOKconstruct); Expression *ex = ((ConstructExp *)ez->exp)->e2; ex->accept(this); } } else er->byref.push(v); } } void visit(ThisExp *e) { if (e->var) er->byref.push(e->var); } void visit(PtrExp *e) { escapeByValue(e->e1, er); } void visit(IndexExp *e) { Type *tb = e->e1->type->toBasetype(); if (e->e1->op == TOKvar) { VarDeclaration *v = ((VarExp *)e->e1)->var->isVarDeclaration(); if (tb->ty == Tarray || tb->ty == Tsarray) { if (v->storage_class & STCvariadic) { er->byref.push(v); return; } } } if (tb->ty == Tsarray) { e->e1->accept(this); } else if (tb->ty == Tarray) { escapeByValue(e->e1, er); } } void visit(DotVarExp *e) { Type *t1b = e->e1->type->toBasetype(); if (t1b->ty == Tclass) escapeByValue(e->e1, er); else e->e1->accept(this); } void visit(BinAssignExp *e) { e->e1->accept(this); } void visit(AssignExp *e) { e->e1->accept(this); } void visit(CommaExp *e) { e->e2->accept(this); } void visit(CondExp *e) { e->e1->accept(this); e->e2->accept(this); } void visit(CallExp *e) { /* If the function returns by ref, check each argument that is * passed as 'return ref'. */ Type *t1 = e->e1->type->toBasetype(); TypeFunction *tf; if (t1->ty == Tdelegate) tf = (TypeFunction *)((TypeDelegate *)t1)->next; else if (t1->ty == Tfunction) tf = (TypeFunction *)t1; else return; if (tf->isref) { if (e->arguments && e->arguments->dim) { /* j=1 if _arguments[] is first argument, * skip it because it is not passed by ref */ size_t j = (tf->linkage == LINKd && tf->varargs == 1); for (size_t i = j; i < e->arguments->dim; ++i) { Expression *arg = (*e->arguments)[i]; size_t nparams = Parameter::dim(tf->parameters); if (i - j < nparams && i >= j) { Parameter *p = Parameter::getNth(tf->parameters, i - j); const StorageClass stc = tf->parameterStorageClass(p); if ((stc & (STCout | STCref)) && (stc & STCreturn)) arg->accept(this); else if ((stc & STCscope) && (stc & STCreturn)) { if (arg->op == TOKdelegate) { DelegateExp *de = (DelegateExp *)arg; if (de->func->isNested()) er->byexp.push(de); } else escapeByValue(arg, er); } } } } // If 'this' is returned by ref, check it too if (e->e1->op == TOKdotvar && t1->ty == Tfunction) { DotVarExp *dve = (DotVarExp *)e->e1; if (dve->var->storage_class & STCreturn || tf->isreturn) { if ((dve->var->storage_class & STCscope) || tf->isscope) escapeByValue(dve->e1, er); else if ((dve->var->storage_class & STCref) || tf->isref) dve->e1->accept(this); } } // If it's a delegate, check it too if (e->e1->op == TOKvar && t1->ty == Tdelegate) { escapeByValue(e->e1, er); } } else er->byexp.push(e); } }; EscapeRefVisitor v(er); e->accept(&v); } /************************* * Find all variables accessed by this delegate that are * in functions enclosing it. * Params: * fd = function * vars = array to append found variables to */ void findAllOuterAccessedVariables(FuncDeclaration *fd, VarDeclarations *vars) { //printf("findAllOuterAccessedVariables(fd: %s)\n", fd.toChars()); for (Dsymbol *p = fd->parent; p; p = p->parent) { FuncDeclaration *fdp = p->isFuncDeclaration(); if (fdp) { for (size_t i = 0; i < fdp->closureVars.dim; i++) { VarDeclaration *v = fdp->closureVars[i]; for (size_t j = 0; j < v->nestedrefs.dim; j++) { FuncDeclaration *fdv = v->nestedrefs[j]; if (fdv == fd) { //printf("accessed: %s, type %s\n", v->toChars(), v->type->toChars()); vars->push(v); } } } } } }