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