// Compiler implementation of the D programming language
// Copyright (c) 1999-2016 by Digital Mars
// 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

module ddmd.escape;

import ddmd.declaration;
import ddmd.dscope;
import ddmd.dsymbol;
import ddmd.errors;
import ddmd.expression;
import ddmd.func;
import ddmd.globals;
import ddmd.init;
import ddmd.mtype;
import ddmd.root.rootobject;
import ddmd.tokens;
import ddmd.visitor;

/************************************
 * Detect cases where pointers to the stack can 'escape' the
 * lifetime of the stack frame.
 * 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 pointers to the stack can escape
 */
bool checkEscape(Scope* sc, Expression e, bool gag)
{
    //printf("[%s] checkEscape, e = %s\n", e.loc.toChars(), e.toChars());
    extern (C++) final class EscapeVisitor : Visitor
    {
        alias visit = super.visit;
    public:
        Scope* sc;
        bool gag;
        bool result;

        extern (D) this(Scope* sc, bool gag)
        {
            this.sc = sc;
            this.gag = gag;
        }

        void error(Loc loc, const(char)* format, Dsymbol s)
        {
            if (!gag)
                .error(loc, format, s.toChars());
            result = true;
        }

        void check(Loc loc, Declaration d)
        {
            VarDeclaration v = d.isVarDeclaration();
            if (v && v.toParent2() == sc.func)
            {
                if (v.isDataseg())
                    return;
                if ((v.storage_class & (STCref | STCout)) == 0)
                    error(loc, "escaping reference to local %s", v);
            }
        }

        override void visit(Expression e)
        {
        }

        override void visit(AddrExp e)
        {
            result |= checkEscapeRef(sc, e.e1, gag);
        }

        override void visit(SymOffExp e)
        {
            check(e.loc, e.var);
        }

        override void visit(VarExp e)
        {
            VarDeclaration v = e.var.isVarDeclaration();
            if (v)
            {
                Type tb = v.type.toBasetype();
                if (v.isScope())
                {
                    /* Today, scope attribute almost doesn't work for escape analysis.
                     * Until the semantics will be completed, it should be left as-is.
                     * See also: fail_compilation/fail_scope.d
                     */
                    if (tb.ty == Tarray || tb.ty == Tsarray || tb.ty == Tclass || tb.ty == Tdelegate)
                    {
                        if (v.needsScopeDtor() || tb.ty == Tclass)
                        {
                            error(e.loc, "escaping reference to scope local %s", v);
                            return;
                        }
                    }
                }
                if (v.storage_class & STCvariadic)
                {
                    if (tb.ty == Tarray || tb.ty == Tsarray)
                        error(e.loc, "escaping reference to variadic parameter %s", v);
                }
            }
        }

        override void visit(TupleExp e)
        {
            for (size_t i = 0; i < e.exps.dim; i++)
            {
                (*e.exps)[i].accept(this);
            }
        }

        override 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++)
                {
                    auto el = (*e.elements)[i];
                    if (!el)
                        continue;
                    el.accept(this);
                }
            }
        }

        override 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);
                }
            }
        }

        override 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);
                }
            }
        }

        override void visit(CastExp e)
        {
            Type tb = e.type.toBasetype();
            if (tb.ty == Tarray && e.e1.type.toBasetype().ty == Tsarray)
            {
                result |= checkEscapeRef(sc, e.e1, gag);
            }
        }

        override void visit(SliceExp e)
        {
            if (e.e1.op == TOKvar)
            {
                VarDeclaration v = (cast(VarExp)e.e1).var.isVarDeclaration();
                Type tb = e.type.toBasetype();
                if (v)
                {
                    if (tb.ty == Tsarray)
                        return;
                    if (v.storage_class & STCvariadic)
                    {
                        error(e.loc, "escaping reference to the payload of variadic parameter %s", v);
                        return;
                    }
                }
            }
            Type t1b = e.e1.type.toBasetype();
            if (t1b.ty == Tsarray)
                result |= checkEscapeRef(sc, e.e1, gag);
            else
                e.e1.accept(this);
        }

        override void visit(BinExp e)
        {
            Type tb = e.type.toBasetype();
            if (tb.ty == Tpointer)
            {
                e.e1.accept(this);
                e.e2.accept(this);
            }
        }

        override void visit(BinAssignExp e)
        {
            e.e2.accept(this);
        }

        override void visit(AssignExp e)
        {
            e.e2.accept(this);
        }

        override void visit(CommaExp e)
        {
            e.e2.accept(this);
        }

        override void visit(CondExp e)
        {
            e.e1.accept(this);
            e.e2.accept(this);
        }
    }

    scope EscapeVisitor v = new EscapeVisitor(sc, gag);
    e.accept(v);
    return v.result;
}

/************************************
 * 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 referencess to the stack can escape
 */
