1 /**
2  * Most of the logic to implement scoped pointers and scoped references is here.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/escape.d, _escape.d)
8  * Documentation:  https://dlang.org/phobos/dmd_escape.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/escape.d
10  */
11 
12 module dmd.escape;
13 
14 import core.stdc.stdio : printf;
15 import core.stdc.stdlib;
16 import core.stdc.string;
17 
18 import dmd.root.rmem;
19 
20 import dmd.aggregate;
21 import dmd.astenums;
22 import dmd.declaration;
23 import dmd.dscope;
24 import dmd.dsymbol;
25 import dmd.errors;
26 import dmd.expression;
27 import dmd.func;
28 import dmd.globals;
29 import dmd.id;
30 import dmd.identifier;
31 import dmd.init;
32 import dmd.location;
33 import dmd.mtype;
34 import dmd.printast;
35 import dmd.root.rootobject;
36 import dmd.tokens;
37 import dmd.visitor;
38 import dmd.arraytypes;
39 
40 /// Groups global state for escape checking together
41 package(dmd) struct EscapeState
42 {
43     // Maps `sequenceNumber` of a `VarDeclaration` to an object that contains the
44     // reason it failed to infer `scope`
45     // https://issues.dlang.org/show_bug.cgi?id=23295
46     private __gshared RootObject[int] scopeInferFailure;
47 
48     /// Called by `initDMD` / `deinitializeDMD` to reset global state
49     static void reset()
50     {
51         scopeInferFailure = null;
52     }
53 }
54 
55 /******************************************************
56  * Checks memory objects passed to a function.
57  * Checks that if a memory object is passed by ref or by pointer,
58  * all of the refs or pointers are const, or there is only one mutable
59  * ref or pointer to it.
60  * References:
61  *      DIP 1021
62  * Params:
63  *      sc = used to determine current function and module
64  *      fd = function being called
65  *      tf = fd's type
66  *      ethis = if not null, the `this` pointer
67  *      arguments = actual arguments to function
68  *      gag = do not print error messages
69  * Returns:
70  *      `true` if error
71  */
72 bool checkMutableArguments(Scope* sc, FuncDeclaration fd, TypeFunction tf,
73     Expression ethis, Expressions* arguments, bool gag)
74 {
75     enum log = false;
76     if (log) printf("[%s] checkMutableArguments, fd: `%s`\n", fd.loc.toChars(), fd.toChars());
77     if (log && ethis) printf("ethis: `%s`\n", ethis.toChars());
78     bool errors = false;
79 
80     /* Outer variable references are treated as if they are extra arguments
81      * passed by ref to the function (which they essentially are via the static link).
82      */
83     VarDeclaration[] outerVars = fd ? fd.outerVars[] : null;
84 
85     const len = arguments.length + (ethis !is null) + outerVars.length;
86     if (len <= 1)
87         return errors;
88 
89     struct EscapeBy
90     {
91         EscapeByResults er;
92         Parameter param;        // null if no Parameter for this argument
93         bool isMutable;         // true if reference to mutable
94     }
95 
96     /* Store escapeBy as static data escapeByStorage so we can keep reusing the same
97      * arrays rather than reallocating them.
98      */
99     __gshared EscapeBy[] escapeByStorage;
100     auto escapeBy = escapeByStorage;
101     if (escapeBy.length < len)
102     {
103         auto newPtr = cast(EscapeBy*)mem.xrealloc(escapeBy.ptr, len * EscapeBy.sizeof);
104         // Clear the new section
105         memset(newPtr + escapeBy.length, 0, (len - escapeBy.length) * EscapeBy.sizeof);
106         escapeBy = newPtr[0 .. len];
107         escapeByStorage = escapeBy;
108     }
109     else
110         escapeBy = escapeBy[0 .. len];
111 
112     const paramLength = tf.parameterList.length;
113 
114     // Fill in escapeBy[] with arguments[], ethis, and outerVars[]
115     foreach (const i, ref eb; escapeBy)
116     {
117         bool refs;
118         Expression arg;
119         if (i < arguments.length)
120         {
121             arg = (*arguments)[i];
122             if (i < paramLength)
123             {
124                 eb.param = tf.parameterList[i];
125                 refs = eb.param.isReference();
126                 eb.isMutable = eb.param.isReferenceToMutable(arg.type);
127             }
128             else
129             {
130                 eb.param = null;
131                 refs = false;
132                 eb.isMutable = arg.type.isReferenceToMutable();
133             }
134         }
135         else if (ethis)
136         {
137             /* ethis is passed by value if a class reference,
138              * by ref if a struct value
139              */
140             eb.param = null;
141             arg = ethis;
142             auto ad = fd.isThis();
143             assert(ad);
144             assert(ethis);
145             if (ad.isClassDeclaration())
146             {
147                 refs = false;
148                 eb.isMutable = arg.type.isReferenceToMutable();
149             }
150             else
151             {
152                 assert(ad.isStructDeclaration());
153                 refs = true;
154                 eb.isMutable = arg.type.isMutable();
155             }
156         }
157         else
158         {
159             // outer variables are passed by ref
160             eb.param = null;
161             refs = true;
162             auto var = outerVars[i - (len - outerVars.length)];
163             eb.isMutable = var.type.isMutable();
164             eb.er.pushRef(var, false);
165             continue;
166         }
167 
168         if (refs)
169             escapeByRef(arg, &eb.er);
170         else
171             escapeByValue(arg, &eb.er);
172     }
173 
174     void checkOnePair(size_t i, ref EscapeBy eb, ref EscapeBy eb2,
175                       VarDeclaration v, VarDeclaration v2, bool of)
176     {
177         if (log) printf("v2: `%s`\n", v2.toChars());
178         if (v2 != v)
179             return;
180         //printf("v %d v2 %d\n", eb.isMutable, eb2.isMutable);
181         if (!(eb.isMutable || eb2.isMutable))
182             return;
183 
184         if (!tf.islive && !(global.params.useDIP1000 == FeatureState.enabled && sc.func.setUnsafe()))
185             return;
186 
187         if (!gag)
188         {
189             // int i; funcThatEscapes(ref int i);
190             // funcThatEscapes(i); // error escaping reference _to_ `i`
191             // int* j; funcThatEscapes2(int* j);
192             // funcThatEscapes2(j); // error escaping reference _of_ `i`
193             const(char)* referenceVerb = of ? "of" : "to";
194             const(char)* msg = eb.isMutable && eb2.isMutable
195                                 ? "more than one mutable reference %s `%s` in arguments to `%s()`"
196                                 : "mutable and const references %s `%s` in arguments to `%s()`";
197             error((*arguments)[i].loc, msg,
198                   referenceVerb,
199                   v.toChars(),
200                   fd ? fd.toPrettyChars() : "indirectly");
201         }
202         errors = true;
203     }
204 
205     void escape(size_t i, ref EscapeBy eb, bool byval)
206     {
207         foreach (VarDeclaration v; byval ? eb.er.byvalue : eb.er.byref)
208         {
209             if (log)
210             {
211                 const(char)* by = byval ? "byval" : "byref";
212                 printf("%s %s\n", by, v.toChars());
213             }
214             if (byval && !v.type.hasPointers())
215                 continue;
216             foreach (ref eb2; escapeBy[i + 1 .. $])
217             {
218                 foreach (VarDeclaration v2; byval ? eb2.er.byvalue : eb2.er.byref)
219                 {
220                     checkOnePair(i, eb, eb2, v, v2, byval);
221                 }
222             }
223         }
224     }
225     foreach (const i, ref eb; escapeBy[0 .. $ - 1])
226     {
227         escape(i, eb, true);
228         escape(i, eb, false);
229     }
230 
231     /* Reset the arrays in escapeBy[] so we can reuse them next time through
232      */
233     foreach (ref eb; escapeBy)
234     {
235         eb.er.reset();
236     }
237 
238     return errors;
239 }
240 
241 /******************************************
242  * Array literal is going to be allocated on the GC heap.
243  * Check its elements to see if any would escape by going on the heap.
244  * Params:
245  *      sc = used to determine current function and module
246  *      ae = array literal expression
247  *      gag = do not print error messages
248  * Returns:
249  *      `true` if any elements escaped
250  */
251 bool checkArrayLiteralEscape(Scope *sc, ArrayLiteralExp ae, bool gag)
252 {
253     bool errors;
254     if (ae.basis)
255         errors = checkNewEscape(sc, ae.basis, gag);
256     foreach (ex; *ae.elements)
257     {
258         if (ex)
259             errors |= checkNewEscape(sc, ex, gag);
260     }
261     return errors;
262 }
263 
264 /******************************************
265  * Associative array literal is going to be allocated on the GC heap.
266  * Check its elements to see if any would escape by going on the heap.
267  * Params:
268  *      sc = used to determine current function and module
269  *      ae = associative array literal expression
270  *      gag = do not print error messages
271  * Returns:
272  *      `true` if any elements escaped
273  */
274 bool checkAssocArrayLiteralEscape(Scope *sc, AssocArrayLiteralExp ae, bool gag)
275 {
276     bool errors;
277     foreach (ex; *ae.keys)
278     {
279         if (ex)
280             errors |= checkNewEscape(sc, ex, gag);
281     }
282     foreach (ex; *ae.values)
283     {
284         if (ex)
285             errors |= checkNewEscape(sc, ex, gag);
286     }
287     return errors;
288 }
289 
290 /**
291  * A `scope` variable was assigned to non-scope parameter `v`.
292  * If applicable, print why the parameter was not inferred `scope`.
293  *
294  * Params:
295  *    printFunc = error/deprecation print function to use
296  *    v = parameter that was not inferred
297  *    recursionLimit = recursion limit for printing the reason
298  */
299 void printScopeFailure(E)(E printFunc, VarDeclaration v, int recursionLimit)
300 {
301     recursionLimit--;
302     if (recursionLimit < 0 || !v)
303         return;
304 
305     if (RootObject* o = v.sequenceNumber in EscapeState.scopeInferFailure)
306     {
307         switch ((*o).dyncast())
308         {
309             case DYNCAST.expression:
310                 Expression e = cast(Expression) *o;
311                 printFunc(e.loc, "which is not `scope` because of `%s`", e.toChars());
312                 break;
313             case DYNCAST.dsymbol:
314                 VarDeclaration v1 = cast(VarDeclaration) *o;
315                 printFunc(v1.loc, "which is assigned to non-scope parameter `%s`", v1.toChars());
316                 printScopeFailure(printFunc, v1, recursionLimit);
317                 break;
318             default:
319                 assert(0);
320         }
321     }
322 }
323 
324 /****************************************
325  * Function parameter `par` is being initialized to `arg`,
326  * and `par` may escape.
327  * Detect if scoped values can escape this way.
328  * Print error messages when these are detected.
329  * Params:
330  *      sc = used to determine current function and module
331  *      fdc = function being called, `null` if called indirectly
332  *      parId = name of function parameter for error messages
333  *      vPar = `VarDeclaration` corresponding to `par`
334  *      parStc = storage classes of function parameter (may have added `scope` from `pure`)
335  *      arg = initializer for param
336  *      assertmsg = true if the parameter is the msg argument to assert(bool, msg).
337  *      gag = do not print error messages
338  * Returns:
339  *      `true` if pointers to the stack can escape via assignment
340  */
341 bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier parId, VarDeclaration vPar, STC parStc, Expression arg, bool assertmsg, bool gag)
342 {
343     enum log = false;
344     if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n",
345         arg ? arg.toChars() : "null",
346         parId ? parId.toChars() : "null");
347     //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers());
348 
349     if (!arg.type.hasPointers())
350         return false;
351 
352     EscapeByResults er;
353 
354     escapeByValue(arg, &er);
355 
356     if (parStc & STC.scope_)
357     {
358         // These errors only apply to non-scope parameters
359         // When the paraneter is `scope`, only `checkScopeVarAddr` on `er.byref` is needed
360         er.byfunc.setDim(0);
361         er.byvalue.setDim(0);
362         er.byexp.setDim(0);
363     }
364 
365     if (!er.byref.length && !er.byvalue.length && !er.byfunc.length && !er.byexp.length)
366         return false;
367 
368     bool result = false;
369 
370     /* 'v' is assigned unsafely to 'par'
371      */
372     void unsafeAssign(string desc)(VarDeclaration v)
373     {
374         if (assertmsg)
375         {
376             result |= sc.setUnsafeDIP1000(gag, arg.loc,
377                 desc ~ " `%s` assigned to non-scope parameter calling `assert()`", v);
378             return;
379         }
380         const(char)* msg =
381             (fdc &&  parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s` calling `%s`") :
382             (fdc && !parId) ? (desc ~ " `%s` assigned to non-scope anonymous parameter calling `%s`") :
383             (!fdc && parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s`") :
384             (desc ~ " `%s` assigned to non-scope anonymous parameter");
385 
386         if (sc.setUnsafeDIP1000(gag, arg.loc, msg, v, parId ? parId : fdc, fdc))
387         {
388             result = true;
389             printScopeFailure(previewSupplementalFunc(sc.isDeprecated(), global.params.useDIP1000), vPar, 10);
390         }
391     }
392 
393     foreach (VarDeclaration v; er.byvalue)
394     {
395         if (log) printf("byvalue %s\n", v.toChars());
396         if (v.isDataseg())
397             continue;
398 
399         Dsymbol p = v.toParent2();
400 
401         notMaybeScope(v, vPar);
402 
403         if (v.isScope())
404         {
405             unsafeAssign!"scope variable"(v);
406         }
407         else if (v.isTypesafeVariadicArray && p == sc.func)
408         {
409             unsafeAssign!"variadic variable"(v);
410         }
411     }
412 
413     foreach (VarDeclaration v; er.byref)
414     {
415         if (log) printf("byref %s\n", v.toChars());
416         if (v.isDataseg())
417             continue;
418 
419         Dsymbol p = v.toParent2();
420 
421         notMaybeScope(v, arg);
422         if (checkScopeVarAddr(v, arg, sc, gag))
423         {
424             result = true;
425             continue;
426         }
427 
428         if (p == sc.func && !(parStc & STC.scope_))
429         {
430             unsafeAssign!"reference to local variable"(v);
431             continue;
432         }
433     }
434 
435     foreach (FuncDeclaration fd; er.byfunc)
436     {
437         //printf("fd = %s, %d\n", fd.toChars(), fd.tookAddressOf);
438         VarDeclarations vars;
439         findAllOuterAccessedVariables(fd, &vars);
440 
441         foreach (v; vars)
442         {
443             //printf("v = %s\n", v.toChars());
444             assert(!v.isDataseg());     // these are not put in the closureVars[]
445 
446             Dsymbol p = v.toParent2();
447 
448             notMaybeScope(v, arg);
449 
450             if ((v.isReference() || v.isScope()) && p == sc.func)
451             {
452                 unsafeAssign!"reference to local"(v);
453                 continue;
454             }
455         }
456     }
457 
458     if (!sc.func)
459         return result;
460 
461     foreach (Expression ee; er.byexp)
462     {
463         const(char)* msg = parId ?
464             "reference to stack allocated value returned by `%s` assigned to non-scope parameter `%s`" :
465             "reference to stack allocated value returned by `%s` assigned to non-scope anonymous parameter";
466 
467         result |= sc.setUnsafeDIP1000(gag, ee.loc, msg, ee, parId);
468     }
469 
470     return result;
471 }
472 
473 /*****************************************************
474  * Function argument initializes a `return` parameter,
475  * and that parameter gets assigned to `firstArg`.
476  * Essentially, treat as `firstArg = arg;`
477  * Params:
478  *      sc = used to determine current function and module
479  *      firstArg = `ref` argument through which `arg` may be assigned
480  *      arg = initializer for parameter
481  *      param = parameter declaration corresponding to `arg`
482  *      gag = do not print error messages
483  * Returns:
484  *      `true` if assignment to `firstArg` would cause an error
485  */
486 bool checkParamArgumentReturn(Scope* sc, Expression firstArg, Expression arg, Parameter param, bool gag)
487 {
488     enum log = false;
489     if (log) printf("checkParamArgumentReturn(firstArg: %s arg: %s)\n",
490         firstArg.toChars(), arg.toChars());
491     //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers());
492 
493     if (!(param.storageClass & STC.return_))
494         return false;
495 
496     if (!arg.type.hasPointers() && !param.isReference())
497         return false;
498 
499     // `byRef` needed for `assign(ref int* x, ref int i) {x = &i};`
500     // Note: taking address of scope pointer is not allowed
501     // `assign(ref int** x, return ref scope int* i) {x = &i};`
502     // Thus no return ref/return scope ambiguity here
503     const byRef = param.isReference() && !(param.storageClass & STC.scope_)
504         && !(param.storageClass & STC.returnScope); // fixme: it's possible to infer returnScope without scope with vaIsFirstRef
505 
506     scope e = new AssignExp(arg.loc, firstArg, arg);
507     return checkAssignEscape(sc, e, gag, byRef);
508 }
509 
510 /*****************************************************
511  * Check struct constructor of the form `s.this(args)`, by
512  * checking each `return` parameter to see if it gets
513  * assigned to `s`.
514  * Params:
515  *      sc = used to determine current function and module
516  *      ce = constructor call of the form `s.this(args)`
517  *      gag = do not print error messages
518  * Returns:
519  *      `true` if construction would cause an escaping reference error
520  */
521 bool checkConstructorEscape(Scope* sc, CallExp ce, bool gag)
522 {
523     enum log = false;
524     if (log) printf("checkConstructorEscape(%s, %s)\n", ce.toChars(), ce.type.toChars());
525     Type tthis = ce.type.toBasetype();
526     assert(tthis.ty == Tstruct);
527     if (!tthis.hasPointers())
528         return false;
529 
530     if (!ce.arguments && ce.arguments.length)
531         return false;
532 
533     DotVarExp dve = ce.e1.isDotVarExp();
534     CtorDeclaration ctor = dve.var.isCtorDeclaration();
535     TypeFunction tf = ctor.type.isTypeFunction();
536 
537     const nparams = tf.parameterList.length;
538     const n = ce.arguments.length;
539 
540     // j=1 if _arguments[] is first argument
541     const j = tf.isDstyleVariadic();
542 
543     /* Attempt to assign each `return` arg to the `this` reference
544      */
545     foreach (const i; 0 .. n)
546     {
547         Expression arg = (*ce.arguments)[i];
548         //printf("\targ[%d]: %s\n", i, arg.toChars());
549 
550         if (i - j < nparams && i >= j)
551         {
552             Parameter p = tf.parameterList[i - j];
553             if (checkParamArgumentReturn(sc, dve.e1, arg, p, gag))
554                 return true;
555         }
556     }
557 
558     return false;
559 }
560 
561 /// How a `return` parameter escapes its pointer value
562 enum ReturnParamDest
563 {
564     returnVal, /// through return statement: `return x`
565     this_,     /// assigned to a struct instance: `this.x = x`
566     firstArg,  /// assigned to first argument: `firstArg = x`
567 }
568 
569 /****************************************
570  * Find out if instead of returning a `return` parameter via a return statement,
571  * it is returned via assignment to either `this` or the first parameter.
572  *
573  * This works the same as returning the value via a return statement.
574  * Although the first argument must be `ref`, it is not regarded as returning by `ref`.
575  *
576  * See_Also: https://dlang.org.spec/function.html#return-ref-parameters
577  *
578  * Params:
579  *   tf = function type
580  *   tthis = type of `this` parameter, or `null` if none
581  * Returns: What a `return` parameter should transfer the lifetime of the argument to
582  */
583 ReturnParamDest returnParamDest(TypeFunction tf, Type tthis)
584 {
585     assert(tf);
586     if (tf.isctor)
587         return ReturnParamDest.this_;
588 
589     if (!tf.nextOf() || (tf.nextOf().ty != Tvoid))
590         return ReturnParamDest.returnVal;
591 
592     if (tthis && tthis.toBasetype().ty == Tstruct) // class `this` is passed by value
593         return ReturnParamDest.this_;
594 
595     if (tf.parameterList.length > 0 && tf.parameterList[0].isReference)
596         return ReturnParamDest.firstArg;
597 
598     return ReturnParamDest.returnVal;
599 }
600 
601 /****************************************
602  * Given an `AssignExp`, determine if the lvalue will cause
603  * the contents of the rvalue to escape.
604  * Print error messages when these are detected.
605  * Infer `scope` attribute for the lvalue where possible, in order
606  * to eliminate the error.
607  * Params:
608  *      sc = used to determine current function and module
609  *      e = `AssignExp` or `CatAssignExp` to check for any pointers to the stack
610  *      gag = do not print error messages
611  *      byRef = set to `true` if `e1` of `e` gets assigned a reference to `e2`
612  * Returns:
613  *      `true` if pointers to the stack can escape via assignment
614  */
615 bool checkAssignEscape(Scope* sc, Expression e, bool gag, bool byRef)
616 {
617     enum log = false;
618     if (log) printf("checkAssignEscape(e: %s, byRef: %d)\n", e.toChars(), byRef);
619     if (e.op != EXP.assign && e.op != EXP.blit && e.op != EXP.construct &&
620         e.op != EXP.concatenateAssign && e.op != EXP.concatenateElemAssign && e.op != EXP.concatenateDcharAssign)
621         return false;
622     auto ae = cast(BinExp)e;
623     Expression e1 = ae.e1;
624     Expression e2 = ae.e2;
625     //printf("type = %s, %d\n", e1.type.toChars(), e1.type.hasPointers());
626 
627     if (!e1.type.hasPointers())
628         return false;
629 
630     if (e1.isSliceExp())
631     {
632         if (VarDeclaration va = expToVariable(e1))
633         {
634             if (!va.type.toBasetype().isTypeSArray() || // treat static array slice same as a variable
635                 !va.type.hasPointers())
636                 return false;
637         }
638         else
639             return false;
640     }
641 
642     /* The struct literal case can arise from the S(e2) constructor call:
643      *    return S(e2);
644      * and appears in this function as:
645      *    structLiteral = e2;
646      * Such an assignment does not necessarily remove scope-ness.
647      */
648     if (e1.isStructLiteralExp())
649         return false;
650 
651     VarDeclaration va = expToVariable(e1);
652     EscapeByResults er;
653 
654     if (byRef)
655         escapeByRef(e2, &er);
656     else
657         escapeByValue(e2, &er);
658 
659     if (!er.byref.length && !er.byvalue.length && !er.byfunc.length && !er.byexp.length)
660         return false;
661 
662 
663     if (va && e.op == EXP.concatenateElemAssign)
664     {
665         /* https://issues.dlang.org/show_bug.cgi?id=17842
666          * Draw an equivalence between:
667          *   *q = p;
668          * and:
669          *   va ~= e;
670          * since we are not assigning to va, but are assigning indirectly through va.
671          */
672         va = null;
673     }
674 
675     if (va && e1.isDotVarExp() && va.type.toBasetype().isTypeClass())
676     {
677         /* https://issues.dlang.org/show_bug.cgi?id=17949
678          * Draw an equivalence between:
679          *   *q = p;
680          * and:
681          *   va.field = e2;
682          * since we are not assigning to va, but are assigning indirectly through class reference va.
683          */
684         va = null;
685     }
686 
687     if (log && va) printf("va: %s\n", va.toChars());
688 
689     FuncDeclaration fd = sc.func;
690 
691 
692     // Determine if va is a `ref` parameter, so it has a lifetime exceding the function scope
693     const bool vaIsRef = va && va.isParameter() && va.isReference();
694     if (log && vaIsRef) printf("va is ref `%s`\n", va.toChars());
695 
696     // Determine if va is the first parameter, through which other 'return' parameters
697     // can be assigned.
698     bool vaIsFirstRef = false;
699     if (fd && fd.type)
700     {
701         final switch (returnParamDest(fd.type.isTypeFunction(), fd.vthis ? fd.vthis.type : null))
702         {
703             case ReturnParamDest.this_:
704                 vaIsFirstRef = va == fd.vthis;
705                 break;
706             case ReturnParamDest.firstArg:
707                 vaIsFirstRef = (*fd.parameters)[0] == va;
708                 break;
709             case ReturnParamDest.returnVal:
710                 break;
711         }
712     }
713     if (log && vaIsFirstRef) printf("va is first ref `%s`\n", va.toChars());
714 
715     bool result = false;
716     foreach (VarDeclaration v; er.byvalue)
717     {
718         if (log) printf("byvalue: %s\n", v.toChars());
719         if (v.isDataseg())
720             continue;
721 
722         if (v == va)
723             continue;
724 
725         Dsymbol p = v.toParent2();
726 
727         if (va && !vaIsRef && !va.isScope() && !v.isScope() &&
728             !v.isTypesafeVariadicArray && !va.isTypesafeVariadicArray &&
729             (va.isParameter() && va.maybeScope && v.isParameter() && v.maybeScope) &&
730             p == fd)
731         {
732             /* Add v to va's list of dependencies
733              */
734             va.addMaybe(v);
735             continue;
736         }
737 
738         if (vaIsFirstRef && p == fd)
739         {
740             inferReturn(fd, v, /*returnScope:*/ true);
741         }
742 
743         if (!(va && va.isScope()) || vaIsRef)
744             notMaybeScope(v, e);
745 
746         if (v.isScope())
747         {
748             if (vaIsFirstRef && v.isParameter() && v.isReturn())
749             {
750                 // va=v, where v is `return scope`
751                 if (inferScope(va))
752                     continue;
753             }
754 
755             // If va's lifetime encloses v's, then error
756             if (EnclosedBy eb = va.enclosesLifetimeOf(v))
757             {
758                 const(char)* msg;
759                 final switch (eb)
760                 {
761                     case EnclosedBy.none: assert(0);
762                     case EnclosedBy.returnScope:
763                         msg = "scope variable `%s` assigned to return scope `%s`";
764                         break;
765                     case EnclosedBy.longerScope:
766                         if (v.storage_class & STC.temp)
767                             continue;
768                         msg = "scope variable `%s` assigned to `%s` with longer lifetime";
769                         break;
770                     case EnclosedBy.refVar:
771                         msg = "scope variable `%s` assigned to `ref` variable `%s` with longer lifetime";
772                         break;
773                     case EnclosedBy.global:
774                         msg = "scope variable `%s` assigned to global variable `%s`";
775                         break;
776                 }
777 
778                 if (sc.setUnsafeDIP1000(gag, ae.loc, msg, v, va))
779                 {
780                     result = true;
781                     continue;
782                 }
783             }
784 
785             // v = scope, va should be scope as well
786             const vaWasScope = va && va.isScope();
787             if (inferScope(va))
788             {
789                 // In case of `scope local = returnScopeParam`, do not infer return scope for `x`
790                 if (!vaWasScope && v.isReturn() && !va.isReturn())
791                 {
792                     if (log) printf("infer return for %s\n", va.toChars());
793                     va.storage_class |= STC.return_ | STC.returninferred;
794 
795                     // Added "return scope" so don't confuse it with "return ref"
796                     if (isRefReturnScope(va.storage_class))
797                         va.storage_class |= STC.returnScope;
798                 }
799                 continue;
800             }
801             result |= sc.setUnsafeDIP1000(gag, ae.loc, "scope variable `%s` assigned to non-scope `%s`", v, e1);
802         }
803         else if (v.isTypesafeVariadicArray && p == fd)
804         {
805             if (inferScope(va))
806                 continue;
807             result |= sc.setUnsafeDIP1000(gag, ae.loc, "variadic variable `%s` assigned to non-scope `%s`", v, e1);
808         }
809         else
810         {
811             /* v is not 'scope', and we didn't check the scope of where we assigned it to.
812              * It may escape via that assignment, therefore, v can never be 'scope'.
813              */
814             //printf("no infer for %s in %s, %d\n", v.toChars(), fd.ident.toChars(), __LINE__);
815             doNotInferScope(v, e);
816         }
817     }
818 
819     foreach (VarDeclaration v; er.byref)
820     {
821         if (log) printf("byref: %s\n", v.toChars());
822         if (v.isDataseg())
823             continue;
824 
825         if (checkScopeVarAddr(v, ae, sc, gag))
826         {
827             result = true;
828             continue;
829         }
830 
831         if (va && va.isScope() && !v.isReference())
832         {
833             if (!va.isReturn())
834             {
835                 va.doNotInferReturn = true;
836             }
837             else
838             {
839                 result |= sc.setUnsafeDIP1000(gag, ae.loc,
840                     "address of local variable `%s` assigned to return scope `%s`", v, va);
841             }
842         }
843 
844         Dsymbol p = v.toParent2();
845 
846         if (vaIsFirstRef && p == fd)
847         {
848             //if (log) printf("inferring 'return' for parameter %s in function %s\n", v.toChars(), fd.toChars());
849             inferReturn(fd, v, /*returnScope:*/ false);
850         }
851 
852         // If va's lifetime encloses v's, then error
853         if (va && !(vaIsFirstRef && v.isReturn()) && va.enclosesLifetimeOf(v))
854         {
855             if (sc.setUnsafeDIP1000(gag, ae.loc, "address of variable `%s` assigned to `%s` with longer lifetime", v, va))
856             {
857                 result = true;
858                 continue;
859             }
860         }
861 
862         if (!(va && va.isScope()))
863             notMaybeScope(v, e);
864 
865         if (p != sc.func)
866             continue;
867 
868         if (inferScope(va))
869         {
870             if (v.isReturn() && !va.isReturn())
871                 va.storage_class |= STC.return_ | STC.returninferred;
872             continue;
873         }
874         if (e1.op == EXP.structLiteral)
875             continue;
876 
877         result |= sc.setUnsafeDIP1000(gag, ae.loc, "reference to local variable `%s` assigned to non-scope `%s`", v, e1);
878     }
879 
880     foreach (FuncDeclaration func; er.byfunc)
881     {
882         if (log) printf("byfunc: %s, %d\n", func.toChars(), func.tookAddressOf);
883         VarDeclarations vars;
884         findAllOuterAccessedVariables(func, &vars);
885 
886         /* https://issues.dlang.org/show_bug.cgi?id=16037
887          * If assigning the address of a delegate to a scope variable,
888          * then uncount that address of. This is so it won't cause a
889          * closure to be allocated.
890          */
891         if (va && va.isScope() && !va.isReturn() && func.tookAddressOf)
892             --func.tookAddressOf;
893 
894         foreach (v; vars)
895         {
896             //printf("v = %s\n", v.toChars());
897             assert(!v.isDataseg());     // these are not put in the closureVars[]
898 
899             Dsymbol p = v.toParent2();
900 
901             if (!(va && va.isScope()))
902                 notMaybeScope(v, e);
903 
904             if (!(v.isReference() || v.isScope()) || p != fd)
905                 continue;
906 
907             if (va && !va.isDataseg() && (va.isScope() || va.maybeScope))
908             {
909                 /* Don't infer STC.scope_ for va, because then a closure
910                  * won't be generated for fd.
911                  */
912                 //if (!va.isScope())
913                     //va.storage_class |= STC.scope_ | STC.scopeinferred;
914                 continue;
915             }
916             result |= sc.setUnsafeDIP1000(gag, ae.loc,
917                 "reference to local `%s` assigned to non-scope `%s` in @safe code", v, e1);
918         }
919     }
920 
921     foreach (Expression ee; er.byexp)
922     {
923         if (log) printf("byexp: %s\n", ee.toChars());
924 
925         /* Do not allow slicing of a static array returned by a function
926          */
927         if (ee.op == EXP.call && ee.type.toBasetype().isTypeSArray() && e1.type.toBasetype().isTypeDArray() &&
928             !(va && va.storage_class & STC.temp))
929         {
930             if (!gag)
931                 deprecation(ee.loc, "slice of static array temporary returned by `%s` assigned to longer lived variable `%s`",
932                     ee.toChars(), e1.toChars());
933             //result = true;
934             continue;
935         }
936 
937         if (ee.op == EXP.call && ee.type.toBasetype().isTypeStruct() &&
938             (!va || !(va.storage_class & STC.temp) && !va.isScope()))
939         {
940             if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct temporary returned by `%s` assigned to longer lived variable `%s`", ee, e1))
941             {
942                 result = true;
943                 continue;
944             }
945         }
946 
947         if (ee.op == EXP.structLiteral &&
948             (!va || !(va.storage_class & STC.temp)))
949         {
950             if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct literal `%s` assigned to longer lived variable `%s`", ee, e1))
951             {
952                 result = true;
953                 continue;
954             }
955         }
956 
957         if (inferScope(va))
958             continue;
959 
960         result |= sc.setUnsafeDIP1000(gag, ee.loc,
961             "reference to stack allocated value returned by `%s` assigned to non-scope `%s`", ee, e1);
962     }
963 
964     return result;
965 }
966 
967 /************************************
968  * Detect cases where pointers to the stack can escape the
969  * lifetime of the stack frame when throwing `e`.
970  * Print error messages when these are detected.
971  * Params:
972  *      sc = used to determine current function and module
973  *      e = expression to check for any pointers to the stack
974  *      gag = do not print error messages
975  * Returns:
976  *      `true` if pointers to the stack can escape
977  */
978 bool checkThrowEscape(Scope* sc, Expression e, bool gag)
979 {
980     //printf("[%s] checkThrowEscape, e = %s\n", e.loc.toChars(), e.toChars());
981     EscapeByResults er;
982 
983     escapeByValue(e, &er);
984 
985     if (!er.byref.length && !er.byvalue.length && !er.byexp.length)
986         return false;
987 
988     bool result = false;
989     foreach (VarDeclaration v; er.byvalue)
990     {
991         //printf("byvalue %s\n", v.toChars());
992         if (v.isDataseg())
993             continue;
994 
995         if (v.isScope() && !v.iscatchvar)       // special case: allow catch var to be rethrown
996                                                 // despite being `scope`
997         {
998             // https://issues.dlang.org/show_bug.cgi?id=17029
999             result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be thrown", v);
1000             continue;
1001         }
1002         else
1003         {
1004             notMaybeScope(v, new ThrowExp(e.loc, e));
1005         }
1006     }
1007     return result;
1008 }
1009 
1010 /************************************
1011  * Detect cases where pointers to the stack can escape the
1012  * lifetime of the stack frame by being placed into a GC allocated object.
1013  * Print error messages when these are detected.
1014  * Params:
1015  *      sc = used to determine current function and module
1016  *      e = expression to check for any pointers to the stack
1017  *      gag = do not print error messages
1018  * Returns:
1019  *      `true` if pointers to the stack can escape
1020  */
1021 bool checkNewEscape(Scope* sc, Expression e, bool gag)
1022 {
1023     import dmd.globals: FeatureState;
1024     import dmd.errors: previewErrorFunc;
1025 
1026     //printf("[%s] checkNewEscape, e = %s\n", e.loc.toChars(), e.toChars());
1027     enum log = false;
1028     if (log) printf("[%s] checkNewEscape, e: `%s`\n", e.loc.toChars(), e.toChars());
1029     EscapeByResults er;
1030 
1031     escapeByValue(e, &er);
1032 
1033     if (!er.byref.length && !er.byvalue.length && !er.byexp.length)
1034         return false;
1035 
1036     bool result = false;
1037     foreach (VarDeclaration v; er.byvalue)
1038     {
1039         if (log) printf("byvalue `%s`\n", v.toChars());
1040         if (v.isDataseg())
1041             continue;
1042 
1043         Dsymbol p = v.toParent2();
1044 
1045         if (v.isScope())
1046         {
1047             if (
1048                 /* This case comes up when the ReturnStatement of a __foreachbody is
1049                  * checked for escapes by the caller of __foreachbody. Skip it.
1050                  *
1051                  * struct S { static int opApply(int delegate(S*) dg); }
1052                  * S* foo() {
1053                  *    foreach (S* s; S) // create __foreachbody for body of foreach
1054                  *        return s;     // s is inferred as 'scope' but incorrectly tested in foo()
1055                  *    return null; }
1056                  */
1057                 !(p.parent == sc.func))
1058             {
1059                 // https://issues.dlang.org/show_bug.cgi?id=20868
1060                 result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be copied into allocated memory", v);
1061                 continue;
1062             }
1063         }
1064         else if (v.isTypesafeVariadicArray && p == sc.func)
1065         {
1066             result |= sc.setUnsafeDIP1000(gag, e.loc,
1067                 "copying `%s` into allocated memory escapes a reference to variadic parameter `%s`", e, v);
1068         }
1069         else
1070         {
1071             //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__);
1072             notMaybeScope(v, e);
1073         }
1074     }
1075 
1076     foreach (VarDeclaration v; er.byref)
1077     {
1078         if (log) printf("byref `%s`\n", v.toChars());
1079 
1080         // 'featureState' tells us whether to emit an error or a deprecation,
1081         // depending on the flag passed to the CLI for DIP25 / DIP1000
1082         bool escapingRef(VarDeclaration v, FeatureState fs)
1083         {
1084             const(char)* msg = v.isParameter() ?
1085                 "copying `%s` into allocated memory escapes a reference to parameter `%s`" :
1086                 "copying `%s` into allocated memory escapes a reference to local variable `%s`";
1087             return sc.setUnsafePreview(fs, gag, e.loc, msg, e, v);
1088         }
1089 
1090         if (v.isDataseg())
1091             continue;
1092 
1093         Dsymbol p = v.toParent2();
1094 
1095         if (!v.isReference())
1096         {
1097             if (p == sc.func)
1098             {
1099                 result |= escapingRef(v, global.params.useDIP1000);
1100                 continue;
1101             }
1102         }
1103 
1104         /* Check for returning a ref variable by 'ref', but should be 'return ref'
1105          * Infer the addition of 'return', or set result to be the offending expression.
1106          */
1107         if (!v.isReference())
1108             continue;
1109 
1110         // https://dlang.org/spec/function.html#return-ref-parameters
1111         if (p == sc.func)
1112         {
1113             //printf("escaping reference to local ref variable %s\n", v.toChars());
1114             //printf("storage class = x%llx\n", v.storage_class);
1115             result |= escapingRef(v, global.params.useDIP25);
1116             continue;
1117         }
1118         // Don't need to be concerned if v's parent does not return a ref
1119         FuncDeclaration func = p.isFuncDeclaration();
1120         if (!func || !func.type)
1121             continue;
1122         if (auto tf = func.type.isTypeFunction())
1123         {
1124             if (!tf.isref)
1125                 continue;
1126 
1127             const(char)* msg = "storing reference to outer local variable `%s` into allocated memory causes it to escape";
1128             if (!gag)
1129             {
1130                 previewErrorFunc(sc.isDeprecated(), global.params.useDIP25)(e.loc, msg, v.toChars());
1131             }
1132 
1133             // If -preview=dip25 is used, the user wants an error
1134             // Otherwise, issue a deprecation
1135             result |= (global.params.useDIP25 == FeatureState.enabled);
1136         }
1137     }
1138 
1139     foreach (Expression ee; er.byexp)
1140     {
1141         if (log) printf("byexp %s\n", ee.toChars());
1142         if (!gag)
1143             error(ee.loc, "storing reference to stack allocated value returned by `%s` into allocated memory causes it to escape",
1144                   ee.toChars());
1145         result = true;
1146     }
1147 
1148     return result;
1149 }
1150 
1151 
1152 /************************************
1153  * Detect cases where pointers to the stack can escape the
1154  * lifetime of the stack frame by returning `e` by value.
1155  * Print error messages when these are detected.
1156  * Params:
1157  *      sc = used to determine current function and module
1158  *      e = expression to check for any pointers to the stack
1159  *      gag = do not print error messages
1160  * Returns:
1161  *      `true` if pointers to the stack can escape
1162  */
1163 bool checkReturnEscape(Scope* sc, Expression e, bool gag)
1164 {
1165     //printf("[%s] checkReturnEscape, e: %s\n", e.loc.toChars(), e.toChars());
1166     return checkReturnEscapeImpl(sc, e, false, gag);
1167 }
1168 
1169 /************************************
1170  * Detect cases where returning `e` by `ref` can result in a reference to the stack
1171  * being returned.
1172  * Print error messages when these are detected.
1173  * Params:
1174  *      sc = used to determine current function and module
1175  *      e = expression to check
1176  *      gag = do not print error messages
1177  * Returns:
1178  *      `true` if references to the stack can escape
1179  */
1180 bool checkReturnEscapeRef(Scope* sc, Expression e, bool gag)
1181 {
1182     version (none)
1183     {
1184         printf("[%s] checkReturnEscapeRef, e = %s\n", e.loc.toChars(), e.toChars());
1185         printf("current function %s\n", sc.func.toChars());
1186         printf("parent2 function %s\n", sc.func.toParent2().toChars());
1187     }
1188 
1189     return checkReturnEscapeImpl(sc, e, true, gag);
1190 }
1191 
1192 /***************************************
1193  * Implementation of checking for escapes in return expressions.
1194  * Params:
1195  *      sc = used to determine current function and module
1196  *      e = expression to check
1197  *      refs = `true`: escape by value, `false`: escape by `ref`
1198  *      gag = do not print error messages
1199  * Returns:
1200  *      `true` if references to the stack can escape
1201  */
1202 private bool checkReturnEscapeImpl(Scope* sc, Expression e, bool refs, bool gag)
1203 {
1204     enum log = false;
1205     if (log) printf("[%s] checkReturnEscapeImpl, refs: %d e: `%s`\n", e.loc.toChars(), refs, e.toChars());
1206     EscapeByResults er;
1207 
1208     if (refs)
1209         escapeByRef(e, &er);
1210     else
1211         escapeByValue(e, &er);
1212 
1213     if (!er.byref.length && !er.byvalue.length && !er.byexp.length)
1214         return false;
1215 
1216     bool result = false;
1217     foreach (VarDeclaration v; er.byvalue)
1218     {
1219         if (log) printf("byvalue `%s`\n", v.toChars());
1220         if (v.isDataseg())
1221             continue;
1222 
1223         const vsr = buildScopeRef(v.storage_class);
1224 
1225         Dsymbol p = v.toParent2();
1226 
1227         if (p == sc.func && inferReturn(sc.func, v, /*returnScope:*/ true))
1228         {
1229             continue;
1230         }
1231 
1232         if (v.isScope())
1233         {
1234             /* If `return scope` applies to v.
1235              */
1236             if (vsr == ScopeRef.ReturnScope ||
1237                 vsr == ScopeRef.Ref_ReturnScope)
1238             {
1239                 continue;
1240             }
1241 
1242             auto pfunc = p.isFuncDeclaration();
1243             if (pfunc &&
1244                 /* This case comes up when the ReturnStatement of a __foreachbody is
1245                  * checked for escapes by the caller of __foreachbody. Skip it.
1246                  *
1247                  * struct S { static int opApply(int delegate(S*) dg); }
1248                  * S* foo() {
1249                  *    foreach (S* s; S) // create __foreachbody for body of foreach
1250                  *        return s;     // s is inferred as 'scope' but incorrectly tested in foo()
1251                  *    return null; }
1252                  */
1253                 !(!refs && p.parent == sc.func && pfunc.fes) &&
1254                 /*
1255                  *  auto p(scope string s) {
1256                  *      string scfunc() { return s; }
1257                  *  }
1258                  */
1259                 !(!refs && sc.func.isFuncDeclaration().getLevel(pfunc, sc.intypeof) > 0)
1260                )
1261             {
1262                 if (v.isParameter() && !v.isReturn())
1263                 {
1264                     // https://issues.dlang.org/show_bug.cgi?id=23191
1265                     if (!gag)
1266                     {
1267                         previewErrorFunc(sc.isDeprecated(), global.params.useDIP1000)(e.loc,
1268                             "scope parameter `%s` may not be returned", v.toChars()
1269                         );
1270                         result = true;
1271                         continue;
1272                     }
1273                 }
1274                 else
1275                 {
1276                     // https://issues.dlang.org/show_bug.cgi?id=17029
1277                     result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be returned", v);
1278                     continue;
1279                 }
1280             }
1281         }
1282         else if (v.isTypesafeVariadicArray && p == sc.func)
1283         {
1284             if (!gag)
1285                 error(e.loc, "returning `%s` escapes a reference to variadic parameter `%s`", e.toChars(), v.toChars());
1286             result = false;
1287         }
1288         else
1289         {
1290             //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__);
1291             doNotInferScope(v, e);
1292         }
1293     }
1294 
1295     foreach (i, VarDeclaration v; er.byref[])
1296     {
1297         if (log)
1298         {
1299             printf("byref `%s` %s\n", v.toChars(), toChars(buildScopeRef(v.storage_class)));
1300         }
1301 
1302         // 'featureState' tells us whether to emit an error or a deprecation,
1303         // depending on the flag passed to the CLI for DIP25
1304         void escapingRef(VarDeclaration v, FeatureState featureState)
1305         {
1306             const(char)* msg = v.isParameter() ?
1307                 "returning `%s` escapes a reference to parameter `%s`" :
1308                 "returning `%s` escapes a reference to local variable `%s`";
1309 
1310             if (v.isParameter() && v.isReference())
1311             {
1312                 if (sc.setUnsafePreview(featureState, gag, e.loc, msg, e, v) ||
1313                     sc.func.isSafeBypassingInference())
1314                 {
1315                     result = true;
1316                     if (v.storage_class & STC.returnScope)
1317                     {
1318                         previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc,
1319                             "perhaps change the `return scope` into `scope return`");
1320                     }
1321                     else
1322                     {
1323                         const(char)* annotateKind = (v.ident is Id.This) ? "function" : "parameter";
1324                         previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc,
1325                             "perhaps annotate the %s with `return`", annotateKind);
1326                     }
1327                 }
1328             }
1329             else
1330             {
1331                 if (er.refRetRefTransition[i])
1332                 {
1333                     result |= sc.setUnsafeDIP1000(gag, e.loc, msg, e, v);
1334                 }
1335                 else
1336                 {
1337                     if (!gag)
1338                         previewErrorFunc(sc.isDeprecated(), featureState)(e.loc, msg, e.toChars(), v.toChars());
1339                     result = true;
1340                 }
1341             }
1342         }
1343 
1344         if (v.isDataseg())
1345             continue;
1346 
1347         const vsr = buildScopeRef(v.storage_class);
1348 
1349         Dsymbol p = v.toParent2();
1350 
1351         // https://issues.dlang.org/show_bug.cgi?id=19965
1352         if (!refs)
1353         {
1354             if (sc.func.vthis == v)
1355                 notMaybeScope(v, e);
1356 
1357             if (checkScopeVarAddr(v, e, sc, gag))
1358             {
1359                 result = true;
1360                 continue;
1361             }
1362         }
1363 
1364         if (!v.isReference())
1365         {
1366             if (p == sc.func)
1367             {
1368                 escapingRef(v, FeatureState.enabled);
1369                 continue;
1370             }
1371             FuncDeclaration fd = p.isFuncDeclaration();
1372             if (fd && sc.func.returnInprocess)
1373             {
1374                 /* Code like:
1375                  *   int x;
1376                  *   auto dg = () { return &x; }
1377                  * Making it:
1378                  *   auto dg = () return { return &x; }
1379                  * Because dg.ptr points to x, this is returning dt.ptr+offset
1380                  */
1381                 sc.func.storage_class |= STC.return_ | STC.returninferred;
1382             }
1383         }
1384 
1385         /* Check for returning a ref variable by 'ref', but should be 'return ref'
1386          * Infer the addition of 'return', or set result to be the offending expression.
1387          */
1388         if ((vsr == ScopeRef.Ref ||
1389              vsr == ScopeRef.RefScope ||
1390              vsr == ScopeRef.Ref_ReturnScope) &&
1391             !(v.storage_class & STC.foreach_))
1392         {
1393             if (p == sc.func && (vsr == ScopeRef.Ref || vsr == ScopeRef.RefScope) &&
1394                 inferReturn(sc.func, v, /*returnScope:*/ false))
1395             {
1396                 continue;
1397             }
1398             else
1399             {
1400                 // https://dlang.org/spec/function.html#return-ref-parameters
1401                 // Only look for errors if in module listed on command line
1402                 if (p == sc.func)
1403                 {
1404                     //printf("escaping reference to local ref variable %s\n", v.toChars());
1405                     //printf("storage class = x%llx\n", v.storage_class);
1406                     escapingRef(v, global.params.useDIP25);
1407                     continue;
1408                 }
1409                 // Don't need to be concerned if v's parent does not return a ref
1410                 FuncDeclaration fd = p.isFuncDeclaration();
1411                 if (fd && fd.type && fd.type.ty == Tfunction)
1412                 {
1413                     TypeFunction tf = fd.type.isTypeFunction();
1414                     if (tf.isref)
1415                     {
1416                         const(char)* msg = "escaping reference to outer local variable `%s`";
1417                         if (!gag)
1418                             previewErrorFunc(sc.isDeprecated(), global.params.useDIP25)(e.loc, msg, v.toChars());
1419                         result = true;
1420                         continue;
1421                     }
1422                 }
1423 
1424             }
1425         }
1426     }
1427 
1428     foreach (i, Expression ee; er.byexp[])
1429     {
1430         if (log) printf("byexp %s\n", ee.toChars());
1431         if (er.expRetRefTransition[i])
1432         {
1433             result |= sc.setUnsafeDIP1000(gag, ee.loc,
1434                 "escaping reference to stack allocated value returned by `%s`", ee);
1435         }
1436         else
1437         {
1438             if (!gag)
1439                 error(ee.loc, "escaping reference to stack allocated value returned by `%s`", ee.toChars());
1440             result = true;
1441         }
1442     }
1443     return result;
1444 }
1445 
1446 /***********************************
1447  * Infer `scope` for a variable
1448  *
1449  * Params:
1450  *      va = variable to infer scope for
1451  * Returns: `true` if succesful or already `scope`
1452  */
1453 bool inferScope(VarDeclaration va)
1454 {
1455     if (!va)
1456         return false;
1457     if (!va.isDataseg() && va.maybeScope && !va.isScope())
1458     {
1459         //printf("inferring scope for %s\n", va.toChars());
1460         va.maybeScope = false;
1461         va.storage_class |= STC.scope_ | STC.scopeinferred;
1462         return true;
1463     }
1464     return va.isScope();
1465 }
1466 
1467 /*************************************
1468  * Variable v needs to have 'return' inferred for it.
1469  * Params:
1470  *      fd = function that v is a parameter to
1471  *      v = parameter that needs to be STC.return_
1472  *      returnScope = infer `return scope` instead of `return ref`
1473  *
1474  * Returns: whether the inference on `v` was successful or `v` already was `return`
1475  */
1476 private bool inferReturn(FuncDeclaration fd, VarDeclaration v, bool returnScope)
1477 {
1478     if (v.isReturn())
1479         return !!(v.storage_class & STC.returnScope) == returnScope;
1480 
1481     if (!v.isParameter() || v.isTypesafeVariadicArray || (returnScope && v.doNotInferReturn))
1482         return false;
1483 
1484     if (!fd.returnInprocess)
1485         return false;
1486 
1487     if (returnScope && !(v.isScope() || v.maybeScope))
1488         return false;
1489 
1490     //printf("for function '%s' inferring 'return' for variable '%s', returnScope: %d\n", fd.toChars(), v.toChars(), returnScope);
1491     auto newStcs = STC.return_ | STC.returninferred | (returnScope ? STC.returnScope : 0);
1492     v.storage_class |= newStcs;
1493 
1494     if (v == fd.vthis)
1495     {
1496         /* v is the 'this' reference, so mark the function
1497          */
1498         fd.storage_class |= newStcs;
1499         if (auto tf = fd.type.isTypeFunction())
1500         {
1501             //printf("'this' too %p %s\n", tf, sc.func.toChars());
1502             tf.isreturnscope = returnScope;
1503             tf.isreturn = true;
1504             tf.isreturninferred = true;
1505         }
1506     }
1507     else
1508     {
1509         // Perform 'return' inference on parameter
1510         if (auto tf = fd.type.isTypeFunction())
1511         {
1512             foreach (i, p; tf.parameterList)
1513             {
1514                 if (p.ident == v.ident)
1515                 {
1516                     p.storageClass |= newStcs;
1517                     break;              // there can be only one
1518                 }
1519             }
1520         }
1521     }
1522     return true;
1523 }
1524 
1525 
1526 /****************************************
1527  * e is an expression to be returned by value, and that value contains pointers.
1528  * Walk e to determine which variables are possibly being
1529  * returned by value, such as:
1530  *      int* function(int* p) { return p; }
1531  * If e is a form of &p, determine which variables have content
1532  * which is being returned as ref, such as:
1533  *      int* function(int i) { return &i; }
1534  * Multiple variables can be inserted, because of expressions like this:
1535  *      int function(bool b, int i, int* p) { return b ? &i : p; }
1536  *
1537  * No side effects.
1538  *
1539  * Params:
1540  *      e = expression to be returned by value
1541  *      er = where to place collected data
1542  *      live = if @live semantics apply, i.e. expressions `p`, `*p`, `**p`, etc., all return `p`.
1543   *     retRefTransition = if `e` is returned through a `return ref scope` function call
1544  */
1545 void escapeByValue(Expression e, EscapeByResults* er, bool live = false, bool retRefTransition = false)
1546 {
1547     //printf("[%s] escapeByValue, e: %s\n", e.loc.toChars(), e.toChars());
1548 
1549     void visit(Expression e)
1550     {
1551     }
1552 
1553     void visitAddr(AddrExp e)
1554     {
1555         /* Taking the address of struct literal is normally not
1556          * allowed, but CTFE can generate one out of a new expression,
1557          * but it'll be placed in static data so no need to check it.
1558          */
1559         if (e.e1.op != EXP.structLiteral)
1560             escapeByRef(e.e1, er, live, retRefTransition);
1561     }
1562 
1563     void visitSymOff(SymOffExp e)
1564     {
1565         VarDeclaration v = e.var.isVarDeclaration();
1566         if (v)
1567             er.pushRef(v, retRefTransition);
1568     }
1569 
1570     void visitVar(VarExp e)
1571     {
1572         if (auto v = e.var.isVarDeclaration())
1573         {
1574             if (v.type.hasPointers() || // not tracking non-pointers
1575                 v.storage_class & STC.lazy_) // lazy variables are actually pointers
1576                 er.byvalue.push(v);
1577         }
1578     }
1579 
1580     void visitThis(ThisExp e)
1581     {
1582         if (e.var)
1583             er.byvalue.push(e.var);
1584     }
1585 
1586     void visitPtr(PtrExp e)
1587     {
1588         if (live && e.type.hasPointers())
1589             escapeByValue(e.e1, er, live, retRefTransition);
1590     }
1591 
1592     void visitDotVar(DotVarExp e)
1593     {
1594         auto t = e.e1.type.toBasetype();
1595         if (e.type.hasPointers() && (live || t.ty == Tstruct))
1596         {
1597             escapeByValue(e.e1, er, live, retRefTransition);
1598         }
1599     }
1600 
1601     void visitDelegate(DelegateExp e)
1602     {
1603         Type t = e.e1.type.toBasetype();
1604         if (t.ty == Tclass || t.ty == Tpointer)
1605             escapeByValue(e.e1, er, live, retRefTransition);
1606         else
1607             escapeByRef(e.e1, er, live, retRefTransition);
1608         er.byfunc.push(e.func);
1609     }
1610 
1611     void visitFunc(FuncExp e)
1612     {
1613         if (e.fd.tok == TOK.delegate_)
1614             er.byfunc.push(e.fd);
1615     }
1616 
1617     void visitTuple(TupleExp e)
1618     {
1619         assert(0); // should have been lowered by now
1620     }
1621 
1622     void visitArrayLiteral(ArrayLiteralExp e)
1623     {
1624         Type tb = e.type.toBasetype();
1625         if (tb.ty == Tsarray || tb.ty == Tarray)
1626         {
1627             if (e.basis)
1628                 escapeByValue(e.basis, er, live, retRefTransition);
1629             foreach (el; *e.elements)
1630             {
1631                 if (el)
1632                     escapeByValue(el, er, live, retRefTransition);
1633             }
1634         }
1635     }
1636 
1637     void visitStructLiteral(StructLiteralExp e)
1638     {
1639         if (e.elements)
1640         {
1641             foreach (ex; *e.elements)
1642             {
1643                 if (ex)
1644                     escapeByValue(ex, er, live, retRefTransition);
1645             }
1646         }
1647     }
1648 
1649     void visitNew(NewExp e)
1650     {
1651         Type tb = e.newtype.toBasetype();
1652         if (tb.ty == Tstruct && !e.member && e.arguments)
1653         {
1654             foreach (ex; *e.arguments)
1655             {
1656                 if (ex)
1657                     escapeByValue(ex, er, live, retRefTransition);
1658             }
1659         }
1660     }
1661 
1662     void visitCast(CastExp e)
1663     {
1664         if (!e.type.hasPointers())
1665             return;
1666         Type tb = e.type.toBasetype();
1667         if (tb.ty == Tarray && e.e1.type.toBasetype().ty == Tsarray)
1668         {
1669             escapeByRef(e.e1, er, live, retRefTransition);
1670         }
1671         else
1672             escapeByValue(e.e1, er, live, retRefTransition);
1673     }
1674 
1675     void visitSlice(SliceExp e)
1676     {
1677         if (auto ve = e.e1.isVarExp())
1678         {
1679             VarDeclaration v = ve.var.isVarDeclaration();
1680             Type tb = e.type.toBasetype();
1681             if (v)
1682             {
1683                 if (tb.ty == Tsarray)
1684                     return;
1685                 if (v.isTypesafeVariadicArray)
1686                 {
1687                     er.byvalue.push(v);
1688                     return;
1689                 }
1690             }
1691         }
1692         Type t1b = e.e1.type.toBasetype();
1693         if (t1b.ty == Tsarray)
1694         {
1695             Type tb = e.type.toBasetype();
1696             if (tb.ty != Tsarray)
1697                 escapeByRef(e.e1, er, live, retRefTransition);
1698         }
1699         else
1700             escapeByValue(e.e1, er, live, retRefTransition);
1701     }
1702 
1703     void visitIndex(IndexExp e)
1704     {
1705         if (e.e1.type.toBasetype().ty == Tsarray ||
1706             live && e.type.hasPointers())
1707         {
1708             escapeByValue(e.e1, er, live, retRefTransition);
1709         }
1710     }
1711 
1712     void visitBin(BinExp e)
1713     {
1714         Type tb = e.type.toBasetype();
1715         if (tb.ty == Tpointer)
1716         {
1717             escapeByValue(e.e1, er, live, retRefTransition);
1718             escapeByValue(e.e2, er, live, retRefTransition);
1719         }
1720     }
1721 
1722     void visitBinAssign(BinAssignExp e)
1723     {
1724         escapeByValue(e.e1, er, live, retRefTransition);
1725     }
1726 
1727     void visitAssign(AssignExp e)
1728     {
1729         escapeByValue(e.e1, er, live, retRefTransition);
1730     }
1731 
1732     void visitComma(CommaExp e)
1733     {
1734         escapeByValue(e.e2, er, live, retRefTransition);
1735     }
1736 
1737     void visitCond(CondExp e)
1738     {
1739         escapeByValue(e.e1, er, live, retRefTransition);
1740         escapeByValue(e.e2, er, live, retRefTransition);
1741     }
1742 
1743     void visitCall(CallExp e)
1744     {
1745         //printf("CallExp(): %s\n", e.toChars());
1746         /* Check each argument that is
1747          * passed as 'return scope'.
1748          */
1749         Type t1 = e.e1.type.toBasetype();
1750         TypeFunction tf;
1751         TypeDelegate dg;
1752         if (t1.ty == Tdelegate)
1753         {
1754             dg = t1.isTypeDelegate();
1755             tf = dg.next.isTypeFunction();
1756         }
1757         else if (t1.ty == Tfunction)
1758             tf = t1.isTypeFunction();
1759         else
1760             return;
1761 
1762         if (!e.type.hasPointers())
1763             return;
1764 
1765         if (e.arguments && e.arguments.length)
1766         {
1767             /* j=1 if _arguments[] is first argument,
1768              * skip it because it is not passed by ref
1769              */
1770             int j = tf.isDstyleVariadic();
1771             for (size_t i = j; i < e.arguments.length; ++i)
1772             {
1773                 Expression arg = (*e.arguments)[i];
1774                 size_t nparams = tf.parameterList.length;
1775                 if (i - j < nparams && i >= j)
1776                 {
1777                     Parameter p = tf.parameterList[i - j];
1778                     const stc = tf.parameterStorageClass(null, p);
1779                     ScopeRef psr = buildScopeRef(stc);
1780                     if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope)
1781                     {
1782                         if (tf.isref)
1783                         {
1784                             /* ignore `ref` on struct constructor return because
1785                              *   struct S { this(return scope int* q) { this.p = q; } int* p; }
1786                              * is different from:
1787                              *   ref char* front(return scope char** q) { return *q; }
1788                              * https://github.com/dlang/dmd/pull/14869
1789                              */
1790                             if (auto dve = e.e1.isDotVarExp())
1791                                 if (auto fd = dve.var.isFuncDeclaration())
1792                                     if (fd.isCtorDeclaration() && tf.next.toBasetype().isTypeStruct())
1793                                     {
1794                                         escapeByValue(arg, er, live, retRefTransition);
1795                                     }
1796                         }
1797                         else
1798                             escapeByValue(arg, er, live, retRefTransition);
1799                     }
1800                     else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope)
1801                     {
1802                         if (tf.isref)
1803                         {
1804                             /* Treat:
1805                              *   ref P foo(return ref P p)
1806                              * as:
1807                              *   p;
1808                              */
1809                             escapeByValue(arg, er, live, retRefTransition);
1810                         }
1811                         else
1812                             escapeByRef(arg, er, live, retRefTransition);
1813                     }
1814                 }
1815             }
1816         }
1817         // If 'this' is returned, check it too
1818         if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction)
1819         {
1820             DotVarExp dve = e.e1.isDotVarExp();
1821             FuncDeclaration fd = dve.var.isFuncDeclaration();
1822             if (fd && fd.isThis())
1823             {
1824                 /* Calling a non-static member function dve.var, which is returning `this`, and with dve.e1 representing `this`
1825                  */
1826 
1827                 /*****************************
1828                  * Concoct storage class for member function's implicit `this` parameter.
1829                  * Params:
1830                  *      fd = member function
1831                  * Returns:
1832                  *      storage class for fd's `this`
1833                  */
1834                 StorageClass getThisStorageClass(FuncDeclaration fd)
1835                 {
1836                     StorageClass stc;
1837                     auto tf = fd.type.toBasetype().isTypeFunction();
1838                     if (tf.isreturn)
1839                         stc |= STC.return_;
1840                     if (tf.isreturnscope)
1841                         stc |= STC.returnScope | STC.scope_;
1842                     auto ad = fd.isThis();
1843                     if (ad.isClassDeclaration() || tf.isScopeQual)
1844                         stc |= STC.scope_;
1845                     if (ad.isStructDeclaration())
1846                         stc |= STC.ref_;        // `this` for a struct member function is passed by `ref`
1847                     return stc;
1848                 }
1849 
1850                 const psr = buildScopeRef(getThisStorageClass(fd));
1851                 if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope)
1852                 {
1853                     if (!tf.isref || tf.isctor)
1854                         escapeByValue(dve.e1, er, live, retRefTransition);
1855                 }
1856                 else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope)
1857                 {
1858                     if (tf.isref)
1859                     {
1860                         /* Treat calling:
1861                          *   struct S { ref S foo() return; }
1862                          * as:
1863                          *   this;
1864                          */
1865                         escapeByValue(dve.e1, er, live, retRefTransition);
1866                     }
1867                     else
1868                         escapeByRef(dve.e1, er, live, psr == ScopeRef.ReturnRef_Scope);
1869                 }
1870             }
1871 
1872             // If it's also a nested function that is 'return scope'
1873             if (fd && fd.isNested())
1874             {
1875                 if (tf.isreturn && tf.isScopeQual)
1876                     er.pushExp(e, false);
1877             }
1878         }
1879 
1880         /* If returning the result of a delegate call, the .ptr
1881          * field of the delegate must be checked.
1882          */
1883         if (dg)
1884         {
1885             if (tf.isreturn)
1886                 escapeByValue(e.e1, er, live, retRefTransition);
1887         }
1888 
1889         /* If it's a nested function that is 'return scope'
1890          */
1891         if (auto ve = e.e1.isVarExp())
1892         {
1893             FuncDeclaration fd = ve.var.isFuncDeclaration();
1894             if (fd && fd.isNested())
1895             {
1896                 if (tf.isreturn && tf.isScopeQual)
1897                     er.pushExp(e, false);
1898             }
1899         }
1900     }
1901 
1902     switch (e.op)
1903     {
1904         case EXP.address: return visitAddr(e.isAddrExp());
1905         case EXP.symbolOffset: return visitSymOff(e.isSymOffExp());
1906         case EXP.variable: return visitVar(e.isVarExp());
1907         case EXP.this_: return visitThis(e.isThisExp());
1908         case EXP.star: return visitPtr(e.isPtrExp());
1909         case EXP.dotVariable: return visitDotVar(e.isDotVarExp());
1910         case EXP.delegate_: return visitDelegate(e.isDelegateExp());
1911         case EXP.function_: return visitFunc(e.isFuncExp());
1912         case EXP.tuple: return visitTuple(e.isTupleExp());
1913         case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp());
1914         case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp());
1915         case EXP.new_: return visitNew(e.isNewExp());
1916         case EXP.cast_: return visitCast(e.isCastExp());
1917         case EXP.slice: return visitSlice(e.isSliceExp());
1918         case EXP.index: return visitIndex(e.isIndexExp());
1919         case EXP.blit: return visitAssign(e.isBlitExp());
1920         case EXP.construct: return visitAssign(e.isConstructExp());
1921         case EXP.assign: return visitAssign(e.isAssignExp());
1922         case EXP.comma: return visitComma(e.isCommaExp());
1923         case EXP.question: return visitCond(e.isCondExp());
1924         case EXP.call: return visitCall(e.isCallExp());
1925         default:
1926             if (auto b = e.isBinExp())
1927                 return visitBin(b);
1928             if (auto ba = e.isBinAssignExp())
1929                 return visitBinAssign(ba);
1930             return visit(e);
1931     }
1932 }
1933 
1934 
1935 /****************************************
1936  * e is an expression to be returned by 'ref'.
1937  * Walk e to determine which variables are possibly being
1938  * returned by ref, such as:
1939  *      ref int function(int i) { return i; }
1940  * If e is a form of *p, determine which variables have content
1941  * which is being returned as ref, such as:
1942  *      ref int function(int* p) { return *p; }
1943  * Multiple variables can be inserted, because of expressions like this:
1944  *      ref int function(bool b, int i, int* p) { return b ? i : *p; }
1945  *
1946  * No side effects.
1947  *
1948  * Params:
1949  *      e = expression to be returned by 'ref'
1950  *      er = where to place collected data
1951  *      live = if @live semantics apply, i.e. expressions `p`, `*p`, `**p`, etc., all return `p`.
1952  *      retRefTransition = if `e` is returned through a `return ref scope` function call
1953  */
1954 void escapeByRef(Expression e, EscapeByResults* er, bool live = false, bool retRefTransition = false)
1955 {
1956     //printf("[%s] escapeByRef, e: %s, retRefTransition: %d\n", e.loc.toChars(), e.toChars(), retRefTransition);
1957     void visit(Expression e)
1958     {
1959     }
1960 
1961     void visitVar(VarExp e)
1962     {
1963         auto v = e.var.isVarDeclaration();
1964         if (v)
1965         {
1966             if (v.storage_class & STC.ref_ && v.storage_class & (STC.foreach_ | STC.temp) && v._init)
1967             {
1968                 /* If compiler generated ref temporary
1969                     *   (ref v = ex; ex)
1970                     * look at the initializer instead
1971                     */
1972                 if (ExpInitializer ez = v._init.isExpInitializer())
1973                 {
1974                     if (auto ce = ez.exp.isConstructExp())
1975                         escapeByRef(ce.e2, er, live, retRefTransition);
1976                     else
1977                         escapeByRef(ez.exp, er, live, retRefTransition);
1978                 }
1979             }
1980             else
1981                 er.pushRef(v, retRefTransition);
1982         }
1983     }
1984 
1985     void visitThis(ThisExp e)
1986     {
1987         if (e.var && e.var.toParent2().isFuncDeclaration().hasDualContext())
1988             escapeByValue(e, er, live, retRefTransition);
1989         else if (e.var)
1990             er.pushRef(e.var, retRefTransition);
1991     }
1992 
1993     void visitPtr(PtrExp e)
1994     {
1995         escapeByValue(e.e1, er, live, retRefTransition);
1996     }
1997 
1998     void visitIndex(IndexExp e)
1999     {
2000         Type tb = e.e1.type.toBasetype();
2001         if (auto ve = e.e1.isVarExp())
2002         {
2003             VarDeclaration v = ve.var.isVarDeclaration();
2004             if (v && v.isTypesafeVariadicArray)
2005             {
2006                 er.pushRef(v, retRefTransition);
2007                 return;
2008             }
2009         }
2010         if (tb.ty == Tsarray)
2011         {
2012             escapeByRef(e.e1, er, live, retRefTransition);
2013         }
2014         else if (tb.ty == Tarray)
2015         {
2016             escapeByValue(e.e1, er, live, retRefTransition);
2017         }
2018     }
2019 
2020     void visitStructLiteral(StructLiteralExp e)
2021     {
2022         if (e.elements)
2023         {
2024             foreach (ex; *e.elements)
2025             {
2026                 if (ex)
2027                     escapeByRef(ex, er, live, retRefTransition);
2028             }
2029         }
2030         er.pushExp(e, retRefTransition);
2031     }
2032 
2033     void visitDotVar(DotVarExp e)
2034     {
2035         Type t1b = e.e1.type.toBasetype();
2036         if (t1b.ty == Tclass)
2037             escapeByValue(e.e1, er, live, retRefTransition);
2038         else
2039             escapeByRef(e.e1, er, live, retRefTransition);
2040     }
2041 
2042     void visitBinAssign(BinAssignExp e)
2043     {
2044         escapeByRef(e.e1, er, live, retRefTransition);
2045     }
2046 
2047     void visitAssign(AssignExp e)
2048     {
2049         escapeByRef(e.e1, er, live, retRefTransition);
2050     }
2051 
2052     void visitComma(CommaExp e)
2053     {
2054         escapeByRef(e.e2, er, live, retRefTransition);
2055     }
2056 
2057     void visitCond(CondExp e)
2058     {
2059         escapeByRef(e.e1, er, live, retRefTransition);
2060         escapeByRef(e.e2, er, live, retRefTransition);
2061     }
2062 
2063     void visitCall(CallExp e)
2064     {
2065         //printf("escapeByRef.CallExp(): %s\n", e.toChars());
2066         /* If the function returns by ref, check each argument that is
2067          * passed as 'return ref'.
2068          */
2069         Type t1 = e.e1.type.toBasetype();
2070         TypeFunction tf;
2071         if (t1.ty == Tdelegate)
2072             tf = t1.isTypeDelegate().next.isTypeFunction();
2073         else if (t1.ty == Tfunction)
2074             tf = t1.isTypeFunction();
2075         else
2076             return;
2077         if (tf.isref)
2078         {
2079             if (e.arguments && e.arguments.length)
2080             {
2081                 /* j=1 if _arguments[] is first argument,
2082                  * skip it because it is not passed by ref
2083                  */
2084                 int j = tf.isDstyleVariadic();
2085                 for (size_t i = j; i < e.arguments.length; ++i)
2086                 {
2087                     Expression arg = (*e.arguments)[i];
2088                     size_t nparams = tf.parameterList.length;
2089                     if (i - j < nparams && i >= j)
2090                     {
2091                         Parameter p = tf.parameterList[i - j];
2092                         const stc = tf.parameterStorageClass(null, p);
2093                         ScopeRef psr = buildScopeRef(stc);
2094                         if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope)
2095                             escapeByRef(arg, er, live, retRefTransition);
2096                         else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope)
2097                         {
2098                             if (auto de = arg.isDelegateExp())
2099                             {
2100                                 if (de.func.isNested())
2101                                     er.pushExp(de, false);
2102                             }
2103                             else
2104                                 escapeByValue(arg, er, live, retRefTransition);
2105                         }
2106                     }
2107                 }
2108             }
2109             // If 'this' is returned by ref, check it too
2110             if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction)
2111             {
2112                 DotVarExp dve = e.e1.isDotVarExp();
2113 
2114                 // https://issues.dlang.org/show_bug.cgi?id=20149#c10
2115                 if (dve.var.isCtorDeclaration())
2116                 {
2117                     er.pushExp(e, false);
2118                     return;
2119                 }
2120 
2121                 StorageClass stc = dve.var.storage_class & (STC.return_ | STC.scope_ | STC.ref_);
2122                 if (tf.isreturn)
2123                     stc |= STC.return_;
2124                 if (tf.isref)
2125                     stc |= STC.ref_;
2126                 if (tf.isScopeQual)
2127                     stc |= STC.scope_;
2128                 if (tf.isreturnscope)
2129                     stc |= STC.returnScope;
2130 
2131                 const psr = buildScopeRef(stc);
2132                 if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope)
2133                     escapeByRef(dve.e1, er, live, psr == ScopeRef.ReturnRef_Scope);
2134                 else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope)
2135                     escapeByValue(dve.e1, er, live, retRefTransition);
2136 
2137                 // If it's also a nested function that is 'return ref'
2138                 if (FuncDeclaration fd = dve.var.isFuncDeclaration())
2139                 {
2140                     if (fd.isNested() && tf.isreturn)
2141                     {
2142                         er.pushExp(e, false);
2143                     }
2144                 }
2145             }
2146             // If it's a delegate, check it too
2147             if (e.e1.op == EXP.variable && t1.ty == Tdelegate)
2148             {
2149                 escapeByValue(e.e1, er, live, retRefTransition);
2150             }
2151 
2152             /* If it's a nested function that is 'return ref'
2153              */
2154             if (auto ve = e.e1.isVarExp())
2155             {
2156                 FuncDeclaration fd = ve.var.isFuncDeclaration();
2157                 if (fd && fd.isNested())
2158                 {
2159                     if (tf.isreturn)
2160                         er.pushExp(e, false);
2161                 }
2162             }
2163         }
2164         else
2165             er.pushExp(e, retRefTransition);
2166     }
2167 
2168     switch (e.op)
2169     {
2170         case EXP.variable: return visitVar(e.isVarExp());
2171         case EXP.this_: return visitThis(e.isThisExp());
2172         case EXP.star: return visitPtr(e.isPtrExp());
2173         case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp());
2174         case EXP.dotVariable: return visitDotVar(e.isDotVarExp());
2175         case EXP.index: return visitIndex(e.isIndexExp());
2176         case EXP.blit: return visitAssign(e.isBlitExp());
2177         case EXP.construct: return visitAssign(e.isConstructExp());
2178         case EXP.assign: return visitAssign(e.isAssignExp());
2179         case EXP.comma: return visitComma(e.isCommaExp());
2180         case EXP.question: return visitCond(e.isCondExp());
2181         case EXP.call: return visitCall(e.isCallExp());
2182         default:
2183             if (auto ba = e.isBinAssignExp())
2184                 return visitBinAssign(ba);
2185             return visit(e);
2186     }
2187 }
2188 
2189 /************************************
2190  * Aggregate the data collected by the escapeBy??() functions.
2191  */
2192 struct EscapeByResults
2193 {
2194     VarDeclarations byref;      // array into which variables being returned by ref are inserted
2195     VarDeclarations byvalue;    // array into which variables with values containing pointers are inserted
2196     private FuncDeclarations byfunc; // nested functions that are turned into delegates
2197     private Expressions byexp;       // array into which temporaries being returned by ref are inserted
2198 
2199     import dmd.root.array: Array;
2200 
2201     /**
2202      * Whether the variable / expression went through a `return ref scope` function call
2203      *
2204      * This is needed for the dip1000 by default transition, since the rules for
2205      * disambiguating `return scope ref` have changed. Therefore, functions in legacy code
2206      * can be mistakenly treated as `return ref` making the compiler believe stack variables
2207      * are being escaped, which is an error even in `@system` code. By keeping track of this
2208      * information, variables escaped through `return ref` can be treated as a deprecation instead
2209      * of error, see test/fail_compilation/dip1000_deprecation.d
2210      */
2211     private Array!bool refRetRefTransition;
2212     private Array!bool expRetRefTransition;
2213 
2214     /** Reset arrays so the storage can be used again
2215      */
2216     void reset()
2217     {
2218         byref.setDim(0);
2219         byvalue.setDim(0);
2220         byfunc.setDim(0);
2221         byexp.setDim(0);
2222 
2223         refRetRefTransition.setDim(0);
2224         expRetRefTransition.setDim(0);
2225     }
2226 
2227     /**
2228      * Escape variable `v` by reference
2229      * Params:
2230      *   v = variable to escape
2231      *   retRefTransition = `v` is escaped through a `return ref scope` function call
2232      */
2233     void pushRef(VarDeclaration v, bool retRefTransition)
2234     {
2235         byref.push(v);
2236         refRetRefTransition.push(retRefTransition);
2237     }
2238 
2239     /**
2240      * Escape a reference to expression `e`
2241      * Params:
2242      *   e = expression to escape
2243      *   retRefTransition = `e` is escaped through a `return ref scope` function call
2244      */
2245     void pushExp(Expression e, bool retRefTransition)
2246     {
2247         byexp.push(e);
2248         expRetRefTransition.push(retRefTransition);
2249     }
2250 }
2251 
2252 /*************************
2253  * Find all variables accessed by this delegate that are
2254  * in functions enclosing it.
2255  * Params:
2256  *      fd = function
2257  *      vars = array to append found variables to
2258  */
2259 public void findAllOuterAccessedVariables(FuncDeclaration fd, VarDeclarations* vars)
2260 {
2261     //printf("findAllOuterAccessedVariables(fd: %s)\n", fd.toChars());
2262     for (auto p = fd.parent; p; p = p.parent)
2263     {
2264         auto fdp = p.isFuncDeclaration();
2265         if (!fdp)
2266             continue;
2267 
2268         foreach (v; fdp.closureVars)
2269         {
2270             foreach (const fdv; v.nestedrefs)
2271             {
2272                 if (fdv == fd)
2273                 {
2274                     //printf("accessed: %s, type %s\n", v.toChars(), v.type.toChars());
2275                     vars.push(v);
2276                 }
2277             }
2278         }
2279     }
2280 }
2281 
2282 /***********************************
2283  * Turn off `maybeScope` for variable `v`.
2284  *
2285  * This exists in order to find where `maybeScope` is getting turned off.
2286  * Params:
2287  *      v = variable
2288  *      o = reason for it being turned off:
2289  *          - `Expression` such as `throw e` or `&e`
2290  *          - `VarDeclaration` of a non-scope parameter it was assigned to
2291  *          - `null` for no reason
2292  */
2293 private void notMaybeScope(VarDeclaration v, RootObject o)
2294 {
2295     if (v.maybeScope)
2296     {
2297         v.maybeScope = false;
2298         if (o && v.isParameter())
2299             EscapeState.scopeInferFailure[v.sequenceNumber] = o;
2300     }
2301 }
2302 
2303 /***********************************
2304  * Turn off `maybeScope` for variable `v` if it's not a parameter.
2305  *
2306  * This is for compatibility with the old system with both `STC.maybescope` and `VarDeclaration.doNotInferScope`,
2307  * which is now just `VarDeclaration.maybeScope`.
2308  * This function should probably be removed in future refactors.
2309  *
2310  * Params:
2311  *      v = variable
2312  *      o = reason for it being turned off
2313  */
2314 private void doNotInferScope(VarDeclaration v, RootObject o)
2315 {
2316     if (!v.isParameter)
2317         notMaybeScope(v, o);
2318 }
2319 
2320 /***********************************
2321  * After semantic analysis of the function body,
2322  * try to infer `scope` / `return` on the parameters
2323  *
2324  * Params:
2325  *   funcdecl = function declaration that was analyzed
2326  *   f = final function type. `funcdecl.type` started as the 'premature type' before attribute
2327  *       inference, then its inferred attributes are copied over to final type `f`
2328  */
2329 void finishScopeParamInference(FuncDeclaration funcdecl, ref TypeFunction f)
2330 {
2331 
2332     if (funcdecl.returnInprocess)
2333     {
2334         funcdecl.returnInprocess = false;
2335         if (funcdecl.storage_class & STC.return_)
2336         {
2337             if (funcdecl.type == f)
2338                 f = cast(TypeFunction)f.copy();
2339             f.isreturn = true;
2340             f.isreturnscope = cast(bool) (funcdecl.storage_class & STC.returnScope);
2341             if (funcdecl.storage_class & STC.returninferred)
2342                 f.isreturninferred = true;
2343         }
2344     }
2345 
2346     if (!funcdecl.inferScope)
2347         return;
2348     funcdecl.inferScope = false;
2349 
2350     // Eliminate maybescope's
2351     {
2352         // Create and fill array[] with maybe candidates from the `this` and the parameters
2353         VarDeclaration[10] tmp = void;
2354         size_t dim = (funcdecl.vthis !is null) + (funcdecl.parameters ? funcdecl.parameters.length : 0);
2355 
2356         import dmd.common.string : SmallBuffer;
2357         auto sb = SmallBuffer!VarDeclaration(dim, tmp[]);
2358         VarDeclaration[] array = sb[];
2359 
2360         size_t n = 0;
2361         if (funcdecl.vthis)
2362             array[n++] = funcdecl.vthis;
2363         if (funcdecl.parameters)
2364         {
2365             foreach (v; *funcdecl.parameters)
2366             {
2367                 array[n++] = v;
2368             }
2369         }
2370         eliminateMaybeScopes(array[0 .. n]);
2371     }
2372 
2373     // Infer STC.scope_
2374     if (funcdecl.parameters && !funcdecl.errors)
2375     {
2376         assert(f.parameterList.length == funcdecl.parameters.length);
2377         foreach (u, p; f.parameterList)
2378         {
2379             auto v = (*funcdecl.parameters)[u];
2380             if (!v.isScope() && v.type.hasPointers() && inferScope(v))
2381             {
2382                 //printf("Inferring scope for %s\n", v.toChars());
2383                 p.storageClass |= STC.scope_ | STC.scopeinferred;
2384             }
2385         }
2386     }
2387 
2388     if (funcdecl.vthis)
2389     {
2390         inferScope(funcdecl.vthis);
2391         f.isScopeQual = funcdecl.vthis.isScope();
2392         f.isscopeinferred = !!(funcdecl.vthis.storage_class & STC.scopeinferred);
2393     }
2394 }
2395 
2396 /**********************************************
2397  * Have some variables that are maybescopes that were
2398  * assigned values from other maybescope variables.
2399  * Now that semantic analysis of the function is
2400  * complete, we can finalize this by turning off
2401  * maybescope for array elements that cannot be scope.
2402  *
2403  * $(TABLE2 Scope Table,
2404  * $(THEAD `va`, `v`,    =>,  `va` ,  `v`  )
2405  * $(TROW maybe, maybe,  =>,  scope,  scope)
2406  * $(TROW scope, scope,  =>,  scope,  scope)
2407  * $(TROW scope, maybe,  =>,  scope,  scope)
2408  * $(TROW maybe, scope,  =>,  scope,  scope)
2409  * $(TROW -    , -    ,  =>,  -    ,  -    )
2410  * $(TROW -    , maybe,  =>,  -    ,  -    )
2411  * $(TROW -    , scope,  =>,  error,  error)
2412  * $(TROW maybe, -    ,  =>,  scope,  -    )
2413  * $(TROW scope, -    ,  =>,  scope,  -    )
2414  * )
2415  * Params:
2416  *      array = array of variables that were assigned to from maybescope variables
2417  */
2418 private void eliminateMaybeScopes(VarDeclaration[] array)
2419 {
2420     enum log = false;
2421     if (log) printf("eliminateMaybeScopes()\n");
2422     bool changes;
2423     do
2424     {
2425         changes = false;
2426         foreach (va; array)
2427         {
2428             if (log) printf("  va = %s\n", va.toChars());
2429             if (!(va.maybeScope || va.isScope()))
2430             {
2431                 if (va.maybes)
2432                 {
2433                     foreach (v; *va.maybes)
2434                     {
2435                         if (log) printf("    v = %s\n", v.toChars());
2436                         if (v.maybeScope)
2437                         {
2438                             // v cannot be scope since it is assigned to a non-scope va
2439                             notMaybeScope(v, va);
2440                             if (!v.isReference())
2441                                 v.storage_class &= ~(STC.return_ | STC.returninferred);
2442                             changes = true;
2443                         }
2444                     }
2445                 }
2446             }
2447         }
2448     } while (changes);
2449 }
2450 
2451 /************************************************
2452  * Is type a reference to a mutable value?
2453  *
2454  * This is used to determine if an argument that does not have a corresponding
2455  * Parameter, i.e. a variadic argument, is a pointer to mutable data.
2456  * Params:
2457  *      t = type of the argument
2458  * Returns:
2459  *      true if it's a pointer (or reference) to mutable data
2460  */
2461 bool isReferenceToMutable(Type t)
2462 {
2463     t = t.baseElemOf();
2464 
2465     if (!t.isMutable() ||
2466         !t.hasPointers())
2467         return false;
2468 
2469     switch (t.ty)
2470     {
2471         case Tpointer:
2472             if (t.nextOf().isTypeFunction())
2473                 break;
2474             goto case;
2475 
2476         case Tarray:
2477         case Taarray:
2478         case Tdelegate:
2479             if (t.nextOf().isMutable())
2480                 return true;
2481             break;
2482 
2483         case Tclass:
2484             return true;        // even if the class fields are not mutable
2485 
2486         case Tstruct:
2487             // Have to look at each field
2488             foreach (VarDeclaration v; t.isTypeStruct().sym.fields)
2489             {
2490                 if (v.storage_class & STC.ref_)
2491                 {
2492                     if (v.type.isMutable())
2493                         return true;
2494                 }
2495                 else if (v.type.isReferenceToMutable())
2496                     return true;
2497             }
2498             break;
2499 
2500         default:
2501             assert(0);
2502     }
2503     return false;
2504 }
2505 
2506 /****************************************
2507  * Is parameter a reference to a mutable value?
2508  *
2509  * This is used if an argument has a corresponding Parameter.
2510  * The argument type is necessary if the Parameter is inout.
2511  * Params:
2512  *      p = Parameter to check
2513  *      t = type of corresponding argument
2514  * Returns:
2515  *      true if it's a pointer (or reference) to mutable data
2516  */
2517 bool isReferenceToMutable(Parameter p, Type t)
2518 {
2519     if (p.isReference())
2520     {
2521         if (p.type.isConst() || p.type.isImmutable())
2522             return false;
2523         if (p.type.isWild())
2524         {
2525             return t.isMutable();
2526         }
2527         return p.type.isMutable();
2528     }
2529     return isReferenceToMutable(p.type);
2530 }
2531 
2532 /// When checking lifetime for assignment `va=v`, the way `va` encloses `v`
2533 private enum EnclosedBy
2534 {
2535     none = 0,
2536     refVar, // `va` is a `ref` variable, which may link to a global variable
2537     global, // `va` is a global variable
2538     returnScope, // `va` is a scope variable that may be returned
2539     longerScope, // `va` is another scope variable declared earlier than `v`
2540 }
2541 
2542 /**********************************
2543  * Determine if `va` has a lifetime that lasts past
2544  * the destruction of `v`
2545  * Params:
2546  *     va = variable assigned to
2547  *     v = variable being assigned
2548  * Returns:
2549  *     The way `va` encloses `v` (if any)
2550  */
2551 private EnclosedBy enclosesLifetimeOf(VarDeclaration va, VarDeclaration v)
2552 {
2553     if (!va)
2554         return EnclosedBy.none;
2555 
2556     if (va.isDataseg())
2557         return EnclosedBy.global;
2558 
2559     if (va.isScope() && va.isReturn() && !v.isReturn())
2560         return EnclosedBy.returnScope;
2561 
2562     if (va.isReference() && va.isParameter())
2563         return EnclosedBy.refVar;
2564 
2565     assert(va.sequenceNumber != va.sequenceNumber.init);
2566     assert(v.sequenceNumber != v.sequenceNumber.init);
2567     if (va.sequenceNumber < v.sequenceNumber)
2568         return EnclosedBy.longerScope;
2569 
2570     return EnclosedBy.none;
2571 }
2572 
2573 /***************************************
2574  * Add variable `v` to maybes[]
2575  *
2576  * When a maybescope variable `v` is assigned to a maybescope variable `va`,
2577  * we cannot determine if `this` is actually scope until the semantic
2578  * analysis for the function is completed. Thus, we save the data
2579  * until then.
2580  * Params:
2581  *     v = a variable with `maybeScope == true` that was assigned to `this`
2582  */
2583 private void addMaybe(VarDeclaration va, VarDeclaration v)
2584 {
2585     //printf("add %s to %s's list of dependencies\n", v.toChars(), toChars());
2586     if (!va.maybes)
2587         va.maybes = new VarDeclarations();
2588     va.maybes.push(v);
2589 }
2590 
2591 // `setUnsafePreview` partially evaluated for dip1000
2592 private bool setUnsafeDIP1000(Scope* sc, bool gag, Loc loc, const(char)* msg,
2593     RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null)
2594 {
2595     return setUnsafePreview(sc, global.params.useDIP1000, gag, loc, msg, arg0, arg1, arg2);
2596 }
2597 
2598 /***************************************
2599  * Check that taking the address of `v` is `@safe`
2600  *
2601  * It's not possible to take the address of a scope variable, because `scope` only applies
2602  * to the top level indirection.
2603  *
2604  * Params:
2605  *     v = variable that a reference is created
2606  *     e = expression that takes the referene
2607  *     sc = used to obtain function / deprecated status
2608  *     gag = don't print errors
2609  * Returns:
2610  *     true if taking the address of `v` is problematic because of the lack of transitive `scope`
2611  */
2612 private bool checkScopeVarAddr(VarDeclaration v, Expression e, Scope* sc, bool gag)
2613 {
2614     if (v.storage_class & STC.temp)
2615         return false;
2616 
2617     if (!v.isScope())
2618     {
2619         notMaybeScope(v, e);
2620         return false;
2621     }
2622 
2623     if (!e.type)
2624         return false;
2625 
2626     // When the type after dereferencing has no pointers, it's okay.
2627     // Comes up when escaping `&someStruct.intMember` of a `scope` struct:
2628     // scope does not apply to the `int`
2629     Type t = e.type.baseElemOf();
2630     if ((t.ty == Tarray || t.ty == Tpointer) && !t.nextOf().toBasetype().hasPointers())
2631         return false;
2632 
2633     // take address of `scope` variable not allowed, requires transitive scope
2634     return sc.setUnsafeDIP1000(gag, e.loc,
2635         "cannot take address of `scope` variable `%s` since `scope` applies to first indirection only", v);
2636 }
2637 
2638 /****************************
2639  * Determine if `v` is a typesafe variadic array, which is implicitly `scope`
2640  * Params:
2641  *      v = variable to check
2642  * Returns:
2643  *      true if `v` is a variadic parameter
2644  */
2645 private bool isTypesafeVariadicArray(VarDeclaration v)
2646 {
2647     if (v.storage_class & STC.variadic)
2648     {
2649         Type tb = v.type.toBasetype();
2650         if (tb.ty == Tarray || tb.ty == Tsarray)
2651             return true;
2652     }
2653     return false;
2654 }