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