bool checkEscapeRef(Scope* sc, Expression e, bool gag)
{
    //printf("[%s] checkEscapeRef, e = %s\n", e.loc.toChars(), e.toChars());
    extern (C++) final class EscapeRefVisitor : Visitor
    {
        alias visit = super.visit;
    public:
        Scope* sc;
        bool gag;
        bool result;

        extern (D) this(Scope* sc, bool gag)
        {
            this.sc = sc;
            this.gag = gag;
        }

        void error(Loc loc, const(char)* format, RootObject o)
        {
            if (!gag)
                .error(loc, format, o.toChars());
            result = true;
        }

        void check(Loc loc, Declaration d)
        {
            assert(d);
            VarDeclaration v = d.isVarDeclaration();
            if (v && v.toParent2() == sc.func)
            {
                if (v.isDataseg())
                    return;
                if ((v.storage_class & (STCref | STCout)) == 0)
                {
                    error(loc, "escaping reference to local variable %s", v);
                    return;
                }
                if (global.params.useDIP25 && (v.storage_class & (STCref | STCout)) && !(v.storage_class & (STCreturn | STCforeach)))
                {
                    if (sc.func.flags & FUNCFLAGreturnInprocess)
                    {
                        //printf("inferring 'return' for variable '%s'\n", v.toChars());
                        v.storage_class |= STCreturn;
                        if (v == sc.func.vthis)
                        {
                            TypeFunction tf = cast(TypeFunction)sc.func.type;
                            if (tf.ty == Tfunction)
                            {
                                //printf("'this' too\n");
                                tf.isreturn = true;
                            }
                        }
                    }
                    else if (sc._module && sc._module.isRoot())
                    {
                        //printf("escaping reference to local ref variable %s\n", v.toChars());
                        //printf("storage class = x%llx\n", v.storage_class);
                        error(loc, "escaping reference to local ref variable %s", v);
                    }
                    return;
                }
                if (v.storage_class & STCref && v.storage_class & (STCforeach | STCtemp) && v._init)
                {
                    // (ref v = ex; ex)
                    if (ExpInitializer ez = v._init.isExpInitializer())
                    {
                        assert(ez.exp && ez.exp.op == TOKconstruct);
                        Expression ex = (cast(ConstructExp)ez.exp).e2;
                        ex.accept(this);
                        return;
                    }
                }
            }
        }

        override void visit(Expression e)
        {
        }

        override void visit(VarExp e)
        {
            check(e.loc, e.var);
        }

        override void visit(ThisExp e)
        {
            if (e.var)
                check(e.loc, e.var);
        }

        override void visit(PtrExp e)
        {
            result |= checkEscape(sc, e.e1, gag);
        }

        override void visit(IndexExp e)
        {
            if (e.e1.op == TOKvar)
            {
                VarDeclaration v = (cast(VarExp)e.e1).var.isVarDeclaration();
                if (v && v.toParent2() == sc.func)
                {
                    Type tb = v.type.toBasetype();
                    if (tb.ty == Tarray || tb.ty == Tsarray)
                    {
                        if (v.storage_class & STCvariadic)
                        {
                            error(e.loc, "escaping reference to the payload of variadic parameter %s", v);
                            return;
                        }
                    }
                }
            }
            Type tb = e.e1.type.toBasetype();
            if (tb.ty == Tsarray)
            {
                e.e1.accept(this);
            }
        }

        override void visit(DotVarExp e)
        {
            Type t1b = e.e1.type.toBasetype();
            if (t1b.ty == Tclass)
                result |= checkEscape(sc, e.e1, gag);
            else
                e.e1.accept(this);
        }

        override void visit(BinAssignExp e)
        {
            e.e1.accept(this);
        }

        override void visit(AssignExp e)
        {
            e.e1.accept(this);
        }

        override void visit(CommaExp e)
        {
            e.e2.accept(this);
        }

        override void visit(CondExp e)
        {
            e.e1.accept(this);
            e.e2.accept(this);
        }

        override 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 = cast(TypeFunction)(cast(TypeDelegate)t1).next;
            else if (t1.ty == Tfunction)
                tf = cast(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
                     */
                    int 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);
                            if ((p.storageClass & (STCout | STCref)) && (p.storageClass & STCreturn))
                                arg.accept(this);
                        }
                    }
                }
                // If 'this' is returned by ref, check it too
                if (tf.isreturn && e.e1.op == TOKdotvar && t1.ty == Tfunction)
                {
                    DotVarExp dve = cast(DotVarExp)e.e1;
                    dve.e1.accept(this);
                }
            }
            else
            {
                error(e.loc, "escaping reference to stack allocated value returned by %s", e);
                return;
            }
        }
    }

    scope EscapeRefVisitor v = new EscapeRefVisitor(sc, gag);
    e.accept(v);
    return v.result;
}
