610 changes: 555 additions & 55 deletions src/escape.d

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions src/expression.d
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import ddmd.dstruct;
import ddmd.dsymbol;
import ddmd.dtemplate;
import ddmd.errors;
import ddmd.escape;
import ddmd.func;
import ddmd.globals;
import ddmd.hdrgen;
Expand Down Expand Up @@ -1690,11 +1691,18 @@ extern (C++) bool functionParameters(Loc loc, Scope* sc, TypeFunction tf, Type t
//printf("arg: %s\n", arg->toChars());
//printf("type: %s\n", arg->type->toChars());

/* Look for arguments that cannot 'escape' from the called
* function.
*/
if (!tf.parameterEscapes(p))
if (tf.parameterEscapes(p))
{
/* Argument value can escape from the called function.
* Check arg to see if it matters.
*/
if (global.params.safe)
err |= checkParamArgumentEscape(sc, fd, p, arg, false);
}
else
{
/* Argument value cannot escape from the called function.
*/
Expression a = arg;
if (a.op == TOKcast)
a = (cast(CastExp)a).e1;
Expand Down Expand Up @@ -13500,7 +13508,9 @@ extern (C++) class AssignExp : BinExp

type = e1.type;
assert(type);
return op == TOKassign ? reorderSettingAAElem(sc) : this;
auto result = op == TOKassign ? reorderSettingAAElem(sc) : this;
checkAssignEscape(sc, result, false);
return result;
}

override final bool isLvalue()
Expand Down
57 changes: 44 additions & 13 deletions src/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ enum FUNCFLAGnothrowInprocess = 4; /// working on determining nothrow
enum FUNCFLAGnogcInprocess = 8; /// working on determining @nogc
enum FUNCFLAGreturnInprocess = 0x10; /// working on inferring 'return' for parameters
enum FUNCFLAGinlineScanned = 0x20; /// function has been scanned for inline possibilities
enum FUNCFLAGinferScope = 0x40; /// infer 'scope' for parameters


/***********************************************************
Expand Down Expand Up @@ -742,10 +743,9 @@ extern (C++) class FuncDeclaration : Declaration
if ((storage_class & STCauto) && !f.isref && !inferRetType)
error("storage class 'auto' has no effect if return type is not inferred");

/* Functions can only be 'scope' if they have a 'this' that is a pointer, not a ref
/* Functions can only be 'scope' if they have a 'this'
*/
if (f.isscope && !isNested() &&
!(ad && ad.isClassDeclaration()))
if (f.isscope && !isNested() && !ad)
{
error("functions cannot be scope");
}
Expand Down Expand Up @@ -1560,6 +1560,8 @@ extern (C++) class FuncDeclaration : Declaration
stc |= STCparameter;
if (f.varargs == 2 && i + 1 == nparams)
stc |= STCvariadic;
if (flags & FUNCFLAGinferScope)
stc |= STCmaybescope;
stc |= fparam.storageClass & (STCin | STCout | STCref | STCreturn | STCscope | STClazy | STCfinal | STC_TYPECTOR | STCnodtor);
v.storage_class = stc;
v.semantic(sc2);
Expand Down Expand Up @@ -2218,7 +2220,26 @@ extern (C++) class FuncDeclaration : Declaration
f.isnogc = true;
}

flags &= ~FUNCFLAGreturnInprocess;
flags &= ~(FUNCFLAGreturnInprocess | FUNCFLAGinferScope);

