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 }