diff --git a/src/dmd/declaration.d b/src/dmd/declaration.d index 5436accf29cd..c6db2a061cb5 100644 --- a/src/dmd/declaration.d +++ b/src/dmd/declaration.d @@ -1088,6 +1088,8 @@ extern (C++) class VarDeclaration : Declaration Expression edtor; // if !=null, does the destruction of the variable IntRange* range; // if !=null, the variable is known to be within the range + VarDeclarations* maybes; // STC.maybescope variables that are assigned to this STC.maybescope variable + final extern (D) this(Loc loc, Type type, Identifier id, Initializer _init, StorageClass storage_class = STC.undefined_) { super(id); @@ -1632,6 +1634,23 @@ extern (C++) class VarDeclaration : Declaration { return sequenceNumber < v.sequenceNumber; } + + /*************************************** + * Add variable to maybes[]. + * When a maybescope variable `v` is assigned to a maybescope variable `this`, + * we cannot determine if `this` is actually scope until the semantic + * analysis for the function is completed. Thus, we save the data + * until then. + * Params: + * v = an STC.maybescope variable that was assigned to `this` + */ + final void addMaybe(VarDeclaration v) + { + //printf("add %s to %s's list of dependencies\n", v.toChars(), toChars()); + if (!maybes) + maybes = new VarDeclarations(); + maybes.push(v); + } } /*********************************************************** diff --git a/src/dmd/escape.d b/src/dmd/escape.d index 8f11cb9c2ff9..f9a9d689d488 100644 --- a/src/dmd/escape.d +++ b/src/dmd/escape.d @@ -96,7 +96,7 @@ bool checkAssocArrayLiteralEscape(Scope *sc, AssocArrayLiteralExp ae, bool gag) bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier par, Expression arg, bool gag) { enum log = false; - if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg.toChars(), par.toChars()); + if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg ? arg.toChars() : "null", par ? par.toChars() : "null"); //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers()); if (!arg.type.hasPointers()) @@ -153,14 +153,15 @@ bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier par, Ex /* v is not 'scope', and is assigned to a parameter that may escape. * Therefore, v can never be 'scope'. */ - if (log) printf("no infer for %s in %s, fdc %s, %d\n", - v.toChars(), sc.func.ident.toChars(), fdc.ident.toChars(), __LINE__); + if (log) printf("no infer for %s in %s loc %s, fdc %s, %d\n", + v.toChars(), sc.func.ident.toChars(), sc.func.loc.toChars(), fdc.ident.toChars(), __LINE__); v.doNotInferScope = true; } } foreach (VarDeclaration v; er.byref) { + if (log) printf("byref %s\n", v.toChars()); if (v.isDataseg()) continue; @@ -283,6 +284,11 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag) bool inferScope = false; if (va && sc.func && sc.func.type && sc.func.type.ty == Tfunction) inferScope = (cast(TypeFunction)sc.func.type).trust != TRUST.system; + //printf("inferScope = %d, %d\n", inferScope, (va.storage_class & STCmaybescope) != 0); + + // Determine if va is a parameter that is an indirect reference + const bool vaIsRef = va && va.storage_class & STC.parameter && + (va.storage_class & (STC.ref_ | STC.out_) || va.type.toBasetype().ty == Tclass); bool result = false; foreach (VarDeclaration v; er.byvalue) @@ -296,7 +302,17 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag) Dsymbol p = v.toParent2(); - if (!(va && va.isScope())) + if (va && !vaIsRef && !va.isScope() && !v.isScope() && + (va.storage_class & v.storage_class & (STC.maybescope | STC.variadic)) == STC.maybescope && + p == sc.func) + { + /* Add v to va's list of dependencies + */ + va.addMaybe(v); + continue; + } + + if (!(va && va.isScope()) || vaIsRef) notMaybeScope(v); if (v.isScope()) @@ -314,8 +330,9 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag) if (va && (va.enclosesLifetimeOf(v) && !(v.storage_class & (STC.parameter | STC.temp)) || // va is class reference - ae.e1.op == TOK.dotVariable && va.type.toBasetype().ty == Tclass && (va.enclosesLifetimeOf(v) || !va.isScope) || - va.storage_class & STC.ref_ && !(v.storage_class & STC.temp)) && + ae.e1.op == TOK.dotVariable && va.type.toBasetype().ty == Tclass && (va.enclosesLifetimeOf(v) || !va.isScope()) || + vaIsRef || + va.storage_class & (STC.ref_ | STC.out_) && !(v.storage_class & STC.temp)) && sc.func.setUnsafe()) { if (!gag) @@ -1580,3 +1597,55 @@ else } } + +/********************************************** + * Have some variables that are maybescopes that were + * assigned values from other maybescope variables. + * Now that semantic analysis of the function is + * complete, we can finalize this by turning off + * maybescope for array elements that cannot be scope. + * + * `va` `v` => `va` `v` + * maybe maybe => scope scope + * scope scope => scope scope + * scope maybe => scope scope + * maybe scope => scope scope + * - - => - - + * - maybe => - - + * - scope => error + * maybe - => scope - + * scope - => scope - + * Params: + * array = array of variables that were assigned to from maybescope variables + */ +void eliminateMaybeScopes(VarDeclaration[] array) +{ + enum log = false; + if (log) printf("eliminateMaybeScopes()\n"); + bool changes; + do + { + changes = false; + foreach (va; array) + { + if (log) printf(" va = %s\n", va.toChars()); + if (!(va.storage_class & (STC.maybescope | STC.scope_))) + { + if (va.maybes) + { + foreach (v; *va.maybes) + { + if (log) printf(" v = %s\n", v.toChars()); + if (v.storage_class & STC.maybescope) + { + // v cannot be scope since it is assigned to a non-scope va + notMaybeScope(v); + changes = true; + } + } + } + } + } + } while (changes); +} + diff --git a/src/dmd/semantic3.d b/src/dmd/semantic3.d index 6f9ea01c8b36..89728aafa6aa 100644 --- a/src/dmd/semantic3.d +++ b/src/dmd/semantic3.d @@ -1178,6 +1178,37 @@ private extern(C++) final class Semantic3Visitor : Visitor funcdecl.flags &= ~FUNCFLAG.inferScope; + // Eliminate maybescope's + { + // Create and fill array[] with maybe candidates from the `this` and the parameters + VarDeclaration[] array = void; + + VarDeclaration[10] tmp = void; + size_t dim = (funcdecl.vthis !is null) + (funcdecl.parameters ? funcdecl.parameters.dim : 0); + if (dim <= tmp.length) + array = tmp[0 .. dim]; + else + { + auto ptr = cast(VarDeclaration*)mem.xmalloc(dim * VarDeclaration.sizeof); + array = ptr[0 .. dim]; + } + size_t n = 0; + if (funcdecl.vthis) + array[n++] = funcdecl.vthis; + if (funcdecl.parameters) + { + foreach (v; *funcdecl.parameters) + { + array[n++] = v; + } + } + + eliminateMaybeScopes(array[0 .. n]); + + if (dim > tmp.length) + mem.xfree(array.ptr); + } + // Infer STC.scope_ if (funcdecl.parameters && !funcdecl.errors) { diff --git a/test/fail_compilation/retscope6.d b/test/fail_compilation/retscope6.d index 67847109487e..eafd67bcfdfe 100644 --- a/test/fail_compilation/retscope6.d +++ b/test/fail_compilation/retscope6.d @@ -21,3 +21,120 @@ int* test() @safe arr[0] ~= &i; return arr[0][0]; } + +/* TEST_OUTPUT: +--- +fail_compilation/retscope6.d(7034): Error: reference to local variable `i` assigned to non-scope parameter `_param_1` calling retscope6.S.emplace!(int*).emplace +fail_compilation/retscope6.d(7035): Error: reference to local variable `i` assigned to non-scope parameter `_param_0` calling retscope6.S.emplace2!(int*).emplace2 +fail_compilation/retscope6.d(7024): Error: scope variable `_param_2` assigned to `s` with longer lifetime +fail_compilation/retscope6.d(7025): Error: scope variable `_param_2` assigned to `t` with longer lifetime +fail_compilation/retscope6.d(7037): Error: template instance `retscope6.S.emplace4!(int*)` error instantiating +--- +*/ + +#line 7000 + +alias T = int*; + +struct S +{ + T payload; + + static void emplace(Args...)(ref S s, Args args) @safe + { + s.payload = args[0]; + } + + void emplace2(Args...)(Args args) @safe + { + payload = args[0]; + } + + static void emplace3(Args...)(S s, Args args) @safe + { + s.payload = args[0]; + } + + static void emplace4(Args...)(scope ref S s, scope out S t, scope Args args) @safe + { + s.payload = args[0]; + t.payload = args[0]; + } + +} + +void foo() @safe +{ + S s; + int i; + s.emplace(s, &i); + s.emplace2(&i); + s.emplace3(s, &i); + s.emplace4(s, s, &i); +} + + +/* TEST_OUTPUT: +--- +fail_compilation/retscope6.d(8016): Error: reference to local variable `i` assigned to non-scope parameter `s` calling retscope6.frank!().frank +fail_compilation/retscope6.d(8031): Error: reference to local variable `i` assigned to non-scope parameter `p` calling retscope6.betty!().betty +fail_compilation/retscope6.d(8031): Error: reference to local variable `j` assigned to non-scope parameter `q` calling retscope6.betty!().betty +fail_compilation/retscope6.d(8048): Error: reference to local variable `j` assigned to non-scope parameter `q` calling retscope6.archie!().archie +--- +*/ + +// https://issues.dlang.org/show_bug.cgi?id=19035 + +#line 8000 +@safe +{ + +void escape(int*); + +/**********************/ + +void frank()(ref scope int* p, int* s) +{ + p = s; // should error here +} + +void testfrankly() +{ + int* p; + int i; + frank(p, &i); +} + +/**********************/ + +void betty()(int* p, int* q) +{ + p = q; + escape(p); +} + +void testbetty() +{ + int i; + int j; + betty(&i, &j); // should error on i and j +} + +/**********************/ + +void archie()(int* p, int* q, int* r) +{ + p = q; + r = p; + escape(q); +} + +void testarchie() +{ + int i; + int j; + int k; + archie(&i, &j, &k); // should error on j +} + +}