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