| /** |
| * Most of the logic to implement scoped pointers and scoped references is here. |
| * |
| * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/escape.d, _escape.d) |
| * Documentation: https://dlang.org/phobos/dmd_escape.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/escape.d |
| */ |
| |
| module dmd.escape; |
| |
| import core.stdc.stdio : printf; |
| import core.stdc.stdlib; |
| import core.stdc.string; |
| |
| import dmd.root.rmem; |
| |
| import dmd.aggregate; |
| import dmd.astenums; |
| import dmd.declaration; |
| import dmd.dscope; |
| import dmd.dsymbol; |
| import dmd.errors; |
| import dmd.expression; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.id; |
| import dmd.identifier; |
| import dmd.init; |
| import dmd.location; |
| import dmd.mtype; |
| import dmd.printast; |
| import dmd.root.rootobject; |
| import dmd.tokens; |
| import dmd.visitor; |
| import dmd.arraytypes; |
| |
| /// Groups global state for escape checking together |
| package(dmd) struct EscapeState |
| { |
| // Maps `sequenceNumber` of a `VarDeclaration` to an object that contains the |
| // reason it failed to infer `scope` |
| // https://issues.dlang.org/show_bug.cgi?id=23295 |
| private __gshared RootObject[int] scopeInferFailure; |
| |
| /// Called by `initDMD` / `deinitializeDMD` to reset global state |
| static void reset() |
| { |
| scopeInferFailure = null; |
| } |
| } |
| |
| /****************************************************** |
| * Checks memory objects passed to a function. |
| * Checks that if a memory object is passed by ref or by pointer, |
| * all of the refs or pointers are const, or there is only one mutable |
| * ref or pointer to it. |
| * References: |
| * DIP 1021 |
| * Params: |
| * sc = used to determine current function and module |
| * fd = function being called |
| * tf = fd's type |
| * ethis = if not null, the `this` pointer |
| * arguments = actual arguments to function |
| * gag = do not print error messages |
| * Returns: |
| * `true` if error |
| */ |
| bool checkMutableArguments(Scope* sc, FuncDeclaration fd, TypeFunction tf, |
| Expression ethis, Expressions* arguments, bool gag) |
| { |
| enum log = false; |
| if (log) printf("[%s] checkMutableArguments, fd: `%s`\n", fd.loc.toChars(), fd.toChars()); |
| if (log && ethis) printf("ethis: `%s`\n", ethis.toChars()); |
| bool errors = false; |
| |
| /* Outer variable references are treated as if they are extra arguments |
| * passed by ref to the function (which they essentially are via the static link). |
| */ |
| VarDeclaration[] outerVars = fd ? fd.outerVars[] : null; |
| |
| const len = arguments.length + (ethis !is null) + outerVars.length; |
| if (len <= 1) |
| return errors; |
| |
| struct EscapeBy |
| { |
| EscapeByResults er; |
| Parameter param; // null if no Parameter for this argument |
| bool isMutable; // true if reference to mutable |
| } |
| |
| /* Store escapeBy as static data escapeByStorage so we can keep reusing the same |
| * arrays rather than reallocating them. |
| */ |
| __gshared EscapeBy[] escapeByStorage; |
| auto escapeBy = escapeByStorage; |
| if (escapeBy.length < len) |
| { |
| auto newPtr = cast(EscapeBy*)mem.xrealloc(escapeBy.ptr, len * EscapeBy.sizeof); |
| // Clear the new section |
| memset(newPtr + escapeBy.length, 0, (len - escapeBy.length) * EscapeBy.sizeof); |
| escapeBy = newPtr[0 .. len]; |
| escapeByStorage = escapeBy; |
| } |
| else |
| escapeBy = escapeBy[0 .. len]; |
| |
| const paramLength = tf.parameterList.length; |
| |
| // Fill in escapeBy[] with arguments[], ethis, and outerVars[] |
| foreach (const i, ref eb; escapeBy) |
| { |
| bool refs; |
| Expression arg; |
| if (i < arguments.length) |
| { |
| arg = (*arguments)[i]; |
| if (i < paramLength) |
| { |
| eb.param = tf.parameterList[i]; |
| refs = eb.param.isReference(); |
| eb.isMutable = eb.param.isReferenceToMutable(arg.type); |
| } |
| else |
| { |
| eb.param = null; |
| refs = false; |
| eb.isMutable = arg.type.isReferenceToMutable(); |
| } |
| } |
| else if (ethis) |
| { |
| /* ethis is passed by value if a class reference, |
| * by ref if a struct value |
| */ |
| eb.param = null; |
| arg = ethis; |
| auto ad = fd.isThis(); |
| assert(ad); |
| assert(ethis); |
| if (ad.isClassDeclaration()) |
| { |
| refs = false; |
| eb.isMutable = arg.type.isReferenceToMutable(); |
| } |
| else |
| { |
| assert(ad.isStructDeclaration()); |
| refs = true; |
| eb.isMutable = arg.type.isMutable(); |
| } |
| } |
| else |
| { |
| // outer variables are passed by ref |
| eb.param = null; |
| refs = true; |
| auto var = outerVars[i - (len - outerVars.length)]; |
| eb.isMutable = var.type.isMutable(); |
| eb.er.pushRef(var, false); |
| continue; |
| } |
| |
| if (refs) |
| escapeByRef(arg, &eb.er); |
| else |
| escapeByValue(arg, &eb.er); |
| } |
| |
| void checkOnePair(size_t i, ref EscapeBy eb, ref EscapeBy eb2, |
| VarDeclaration v, VarDeclaration v2, bool of) |
| { |
| if (log) printf("v2: `%s`\n", v2.toChars()); |
| if (v2 != v) |
| return; |
| //printf("v %d v2 %d\n", eb.isMutable, eb2.isMutable); |
| if (!(eb.isMutable || eb2.isMutable)) |
| return; |
| |
| if (!tf.islive && !(global.params.useDIP1000 == FeatureState.enabled && sc.func.setUnsafe())) |
| return; |
| |
| if (!gag) |
| { |
| // int i; funcThatEscapes(ref int i); |
| // funcThatEscapes(i); // error escaping reference _to_ `i` |
| // int* j; funcThatEscapes2(int* j); |
| // funcThatEscapes2(j); // error escaping reference _of_ `i` |
| const(char)* referenceVerb = of ? "of" : "to"; |
| const(char)* msg = eb.isMutable && eb2.isMutable |
| ? "more than one mutable reference %s `%s` in arguments to `%s()`" |
| : "mutable and const references %s `%s` in arguments to `%s()`"; |
| error((*arguments)[i].loc, msg, |
| referenceVerb, |
| v.toChars(), |
| fd ? fd.toPrettyChars() : "indirectly"); |
| } |
| errors = true; |
| } |
| |
| void escape(size_t i, ref EscapeBy eb, bool byval) |
| { |
| foreach (VarDeclaration v; byval ? eb.er.byvalue : eb.er.byref) |
| { |
| if (log) |
| { |
| const(char)* by = byval ? "byval" : "byref"; |
| printf("%s %s\n", by, v.toChars()); |
| } |
| if (byval && !v.type.hasPointers()) |
| continue; |
| foreach (ref eb2; escapeBy[i + 1 .. $]) |
| { |
| foreach (VarDeclaration v2; byval ? eb2.er.byvalue : eb2.er.byref) |
| { |
| checkOnePair(i, eb, eb2, v, v2, byval); |
| } |
| } |
| } |
| } |
| foreach (const i, ref eb; escapeBy[0 .. $ - 1]) |
| { |
| escape(i, eb, true); |
| escape(i, eb, false); |
| } |
| |
| /* Reset the arrays in escapeBy[] so we can reuse them next time through |
| */ |
| foreach (ref eb; escapeBy) |
| { |
| eb.er.reset(); |
| } |
| |
| return errors; |
| } |
| |
| /****************************************** |
| * Array literal is going to be allocated on the GC heap. |
| * Check its elements to see if any would escape by going on the heap. |
| * Params: |
| * sc = used to determine current function and module |
| * ae = array literal expression |
| * gag = do not print error messages |
| * Returns: |
| * `true` if any elements escaped |
| */ |
| bool checkArrayLiteralEscape(Scope *sc, ArrayLiteralExp ae, bool gag) |
| { |
| bool errors; |
| if (ae.basis) |
| errors = checkNewEscape(sc, ae.basis, gag); |
| foreach (ex; *ae.elements) |
| { |
| if (ex) |
| errors |= checkNewEscape(sc, ex, gag); |
| } |
| return errors; |
| } |
| |
| /****************************************** |
| * Associative array literal is going to be allocated on the GC heap. |
| * Check its elements to see if any would escape by going on the heap. |
| * Params: |
| * sc = used to determine current function and module |
| * ae = associative array literal expression |
| * gag = do not print error messages |
| * Returns: |
| * `true` if any elements escaped |
| */ |
| bool checkAssocArrayLiteralEscape(Scope *sc, AssocArrayLiteralExp ae, bool gag) |
| { |
| bool errors; |
| foreach (ex; *ae.keys) |
| { |
| if (ex) |
| errors |= checkNewEscape(sc, ex, gag); |
| } |
| foreach (ex; *ae.values) |
| { |
| if (ex) |
| errors |= checkNewEscape(sc, ex, gag); |
| } |
| return errors; |
| } |
| |
| /** |
| * A `scope` variable was assigned to non-scope parameter `v`. |
| * If applicable, print why the parameter was not inferred `scope`. |
| * |
| * Params: |
| * printFunc = error/deprecation print function to use |
| * v = parameter that was not inferred |
| * recursionLimit = recursion limit for printing the reason |
| */ |
| void printScopeFailure(E)(E printFunc, VarDeclaration v, int recursionLimit) |
| { |
| recursionLimit--; |
| if (recursionLimit < 0 || !v) |
| return; |
| |
| if (RootObject* o = v.sequenceNumber in EscapeState.scopeInferFailure) |
| { |
| switch ((*o).dyncast()) |
| { |
| case DYNCAST.expression: |
| Expression e = cast(Expression) *o; |
| printFunc(e.loc, "which is not `scope` because of `%s`", e.toChars()); |
| break; |
| case DYNCAST.dsymbol: |
| VarDeclaration v1 = cast(VarDeclaration) *o; |
| printFunc(v1.loc, "which is assigned to non-scope parameter `%s`", v1.toChars()); |
| printScopeFailure(printFunc, v1, recursionLimit); |
| break; |
| default: |
| assert(0); |
| } |
| } |
| } |
| |
| /**************************************** |
| * Function parameter `par` is being initialized to `arg`, |
| * and `par` may escape. |
| * Detect if scoped values can escape this way. |
| * Print error messages when these are detected. |
| * Params: |
| * sc = used to determine current function and module |
| * fdc = function being called, `null` if called indirectly |
| * parId = name of function parameter for error messages |
| * vPar = `VarDeclaration` corresponding to `par` |
| * parStc = storage classes of function parameter (may have added `scope` from `pure`) |
| * arg = initializer for param |
| * assertmsg = true if the parameter is the msg argument to assert(bool, msg). |
| * gag = do not print error messages |
| * Returns: |
| * `true` if pointers to the stack can escape via assignment |
| */ |
| bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier parId, VarDeclaration vPar, STC parStc, Expression arg, bool assertmsg, bool gag) |
| { |
| enum log = false; |
| if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n", |
| arg ? arg.toChars() : "null", |
| parId ? parId.toChars() : "null"); |
| //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers()); |
| |
| if (!arg.type.hasPointers()) |
| return false; |
| |
| EscapeByResults er; |
| |
| escapeByValue(arg, &er); |
| |
| if (parStc & STC.scope_) |
| { |
| // These errors only apply to non-scope parameters |
| // When the paraneter is `scope`, only `checkScopeVarAddr` on `er.byref` is needed |
| er.byfunc.setDim(0); |
| er.byvalue.setDim(0); |
| er.byexp.setDim(0); |
| } |
| |
| if (!er.byref.length && !er.byvalue.length && !er.byfunc.length && !er.byexp.length) |
| return false; |
| |
| bool result = false; |
| |
| /* 'v' is assigned unsafely to 'par' |
| */ |
| void unsafeAssign(string desc)(VarDeclaration v) |
| { |
| if (assertmsg) |
| { |
| result |= sc.setUnsafeDIP1000(gag, arg.loc, |
| desc ~ " `%s` assigned to non-scope parameter calling `assert()`", v); |
| return; |
| } |
| const(char)* msg = |
| (fdc && parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s` calling `%s`") : |
| (fdc && !parId) ? (desc ~ " `%s` assigned to non-scope anonymous parameter calling `%s`") : |
| (!fdc && parId) ? (desc ~ " `%s` assigned to non-scope parameter `%s`") : |
| (desc ~ " `%s` assigned to non-scope anonymous parameter"); |
| |
| if (sc.setUnsafeDIP1000(gag, arg.loc, msg, v, parId ? parId : fdc, fdc)) |
| { |
| result = true; |
| printScopeFailure(previewSupplementalFunc(sc.isDeprecated(), global.params.useDIP1000), vPar, 10); |
| } |
| } |
| |
| foreach (VarDeclaration v; er.byvalue) |
| { |
| if (log) printf("byvalue %s\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| Dsymbol p = v.toParent2(); |
| |
| notMaybeScope(v, vPar); |
| |
| if (v.isScope()) |
| { |
| unsafeAssign!"scope variable"(v); |
| } |
| else if (v.isTypesafeVariadicArray && p == sc.func) |
| { |
| unsafeAssign!"variadic variable"(v); |
| } |
| } |
| |
| foreach (VarDeclaration v; er.byref) |
| { |
| if (log) printf("byref %s\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| Dsymbol p = v.toParent2(); |
| |
| notMaybeScope(v, arg); |
| if (checkScopeVarAddr(v, arg, sc, gag)) |
| { |
| result = true; |
| continue; |
| } |
| |
| if (p == sc.func && !(parStc & STC.scope_)) |
| { |
| unsafeAssign!"reference to local variable"(v); |
| continue; |
| } |
| } |
| |
| foreach (FuncDeclaration fd; er.byfunc) |
| { |
| //printf("fd = %s, %d\n", fd.toChars(), fd.tookAddressOf); |
| VarDeclarations vars; |
| findAllOuterAccessedVariables(fd, &vars); |
| |
| foreach (v; vars) |
| { |
| //printf("v = %s\n", v.toChars()); |
| assert(!v.isDataseg()); // these are not put in the closureVars[] |
| |
| Dsymbol p = v.toParent2(); |
| |
| notMaybeScope(v, arg); |
| |
| if ((v.isReference() || v.isScope()) && p == sc.func) |
| { |
| unsafeAssign!"reference to local"(v); |
| continue; |
| } |
| } |
| } |
| |
| if (!sc.func) |
| return result; |
| |
| foreach (Expression ee; er.byexp) |
| { |
| const(char)* msg = parId ? |
| "reference to stack allocated value returned by `%s` assigned to non-scope parameter `%s`" : |
| "reference to stack allocated value returned by `%s` assigned to non-scope anonymous parameter"; |
| |
| result |= sc.setUnsafeDIP1000(gag, ee.loc, msg, ee, parId); |
| } |
| |
| return result; |
| } |
| |
| /***************************************************** |
| * Function argument initializes a `return` parameter, |
| * and that parameter gets assigned to `firstArg`. |
| * Essentially, treat as `firstArg = arg;` |
| * Params: |
| * sc = used to determine current function and module |
| * firstArg = `ref` argument through which `arg` may be assigned |
| * arg = initializer for parameter |
| * param = parameter declaration corresponding to `arg` |
| * gag = do not print error messages |
| * Returns: |
| * `true` if assignment to `firstArg` would cause an error |
| */ |
| bool checkParamArgumentReturn(Scope* sc, Expression firstArg, Expression arg, Parameter param, bool gag) |
| { |
| enum log = false; |
| if (log) printf("checkParamArgumentReturn(firstArg: %s arg: %s)\n", |
| firstArg.toChars(), arg.toChars()); |
| //printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers()); |
| |
| if (!(param.storageClass & STC.return_)) |
| return false; |
| |
| if (!arg.type.hasPointers() && !param.isReference()) |
| return false; |
| |
| // `byRef` needed for `assign(ref int* x, ref int i) {x = &i};` |
| // Note: taking address of scope pointer is not allowed |
| // `assign(ref int** x, return ref scope int* i) {x = &i};` |
| // Thus no return ref/return scope ambiguity here |
| const byRef = param.isReference() && !(param.storageClass & STC.scope_) |
| && !(param.storageClass & STC.returnScope); // fixme: it's possible to infer returnScope without scope with vaIsFirstRef |
| |
| scope e = new AssignExp(arg.loc, firstArg, arg); |
| return checkAssignEscape(sc, e, gag, byRef); |
| } |
| |
| /***************************************************** |
| * Check struct constructor of the form `s.this(args)`, by |
| * checking each `return` parameter to see if it gets |
| * assigned to `s`. |
| * Params: |
| * sc = used to determine current function and module |
| * ce = constructor call of the form `s.this(args)` |
| * gag = do not print error messages |
| * Returns: |
| * `true` if construction would cause an escaping reference error |
| */ |
| bool checkConstructorEscape(Scope* sc, CallExp ce, bool gag) |
| { |
| enum log = false; |
| if (log) printf("checkConstructorEscape(%s, %s)\n", ce.toChars(), ce.type.toChars()); |
| Type tthis = ce.type.toBasetype(); |
| assert(tthis.ty == Tstruct); |
| if (!tthis.hasPointers()) |
| return false; |
| |
| if (!ce.arguments && ce.arguments.length) |
| return false; |
| |
| DotVarExp dve = ce.e1.isDotVarExp(); |
| CtorDeclaration ctor = dve.var.isCtorDeclaration(); |
| TypeFunction tf = ctor.type.isTypeFunction(); |
| |
| const nparams = tf.parameterList.length; |
| const n = ce.arguments.length; |
| |
| // j=1 if _arguments[] is first argument |
| const j = tf.isDstyleVariadic(); |
| |
| /* Attempt to assign each `return` arg to the `this` reference |
| */ |
| foreach (const i; 0 .. n) |
| { |
| Expression arg = (*ce.arguments)[i]; |
| //printf("\targ[%d]: %s\n", i, arg.toChars()); |
| |
| if (i - j < nparams && i >= j) |
| { |
| Parameter p = tf.parameterList[i - j]; |
| if (checkParamArgumentReturn(sc, dve.e1, arg, p, gag)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /// How a `return` parameter escapes its pointer value |
| enum ReturnParamDest |
| { |
| returnVal, /// through return statement: `return x` |
| this_, /// assigned to a struct instance: `this.x = x` |
| firstArg, /// assigned to first argument: `firstArg = x` |
| } |
| |
| /**************************************** |
| * Find out if instead of returning a `return` parameter via a return statement, |
| * it is returned via assignment to either `this` or the first parameter. |
| * |
| * This works the same as returning the value via a return statement. |
| * Although the first argument must be `ref`, it is not regarded as returning by `ref`. |
| * |
| * See_Also: https://dlang.org.spec/function.html#return-ref-parameters |
| * |
| * Params: |
| * tf = function type |
| * tthis = type of `this` parameter, or `null` if none |
| * Returns: What a `return` parameter should transfer the lifetime of the argument to |
| */ |
| ReturnParamDest returnParamDest(TypeFunction tf, Type tthis) |
| { |
| assert(tf); |
| if (tf.isctor) |
| return ReturnParamDest.this_; |
| |
| if (!tf.nextOf() || (tf.nextOf().ty != Tvoid)) |
| return ReturnParamDest.returnVal; |
| |
| if (tthis && tthis.toBasetype().ty == Tstruct) // class `this` is passed by value |
| return ReturnParamDest.this_; |
| |
| if (tf.parameterList.length > 0 && tf.parameterList[0].isReference) |
| return ReturnParamDest.firstArg; |
| |
| return ReturnParamDest.returnVal; |
| } |
| |
| /**************************************** |
| * Given an `AssignExp`, determine if the lvalue will cause |
| * the contents of the rvalue to escape. |
| * Print error messages when these are detected. |
| * Infer `scope` attribute for the lvalue where possible, in order |
| * to eliminate the error. |
| * Params: |
| * sc = used to determine current function and module |
| * e = `AssignExp` or `CatAssignExp` to check for any pointers to the stack |
| * gag = do not print error messages |
| * byRef = set to `true` if `e1` of `e` gets assigned a reference to `e2` |
| * Returns: |
| * `true` if pointers to the stack can escape via assignment |
| */ |
| bool checkAssignEscape(Scope* sc, Expression e, bool gag, bool byRef) |
| { |
| enum log = false; |
| if (log) printf("checkAssignEscape(e: %s, byRef: %d)\n", e.toChars(), byRef); |
| if (e.op != EXP.assign && e.op != EXP.blit && e.op != EXP.construct && |
| e.op != EXP.concatenateAssign && e.op != EXP.concatenateElemAssign && e.op != EXP.concatenateDcharAssign) |
| return false; |
| auto ae = cast(BinExp)e; |
| Expression e1 = ae.e1; |
| Expression e2 = ae.e2; |
| //printf("type = %s, %d\n", e1.type.toChars(), e1.type.hasPointers()); |
| |
| if (!e1.type.hasPointers()) |
| return false; |
| |
| if (e1.isSliceExp()) |
| { |
| if (VarDeclaration va = expToVariable(e1)) |
| { |
| if (!va.type.toBasetype().isTypeSArray() || // treat static array slice same as a variable |
| !va.type.hasPointers()) |
| return false; |
| } |
| else |
| return false; |
| } |
| |
| /* The struct literal case can arise from the S(e2) constructor call: |
| * return S(e2); |
| * and appears in this function as: |
| * structLiteral = e2; |
| * Such an assignment does not necessarily remove scope-ness. |
| */ |
| if (e1.isStructLiteralExp()) |
| return false; |
| |
| VarDeclaration va = expToVariable(e1); |
| EscapeByResults er; |
| |
| if (byRef) |
| escapeByRef(e2, &er); |
| else |
| escapeByValue(e2, &er); |
| |
| if (!er.byref.length && !er.byvalue.length && !er.byfunc.length && !er.byexp.length) |
| return false; |
| |
| |
| if (va && e.op == EXP.concatenateElemAssign) |
| { |
| /* https://issues.dlang.org/show_bug.cgi?id=17842 |
| * Draw an equivalence between: |
| * *q = p; |
| * and: |
| * va ~= e; |
| * since we are not assigning to va, but are assigning indirectly through va. |
| */ |
| va = null; |
| } |
| |
| if (va && e1.isDotVarExp() && va.type.toBasetype().isTypeClass()) |
| { |
| /* https://issues.dlang.org/show_bug.cgi?id=17949 |
| * Draw an equivalence between: |
| * *q = p; |
| * and: |
| * va.field = e2; |
| * since we are not assigning to va, but are assigning indirectly through class reference va. |
| */ |
| va = null; |
| } |
| |
| if (log && va) printf("va: %s\n", va.toChars()); |
| |
| FuncDeclaration fd = sc.func; |
| |
| |
| // Determine if va is a `ref` parameter, so it has a lifetime exceding the function scope |
| const bool vaIsRef = va && va.isParameter() && va.isReference(); |
| if (log && vaIsRef) printf("va is ref `%s`\n", va.toChars()); |
| |
| // Determine if va is the first parameter, through which other 'return' parameters |
| // can be assigned. |
| bool vaIsFirstRef = false; |
| if (fd && fd.type) |
| { |
| final switch (returnParamDest(fd.type.isTypeFunction(), fd.vthis ? fd.vthis.type : null)) |
| { |
| case ReturnParamDest.this_: |
| vaIsFirstRef = va == fd.vthis; |
| break; |
| case ReturnParamDest.firstArg: |
| vaIsFirstRef = (*fd.parameters)[0] == va; |
| break; |
| case ReturnParamDest.returnVal: |
| break; |
| } |
| } |
| if (log && vaIsFirstRef) printf("va is first ref `%s`\n", va.toChars()); |
| |
| bool result = false; |
| foreach (VarDeclaration v; er.byvalue) |
| { |
| if (log) printf("byvalue: %s\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| if (v == va) |
| continue; |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (va && !vaIsRef && !va.isScope() && !v.isScope() && |
| !v.isTypesafeVariadicArray && !va.isTypesafeVariadicArray && |
| (va.isParameter() && va.maybeScope && v.isParameter() && v.maybeScope) && |
| p == fd) |
| { |
| /* Add v to va's list of dependencies |
| */ |
| va.addMaybe(v); |
| continue; |
| } |
| |
| if (vaIsFirstRef && p == fd) |
| { |
| inferReturn(fd, v, /*returnScope:*/ true); |
| } |
| |
| if (!(va && va.isScope()) || vaIsRef) |
| notMaybeScope(v, e); |
| |
| if (v.isScope()) |
| { |
| if (vaIsFirstRef && v.isParameter() && v.isReturn()) |
| { |
| // va=v, where v is `return scope` |
| if (inferScope(va)) |
| continue; |
| } |
| |
| // If va's lifetime encloses v's, then error |
| if (EnclosedBy eb = va.enclosesLifetimeOf(v)) |
| { |
| const(char)* msg; |
| final switch (eb) |
| { |
| case EnclosedBy.none: assert(0); |
| case EnclosedBy.returnScope: |
| msg = "scope variable `%s` assigned to return scope `%s`"; |
| break; |
| case EnclosedBy.longerScope: |
| if (v.storage_class & STC.temp) |
| continue; |
| msg = "scope variable `%s` assigned to `%s` with longer lifetime"; |
| break; |
| case EnclosedBy.refVar: |
| msg = "scope variable `%s` assigned to `ref` variable `%s` with longer lifetime"; |
| break; |
| case EnclosedBy.global: |
| msg = "scope variable `%s` assigned to global variable `%s`"; |
| break; |
| } |
| |
| if (sc.setUnsafeDIP1000(gag, ae.loc, msg, v, va)) |
| { |
| result = true; |
| continue; |
| } |
| } |
| |
| // v = scope, va should be scope as well |
| const vaWasScope = va && va.isScope(); |
| if (inferScope(va)) |
| { |
| // In case of `scope local = returnScopeParam`, do not infer return scope for `x` |
| if (!vaWasScope && v.isReturn() && !va.isReturn()) |
| { |
| if (log) printf("infer return for %s\n", va.toChars()); |
| va.storage_class |= STC.return_ | STC.returninferred; |
| |
| // Added "return scope" so don't confuse it with "return ref" |
| if (isRefReturnScope(va.storage_class)) |
| va.storage_class |= STC.returnScope; |
| } |
| continue; |
| } |
| result |= sc.setUnsafeDIP1000(gag, ae.loc, "scope variable `%s` assigned to non-scope `%s`", v, e1); |
| } |
| else if (v.isTypesafeVariadicArray && p == fd) |
| { |
| if (inferScope(va)) |
| continue; |
| result |= sc.setUnsafeDIP1000(gag, ae.loc, "variadic variable `%s` assigned to non-scope `%s`", v, e1); |
| } |
| else |
| { |
| /* v is not 'scope', and we didn't check the scope of where we assigned it to. |
| * It may escape via that assignment, therefore, v can never be 'scope'. |
| */ |
| //printf("no infer for %s in %s, %d\n", v.toChars(), fd.ident.toChars(), __LINE__); |
| doNotInferScope(v, e); |
| } |
| } |
| |
| foreach (VarDeclaration v; er.byref) |
| { |
| if (log) printf("byref: %s\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| if (checkScopeVarAddr(v, ae, sc, gag)) |
| { |
| result = true; |
| continue; |
| } |
| |
| if (va && va.isScope() && !v.isReference()) |
| { |
| if (!va.isReturn()) |
| { |
| va.doNotInferReturn = true; |
| } |
| else |
| { |
| result |= sc.setUnsafeDIP1000(gag, ae.loc, |
| "address of local variable `%s` assigned to return scope `%s`", v, va); |
| } |
| } |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (vaIsFirstRef && p == fd) |
| { |
| //if (log) printf("inferring 'return' for parameter %s in function %s\n", v.toChars(), fd.toChars()); |
| inferReturn(fd, v, /*returnScope:*/ false); |
| } |
| |
| // If va's lifetime encloses v's, then error |
| if (va && !(vaIsFirstRef && v.isReturn()) && va.enclosesLifetimeOf(v)) |
| { |
| if (sc.setUnsafeDIP1000(gag, ae.loc, "address of variable `%s` assigned to `%s` with longer lifetime", v, va)) |
| { |
| result = true; |
| continue; |
| } |
| } |
| |
| if (!(va && va.isScope())) |
| notMaybeScope(v, e); |
| |
| if (p != sc.func) |
| continue; |
| |
| if (inferScope(va)) |
| { |
| if (v.isReturn() && !va.isReturn()) |
| va.storage_class |= STC.return_ | STC.returninferred; |
| continue; |
| } |
| if (e1.op == EXP.structLiteral) |
| continue; |
| |
| result |= sc.setUnsafeDIP1000(gag, ae.loc, "reference to local variable `%s` assigned to non-scope `%s`", v, e1); |
| } |
| |
| foreach (FuncDeclaration func; er.byfunc) |
| { |
| if (log) printf("byfunc: %s, %d\n", func.toChars(), func.tookAddressOf); |
| VarDeclarations vars; |
| findAllOuterAccessedVariables(func, &vars); |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=16037 |
| * If assigning the address of a delegate to a scope variable, |
| * then uncount that address of. This is so it won't cause a |
| * closure to be allocated. |
| */ |
| if (va && va.isScope() && !va.isReturn() && func.tookAddressOf) |
| --func.tookAddressOf; |
| |
| foreach (v; vars) |
| { |
| //printf("v = %s\n", v.toChars()); |
| assert(!v.isDataseg()); // these are not put in the closureVars[] |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (!(va && va.isScope())) |
| notMaybeScope(v, e); |
| |
| if (!(v.isReference() || v.isScope()) || p != fd) |
| continue; |
| |
| if (va && !va.isDataseg() && (va.isScope() || va.maybeScope)) |
| { |
| /* Don't infer STC.scope_ for va, because then a closure |
| * won't be generated for fd. |
| */ |
| //if (!va.isScope()) |
| //va.storage_class |= STC.scope_ | STC.scopeinferred; |
| continue; |
| } |
| result |= sc.setUnsafeDIP1000(gag, ae.loc, |
| "reference to local `%s` assigned to non-scope `%s` in @safe code", v, e1); |
| } |
| } |
| |
| foreach (Expression ee; er.byexp) |
| { |
| if (log) printf("byexp: %s\n", ee.toChars()); |
| |
| /* Do not allow slicing of a static array returned by a function |
| */ |
| if (ee.op == EXP.call && ee.type.toBasetype().isTypeSArray() && e1.type.toBasetype().isTypeDArray() && |
| !(va && va.storage_class & STC.temp)) |
| { |
| if (!gag) |
| deprecation(ee.loc, "slice of static array temporary returned by `%s` assigned to longer lived variable `%s`", |
| ee.toChars(), e1.toChars()); |
| //result = true; |
| continue; |
| } |
| |
| if (ee.op == EXP.call && ee.type.toBasetype().isTypeStruct() && |
| (!va || !(va.storage_class & STC.temp) && !va.isScope())) |
| { |
| if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct temporary returned by `%s` assigned to longer lived variable `%s`", ee, e1)) |
| { |
| result = true; |
| continue; |
| } |
| } |
| |
| if (ee.op == EXP.structLiteral && |
| (!va || !(va.storage_class & STC.temp))) |
| { |
| if (sc.setUnsafeDIP1000(gag, ee.loc, "address of struct literal `%s` assigned to longer lived variable `%s`", ee, e1)) |
| { |
| result = true; |
| continue; |
| } |
| } |
| |
| if (inferScope(va)) |
| continue; |
| |
| result |= sc.setUnsafeDIP1000(gag, ee.loc, |
| "reference to stack allocated value returned by `%s` assigned to non-scope `%s`", ee, e1); |
| } |
| |
| return result; |
| } |
| |
| /************************************ |
| * Detect cases where pointers to the stack can escape the |
| * lifetime of the stack frame when throwing `e`. |
| * Print error messages when these are detected. |
| * Params: |
| * sc = used to determine current function and module |
| * e = expression to check for any pointers to the stack |
| * gag = do not print error messages |
| * Returns: |
| * `true` if pointers to the stack can escape |
| */ |
| bool checkThrowEscape(Scope* sc, Expression e, bool gag) |
| { |
| //printf("[%s] checkThrowEscape, e = %s\n", e.loc.toChars(), e.toChars()); |
| EscapeByResults er; |
| |
| escapeByValue(e, &er); |
| |
| if (!er.byref.length && !er.byvalue.length && !er.byexp.length) |
| return false; |
| |
| bool result = false; |
| foreach (VarDeclaration v; er.byvalue) |
| { |
| //printf("byvalue %s\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| if (v.isScope() && !v.iscatchvar) // special case: allow catch var to be rethrown |
| // despite being `scope` |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=17029 |
| result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be thrown", v); |
| continue; |
| } |
| else |
| { |
| notMaybeScope(v, new ThrowExp(e.loc, e)); |
| } |
| } |
| return result; |
| } |
| |
| /************************************ |
| * Detect cases where pointers to the stack can escape the |
| * lifetime of the stack frame by being placed into a GC allocated object. |
| * Print error messages when these are detected. |
| * Params: |
| * sc = used to determine current function and module |
| * e = expression to check for any pointers to the stack |
| * gag = do not print error messages |
| * Returns: |
| * `true` if pointers to the stack can escape |
| */ |
| bool checkNewEscape(Scope* sc, Expression e, bool gag) |
| { |
| import dmd.globals: FeatureState; |
| import dmd.errors: previewErrorFunc; |
| |
| //printf("[%s] checkNewEscape, e = %s\n", e.loc.toChars(), e.toChars()); |
| enum log = false; |
| if (log) printf("[%s] checkNewEscape, e: `%s`\n", e.loc.toChars(), e.toChars()); |
| EscapeByResults er; |
| |
| escapeByValue(e, &er); |
| |
| if (!er.byref.length && !er.byvalue.length && !er.byexp.length) |
| return false; |
| |
| bool result = false; |
| foreach (VarDeclaration v; er.byvalue) |
| { |
| if (log) printf("byvalue `%s`\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (v.isScope()) |
| { |
| if ( |
| /* This case comes up when the ReturnStatement of a __foreachbody is |
| * checked for escapes by the caller of __foreachbody. Skip it. |
| * |
| * struct S { static int opApply(int delegate(S*) dg); } |
| * S* foo() { |
| * foreach (S* s; S) // create __foreachbody for body of foreach |
| * return s; // s is inferred as 'scope' but incorrectly tested in foo() |
| * return null; } |
| */ |
| !(p.parent == sc.func)) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=20868 |
| result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be copied into allocated memory", v); |
| continue; |
| } |
| } |
| else if (v.isTypesafeVariadicArray && p == sc.func) |
| { |
| result |= sc.setUnsafeDIP1000(gag, e.loc, |
| "copying `%s` into allocated memory escapes a reference to variadic parameter `%s`", e, v); |
| } |
| else |
| { |
| //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__); |
| notMaybeScope(v, e); |
| } |
| } |
| |
| foreach (VarDeclaration v; er.byref) |
| { |
| if (log) printf("byref `%s`\n", v.toChars()); |
| |
| // 'featureState' tells us whether to emit an error or a deprecation, |
| // depending on the flag passed to the CLI for DIP25 / DIP1000 |
| bool escapingRef(VarDeclaration v, FeatureState fs) |
| { |
| const(char)* msg = v.isParameter() ? |
| "copying `%s` into allocated memory escapes a reference to parameter `%s`" : |
| "copying `%s` into allocated memory escapes a reference to local variable `%s`"; |
| return sc.setUnsafePreview(fs, gag, e.loc, msg, e, v); |
| } |
| |
| if (v.isDataseg()) |
| continue; |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (!v.isReference()) |
| { |
| if (p == sc.func) |
| { |
| result |= escapingRef(v, global.params.useDIP1000); |
| continue; |
| } |
| } |
| |
| /* Check for returning a ref variable by 'ref', but should be 'return ref' |
| * Infer the addition of 'return', or set result to be the offending expression. |
| */ |
| if (!v.isReference()) |
| continue; |
| |
| // https://dlang.org/spec/function.html#return-ref-parameters |
| if (p == sc.func) |
| { |
| //printf("escaping reference to local ref variable %s\n", v.toChars()); |
| //printf("storage class = x%llx\n", v.storage_class); |
| result |= escapingRef(v, global.params.useDIP25); |
| continue; |
| } |
| // Don't need to be concerned if v's parent does not return a ref |
| FuncDeclaration func = p.isFuncDeclaration(); |
| if (!func || !func.type) |
| continue; |
| if (auto tf = func.type.isTypeFunction()) |
| { |
| if (!tf.isref) |
| continue; |
| |
| const(char)* msg = "storing reference to outer local variable `%s` into allocated memory causes it to escape"; |
| if (!gag) |
| { |
| previewErrorFunc(sc.isDeprecated(), global.params.useDIP25)(e.loc, msg, v.toChars()); |
| } |
| |
| // If -preview=dip25 is used, the user wants an error |
| // Otherwise, issue a deprecation |
| result |= (global.params.useDIP25 == FeatureState.enabled); |
| } |
| } |
| |
| foreach (Expression ee; er.byexp) |
| { |
| if (log) printf("byexp %s\n", ee.toChars()); |
| if (!gag) |
| error(ee.loc, "storing reference to stack allocated value returned by `%s` into allocated memory causes it to escape", |
| ee.toChars()); |
| result = true; |
| } |
| |
| return result; |
| } |
| |
| |
| /************************************ |
| * Detect cases where pointers to the stack can escape the |
| * lifetime of the stack frame by returning `e` by value. |
| * Print error messages when these are detected. |
| * Params: |
| * sc = used to determine current function and module |
| * e = expression to check for any pointers to the stack |
| * gag = do not print error messages |
| * Returns: |
| * `true` if pointers to the stack can escape |
| */ |
| bool checkReturnEscape(Scope* sc, Expression e, bool gag) |
| { |
| //printf("[%s] checkReturnEscape, e: %s\n", e.loc.toChars(), e.toChars()); |
| return checkReturnEscapeImpl(sc, e, false, gag); |
| } |
| |
| /************************************ |
| * Detect cases where returning `e` by `ref` can result in a reference to the stack |
| * being returned. |
| * Print error messages when these are detected. |
| * Params: |
| * sc = used to determine current function and module |
| * e = expression to check |
| * gag = do not print error messages |
| * Returns: |
| * `true` if references to the stack can escape |
| */ |
| bool checkReturnEscapeRef(Scope* sc, Expression e, bool gag) |
| { |
| version (none) |
| { |
| printf("[%s] checkReturnEscapeRef, e = %s\n", e.loc.toChars(), e.toChars()); |
| printf("current function %s\n", sc.func.toChars()); |
| printf("parent2 function %s\n", sc.func.toParent2().toChars()); |
| } |
| |
| return checkReturnEscapeImpl(sc, e, true, gag); |
| } |
| |
| /*************************************** |
| * Implementation of checking for escapes in return expressions. |
| * Params: |
| * sc = used to determine current function and module |
| * e = expression to check |
| * refs = `true`: escape by value, `false`: escape by `ref` |
| * gag = do not print error messages |
| * Returns: |
| * `true` if references to the stack can escape |
| */ |
| private bool checkReturnEscapeImpl(Scope* sc, Expression e, bool refs, bool gag) |
| { |
| enum log = false; |
| if (log) printf("[%s] checkReturnEscapeImpl, refs: %d e: `%s`\n", e.loc.toChars(), refs, e.toChars()); |
| EscapeByResults er; |
| |
| if (refs) |
| escapeByRef(e, &er); |
| else |
| escapeByValue(e, &er); |
| |
| if (!er.byref.length && !er.byvalue.length && !er.byexp.length) |
| return false; |
| |
| bool result = false; |
| foreach (VarDeclaration v; er.byvalue) |
| { |
| if (log) printf("byvalue `%s`\n", v.toChars()); |
| if (v.isDataseg()) |
| continue; |
| |
| const vsr = buildScopeRef(v.storage_class); |
| |
| Dsymbol p = v.toParent2(); |
| |
| if (p == sc.func && inferReturn(sc.func, v, /*returnScope:*/ true)) |
| { |
| continue; |
| } |
| |
| if (v.isScope()) |
| { |
| /* If `return scope` applies to v. |
| */ |
| if (vsr == ScopeRef.ReturnScope || |
| vsr == ScopeRef.Ref_ReturnScope) |
| { |
| continue; |
| } |
| |
| auto pfunc = p.isFuncDeclaration(); |
| if (pfunc && |
| /* This case comes up when the ReturnStatement of a __foreachbody is |
| * checked for escapes by the caller of __foreachbody. Skip it. |
| * |
| * struct S { static int opApply(int delegate(S*) dg); } |
| * S* foo() { |
| * foreach (S* s; S) // create __foreachbody for body of foreach |
| * return s; // s is inferred as 'scope' but incorrectly tested in foo() |
| * return null; } |
| */ |
| !(!refs && p.parent == sc.func && pfunc.fes) && |
| /* |
| * auto p(scope string s) { |
| * string scfunc() { return s; } |
| * } |
| */ |
| !(!refs && sc.func.isFuncDeclaration().getLevel(pfunc, sc.intypeof) > 0) |
| ) |
| { |
| if (v.isParameter() && !v.isReturn()) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=23191 |
| if (!gag) |
| { |
| previewErrorFunc(sc.isDeprecated(), global.params.useDIP1000)(e.loc, |
| "scope parameter `%s` may not be returned", v.toChars() |
| ); |
| result = true; |
| continue; |
| } |
| } |
| else |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=17029 |
| result |= sc.setUnsafeDIP1000(gag, e.loc, "scope variable `%s` may not be returned", v); |
| continue; |
| } |
| } |
| } |
| else if (v.isTypesafeVariadicArray && p == sc.func) |
| { |
| if (!gag) |
| error(e.loc, "returning `%s` escapes a reference to variadic parameter `%s`", e.toChars(), v.toChars()); |
| result = false; |
| } |
| else |
| { |
| //printf("no infer for %s in %s, %d\n", v.toChars(), sc.func.ident.toChars(), __LINE__); |
| doNotInferScope(v, e); |
| } |
| } |
| |
| foreach (i, VarDeclaration v; er.byref[]) |
| { |
| if (log) |
| { |
| printf("byref `%s` %s\n", v.toChars(), toChars(buildScopeRef(v.storage_class))); |
| } |
| |
| // 'featureState' tells us whether to emit an error or a deprecation, |
| // depending on the flag passed to the CLI for DIP25 |
| void escapingRef(VarDeclaration v, FeatureState featureState) |
| { |
| const(char)* msg = v.isParameter() ? |
| "returning `%s` escapes a reference to parameter `%s`" : |
| "returning `%s` escapes a reference to local variable `%s`"; |
| |
| if (v.isParameter() && v.isReference()) |
| { |
| if (sc.setUnsafePreview(featureState, gag, e.loc, msg, e, v) || |
| sc.func.isSafeBypassingInference()) |
| { |
| result = true; |
| if (v.storage_class & STC.returnScope) |
| { |
| previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc, |
| "perhaps change the `return scope` into `scope return`"); |
| } |
| else |
| { |
| const(char)* annotateKind = (v.ident is Id.This) ? "function" : "parameter"; |
| previewSupplementalFunc(sc.isDeprecated(), featureState)(v.loc, |
| "perhaps annotate the %s with `return`", annotateKind); |
| } |
| } |
| } |
| else |
| { |
| if (er.refRetRefTransition[i]) |
| { |
| result |= sc.setUnsafeDIP1000(gag, e.loc, msg, e, v); |
| } |
| else |
| { |
| if (!gag) |
| previewErrorFunc(sc.isDeprecated(), featureState)(e.loc, msg, e.toChars(), v.toChars()); |
| result = true; |
| } |
| } |
| } |
| |
| if (v.isDataseg()) |
| continue; |
| |
| const vsr = buildScopeRef(v.storage_class); |
| |
| Dsymbol p = v.toParent2(); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=19965 |
| if (!refs) |
| { |
| if (sc.func.vthis == v) |
| notMaybeScope(v, e); |
| |
| if (checkScopeVarAddr(v, e, sc, gag)) |
| { |
| result = true; |
| continue; |
| } |
| } |
| |
| if (!v.isReference()) |
| { |
| if (p == sc.func) |
| { |
| escapingRef(v, FeatureState.enabled); |
| continue; |
| } |
| FuncDeclaration fd = p.isFuncDeclaration(); |
| if (fd && sc.func.returnInprocess) |
| { |
| /* Code like: |
| * int x; |
| * auto dg = () { return &x; } |
| * Making it: |
| * auto dg = () return { return &x; } |
| * Because dg.ptr points to x, this is returning dt.ptr+offset |
| */ |
| sc.func.storage_class |= STC.return_ | STC.returninferred; |
| } |
| } |
| |
| /* Check for returning a ref variable by 'ref', but should be 'return ref' |
| * Infer the addition of 'return', or set result to be the offending expression. |
| */ |
| if ((vsr == ScopeRef.Ref || |
| vsr == ScopeRef.RefScope || |
| vsr == ScopeRef.Ref_ReturnScope) && |
| !(v.storage_class & STC.foreach_)) |
| { |
| if (p == sc.func && (vsr == ScopeRef.Ref || vsr == ScopeRef.RefScope) && |
| inferReturn(sc.func, v, /*returnScope:*/ false)) |
| { |
| continue; |
| } |
| else |
| { |
| // https://dlang.org/spec/function.html#return-ref-parameters |
| // Only look for errors if in module listed on command line |
| if (p == sc.func) |
| { |
| //printf("escaping reference to local ref variable %s\n", v.toChars()); |
| //printf("storage class = x%llx\n", v.storage_class); |
| escapingRef(v, global.params.useDIP25); |
| continue; |
| } |
| // Don't need to be concerned if v's parent does not return a ref |
| FuncDeclaration fd = p.isFuncDeclaration(); |
| if (fd && fd.type && fd.type.ty == Tfunction) |
| { |
| TypeFunction tf = fd.type.isTypeFunction(); |
| if (tf.isref) |
| { |
| const(char)* msg = "escaping reference to outer local variable `%s`"; |
| if (!gag) |
| previewErrorFunc(sc.isDeprecated(), global.params.useDIP25)(e.loc, msg, v.toChars()); |
| result = true; |
| continue; |
| } |
| } |
| |
| } |
| } |
| } |
| |
| foreach (i, Expression ee; er.byexp[]) |
| { |
| if (log) printf("byexp %s\n", ee.toChars()); |
| if (er.expRetRefTransition[i]) |
| { |
| result |= sc.setUnsafeDIP1000(gag, ee.loc, |
| "escaping reference to stack allocated value returned by `%s`", ee); |
| } |
| else |
| { |
| if (!gag) |
| error(ee.loc, "escaping reference to stack allocated value returned by `%s`", ee.toChars()); |
| result = true; |
| } |
| } |
| return result; |
| } |
| |
| /*********************************** |
| * Infer `scope` for a variable |
| * |
| * Params: |
| * va = variable to infer scope for |
| * Returns: `true` if succesful or already `scope` |
| */ |
| bool inferScope(VarDeclaration va) |
| { |
| if (!va) |
| return false; |
| if (!va.isDataseg() && va.maybeScope && !va.isScope()) |
| { |
| //printf("inferring scope for %s\n", va.toChars()); |
| va.maybeScope = false; |
| va.storage_class |= STC.scope_ | STC.scopeinferred; |
| return true; |
| } |
| return va.isScope(); |
| } |
| |
| /************************************* |
| * Variable v needs to have 'return' inferred for it. |
| * Params: |
| * fd = function that v is a parameter to |
| * v = parameter that needs to be STC.return_ |
| * returnScope = infer `return scope` instead of `return ref` |
| * |
| * Returns: whether the inference on `v` was successful or `v` already was `return` |
| */ |
| private bool inferReturn(FuncDeclaration fd, VarDeclaration v, bool returnScope) |
| { |
| if (v.isReturn()) |
| return !!(v.storage_class & STC.returnScope) == returnScope; |
| |
| if (!v.isParameter() || v.isTypesafeVariadicArray || (returnScope && v.doNotInferReturn)) |
| return false; |
| |
| if (!fd.returnInprocess) |
| return false; |
| |
| if (returnScope && !(v.isScope() || v.maybeScope)) |
| return false; |
| |
| //printf("for function '%s' inferring 'return' for variable '%s', returnScope: %d\n", fd.toChars(), v.toChars(), returnScope); |
| auto newStcs = STC.return_ | STC.returninferred | (returnScope ? STC.returnScope : 0); |
| v.storage_class |= newStcs; |
| |
| if (v == fd.vthis) |
| { |
| /* v is the 'this' reference, so mark the function |
| */ |
| fd.storage_class |= newStcs; |
| if (auto tf = fd.type.isTypeFunction()) |
| { |
| //printf("'this' too %p %s\n", tf, sc.func.toChars()); |
| tf.isreturnscope = returnScope; |
| tf.isreturn = true; |
| tf.isreturninferred = true; |
| } |
| } |
| else |
| { |
| // Perform 'return' inference on parameter |
| if (auto tf = fd.type.isTypeFunction()) |
| { |
| foreach (i, p; tf.parameterList) |
| { |
| if (p.ident == v.ident) |
| { |
| p.storageClass |= newStcs; |
| break; // there can be only one |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| |
| /**************************************** |
| * e is an expression to be returned by value, and that value contains pointers. |
| * Walk e to determine which variables are possibly being |
| * returned by value, such as: |
| * int* function(int* p) { return p; } |
| * If e is a form of &p, determine which variables have content |
| * which is being returned as ref, such as: |
| * int* function(int i) { return &i; } |
| * Multiple variables can be inserted, because of expressions like this: |
| * int function(bool b, int i, int* p) { return b ? &i : p; } |
| * |
| * No side effects. |
| * |
| * Params: |
| * e = expression to be returned by value |
| * er = where to place collected data |
| * live = if @live semantics apply, i.e. expressions `p`, `*p`, `**p`, etc., all return `p`. |
| * retRefTransition = if `e` is returned through a `return ref scope` function call |
| */ |
| void escapeByValue(Expression e, EscapeByResults* er, bool live = false, bool retRefTransition = false) |
| { |
| //printf("[%s] escapeByValue, e: %s\n", e.loc.toChars(), e.toChars()); |
| |
| void visit(Expression e) |
| { |
| } |
| |
| void visitAddr(AddrExp e) |
| { |
| /* Taking the address of struct literal is normally not |
| * allowed, but CTFE can generate one out of a new expression, |
| * but it'll be placed in static data so no need to check it. |
| */ |
| if (e.e1.op != EXP.structLiteral) |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitSymOff(SymOffExp e) |
| { |
| VarDeclaration v = e.var.isVarDeclaration(); |
| if (v) |
| er.pushRef(v, retRefTransition); |
| } |
| |
| void visitVar(VarExp e) |
| { |
| if (auto v = e.var.isVarDeclaration()) |
| { |
| if (v.type.hasPointers() || // not tracking non-pointers |
| v.storage_class & STC.lazy_) // lazy variables are actually pointers |
| er.byvalue.push(v); |
| } |
| } |
| |
| void visitThis(ThisExp e) |
| { |
| if (e.var) |
| er.byvalue.push(e.var); |
| } |
| |
| void visitPtr(PtrExp e) |
| { |
| if (live && e.type.hasPointers()) |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitDotVar(DotVarExp e) |
| { |
| auto t = e.e1.type.toBasetype(); |
| if (e.type.hasPointers() && (live || t.ty == Tstruct)) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| } |
| |
| void visitDelegate(DelegateExp e) |
| { |
| Type t = e.e1.type.toBasetype(); |
| if (t.ty == Tclass || t.ty == Tpointer) |
| escapeByValue(e.e1, er, live, retRefTransition); |
| else |
| escapeByRef(e.e1, er, live, retRefTransition); |
| er.byfunc.push(e.func); |
| } |
| |
| void visitFunc(FuncExp e) |
| { |
| if (e.fd.tok == TOK.delegate_) |
| er.byfunc.push(e.fd); |
| } |
| |
| void visitTuple(TupleExp e) |
| { |
| assert(0); // should have been lowered by now |
| } |
| |
| void visitArrayLiteral(ArrayLiteralExp e) |
| { |
| Type tb = e.type.toBasetype(); |
| if (tb.ty == Tsarray || tb.ty == Tarray) |
| { |
| if (e.basis) |
| escapeByValue(e.basis, er, live, retRefTransition); |
| foreach (el; *e.elements) |
| { |
| if (el) |
| escapeByValue(el, er, live, retRefTransition); |
| } |
| } |
| } |
| |
| void visitStructLiteral(StructLiteralExp e) |
| { |
| if (e.elements) |
| { |
| foreach (ex; *e.elements) |
| { |
| if (ex) |
| escapeByValue(ex, er, live, retRefTransition); |
| } |
| } |
| } |
| |
| void visitNew(NewExp e) |
| { |
| Type tb = e.newtype.toBasetype(); |
| if (tb.ty == Tstruct && !e.member && e.arguments) |
| { |
| foreach (ex; *e.arguments) |
| { |
| if (ex) |
| escapeByValue(ex, er, live, retRefTransition); |
| } |
| } |
| } |
| |
| void visitCast(CastExp e) |
| { |
| if (!e.type.hasPointers()) |
| return; |
| Type tb = e.type.toBasetype(); |
| if (tb.ty == Tarray && e.e1.type.toBasetype().ty == Tsarray) |
| { |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| else |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitSlice(SliceExp e) |
| { |
| if (auto ve = e.e1.isVarExp()) |
| { |
| VarDeclaration v = ve.var.isVarDeclaration(); |
| Type tb = e.type.toBasetype(); |
| if (v) |
| { |
| if (tb.ty == Tsarray) |
| return; |
| if (v.isTypesafeVariadicArray) |
| { |
| er.byvalue.push(v); |
| return; |
| } |
| } |
| } |
| Type t1b = e.e1.type.toBasetype(); |
| if (t1b.ty == Tsarray) |
| { |
| Type tb = e.type.toBasetype(); |
| if (tb.ty != Tsarray) |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| else |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitIndex(IndexExp e) |
| { |
| if (e.e1.type.toBasetype().ty == Tsarray || |
| live && e.type.hasPointers()) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| } |
| |
| void visitBin(BinExp e) |
| { |
| Type tb = e.type.toBasetype(); |
| if (tb.ty == Tpointer) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| escapeByValue(e.e2, er, live, retRefTransition); |
| } |
| } |
| |
| void visitBinAssign(BinAssignExp e) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitAssign(AssignExp e) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitComma(CommaExp e) |
| { |
| escapeByValue(e.e2, er, live, retRefTransition); |
| } |
| |
| void visitCond(CondExp e) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| escapeByValue(e.e2, er, live, retRefTransition); |
| } |
| |
| void visitCall(CallExp e) |
| { |
| //printf("CallExp(): %s\n", e.toChars()); |
| /* Check each argument that is |
| * passed as 'return scope'. |
| */ |
| Type t1 = e.e1.type.toBasetype(); |
| TypeFunction tf; |
| TypeDelegate dg; |
| if (t1.ty == Tdelegate) |
| { |
| dg = t1.isTypeDelegate(); |
| tf = dg.next.isTypeFunction(); |
| } |
| else if (t1.ty == Tfunction) |
| tf = t1.isTypeFunction(); |
| else |
| return; |
| |
| if (!e.type.hasPointers()) |
| return; |
| |
| if (e.arguments && e.arguments.length) |
| { |
| /* j=1 if _arguments[] is first argument, |
| * skip it because it is not passed by ref |
| */ |
| int j = tf.isDstyleVariadic(); |
| for (size_t i = j; i < e.arguments.length; ++i) |
| { |
| Expression arg = (*e.arguments)[i]; |
| size_t nparams = tf.parameterList.length; |
| if (i - j < nparams && i >= j) |
| { |
| Parameter p = tf.parameterList[i - j]; |
| const stc = tf.parameterStorageClass(null, p); |
| ScopeRef psr = buildScopeRef(stc); |
| if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) |
| { |
| if (tf.isref) |
| { |
| /* ignore `ref` on struct constructor return because |
| * struct S { this(return scope int* q) { this.p = q; } int* p; } |
| * is different from: |
| * ref char* front(return scope char** q) { return *q; } |
| * https://github.com/dlang/dmd/pull/14869 |
| */ |
| if (auto dve = e.e1.isDotVarExp()) |
| if (auto fd = dve.var.isFuncDeclaration()) |
| if (fd.isCtorDeclaration() && tf.next.toBasetype().isTypeStruct()) |
| { |
| escapeByValue(arg, er, live, retRefTransition); |
| } |
| } |
| else |
| escapeByValue(arg, er, live, retRefTransition); |
| } |
| else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) |
| { |
| if (tf.isref) |
| { |
| /* Treat: |
| * ref P foo(return ref P p) |
| * as: |
| * p; |
| */ |
| escapeByValue(arg, er, live, retRefTransition); |
| } |
| else |
| escapeByRef(arg, er, live, retRefTransition); |
| } |
| } |
| } |
| } |
| // If 'this' is returned, check it too |
| if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction) |
| { |
| DotVarExp dve = e.e1.isDotVarExp(); |
| FuncDeclaration fd = dve.var.isFuncDeclaration(); |
| if (fd && fd.isThis()) |
| { |
| /* Calling a non-static member function dve.var, which is returning `this`, and with dve.e1 representing `this` |
| */ |
| |
| /***************************** |
| * Concoct storage class for member function's implicit `this` parameter. |
| * Params: |
| * fd = member function |
| * Returns: |
| * storage class for fd's `this` |
| */ |
| StorageClass getThisStorageClass(FuncDeclaration fd) |
| { |
| StorageClass stc; |
| auto tf = fd.type.toBasetype().isTypeFunction(); |
| if (tf.isreturn) |
| stc |= STC.return_; |
| if (tf.isreturnscope) |
| stc |= STC.returnScope | STC.scope_; |
| auto ad = fd.isThis(); |
| if (ad.isClassDeclaration() || tf.isScopeQual) |
| stc |= STC.scope_; |
| if (ad.isStructDeclaration()) |
| stc |= STC.ref_; // `this` for a struct member function is passed by `ref` |
| return stc; |
| } |
| |
| const psr = buildScopeRef(getThisStorageClass(fd)); |
| if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) |
| { |
| if (!tf.isref || tf.isctor) |
| escapeByValue(dve.e1, er, live, retRefTransition); |
| } |
| else if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) |
| { |
| if (tf.isref) |
| { |
| /* Treat calling: |
| * struct S { ref S foo() return; } |
| * as: |
| * this; |
| */ |
| escapeByValue(dve.e1, er, live, retRefTransition); |
| } |
| else |
| escapeByRef(dve.e1, er, live, psr == ScopeRef.ReturnRef_Scope); |
| } |
| } |
| |
| // If it's also a nested function that is 'return scope' |
| if (fd && fd.isNested()) |
| { |
| if (tf.isreturn && tf.isScopeQual) |
| er.pushExp(e, false); |
| } |
| } |
| |
| /* If returning the result of a delegate call, the .ptr |
| * field of the delegate must be checked. |
| */ |
| if (dg) |
| { |
| if (tf.isreturn) |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| /* If it's a nested function that is 'return scope' |
| */ |
| if (auto ve = e.e1.isVarExp()) |
| { |
| FuncDeclaration fd = ve.var.isFuncDeclaration(); |
| if (fd && fd.isNested()) |
| { |
| if (tf.isreturn && tf.isScopeQual) |
| er.pushExp(e, false); |
| } |
| } |
| } |
| |
| switch (e.op) |
| { |
| case EXP.address: return visitAddr(e.isAddrExp()); |
| case EXP.symbolOffset: return visitSymOff(e.isSymOffExp()); |
| case EXP.variable: return visitVar(e.isVarExp()); |
| case EXP.this_: return visitThis(e.isThisExp()); |
| case EXP.star: return visitPtr(e.isPtrExp()); |
| case EXP.dotVariable: return visitDotVar(e.isDotVarExp()); |
| case EXP.delegate_: return visitDelegate(e.isDelegateExp()); |
| case EXP.function_: return visitFunc(e.isFuncExp()); |
| case EXP.tuple: return visitTuple(e.isTupleExp()); |
| case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp()); |
| case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp()); |
| case EXP.new_: return visitNew(e.isNewExp()); |
| case EXP.cast_: return visitCast(e.isCastExp()); |
| case EXP.slice: return visitSlice(e.isSliceExp()); |
| case EXP.index: return visitIndex(e.isIndexExp()); |
| case EXP.blit: return visitAssign(e.isBlitExp()); |
| case EXP.construct: return visitAssign(e.isConstructExp()); |
| case EXP.assign: return visitAssign(e.isAssignExp()); |
| case EXP.comma: return visitComma(e.isCommaExp()); |
| case EXP.question: return visitCond(e.isCondExp()); |
| case EXP.call: return visitCall(e.isCallExp()); |
| default: |
| if (auto b = e.isBinExp()) |
| return visitBin(b); |
| if (auto ba = e.isBinAssignExp()) |
| return visitBinAssign(ba); |
| return visit(e); |
| } |
| } |
| |
| |
| /**************************************** |
| * e is an expression to be returned by 'ref'. |
| * Walk e to determine which variables are possibly being |
| * returned by ref, such as: |
| * ref int function(int i) { return i; } |
| * If e is a form of *p, determine which variables have content |
| * which is being returned as ref, such as: |
| * ref int function(int* p) { return *p; } |
| * Multiple variables can be inserted, because of expressions like this: |
| * ref int function(bool b, int i, int* p) { return b ? i : *p; } |
| * |
| * No side effects. |
| * |
| * Params: |
| * e = expression to be returned by 'ref' |
| * er = where to place collected data |
| * live = if @live semantics apply, i.e. expressions `p`, `*p`, `**p`, etc., all return `p`. |
| * retRefTransition = if `e` is returned through a `return ref scope` function call |
| */ |
| void escapeByRef(Expression e, EscapeByResults* er, bool live = false, bool retRefTransition = false) |
| { |
| //printf("[%s] escapeByRef, e: %s, retRefTransition: %d\n", e.loc.toChars(), e.toChars(), retRefTransition); |
| void visit(Expression e) |
| { |
| } |
| |
| void visitVar(VarExp e) |
| { |
| auto v = e.var.isVarDeclaration(); |
| if (v) |
| { |
| if (v.storage_class & STC.ref_ && v.storage_class & (STC.foreach_ | STC.temp) && v._init) |
| { |
| /* If compiler generated ref temporary |
| * (ref v = ex; ex) |
| * look at the initializer instead |
| */ |
| if (ExpInitializer ez = v._init.isExpInitializer()) |
| { |
| if (auto ce = ez.exp.isConstructExp()) |
| escapeByRef(ce.e2, er, live, retRefTransition); |
| else |
| escapeByRef(ez.exp, er, live, retRefTransition); |
| } |
| } |
| else |
| er.pushRef(v, retRefTransition); |
| } |
| } |
| |
| void visitThis(ThisExp e) |
| { |
| if (e.var && e.var.toParent2().isFuncDeclaration().hasDualContext()) |
| escapeByValue(e, er, live, retRefTransition); |
| else if (e.var) |
| er.pushRef(e.var, retRefTransition); |
| } |
| |
| void visitPtr(PtrExp e) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitIndex(IndexExp e) |
| { |
| Type tb = e.e1.type.toBasetype(); |
| if (auto ve = e.e1.isVarExp()) |
| { |
| VarDeclaration v = ve.var.isVarDeclaration(); |
| if (v && v.isTypesafeVariadicArray) |
| { |
| er.pushRef(v, retRefTransition); |
| return; |
| } |
| } |
| if (tb.ty == Tsarray) |
| { |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| else if (tb.ty == Tarray) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| } |
| |
| void visitStructLiteral(StructLiteralExp e) |
| { |
| if (e.elements) |
| { |
| foreach (ex; *e.elements) |
| { |
| if (ex) |
| escapeByRef(ex, er, live, retRefTransition); |
| } |
| } |
| er.pushExp(e, retRefTransition); |
| } |
| |
| void visitDotVar(DotVarExp e) |
| { |
| Type t1b = e.e1.type.toBasetype(); |
| if (t1b.ty == Tclass) |
| escapeByValue(e.e1, er, live, retRefTransition); |
| else |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitBinAssign(BinAssignExp e) |
| { |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitAssign(AssignExp e) |
| { |
| escapeByRef(e.e1, er, live, retRefTransition); |
| } |
| |
| void visitComma(CommaExp e) |
| { |
| escapeByRef(e.e2, er, live, retRefTransition); |
| } |
| |
| void visitCond(CondExp e) |
| { |
| escapeByRef(e.e1, er, live, retRefTransition); |
| escapeByRef(e.e2, er, live, retRefTransition); |
| } |
| |
| void visitCall(CallExp e) |
| { |
| //printf("escapeByRef.CallExp(): %s\n", e.toChars()); |
| /* If the function returns by ref, check each argument that is |
| * passed as 'return ref'. |
| */ |
| Type t1 = e.e1.type.toBasetype(); |
| TypeFunction tf; |
| if (t1.ty == Tdelegate) |
| tf = t1.isTypeDelegate().next.isTypeFunction(); |
| else if (t1.ty == Tfunction) |
| tf = t1.isTypeFunction(); |
| else |
| return; |
| if (tf.isref) |
| { |
| if (e.arguments && e.arguments.length) |
| { |
| /* j=1 if _arguments[] is first argument, |
| * skip it because it is not passed by ref |
| */ |
| int j = tf.isDstyleVariadic(); |
| for (size_t i = j; i < e.arguments.length; ++i) |
| { |
| Expression arg = (*e.arguments)[i]; |
| size_t nparams = tf.parameterList.length; |
| if (i - j < nparams && i >= j) |
| { |
| Parameter p = tf.parameterList[i - j]; |
| const stc = tf.parameterStorageClass(null, p); |
| ScopeRef psr = buildScopeRef(stc); |
| if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) |
| escapeByRef(arg, er, live, retRefTransition); |
| else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) |
| { |
| if (auto de = arg.isDelegateExp()) |
| { |
| if (de.func.isNested()) |
| er.pushExp(de, false); |
| } |
| else |
| escapeByValue(arg, er, live, retRefTransition); |
| } |
| } |
| } |
| } |
| // If 'this' is returned by ref, check it too |
| if (e.e1.op == EXP.dotVariable && t1.ty == Tfunction) |
| { |
| DotVarExp dve = e.e1.isDotVarExp(); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20149#c10 |
| if (dve.var.isCtorDeclaration()) |
| { |
| er.pushExp(e, false); |
| return; |
| } |
| |
| StorageClass stc = dve.var.storage_class & (STC.return_ | STC.scope_ | STC.ref_); |
| if (tf.isreturn) |
| stc |= STC.return_; |
| if (tf.isref) |
| stc |= STC.ref_; |
| if (tf.isScopeQual) |
| stc |= STC.scope_; |
| if (tf.isreturnscope) |
| stc |= STC.returnScope; |
| |
| const psr = buildScopeRef(stc); |
| if (psr == ScopeRef.ReturnRef || psr == ScopeRef.ReturnRef_Scope) |
| escapeByRef(dve.e1, er, live, psr == ScopeRef.ReturnRef_Scope); |
| else if (psr == ScopeRef.ReturnScope || psr == ScopeRef.Ref_ReturnScope) |
| escapeByValue(dve.e1, er, live, retRefTransition); |
| |
| // If it's also a nested function that is 'return ref' |
| if (FuncDeclaration fd = dve.var.isFuncDeclaration()) |
| { |
| if (fd.isNested() && tf.isreturn) |
| { |
| er.pushExp(e, false); |
| } |
| } |
| } |
| // If it's a delegate, check it too |
| if (e.e1.op == EXP.variable && t1.ty == Tdelegate) |
| { |
| escapeByValue(e.e1, er, live, retRefTransition); |
| } |
| |
| /* If it's a nested function that is 'return ref' |
| */ |
| if (auto ve = e.e1.isVarExp()) |
| { |
| FuncDeclaration fd = ve.var.isFuncDeclaration(); |
| if (fd && fd.isNested()) |
| { |
| if (tf.isreturn) |
| er.pushExp(e, false); |
| } |
| } |
| } |
| else |
| er.pushExp(e, retRefTransition); |
| } |
| |
| switch (e.op) |
| { |
| case EXP.variable: return visitVar(e.isVarExp()); |
| case EXP.this_: return visitThis(e.isThisExp()); |
| case EXP.star: return visitPtr(e.isPtrExp()); |
| case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp()); |
| case EXP.dotVariable: return visitDotVar(e.isDotVarExp()); |
| case EXP.index: return visitIndex(e.isIndexExp()); |
| case EXP.blit: return visitAssign(e.isBlitExp()); |
| case EXP.construct: return visitAssign(e.isConstructExp()); |
| case EXP.assign: return visitAssign(e.isAssignExp()); |
| case EXP.comma: return visitComma(e.isCommaExp()); |
| case EXP.question: return visitCond(e.isCondExp()); |
| case EXP.call: return visitCall(e.isCallExp()); |
| default: |
| if (auto ba = e.isBinAssignExp()) |
| return visitBinAssign(ba); |
| return visit(e); |
| } |
| } |
| |
| /************************************ |
| * Aggregate the data collected by the escapeBy??() functions. |
| */ |
| struct EscapeByResults |
| { |
| VarDeclarations byref; // array into which variables being returned by ref are inserted |
| VarDeclarations byvalue; // array into which variables with values containing pointers are inserted |
| private FuncDeclarations byfunc; // nested functions that are turned into delegates |
| private Expressions byexp; // array into which temporaries being returned by ref are inserted |
| |
| import dmd.root.array: Array; |
| |
| /** |
| * Whether the variable / expression went through a `return ref scope` function call |
| * |
| * This is needed for the dip1000 by default transition, since the rules for |
| * disambiguating `return scope ref` have changed. Therefore, functions in legacy code |
| * can be mistakenly treated as `return ref` making the compiler believe stack variables |
| * are being escaped, which is an error even in `@system` code. By keeping track of this |
| * information, variables escaped through `return ref` can be treated as a deprecation instead |
| * of error, see test/fail_compilation/dip1000_deprecation.d |
| */ |
| private Array!bool refRetRefTransition; |
| private Array!bool expRetRefTransition; |
| |
| /** Reset arrays so the storage can be used again |
| */ |
| void reset() |
| { |
| byref.setDim(0); |
| byvalue.setDim(0); |
| byfunc.setDim(0); |
| byexp.setDim(0); |
| |
| refRetRefTransition.setDim(0); |
| expRetRefTransition.setDim(0); |
| } |
| |
| /** |
| * Escape variable `v` by reference |
| * Params: |
| * v = variable to escape |
| * retRefTransition = `v` is escaped through a `return ref scope` function call |
| */ |
| void pushRef(VarDeclaration v, bool retRefTransition) |
| { |
| byref.push(v); |
| refRetRefTransition.push(retRefTransition); |
| } |
| |
| /** |
| * Escape a reference to expression `e` |
| * Params: |
| * e = expression to escape |
| * retRefTransition = `e` is escaped through a `return ref scope` function call |
| */ |
| void pushExp(Expression e, bool retRefTransition) |
| { |
| byexp.push(e); |
| expRetRefTransition.push(retRefTransition); |
| } |
| } |
| |
| /************************* |
| * Find all variables accessed by this delegate that are |
| * in functions enclosing it. |
| * Params: |
| * fd = function |
| * vars = array to append found variables to |
| */ |
| public void findAllOuterAccessedVariables(FuncDeclaration fd, VarDeclarations* vars) |
| { |
| //printf("findAllOuterAccessedVariables(fd: %s)\n", fd.toChars()); |
| for (auto p = fd.parent; p; p = p.parent) |
| { |
| auto fdp = p.isFuncDeclaration(); |
| if (!fdp) |
| continue; |
| |
| foreach (v; fdp.closureVars) |
| { |
| foreach (const fdv; v.nestedrefs) |
| { |
| if (fdv == fd) |
| { |
| //printf("accessed: %s, type %s\n", v.toChars(), v.type.toChars()); |
| vars.push(v); |
| } |
| } |
| } |
| } |
| } |
| |
| /*********************************** |
| * Turn off `maybeScope` for variable `v`. |
| * |
| * This exists in order to find where `maybeScope` is getting turned off. |
| * Params: |
| * v = variable |
| * o = reason for it being turned off: |
| * - `Expression` such as `throw e` or `&e` |
| * - `VarDeclaration` of a non-scope parameter it was assigned to |
| * - `null` for no reason |
| */ |
| private void notMaybeScope(VarDeclaration v, RootObject o) |
| { |
| if (v.maybeScope) |
| { |
| v.maybeScope = false; |
| if (o && v.isParameter()) |
| EscapeState.scopeInferFailure[v.sequenceNumber] = o; |
| } |
| } |
| |
| /*********************************** |
| * Turn off `maybeScope` for variable `v` if it's not a parameter. |
| * |
| * This is for compatibility with the old system with both `STC.maybescope` and `VarDeclaration.doNotInferScope`, |
| * which is now just `VarDeclaration.maybeScope`. |
| * This function should probably be removed in future refactors. |
| * |
| * Params: |
| * v = variable |
| * o = reason for it being turned off |
| */ |
| private void doNotInferScope(VarDeclaration v, RootObject o) |
| { |
| if (!v.isParameter) |
| notMaybeScope(v, o); |
| } |
| |
| /*********************************** |
| * After semantic analysis of the function body, |
| * try to infer `scope` / `return` on the parameters |
| * |
| * Params: |
| * funcdecl = function declaration that was analyzed |
| * f = final function type. `funcdecl.type` started as the 'premature type' before attribute |
| * inference, then its inferred attributes are copied over to final type `f` |
| */ |
| void finishScopeParamInference(FuncDeclaration funcdecl, ref TypeFunction f) |
| { |
| |
| if (funcdecl.returnInprocess) |
| { |
| funcdecl.returnInprocess = false; |
| if (funcdecl.storage_class & STC.return_) |
| { |
| if (funcdecl.type == f) |
| f = cast(TypeFunction)f.copy(); |
| f.isreturn = true; |
| f.isreturnscope = cast(bool) (funcdecl.storage_class & STC.returnScope); |
| if (funcdecl.storage_class & STC.returninferred) |
| f.isreturninferred = true; |
| } |
| } |
| |
| if (!funcdecl.inferScope) |
| return; |
| funcdecl.inferScope = false; |
| |
| // Eliminate maybescope's |
| { |
| // Create and fill array[] with maybe candidates from the `this` and the parameters |
| VarDeclaration[10] tmp = void; |
| size_t dim = (funcdecl.vthis !is null) + (funcdecl.parameters ? funcdecl.parameters.length : 0); |
| |
| import dmd.common.string : SmallBuffer; |
| auto sb = SmallBuffer!VarDeclaration(dim, tmp[]); |
| VarDeclaration[] array = sb[]; |
| |
| size_t n = 0; |
| if (funcdecl.vthis) |
| array[n++] = funcdecl.vthis; |
| if (funcdecl.parameters) |
| { |
| foreach (v; *funcdecl.parameters) |
| { |
| array[n++] = v; |
| } |
| } |
| eliminateMaybeScopes(array[0 .. n]); |
| } |
| |
| // Infer STC.scope_ |
| if (funcdecl.parameters && !funcdecl.errors) |
| { |
| assert(f.parameterList.length == funcdecl.parameters.length); |
| foreach (u, p; f.parameterList) |
| { |
| auto v = (*funcdecl.parameters)[u]; |
| if (!v.isScope() && inferScope(v)) |
| { |
| //printf("Inferring scope for %s\n", v.toChars()); |
| p.storageClass |= STC.scope_ | STC.scopeinferred; |
| } |
| } |
| } |
| |
| if (funcdecl.vthis) |
| { |
| inferScope(funcdecl.vthis); |
| f.isScopeQual = funcdecl.vthis.isScope(); |
| f.isscopeinferred = !!(funcdecl.vthis.storage_class & STC.scopeinferred); |
| } |
| } |
| |
| /********************************************** |
| * Have some variables that are maybescopes that were |
| * assigned values from other maybescope variables. |
| * Now that semantic analysis of the function is |
| * complete, we can finalize this by turning off |
| * maybescope for array elements that cannot be scope. |
| * |
| * $(TABLE2 Scope Table, |
| * $(THEAD `va`, `v`, =>, `va` , `v` ) |
| * $(TROW maybe, maybe, =>, scope, scope) |
| * $(TROW scope, scope, =>, scope, scope) |
| * $(TROW scope, maybe, =>, scope, scope) |
| * $(TROW maybe, scope, =>, scope, scope) |
| * $(TROW - , - , =>, - , - ) |
| * $(TROW - , maybe, =>, - , - ) |
| * $(TROW - , scope, =>, error, error) |
| * $(TROW maybe, - , =>, scope, - ) |
| * $(TROW scope, - , =>, scope, - ) |
| * ) |
| * Params: |
| * array = array of variables that were assigned to from maybescope variables |
| */ |
| private void eliminateMaybeScopes(VarDeclaration[] array) |
| { |
| enum log = false; |
| if (log) printf("eliminateMaybeScopes()\n"); |
| bool changes; |
| do |
| { |
| changes = false; |
| foreach (va; array) |
| { |
| if (log) printf(" va = %s\n", va.toChars()); |
| if (!(va.maybeScope || va.isScope())) |
| { |
| if (va.maybes) |
| { |
| foreach (v; *va.maybes) |
| { |
| if (log) printf(" v = %s\n", v.toChars()); |
| if (v.maybeScope) |
| { |
| // v cannot be scope since it is assigned to a non-scope va |
| notMaybeScope(v, va); |
| if (!v.isReference()) |
| v.storage_class &= ~(STC.return_ | STC.returninferred); |
| changes = true; |
| } |
| } |
| } |
| } |
| } |
| } while (changes); |
| } |
| |
| /************************************************ |
| * Is type a reference to a mutable value? |
| * |
| * This is used to determine if an argument that does not have a corresponding |
| * Parameter, i.e. a variadic argument, is a pointer to mutable data. |
| * Params: |
| * t = type of the argument |
| * Returns: |
| * true if it's a pointer (or reference) to mutable data |
| */ |
| bool isReferenceToMutable(Type t) |
| { |
| t = t.baseElemOf(); |
| |
| if (!t.isMutable() || |
| !t.hasPointers()) |
| return false; |
| |
| switch (t.ty) |
| { |
| case Tpointer: |
| if (t.nextOf().isTypeFunction()) |
| break; |
| goto case; |
| |
| case Tarray: |
| case Taarray: |
| case Tdelegate: |
| if (t.nextOf().isMutable()) |
| return true; |
| break; |
| |
| case Tclass: |
| return true; // even if the class fields are not mutable |
| |
| case Tstruct: |
| // Have to look at each field |
| foreach (VarDeclaration v; t.isTypeStruct().sym.fields) |
| { |
| if (v.storage_class & STC.ref_) |
| { |
| if (v.type.isMutable()) |
| return true; |
| } |
| else if (v.type.isReferenceToMutable()) |
| return true; |
| } |
| break; |
| |
| default: |
| assert(0); |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Is parameter a reference to a mutable value? |
| * |
| * This is used if an argument has a corresponding Parameter. |
| * The argument type is necessary if the Parameter is inout. |
| * Params: |
| * p = Parameter to check |
| * t = type of corresponding argument |
| * Returns: |
| * true if it's a pointer (or reference) to mutable data |
| */ |
| bool isReferenceToMutable(Parameter p, Type t) |
| { |
| if (p.isReference()) |
| { |
| if (p.type.isConst() || p.type.isImmutable()) |
| return false; |
| if (p.type.isWild()) |
| { |
| return t.isMutable(); |
| } |
| return p.type.isMutable(); |
| } |
| return isReferenceToMutable(p.type); |
| } |
| |
| /// When checking lifetime for assignment `va=v`, the way `va` encloses `v` |
| private enum EnclosedBy |
| { |
| none = 0, |
| refVar, // `va` is a `ref` variable, which may link to a global variable |
| global, // `va` is a global variable |
| returnScope, // `va` is a scope variable that may be returned |
| longerScope, // `va` is another scope variable declared earlier than `v` |
| } |
| |
| /********************************** |
| * Determine if `va` has a lifetime that lasts past |
| * the destruction of `v` |
| * Params: |
| * va = variable assigned to |
| * v = variable being assigned |
| * Returns: |
| * The way `va` encloses `v` (if any) |
| */ |
| private EnclosedBy enclosesLifetimeOf(VarDeclaration va, VarDeclaration v) |
| { |
| if (!va) |
| return EnclosedBy.none; |
| |
| if (va.isDataseg()) |
| return EnclosedBy.global; |
| |
| if (va.isScope() && va.isReturn() && !v.isReturn()) |
| return EnclosedBy.returnScope; |
| |
| if (va.isReference() && va.isParameter()) |
| return EnclosedBy.refVar; |
| |
| assert(va.sequenceNumber != va.sequenceNumber.init); |
| assert(v.sequenceNumber != v.sequenceNumber.init); |
| if (va.sequenceNumber < v.sequenceNumber) |
| return EnclosedBy.longerScope; |
| |
| return EnclosedBy.none; |
| } |
| |
| /*************************************** |
| * Add variable `v` to maybes[] |
| * |
| * When a maybescope variable `v` is assigned to a maybescope variable `va`, |
| * we cannot determine if `this` is actually scope until the semantic |
| * analysis for the function is completed. Thus, we save the data |
| * until then. |
| * Params: |
| * v = a variable with `maybeScope == true` that was assigned to `this` |
| */ |
| private void addMaybe(VarDeclaration va, VarDeclaration v) |
| { |
| //printf("add %s to %s's list of dependencies\n", v.toChars(), toChars()); |
| if (!va.maybes) |
| va.maybes = new VarDeclarations(); |
| va.maybes.push(v); |
| } |
| |
| // `setUnsafePreview` partially evaluated for dip1000 |
| private bool setUnsafeDIP1000(Scope* sc, bool gag, Loc loc, const(char)* msg, |
| RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) |
| { |
| return setUnsafePreview(sc, global.params.useDIP1000, gag, loc, msg, arg0, arg1, arg2); |
| } |
| |
| /*************************************** |
| * Check that taking the address of `v` is `@safe` |
| * |
| * It's not possible to take the address of a scope variable, because `scope` only applies |
| * to the top level indirection. |
| * |
| * Params: |
| * v = variable that a reference is created |
| * e = expression that takes the referene |
| * sc = used to obtain function / deprecated status |
| * gag = don't print errors |
| * Returns: |
| * true if taking the address of `v` is problematic because of the lack of transitive `scope` |
| */ |
| private bool checkScopeVarAddr(VarDeclaration v, Expression e, Scope* sc, bool gag) |
| { |
| if (v.storage_class & STC.temp) |
| return false; |
| |
| if (!v.isScope()) |
| { |
| notMaybeScope(v, e); |
| return false; |
| } |
| |
| if (!e.type) |
| return false; |
| |
| // When the type after dereferencing has no pointers, it's okay. |
| // Comes up when escaping `&someStruct.intMember` of a `scope` struct: |
| // scope does not apply to the `int` |
| Type t = e.type.baseElemOf(); |
| if ((t.ty == Tarray || t.ty == Tpointer) && !t.nextOf().toBasetype().hasPointers()) |
| return false; |
| |
| // take address of `scope` variable not allowed, requires transitive scope |
| return sc.setUnsafeDIP1000(gag, e.loc, |
| "cannot take address of `scope` variable `%s` since `scope` applies to first indirection only", v); |
| } |
| |
| /**************************** |
| * Determine if `v` is a typesafe variadic array, which is implicitly `scope` |
| * Params: |
| * v = variable to check |
| * Returns: |
| * true if `v` is a variadic parameter |
| */ |
| private bool isTypesafeVariadicArray(VarDeclaration v) |
| { |
| if (v.storage_class & STC.variadic) |
| { |
| Type tb = v.type.toBasetype(); |
| if (tb.ty == Tarray || tb.ty == Tsarray) |
| return true; |
| } |
| return false; |
| } |