// Infer STCscope
if (parameters)
{
size_t nfparams = Parameter.dim(f.parameters);
assert(nfparams == parameters.dim);
foreach (u, v; *parameters)
{
if (v.storage_class & STCmaybescope)
{
//printf("Inferring scope for %s\n", v.toChars());
Parameter p = Parameter.getNth(f.parameters, u);
v.storage_class &= ~STCmaybescope;
v.storage_class |= STCscope;
p.storageClass |= STCscope;
assert(!(p.storageClass & STCmaybescope));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this unset STCreturn if that doesn't apply?


// reset deco to apply inference result to mangled name
if (f != type)
Expand Down Expand Up @@ -2375,8 +2396,14 @@ extern (C++) class FuncDeclaration : Declaration
if (type.ty == Tfunction && (cast(TypeFunction)type).iswild & 2)
v.storage_class |= STCreturn;
}
if (type.ty == Tfunction && (cast(TypeFunction)type).isreturn)
v.storage_class |= STCreturn;
if (type.ty == Tfunction)
{
TypeFunction tf = cast(TypeFunction)type;
if (tf.isreturn)
v.storage_class |= STCreturn;
if (tf.isscope)
v.storage_class |= STCscope;
}

v.semantic(sc);
if (!sc.insert(v))
Expand Down Expand Up @@ -3095,6 +3122,10 @@ extern (C++) class FuncDeclaration : Declaration

if (!isVirtual() || introducing)
flags |= FUNCFLAGreturnInprocess;

// Initialize for inferring STCscope
if (global.params.safe)
flags |= FUNCFLAGinferScope;
}

final PURE isPure()
Expand Down Expand Up @@ -3542,14 +3573,14 @@ extern (C++) class FuncDeclaration : Declaration
for (size_t i = 0; i < closureVars.dim; i++)
{
VarDeclaration v = closureVars[i];
//printf("\tv = %s\n", v->toChars());
//printf("\tv = %s\n", v.toChars());

for (size_t j = 0; j < v.nestedrefs.dim; j++)
{
FuncDeclaration f = v.nestedrefs[j];
assert(f != this);

//printf("\t\tf = %s, isVirtual=%d, isThis=%p, tookAddressOf=%d\n", f->toChars(), f->isVirtual(), f->isThis(), f->tookAddressOf);
//printf("\t\tf = %p, %s, isVirtual=%d, isThis=%p, tookAddressOf=%d\n", f, f.toChars(), f.isVirtual(), f.isThis(), f.tookAddressOf);

/* Look to see if f escapes. We consider all parents of f within
* this, and also all siblings which call f; if any of them escape,
Expand All @@ -3563,7 +3594,7 @@ extern (C++) class FuncDeclaration : Declaration
continue;
if (fx.isThis() || fx.tookAddressOf)
{
//printf("\t\tfx = %s, isVirtual=%d, isThis=%p, tookAddressOf=%d\n", fx->toChars(), fx->isVirtual(), fx->isThis(), fx->tookAddressOf);
//printf("\t\tfx = %s, isVirtual=%d, isThis=%p, tookAddressOf=%d\n", fx.toChars(), fx.isVirtual(), fx.isThis(), fx.tookAddressOf);

/* Mark as needing closure any functions between this and f
*/
Expand Down Expand Up @@ -3596,17 +3627,17 @@ extern (C++) class FuncDeclaration : Declaration
Type tret = (cast(TypeFunction)type).next;
assert(tret);
tret = tret.toBasetype();
//printf("\t\treturning %s\n", tret->toChars());
//printf("\t\treturning %s\n", tret.toChars());
if (tret.ty == Tclass || tret.ty == Tstruct)
{
Dsymbol st = tret.toDsymbol(null);
//printf("\t\treturning class/struct %s\n", tret->toChars());
//printf("\t\treturning class/struct %s\n", tret.toChars());
for (Dsymbol s = st.parent; s; s = s.parent)
{
//printf("\t\t\tparent = %s %s\n", s->kind(), s->toChars());
//printf("\t\t\tparent = %s %s\n", s.kind(), s.toChars());
if (s == this)
{
//printf("\t\treturning local %s\n", st->toChars());
//printf("\t\treturning local %s\n", st.toChars());
goto Lyes;
}
}
Expand Down
134 changes: 97 additions & 37 deletions src/mtype.d
Original file line number Diff line number Diff line change
Expand Up @@ -700,15 +700,7 @@ extern (C++) abstract class Type : RootObject
{
goto Ldistinct;
}
const(StorageClass) sc = STCref | STCin | STCout | STClazy;
if ((fparam1.storageClass & sc) != (fparam2.storageClass & sc))
inoutmismatch = 1;
// We can add scope, but not subtract it
if (!(fparam1.storageClass & STCscope) && (fparam2.storageClass & STCscope))
inoutmismatch = 1;
// We can subtract return, but not add it
if ((fparam1.storageClass & STCreturn) && !(fparam2.storageClass & STCreturn))
inoutmismatch = 1;
inoutmismatch = !fparam1.isCovariant(fparam2);
}
}
else if (t1.parameters != t2.parameters)
Expand Down Expand Up @@ -5426,6 +5418,7 @@ extern (C++) final class TypeAArray : TypeArray
sd.semantic(null);

// duplicate a part of StructDeclaration::semanticTypeInfoMembers
//printf("AA = %s, key: xeq = %p, xerreq = %p xhash = %p\n", toChars(), sd.xeq, sd.xerreq, sd.xhash);
if (sd.xeq && sd.xeq._scope && sd.xeq.semanticRun < PASSsemantic3done)
{
uint errors = global.startGagging();
Expand All @@ -5434,7 +5427,6 @@ extern (C++) final class TypeAArray : TypeArray
sd.xeq = sd.xerreq;
}

//printf("AA = %s, key: xeq = %p, xhash = %p\n", toChars(), sd->xeq, sd->xhash);
const(char)* s = (index.toBasetype().ty != Tstruct) ? "bottom of " : "";
if (!sd.xeq)
{
Expand Down Expand Up @@ -6247,9 +6239,6 @@ extern (C++) final class TypeFunction : TypeNext
fparam.storageClass |= STCscope; // 'return' implies 'scope'
if (tf.isref)
{
error(loc, "parameter %s is 'return' but function returns 'ref'",
fparam.ident ? fparam.ident.toChars() : "");
errors = true;
}
else if (tf.next && !tf.next.hasPointers())
{
Expand Down Expand Up @@ -6284,7 +6273,11 @@ extern (C++) final class TypeFunction : TypeNext
}

if (fparam.storageClass & STCscope && !fparam.type.hasPointers())
fparam.storageClass &= ~(STCreturn | STCscope);
{
fparam.storageClass &= ~STCscope;
if (!(fparam.storageClass & STCref))
fparam.storageClass &= ~STCreturn;
}

if (t.hasWild())
{
Expand Down Expand Up @@ -6572,46 +6565,92 @@ extern (C++) final class TypeFunction : TypeNext

/***************************
* Examine function signature for parameter p and see if
* p can 'escape' the scope of the function.
* the value of p can 'escape' the scope of the function.
* This is useful to minimize the needed annotations for the parameters.
* Params:
* p = parameter to this function
* Returns:
* true if escapes via assignment to global or through a parameter
*/
bool parameterEscapes(Parameter p)
{
purityLevel();

/* Scope parameters do not escape.
* Allow 'lazy' to imply 'scope' -
* lazy parameters can be passed along
* as lazy parameters to the next function, but that isn't
* escaping.
*/
if (p.storageClass & (STCscope | STClazy))
if (parameterStorageClass(p) & (STCscope | STClazy))
return false;
if (p.storageClass & STCreturn)
return true;
return true;
}


/* If haven't inferred the return type yet, assume it escapes
/************************************
* Take the specified storage class for p,
* and use the function signature to infer whether
* STCscope and STCreturn should be OR'd in.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be OR'd in is a bit colloquial.
I think what you're trying to do here is to infer whether parameters cannot escape, then add STCscope, but also STCreturn if they can be returned.

* (This will not affect the name mangling.)
* Params:
* p = one of the parameters to 'this'
* Returns:
* storage class with STCscope or STCreturn OR'd in
*/
final StorageClass parameterStorageClass(Parameter p)
{
auto stc = p.storageClass;
if (!global.params.safe)
return stc;

if (stc & (STCscope | STCreturn | STClazy) || purity == PUREimpure)
return stc;

/* If haven't inferred the return type yet, can't infer storage classes
*/
if (!nextOf())
return true;
return stc;

purityLevel();

if (purity > PUREweak)
// See if p can escape via any of the other parameters
if (purity == PUREweak)
{
/* With pure functions, we need only be concerned if p escapes
* via any return statement.
*/
Type tret = nextOf().toBasetype();
if (!isref && !tret.hasPointers())
const dim = Parameter.dim(parameters);
foreach (const i; 0 .. dim)
{
/* The result has no references, so p could not be escaping
* that way.
*/
return false;
Parameter fparam = Parameter.getNth(parameters, i);
Type t = fparam.type;
if (!t)
continue;
t = t.baseElemOf();
if (t.isMutable() && t.hasPointers())
{
if (fparam.storageClass & (STCref | STCout))
{
}
else if (t.ty == Tarray || t.ty == Tpointer)
{
Type tn = t.nextOf().toBasetype();
if (!(tn.isMutable() && tn.hasPointers()))
continue;
}
return stc;
}
}
}

/* Assume it escapes in the absence of better information.
*/
return true;
stc |= STCscope;

Type tret = nextOf().toBasetype();
if (isref || tret.hasPointers())
{
/* The result has references, so p could be escaping
* that way.
*/
stc |= STCreturn;
}

return stc;
}

override Type addStorageClass(StorageClass stc)
Expand Down Expand Up @@ -7122,9 +7161,9 @@ extern (C++) final class TypeDelegate : TypeNext

override MATCH implicitConvTo(Type to)
{
//printf("TypeDelegate::implicitConvTo(this=%p, to=%p)\n", this, to);
//printf("TypeDelegate.implicitConvTo(this=%p, to=%p)\n", this, to);
//printf("from: %s\n", toChars());
//printf("to : %s\n", to->toChars());
//printf("to : %s\n", to.toChars());
if (this == to)
return MATCHexact;

Expand Down Expand Up @@ -10175,4 +10214,25 @@ extern (C++) final class Parameter : RootObject
{
return ident ? ident.toChars() : "__anonymous_param";
}

/*********************************
* Compute covariance of parameters `this` and `p`
* as determined by the storage classes of both.
* Params:
* p = Parameter to compare with
* Returns:
* true = `this` can be used in place of `p`
* false = nope
*/
final bool isCovariant(const Parameter p) const pure nothrow @nogc @safe
{
enum stc = STCref | STCin | STCout | STClazy;
return !((this.storageClass & stc) != (p.storageClass & stc) ||

// We can add scope, but not subtract it
(!(this.storageClass & STCscope) && (p.storageClass & STCscope)) ||

// We can subtract return, but not add it
((this.storageClass & STCreturn) && !(p.storageClass & STCreturn)));
}
}
4 changes: 2 additions & 2 deletions src/parse.d
Original file line number Diff line number Diff line change
Expand Up @@ -2863,8 +2863,8 @@ final class Parser : Lexer
// if stc is not a power of 2
if (stc & (stc - 1) && !(stc == (STCin | STCref)))
error("incompatible parameter storage classes");
if ((storageClass & STCscope) && (storageClass & (STCref | STCout)))
error("scope cannot be ref or out");
//if ((storageClass & STCscope) && (storageClass & (STCref | STCout)))
//error("scope cannot be ref or out");

Token* t;
if (tpl && token.value == TOKidentifier && (t = peek(&token), (t.value == TOKcomma || t.value == TOKrparen || t.value == TOKdotdotdot)))
Expand Down
4 changes: 2 additions & 2 deletions test/compilable/interpret3.d
Original file line number Diff line number Diff line change
Expand Up @@ -4250,7 +4250,7 @@ static assert({ bug6851(); return true; }());
7876
**************************************************/

int* bug7876(int n)
int* bug7876(int n) @system
{
int x;
auto ptr = &x;
Expand All @@ -4264,7 +4264,7 @@ struct S7876
int* p;
}

S7876 bug7876b(int n)
S7876 bug7876b(int n) @system
{
int x;
S7876 s;
Expand Down
8 changes: 0 additions & 8 deletions test/fail_compilation/fail191.d

This file was deleted.

216 changes: 216 additions & 0 deletions test/fail_compilation/retscope.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
REQUIRED_ARGS: -transition=safe
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/retscope.d(23): Error: scope variable p may not be returned
fail_compilation/retscope.d(33): Error: escaping reference to local variable j
fail_compilation/retscope.d(46): Error: scope variable p assigned to non-scope q
fail_compilation/retscope.d(48): Error: cannot take address of local i in @safe function test2
fail_compilation/retscope.d(49): Error: variadic variable a assigned to non-scope b
fail_compilation/retscope.d(50): Error: reference to stack allocated value returned by (*fp2)() assigned to non-scope q
---
*/




int* foo1(return scope int* p) { return p; } // ok

int* foo2()(scope int* p) { return p; } // ok, 'return' is inferred
alias foo2a = foo2!();

int* foo3(scope int* p) { return p; } // error

int* foo4(bool b)
{
int i;
int j;

int* nested1(scope int* p) { return null; }
int* nested2(return scope int* p) { return p; }

return b ? nested1(&i) : nested2(&j);
}

/************************************************/

struct S2 { int a,b,c,d; }

@safe S2 function() fp2;

void test2(scope int* p, int[] a ...) @safe
{
static int* q;
static int[] b;
q = p;
int i;
q = &i;
b = a;
q = &fp2().d;
}

/**************************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(76): Error: function retscope.HTTP.Impl.onReceive is @nogc yet allocates closures with the GC
fail_compilation/retscope.d(78): retscope.HTTP.Impl.onReceive.__lambda1 closes over variable this at fail_compilation/retscope.d(76)
---
*/


struct Curl
{
int delegate() dg;
}

struct HTTP
{
struct Impl
{
Curl curl;
int x;

@nogc void onReceive()
{
auto dg = ( ) { return x; };
curl.dg = dg;
}
}
}

/***********************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(97): Error: reference to local variable sa assigned to non-scope parameter a calling retscope.bar8
---
*/
// https://issues.dlang.org/show_bug.cgi?id=8838

int[] foo8() @safe
{
int[5] sa;
return bar8(sa);
}

int[] bar8(int[] a) @safe
{
return a;
}


/*************************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(124): Error: escaping reference to local variable tmp
---
*/

char[] foo9(char[] a) @safe pure nothrow @nogc
{
return a;
}

char[] bar9() @safe
{
char[20] tmp;
foo9(tmp); // ok
return foo9(tmp); // error
}

/*************************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(143): To enforce @safe compiler allocates a closure unless the opApply() uses 'scope'
---
*/

struct S10
{
static int opApply(int delegate(S10*) dg);
}

S10* test10()
{
foreach (S10* m; S10)
return m;
return null;
}

/************************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(159): Error: scope variable this may not be returned
---
*/

class C11
{
@safe C11 foo() scope { return this; }
}


/****************************************************/

/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(178): Error: address of variable i assigned to p with longer lifetime
---
*/



void foo11() @safe
{
int[] p;
int[3] i;
p = i[];
}

/************************************************/
/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(198): Error: scope variable e may not be returned
---
*/

struct Escaper
{
void* DG;
}

void* escapeDg1(scope void* d) @safe
{
Escaper e;
e.DG = d;
return e.DG;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, how does this work @WalterBright? Does the field get tainted by the assignment of a scoped value?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment to s.field is the same as an assignment to s, as far as scope rules go.

}

/*************************************************/
/*
TEST_OUTPUT:
---
fail_compilation/retscope.d(213): Error: scope variable p assigned to non-scope e
---
*/
struct Escaper3 { void* e; }

void* escape3 (scope void* p) @safe {
Escaper3 e;
scope dg = () { return e.e; };
e.e = p;
return dg();
}

30 changes: 30 additions & 0 deletions test/fail_compilation/test14238.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* REQUIRED_ARGS: -dip25 -transition=safe
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/test14238.d(21): Error: scope variable fn may not be returned
fail_compilation/test14238.d(29): Error: escaping reference to stack allocated value returned by &baz
---
*/
// https://issues.dlang.org/show_bug.cgi?id=14238

@safe:

alias Fn = ref int delegate();

ref int foo(return scope Fn fn)
{
return fn(); // Ok
}

ref int foo2(scope Fn fn) {
return fn(); // Error
}

ref int bar() {
int x;
ref int baz() {
return x;
}
return foo(&baz);
}
51 changes: 51 additions & 0 deletions test/fail_compilation/test15544.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
REQUIRED_ARGS: -transition=safe
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/test15544.d(21): Error: reference to local this assigned to non-scope _del in @safe code
fail_compilation/test15544.d(23): Error: reference to local this assigned to non-scope _del in @safe code
---
*/

// https://issues.dlang.org/show_bug.cgi?id=15544

void delegate() @safe _del;

struct S {
int x = 42;

@safe void test()
{
void foo() { assert(x == 42); }
_del = &foo;

_del = { assert(x == 42); };
}
}

/*
TEST_OUTPUT:
---
fail_compilation/test15544.d(47): Error: reference to local y assigned to non-scope dg in @safe code
---
*/

int delegate() dg;

void testClosure1()
{
int* x;
int bar() { return *x; }
dg = &bar;
}

@safe void testClosure2()
{
scope int* y;
int bar() { return *y; }
dg = &bar; // Error
auto dg2 = &bar;
}


2 changes: 1 addition & 1 deletion test/runnable/sdtor.d
Original file line number Diff line number Diff line change
Expand Up @@ -2783,7 +2783,7 @@ struct S9985

static void* ptr;
}
auto ref makeS9985()
auto ref makeS9985() @system
{
S9985 s;
s.b = s.buf.ptr;
Expand Down
2 changes: 1 addition & 1 deletion test/runnable/test28.d
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ void test37()

class C38 { }

const(Object)[] foo38(C38[3] c)
const(Object)[] foo38(C38[3] c) @system
{ const(Object)[] x = c;
return x;
}
Expand Down