diff options
author | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gcc.gnu.org> | 2018-10-28 19:51:47 +0000 |
commit | b4c522fabd0df7be08882d2207df8b2765026110 (patch) | |
tree | b5ffc312b0a441c1ba24323152aec463fdbe5e9f /gcc/d/dmd/escape.c | |
parent | 01ce9e31a02c8039d88e90f983735104417bf034 (diff) | |
download | gcc-b4c522fabd0df7be08882d2207df8b2765026110.zip gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.gz gcc-b4c522fabd0df7be08882d2207df8b2765026110.tar.bz2 |
Add D front-end, libphobos library, and D2 testsuite.
ChangeLog:
* Makefile.def (target_modules): Add libphobos.
(flags_to_pass): Add GDC, GDCFLAGS, GDC_FOR_TARGET and
GDCFLAGS_FOR_TARGET.
(dependencies): Make libphobos depend on libatomic, libbacktrace
configure, and zlib configure.
(language): Add language d.
* Makefile.in: Rebuild.
* Makefile.tpl (BUILD_EXPORTS): Add GDC and GDCFLAGS.
(HOST_EXPORTS): Add GDC.
(POSTSTAGE1_HOST_EXPORTS): Add GDC and GDC_FOR_BUILD.
(BASE_TARGET_EXPORTS): Add GDC.
(GDC_FOR_BUILD, GDC, GDCFLAGS): New variables.
(GDC_FOR_TARGET, GDC_FLAGS_FOR_TARGET): New variables.
(EXTRA_HOST_FLAGS): Add GDC.
(STAGE1_FLAGS_TO_PASS): Add GDC.
(EXTRA_TARGET_FLAGS): Add GDC and GDCFLAGS.
* config-ml.in: Treat GDC and GDCFLAGS like other compiler/flag
environment variables.
* configure: Rebuild.
* configure.ac: Add target-libphobos to target_libraries. Set and
substitute GDC_FOR_BUILD and GDC_FOR_TARGET.
config/ChangeLog:
* multi.m4: Set GDC.
gcc/ChangeLog:
* Makefile.in (tm_d_file_list, tm_d_include_list): New variables.
(TM_D_H, D_TARGET_DEF, D_TARGET_H, D_TARGET_OBJS): New variables.
(tm_d.h, cs-tm_d.h, default-d.o): New rules.
(d/d-target-hooks-def.h, s-d-target-hooks-def-h): New rules.
(s-tm-texi): Also check timestamp on d-target.def.
(generated_files): Add TM_D_H and d-target-hooks-def.h.
(build/genhooks.o): Also depend on D_TARGET_DEF.
* config.gcc (tm_d_file, d_target_objs, target_has_targetdm): New
variables.
* config/aarch64/aarch64-d.c: New file.
* config/aarch64/aarch64-linux.h (GNU_USER_TARGET_D_CRITSEC_SIZE):
Define.
* config/aarch64/aarch64-protos.h (aarch64_d_target_versions): New
prototype.
* config/aarch64/aarch64.h (TARGET_D_CPU_VERSIONS): Define.
* config/aarch64/t-aarch64 (aarch64-d.o): New rule.
* config/arm/arm-d.c: New file.
* config/arm/arm-protos.h (arm_d_target_versions): New prototype.
* config/arm/arm.h (TARGET_D_CPU_VERSIONS): Define.
* config/arm/linux-eabi.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/arm/t-arm (arm-d.o): New rule.
* config/default-d.c: New file.
* config/glibc-d.c: New file.
* config/gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/i386/i386-d.c: New file.
* config/i386/i386-protos.h (ix86_d_target_versions): New prototype.
* config/i386/i386.h (TARGET_D_CPU_VERSIONS): Define.
* config/i386/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
(GNU_USER_TARGET_D_CRITSEC_SIZE): Define.
* config/i386/t-i386 (i386-d.o): New rule.
* config/kfreebsd-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/kopensolaris-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/linux-android.h (ANDROID_TARGET_D_OS_VERSIONS): Define.
* config/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/mips/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define.
* config/mips/mips-d.c: New file.
* config/mips/mips-protos.h (mips_d_target_versions): New prototype.
* config/mips/mips.h (TARGET_D_CPU_VERSIONS): Define.
* config/mips/t-mips (mips-d.o): New rule.
* config/powerpcspe/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/powerpcspe/powerpcspe-d.c: New file.
* config/powerpcspe/powerpcspe-protos.h (rs6000_d_target_versions):
New prototype.
* config/powerpcspe/powerpcspe.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/powerpcspe/powerpcspe.h (TARGET_D_CPU_VERSIONS): Define.
* config/powerpcspe/t-powerpcspe (powerpcspe-d.o): New rule.
* config/riscv/riscv-d.c: New file.
* config/riscv/riscv-protos.h (riscv_d_target_versions): New
prototype.
* config/riscv/riscv.h (TARGET_D_CPU_VERSIONS): Define.
* config/riscv/t-riscv (riscv-d.o): New rule.
* config/rs6000/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define.
* config/rs6000/rs6000-d.c: New file.
* config/rs6000/rs6000-protos.h (rs6000_d_target_versions): New
prototype.
* config/rs6000/rs6000.c (rs6000_output_function_epilogue):
Support GNU D by using 0 as the language type.
* config/rs6000/rs6000.h (TARGET_D_CPU_VERSIONS): Define.
* config/rs6000/t-rs6000 (rs6000-d.o): New rule.
* config/s390/s390-d.c: New file.
* config/s390/s390-protos.h (s390_d_target_versions): New prototype.
* config/s390/s390.h (TARGET_D_CPU_VERSIONS): Define.
* config/s390/t-s390 (s390-d.o): New rule.
* config/sparc/sparc-d.c: New file.
* config/sparc/sparc-protos.h (sparc_d_target_versions): New
prototype.
* config/sparc/sparc.h (TARGET_D_CPU_VERSIONS): Define.
* config/sparc/t-sparc (sparc-d.o): New rule.
* config/t-glibc (glibc-d.o): New rule.
* configure: Regenerated.
* configure.ac (tm_d_file): New variable.
(tm_d_file_list, tm_d_include_list, d_target_objs): Add substitutes.
* doc/contrib.texi (Contributors): Add self for the D frontend.
* doc/frontends.texi (G++ and GCC): Mention D as a supported language.
* doc/install.texi (Configuration): Mention libphobos as an option for
--enable-shared. Mention d as an option for --enable-languages.
(Testing): Mention check-d as a target.
* doc/invoke.texi (Overall Options): Mention .d, .dd, and .di as file
name suffixes. Mention d as a -x option.
* doc/sourcebuild.texi (Top Level): Mention libphobos.
* doc/standards.texi (Standards): Add section on D language.
* doc/tm.texi: Regenerated.
* doc/tm.texi.in: Add @node for D language and ABI, and @hook for
TARGET_CPU_VERSIONS, TARGET_D_OS_VERSIONS, and TARGET_D_CRITSEC_SIZE.
* dwarf2out.c (is_dlang): New function.
(gen_compile_unit_die): Use DW_LANG_D for D.
(declare_in_namespace): Return module die for D, instead of adding
extra declarations into the namespace.
(gen_namespace_die): Generate DW_TAG_module for D.
(gen_decl_die): Handle CONST_DECLSs for D.
(dwarf2out_decl): Likewise.
(prune_unused_types_walk_local_classes): Handle DW_tag_interface_type.
(prune_unused_types_walk): Handle DW_tag_interface_type same as other
kinds of aggregates.
* gcc.c (default_compilers): Add entries for .d, .dd and .di.
* genhooks.c: Include d/d-target.def.
gcc/po/ChangeLog:
* EXCLUDES: Add sources from d/dmd.
gcc/testsuite/ChangeLog:
* gcc.misc-tests/help.exp: Add D to option descriptions check.
* gdc.dg/asan/asan.exp: New file.
* gdc.dg/asan/gdc272.d: New test.
* gdc.dg/compilable.d: New test.
* gdc.dg/dg.exp: New file.
* gdc.dg/gdc254.d: New test.
* gdc.dg/gdc260.d: New test.
* gdc.dg/gdc270a.d: New test.
* gdc.dg/gdc270b.d: New test.
* gdc.dg/gdc282.d: New test.
* gdc.dg/gdc283.d: New test.
* gdc.dg/imports/gdc170.d: New test.
* gdc.dg/imports/gdc231.d: New test.
* gdc.dg/imports/gdc239.d: New test.
* gdc.dg/imports/gdc241a.d: New test.
* gdc.dg/imports/gdc241b.d: New test.
* gdc.dg/imports/gdc251a.d: New test.
* gdc.dg/imports/gdc251b.d: New test.
* gdc.dg/imports/gdc253.d: New test.
* gdc.dg/imports/gdc254a.d: New test.
* gdc.dg/imports/gdc256.d: New test.
* gdc.dg/imports/gdc27.d: New test.
* gdc.dg/imports/gdcpkg256/package.d: New test.
* gdc.dg/imports/runnable.d: New test.
* gdc.dg/link.d: New test.
* gdc.dg/lto/lto.exp: New file.
* gdc.dg/lto/ltotests_0.d: New test.
* gdc.dg/lto/ltotests_1.d: New test.
* gdc.dg/runnable.d: New test.
* gdc.dg/simd.d: New test.
* gdc.test/gdc-test.exp: New file.
* lib/gdc-dg.exp: New file.
* lib/gdc.exp: New file.
libphobos/ChangeLog:
* Makefile.am: New file.
* Makefile.in: New file.
* acinclude.m4: New file.
* aclocal.m4: New file.
* config.h.in: New file.
* configure: New file.
* configure.ac: New file.
* d_rules.am: New file.
* libdruntime/Makefile.am: New file.
* libdruntime/Makefile.in: New file.
* libdruntime/__entrypoint.di: New file.
* libdruntime/__main.di: New file.
* libdruntime/gcc/attribute.d: New file.
* libdruntime/gcc/backtrace.d: New file.
* libdruntime/gcc/builtins.d: New file.
* libdruntime/gcc/config.d.in: New file.
* libdruntime/gcc/deh.d: New file.
* libdruntime/gcc/libbacktrace.d.in: New file.
* libdruntime/gcc/unwind/arm.d: New file.
* libdruntime/gcc/unwind/arm_common.d: New file.
* libdruntime/gcc/unwind/c6x.d: New file.
* libdruntime/gcc/unwind/generic.d: New file.
* libdruntime/gcc/unwind/package.d: New file.
* libdruntime/gcc/unwind/pe.d: New file.
* m4/autoconf.m4: New file.
* m4/druntime.m4: New file.
* m4/druntime/cpu.m4: New file.
* m4/druntime/libraries.m4: New file.
* m4/druntime/os.m4: New file.
* m4/gcc_support.m4: New file.
* m4/gdc.m4: New file.
* m4/libtool.m4: New file.
* src/Makefile.am: New file.
* src/Makefile.in: New file.
* src/libgphobos.spec.in: New file.
* testsuite/Makefile.am: New file.
* testsuite/Makefile.in: New file.
* testsuite/config/default.exp: New file.
* testsuite/lib/libphobos-dg.exp: New file.
* testsuite/lib/libphobos.exp: New file.
* testsuite/testsuite_flags.in: New file.
From-SVN: r265573
Diffstat (limited to 'gcc/d/dmd/escape.c')
-rw-r--r-- | gcc/d/dmd/escape.c | 1234 |
1 files changed, 1234 insertions, 0 deletions
diff --git a/gcc/d/dmd/escape.c b/gcc/d/dmd/escape.c new file mode 100644 index 0000000..353e56f --- /dev/null +++ b/gcc/d/dmd/escape.c @@ -0,0 +1,1234 @@ + +/* Compiler implementation of the D programming language + * Copyright (C) 1999-2018 by The D Language Foundation, All Rights Reserved + * written by Walter Bright + * http://www.digitalmars.com + * Distributed under the Boost Software License, Version 1.0. + * http://www.boost.org/LICENSE_1_0.txt + * https://github.com/D-Programming-Language/dmd/blob/master/src/escape.c + */ + +#include "mars.h" +#include "init.h" +#include "expression.h" +#include "scope.h" +#include "aggregate.h" +#include "declaration.h" +#include "module.h" + +/************************************ + * 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 + FuncDeclarations byfunc; // nested functions that are turned into delegates + Expressions byexp; // array into which temporaries being returned by ref are inserted +}; + +static bool checkReturnEscapeImpl(Scope *sc, Expression *e, bool refs, bool gag); +static void inferReturn(FuncDeclaration *fd, VarDeclaration *v); +static void escapeByValue(Expression *e, EscapeByResults *er); +static void escapeByRef(Expression *e, EscapeByResults *er); +static void findAllOuterAccessedVariables(FuncDeclaration *fd, VarDeclarations *vars); + +/* 'v' is assigned unsafely to 'par' +*/ +static void unsafeAssign(Scope *sc, FuncDeclaration *fdc, Identifier *par, Expression *arg, bool gag, + bool &result, VarDeclaration *v, const char *desc) +{ + if (global.params.vsafe && sc->func->setUnsafe()) + { + if (!gag) + error(arg->loc, "%s %s assigned to non-scope parameter %s calling %s", + desc, v->toChars(), + par ? par->toChars() : "unnamed", + fdc ? fdc->toPrettyChars() : "indirectly"); + result = true; + } +} + +/**************************************** + * 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 + * par = identifier of function parameter + * arg = initializer for param + * gag = do not print error messages + * Returns: + * true if pointers to the stack can escape via assignment + */ +bool checkParamArgumentEscape(Scope *sc, FuncDeclaration *fdc, Identifier *par, Expression *arg, bool gag) +{ + //printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg->toChars(), par->toChars()); + //printf("type = %s, %d\n", arg->type->toChars(), arg->type->hasPointers()); + + if (!arg->type->hasPointers()) + return false; + + EscapeByResults er; + + escapeByValue(arg, &er); + + if (!er.byref.dim && !er.byvalue.dim && !er.byfunc.dim && !er.byexp.dim) + return false; + + bool result = false; + + for (size_t i = 0; i < er.byvalue.dim; i++) + { + //printf("byvalue %s\n", v->toChars()); + VarDeclaration *v = er.byvalue[i]; + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + v->storage_class &= ~STCmaybescope; + + if (v->isScope()) + { + unsafeAssign(sc, fdc, par, arg, gag, result, v, "scope variable"); + } + else if (v->storage_class & STCvariadic && p == sc->func) + { + Type *tb = v->type->toBasetype(); + if (tb->ty == Tarray || tb->ty == Tsarray) + { + unsafeAssign(sc, fdc, par, arg, gag, result, v, "variadic variable"); + } + } + else + { + /* v is not 'scope', and is assigned to a parameter that may escape. + * Therefore, v can never be 'scope'. + */ + v->doNotInferScope = true; + } + } + + for (size_t i = 0; i < er.byref.dim; i++) + { + VarDeclaration *v = er.byref[i]; + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + v->storage_class &= ~STCmaybescope; + + if ((v->storage_class & (STCref | STCout)) == 0 && p == sc->func) + { + unsafeAssign(sc, fdc, par, arg, gag, result, v, "reference to local variable"); + continue; + } + } + + for (size_t i = 0; i < er.byfunc.dim; i++) + { + FuncDeclaration *fd = er.byfunc[i]; + //printf("fd = %s, %d\n", fd->toChars(), fd->tookAddressOf); + VarDeclarations vars; + findAllOuterAccessedVariables(fd, &vars); + + for (size_t j = 0; j < vars.dim; j++) + { + VarDeclaration *v = vars[j]; + //printf("v = %s\n", v->toChars()); + assert(!v->isDataseg()); // these are not put in the closureVars[] + + Dsymbol *p = v->toParent2(); + + v->storage_class &= ~STCmaybescope; + + if ((v->storage_class & (STCref | STCout | STCscope)) && p == sc->func) + { + unsafeAssign(sc, fdc, par, arg, gag, result, v, "reference to local"); + continue; + } + } + } + + for (size_t i = 0; i < er.byexp.dim; i++) + { + Expression *ee = er.byexp[i]; + if (sc->func->setUnsafe()) + { + if (!gag) + error(ee->loc, "reference to stack allocated value returned by %s assigned to non-scope parameter %s", + ee->toChars(), + par ? par->toChars() : "unnamed"); + result = true; + } + } + + return result; +} + +/**************************************** + * 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' for the lvalue where possible, in order + * to eliminate the error. + * Params: + * sc = used to determine current function and module + * ae = AssignExp to check for any pointers to the stack + * gag = do not print error messages + * Returns: + * true if pointers to the stack can escape via assignment + */ +bool checkAssignEscape(Scope *sc, Expression *e, bool gag) +{ + //printf("checkAssignEscape(e: %s)\n", e->toChars()); + if (e->op != TOKassign && e->op != TOKblit && e->op != TOKconstruct) + return false; + AssignExp *ae = (AssignExp *)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->op == TOKslice) + return false; + + EscapeByResults er; + + escapeByValue(e2, &er); + + if (!er.byref.dim && !er.byvalue.dim && !er.byfunc.dim && !er.byexp.dim) + return false; + + VarDeclaration *va = NULL; + while (e1->op == TOKdotvar) + e1 = ((DotVarExp *)e1)->e1; + + if (e1->op == TOKvar) + va = ((VarExp *)e1)->var->isVarDeclaration(); + else if (e1->op == TOKthis) + va = ((ThisExp *)e1)->var->isVarDeclaration(); + else if (e1->op == TOKindex) + { + IndexExp *ie = (IndexExp *)e1; + if (ie->e1->op == TOKvar && ie->e1->type->toBasetype()->ty == Tsarray) + va = ((VarExp *)ie->e1)->var->isVarDeclaration(); + } + + // Try to infer 'scope' for va if in a function not marked @system + bool inferScope = false; + if (va && sc->func && sc->func->type && sc->func->type->ty == Tfunction) + inferScope = ((TypeFunction *)sc->func->type)->trust != TRUSTsystem; + + bool result = false; + for (size_t i = 0; i < er.byvalue.dim; i++) + { + VarDeclaration *v = er.byvalue[i]; + //printf("byvalue: %s\n", v->toChars()); + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + if (!(va && va->isScope())) + v->storage_class &= ~STCmaybescope; + + if (v->isScope()) + { + if (va && va->isScope() && va->storage_class & STCreturn && !(v->storage_class & STCreturn) && + sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "scope variable %s assigned to return scope %s", v->toChars(), va->toChars()); + result = true; + continue; + } + + // If va's lifetime encloses v's, then error + if (va && + ((va->enclosesLifetimeOf(v) && !(v->storage_class & STCparameter)) || + // va is class reference + (ae->e1->op == TOKdotvar && va->type->toBasetype()->ty == Tclass && (va->enclosesLifetimeOf(v) || !va->isScope())) || + va->storage_class & STCref) && + sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "scope variable %s assigned to %s with longer lifetime", v->toChars(), va->toChars()); + result = true; + continue; + } + + if (va && !va->isDataseg() && !va->doNotInferScope) + { + if (!va->isScope() && inferScope) + { //printf("inferring scope for %s\n", va->toChars()); + va->storage_class |= STCscope | STCscopeinferred; + va->storage_class |= v->storage_class & STCreturn; + } + continue; + } + if (sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "scope variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); + result = true; + } + } + else if (v->storage_class & STCvariadic && p == sc->func) + { + Type *tb = v->type->toBasetype(); + if (tb->ty == Tarray || tb->ty == Tsarray) + { + if (va && !va->isDataseg() && !va->doNotInferScope) + { + if (!va->isScope() && inferScope) + { //printf("inferring scope for %s\n", va->toChars()); + va->storage_class |= STCscope | STCscopeinferred; + } + continue; + } + if (sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "variadic variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); + result = true; + } + } + } + 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'. + */ + v->doNotInferScope = true; + } + } + + for (size_t i = 0; i < er.byref.dim; i++) + { + VarDeclaration *v = er.byref[i]; + //printf("byref: %s\n", v->toChars()); + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + // If va's lifetime encloses v's, then error + if (va && + ((va->enclosesLifetimeOf(v) && !(v->storage_class & STCparameter)) || va->storage_class & STCref) && + sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "address of variable %s assigned to %s with longer lifetime", v->toChars(), va->toChars()); + result = true; + continue; + } + + if (!(va && va->isScope())) + v->storage_class &= ~STCmaybescope; + + if ((v->storage_class & (STCref | STCout)) == 0 && p == sc->func) + { + if (va && !va->isDataseg() && !va->doNotInferScope) + { + if (!va->isScope() && inferScope) + { //printf("inferring scope for %s\n", va->toChars()); + va->storage_class |= STCscope | STCscopeinferred; + } + continue; + } + if (sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "reference to local variable %s assigned to non-scope %s", v->toChars(), e1->toChars()); + result = true; + } + continue; + } + } + + for (size_t i = 0; i < er.byfunc.dim; i++) + { + FuncDeclaration *fd = er.byfunc[i]; + //printf("fd = %s, %d\n", fd->toChars(), fd->tookAddressOf); + VarDeclarations vars; + findAllOuterAccessedVariables(fd, &vars); + + for (size_t j = 0; j < vars.dim; j++) + { + VarDeclaration *v = vars[j]; + //printf("v = %s\n", v->toChars()); + assert(!v->isDataseg()); // these are not put in the closureVars[] + + Dsymbol *p = v->toParent2(); + + if (!(va && va->isScope())) + v->storage_class &= ~STCmaybescope; + + if ((v->storage_class & (STCref | STCout | STCscope)) && p == sc->func) + { + if (va && !va->isDataseg() && !va->doNotInferScope) + { + /* Don't infer STCscope for va, because then a closure + * won't be generated for sc->func. + */ + //if (!va->isScope() && inferScope) + //va->storage_class |= STCscope | STCscopeinferred; + continue; + } + if (sc->func->setUnsafe()) + { + if (!gag) + error(ae->loc, "reference to local %s assigned to non-scope %s in @safe code", v->toChars(), e1->toChars()); + result = true; + } + continue; + } + } + } + + for (size_t i = 0; i < er.byexp.dim; i++) + { + Expression *ee = er.byexp[i]; + if (va && !va->isDataseg() && !va->doNotInferScope) + { + if (!va->isScope() && inferScope) + { //printf("inferring scope for %s\n", va->toChars()); + va->storage_class |= STCscope | STCscopeinferred; + } + continue; + } + if (sc->func->setUnsafe()) + { + if (!gag) + error(ee->loc, "reference to stack allocated value returned by %s assigned to non-scope %s", + ee->toChars(), e1->toChars()); + result = true; + } + } + + 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.dim && !er.byvalue.dim && !er.byexp.dim) + return false; + + bool result = false; + for (size_t i = 0; i < er.byvalue.dim; i++) + { + VarDeclaration *v = er.byvalue[i]; + //printf("byvalue %s\n", v->toChars()); + if (v->isDataseg()) + continue; + + if (v->isScope()) + { + if (sc->_module && sc->_module->isRoot()) + { + // Only look for errors if in module listed on command line + if (global.params.vsafe) // https://issues.dlang.org/show_bug.cgi?id=17029 + { + if (!gag) + error(e->loc, "scope variable %s may not be thrown", v->toChars()); + result = true; + } + continue; + } + } + else + { + //printf("no infer for %s\n", v->toChars()); + v->doNotInferScope = true; + } + } + return result; +} + +/************************************ + * Detect cases where pointers to the stack can 'escape' the + * lifetime of the stack frame by returning 'e' by value. + * 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) +{ + //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); +} + +static void escapingRef(VarDeclaration *v, Expression *e, bool &result, bool gag) +{ + if (!gag) + { + const char *msg; + if (v->storage_class & STCparameter) + msg = "returning `%s` escapes a reference to parameter `%s`, perhaps annotate with `return`"; + else + msg = "returning `%s` escapes a reference to local variable `%s`"; + error(e->loc, msg, e->toChars(), v->toChars()); + } + result = true; +} + +static bool checkReturnEscapeImpl(Scope *sc, Expression *e, bool refs, bool gag) +{ + //printf("[%s] checkReturnEscapeImpl, e = %s\n", e->loc->toChars(), e->toChars()); + EscapeByResults er; + + if (refs) + escapeByRef(e, &er); + else + escapeByValue(e, &er); + + if (!er.byref.dim && !er.byvalue.dim && !er.byexp.dim) + return false; + + bool result = false; + for (size_t i = 0; i < er.byvalue.dim; i++) + { + VarDeclaration *v = er.byvalue[i]; + //printf("byvalue %s\n", v->toChars()); + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + if ((v->isScope() || (v->storage_class & STCmaybescope)) && + !(v->storage_class & STCreturn) && + v->isParameter() && + sc->func->flags & FUNCFLAGreturnInprocess && + p == sc->func) + { + inferReturn(sc->func, v); // infer addition of 'return' + continue; + } + + if (v->isScope()) + { + if (v->storage_class & STCreturn) + continue; + + if (sc->_module && sc->_module->isRoot() && + /* 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)) + { + // Only look for errors if in module listed on command line + if (global.params.vsafe) // https://issues.dlang.org/show_bug.cgi?id=17029 + { + if (!gag) + error(e->loc, "scope variable %s may not be returned", v->toChars()); + result = true; + } + continue; + } + } + else if (v->storage_class & STCvariadic && p == sc->func) + { + Type *tb = v->type->toBasetype(); + if (tb->ty == Tarray || tb->ty == Tsarray) + { + 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\n", v->toChars()); + v->doNotInferScope = true; + } + } + + for (size_t i = 0; i < er.byref.dim; i++) + { + VarDeclaration *v = er.byref[i]; + //printf("byref %s\n", v->toChars()); + if (v->isDataseg()) + continue; + + Dsymbol *p = v->toParent2(); + + if ((v->storage_class & (STCref | STCout)) == 0) + { + if (p == sc->func) + { + escapingRef(v, e, result, gag); + continue; + } + FuncDeclaration *fd = p->isFuncDeclaration(); + if (fd && sc->func->flags & FUNCFLAGreturnInprocess) + { + /* 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 + */ + if (global.params.vsafe) + sc->func->storage_class |= STCreturn; + } + } + + /* 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->storage_class & (STCref | STCout)) && + !(v->storage_class & (STCreturn | STCforeach))) + { + if ((sc->func->flags & FUNCFLAGreturnInprocess) && p == sc->func) + { + inferReturn(sc->func, v); // infer addition of 'return' + } + else if (global.params.useDIP25 && + sc->_module && sc->_module->isRoot()) + { + // 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, e, result, gag); + 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 = (TypeFunction *)fd->type; + if (tf->isref) + { + if (!gag) + error(e->loc, "escaping reference to outer local variable %s", v->toChars()); + result = true; + continue; + } + } + } + } + } + + for (size_t i = 0; i < er.byexp.dim; i++) + { + Expression *ee = er.byexp[i]; + //printf("byexp %s\n", ee->toChars()); + if (!gag) + error(ee->loc, "escaping reference to stack allocated value returned by %s", ee->toChars()); + result = true; + } + + return result; +} + + +/************************************* + * Variable v needs to have 'return' inferred for it. + * Params: + * fd = function that v is a parameter to + * v = parameter that needs to be STCreturn + */ + +static void inferReturn(FuncDeclaration *fd, VarDeclaration *v) +{ + // v is a local in the current function + + //printf("for function '%s' inferring 'return' for variable '%s'\n", fd->toChars(), v->toChars()); + v->storage_class |= STCreturn; + + TypeFunction *tf = (TypeFunction *)fd->type; + if (v == fd->vthis) + { + /* v is the 'this' reference, so mark the function + */ + fd->storage_class |= STCreturn; + if (tf->ty == Tfunction) + { + //printf("'this' too %p %s\n", tf, sc->func->toChars()); + tf->isreturn = true; + } + } + else + { + // Perform 'return' inference on parameter + if (tf->ty == Tfunction && tf->parameters) + { + const size_t dim = Parameter::dim(tf->parameters); + for (size_t i = 0; i < dim; i++) + { + Parameter *p = Parameter::getNth(tf->parameters, i); + if (p->ident == v->ident) + { + p->storageClass |= STCreturn; + break; // there can be only one + } + } + } + } +} + + +/**************************************** + * 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 + */ +static void escapeByValue(Expression *e, EscapeByResults *er) +{ + //printf("[%s] escapeByValue, e: %s\n", e->loc.toChars(), e->toChars()); + + class EscapeVisitor : public Visitor + { + public: + EscapeByResults *er; + + EscapeVisitor(EscapeByResults *er) + : er(er) + { + } + + void visit(Expression *) + { + } + + void visit(AddrExp *e) + { + escapeByRef(e->e1, er); + } + + void visit(SymOffExp *e) + { + VarDeclaration *v = e->var->isVarDeclaration(); + if (v) + er->byref.push(v); + } + + void visit(VarExp *e) + { + VarDeclaration *v = e->var->isVarDeclaration(); + if (v) + er->byvalue.push(v); + } + + void visit(ThisExp *e) + { + if (e->var) + er->byvalue.push(e->var); + } + + void visit(DotVarExp *e) + { + Type *t = e->e1->type->toBasetype(); + if (t->ty == Tstruct) + e->e1->accept(this); + } + + void visit(DelegateExp *e) + { + Type *t = e->e1->type->toBasetype(); + if (t->ty == Tclass || t->ty == Tpointer) + escapeByValue(e->e1, er); + else + escapeByRef(e->e1, er); + er->byfunc.push(e->func); + } + + void visit(FuncExp *e) + { + if (e->fd->tok == TOKdelegate) + er->byfunc.push(e->fd); + } + + void visit(TupleExp *) + { + assert(0); // should have been lowered by now + } + + void visit(ArrayLiteralExp *e) + { + Type *tb = e->type->toBasetype(); + if (tb->ty == Tsarray || tb->ty == Tarray) + { + if (e->basis) + e->basis->accept(this); + for (size_t i = 0; i < e->elements->dim; i++) + { + Expression *el = (*e->elements)[i]; + if (el) + el->accept(this); + } + } + } + + void visit(StructLiteralExp *e) + { + if (e->elements) + { + for (size_t i = 0; i < e->elements->dim; i++) + { + Expression *ex = (*e->elements)[i]; + if (ex) + ex->accept(this); + } + } + } + + void visit(NewExp *e) + { + Type *tb = e->newtype->toBasetype(); + if (tb->ty == Tstruct && !e->member && e->arguments) + { + for (size_t i = 0; i < e->arguments->dim; i++) + { + Expression *ex = (*e->arguments)[i]; + if (ex) + ex->accept(this); + } + } + } + + void visit(CastExp *e) + { + Type *tb = e->type->toBasetype(); + if (tb->ty == Tarray && + e->e1->type->toBasetype()->ty == Tsarray) + { + escapeByRef(e->e1, er); + } + else + e->e1->accept(this); + } + + void visit(SliceExp *e) + { + if (e->e1->op == TOKvar) + { + VarDeclaration *v = ((VarExp *)e->e1)->var->isVarDeclaration(); + Type *tb = e->type->toBasetype(); + if (v) + { + if (tb->ty == Tsarray) + return; + if (v->storage_class & STCvariadic) + { + 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); + } + else + e->e1->accept(this); + } + + void visit(BinExp *e) + { + Type *tb = e->type->toBasetype(); + if (tb->ty == Tpointer) + { + e->e1->accept(this); + e->e2->accept(this); + } + } + + void visit(BinAssignExp *e) + { + e->e1->accept(this); + } + + void visit(AssignExp *e) + { + e->e1->accept(this); + } + + void visit(CommaExp *e) + { + e->e2->accept(this); + } + + void visit(CondExp *e) + { + e->e1->accept(this); + e->e2->accept(this); + } + + void visit(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 = NULL; + TypeDelegate *dg = NULL; + if (t1->ty == Tdelegate) + { + dg = (TypeDelegate *)t1; + tf = (TypeFunction *)dg->next; + } + else if (t1->ty == Tfunction) + tf = (TypeFunction *)t1; + else + return; + + if (e->arguments && e->arguments->dim) + { + /* j=1 if _arguments[] is first argument, + * skip it because it is not passed by ref + */ + size_t j = (tf->linkage == LINKd && tf->varargs == 1); + for (size_t i = j; i < e->arguments->dim; ++i) + { + Expression *arg = (*e->arguments)[i]; + size_t nparams = Parameter::dim(tf->parameters); + if (i - j < nparams && i >= j) + { + Parameter *p = Parameter::getNth(tf->parameters, i - j); + const StorageClass stc = tf->parameterStorageClass(p); + if ((stc & (STCscope)) && (stc & STCreturn)) + arg->accept(this); + else if ((stc & (STCref)) && (stc & STCreturn)) + escapeByRef(arg, er); + } + } + } + // If 'this' is returned, check it too + if (e->e1->op == TOKdotvar && t1->ty == Tfunction) + { + DotVarExp *dve = (DotVarExp *)e->e1; + FuncDeclaration *fd = dve->var->isFuncDeclaration(); + AggregateDeclaration *ad = NULL; + if (global.params.vsafe && tf->isreturn && fd && (ad = fd->isThis()) != NULL) + { + if (ad->isClassDeclaration() || tf->isscope) // this is 'return scope' + dve->e1->accept(this); + else if (ad->isStructDeclaration()) // this is 'return ref' + escapeByRef(dve->e1, er); + } + else if (dve->var->storage_class & STCreturn || tf->isreturn) + { + if (dve->var->storage_class & STCscope) + dve->e1->accept(this); + else if (dve->var->storage_class & STCref) + escapeByRef(dve->e1, er); + } + } + + /* If returning the result of a delegate call, the .ptr + * field of the delegate must be checked. + */ + if (dg) + { + if (tf->isreturn) + e->e1->accept(this); + } + } + }; + + EscapeVisitor v(er); + e->accept(&v); +} + +/**************************************** + * 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 + */ +static void escapeByRef(Expression *e, EscapeByResults *er) +{ + //printf("[%s] escapeByRef, e: %s\n", e->loc->toChars(), e->toChars()); + class EscapeRefVisitor : public Visitor + { + public: + EscapeByResults *er; + + EscapeRefVisitor(EscapeByResults *er) + : er(er) + { + } + + void visit(Expression *) + { + } + + void visit(VarExp *e) + { + VarDeclaration *v = e->var->isVarDeclaration(); + if (v) + { + if (v->storage_class & STCref && v->storage_class & (STCforeach | STCtemp) && v->_init) + { + /* If compiler generated ref temporary + * (ref v = ex; ex) + * look at the initializer instead + */ + if (ExpInitializer *ez = v->_init->isExpInitializer()) + { + assert(ez->exp && ez->exp->op == TOKconstruct); + Expression *ex = ((ConstructExp *)ez->exp)->e2; + ex->accept(this); + } + } + else + er->byref.push(v); + } + } + + void visit(ThisExp *e) + { + if (e->var) + er->byref.push(e->var); + } + + void visit(PtrExp *e) + { + escapeByValue(e->e1, er); + } + + void visit(IndexExp *e) + { + Type *tb = e->e1->type->toBasetype(); + if (e->e1->op == TOKvar) + { + VarDeclaration *v = ((VarExp *)e->e1)->var->isVarDeclaration(); + if (tb->ty == Tarray || tb->ty == Tsarray) + { + if (v->storage_class & STCvariadic) + { + er->byref.push(v); + return; + } + } + } + if (tb->ty == Tsarray) + { + e->e1->accept(this); + } + else if (tb->ty == Tarray) + { + escapeByValue(e->e1, er); + } + } + + void visit(DotVarExp *e) + { + Type *t1b = e->e1->type->toBasetype(); + if (t1b->ty == Tclass) + escapeByValue(e->e1, er); + else + e->e1->accept(this); + } + + void visit(BinAssignExp *e) + { + e->e1->accept(this); + } + + void visit(AssignExp *e) + { + e->e1->accept(this); + } + + void visit(CommaExp *e) + { + e->e2->accept(this); + } + + void visit(CondExp *e) + { + e->e1->accept(this); + e->e2->accept(this); + } + + void visit(CallExp *e) + { + /* 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 = (TypeFunction *)((TypeDelegate *)t1)->next; + else if (t1->ty == Tfunction) + tf = (TypeFunction *)t1; + else + return; + if (tf->isref) + { + if (e->arguments && e->arguments->dim) + { + /* j=1 if _arguments[] is first argument, + * skip it because it is not passed by ref + */ + size_t j = (tf->linkage == LINKd && tf->varargs == 1); + + for (size_t i = j; i < e->arguments->dim; ++i) + { + Expression *arg = (*e->arguments)[i]; + size_t nparams = Parameter::dim(tf->parameters); + if (i - j < nparams && i >= j) + { + Parameter *p = Parameter::getNth(tf->parameters, i - j); + const StorageClass stc = tf->parameterStorageClass(p); + if ((stc & (STCout | STCref)) && (stc & STCreturn)) + arg->accept(this); + else if ((stc & STCscope) && (stc & STCreturn)) + { + if (arg->op == TOKdelegate) + { + DelegateExp *de = (DelegateExp *)arg; + if (de->func->isNested()) + er->byexp.push(de); + } + else + escapeByValue(arg, er); + } + } + } + } + + // If 'this' is returned by ref, check it too + if (e->e1->op == TOKdotvar && t1->ty == Tfunction) + { + DotVarExp *dve = (DotVarExp *)e->e1; + if (dve->var->storage_class & STCreturn || tf->isreturn) + { + if ((dve->var->storage_class & STCscope) || tf->isscope) + escapeByValue(dve->e1, er); + else if ((dve->var->storage_class & STCref) || tf->isref) + dve->e1->accept(this); + } + + } + // If it's a delegate, check it too + if (e->e1->op == TOKvar && t1->ty == Tdelegate) + { + escapeByValue(e->e1, er); + } + } + else + er->byexp.push(e); + } + }; + + EscapeRefVisitor v(er); + e->accept(&v); +} + +/************************* + * Find all variables accessed by this delegate that are + * in functions enclosing it. + * Params: + * fd = function + * vars = array to append found variables to + */ +void findAllOuterAccessedVariables(FuncDeclaration *fd, VarDeclarations *vars) +{ + //printf("findAllOuterAccessedVariables(fd: %s)\n", fd.toChars()); + for (Dsymbol *p = fd->parent; p; p = p->parent) + { + FuncDeclaration *fdp = p->isFuncDeclaration(); + if (fdp) + { + for (size_t i = 0; i < fdp->closureVars.dim; i++) + { + VarDeclaration *v = fdp->closureVars[i]; + for (size_t j = 0; j < v->nestedrefs.dim; j++) + { + FuncDeclaration *fdv = v->nestedrefs[j]; + if (fdv == fd) + { + //printf("accessed: %s, type %s\n", v->toChars(), v->type->toChars()); + vars->push(v); + } + } + } + } + } +} |