
/* Compiler implementation of the D programming language
 * Copyright (C) 1999-2019 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/canthrow.c
 */

#include "root/dsystem.h"

#include "mars.h"
#include "init.h"
#include "expression.h"
#include "template.h"
#include "statement.h"
#include "mtype.h"
#include "utf.h"
#include "declaration.h"
#include "aggregate.h"
#include "scope.h"
#include "attrib.h"
#include "tokens.h"

bool Dsymbol_canThrow(Dsymbol *s, FuncDeclaration *func, bool mustNotThrow);
bool walkPostorder(Expression *e, StoppableVisitor *v);

/********************************************
 * Returns true if the expression may throw exceptions.
 * If 'mustNotThrow' is true, generate an error if it throws
 */

bool canThrow(Expression *e, FuncDeclaration *func, bool mustNotThrow)
{
    //printf("Expression::canThrow(%d) %s\n", mustNotThrow, toChars());

    // stop walking if we determine this expression can throw
    class CanThrow : public StoppableVisitor
    {
        FuncDeclaration *func;
        bool mustNotThrow;

    public:
        CanThrow(FuncDeclaration *func, bool mustNotThrow)
            : func(func), mustNotThrow(mustNotThrow)
        {
        }

        void visit(Expression *)
        {
        }

        void visit(DeclarationExp *de)
        {
            stop = Dsymbol_canThrow(de->declaration, func, mustNotThrow);
        }

        void visit(CallExp *ce)
        {
            if (global.errors && !ce->e1->type)
                return;                       // error recovery

            /* If calling a function or delegate that is typed as nothrow,
             * then this expression cannot throw.
             * Note that pure functions can throw.
             */
            Type *t = ce->e1->type->toBasetype();
            if (ce->f && ce->f == func)
                return;
            if (t->ty == Tfunction && ((TypeFunction *)t)->isnothrow)
                return;
            if (t->ty == Tdelegate && ((TypeFunction *)((TypeDelegate *)t)->next)->isnothrow)
                return;

            if (mustNotThrow)
            {
                if (ce->f)
                {
                    ce->error("%s '%s' is not nothrow",
                        ce->f->kind(), ce->f->toPrettyChars());
                }
                else
                {
                    Expression *e1 = ce->e1;
                    if (e1->op == TOKstar)   // print 'fp' if e1 is (*fp)
                        e1 = ((PtrExp *)e1)->e1;
                    ce->error("'%s' is not nothrow", e1->toChars());
                }
            }
            stop = true;
        }

        void visit(NewExp *ne)
        {
            if (ne->member)
            {
                if (ne->allocator)
                {
                    // Bugzilla 14407
                    Type *t = ne->allocator->type->toBasetype();
                    if (t->ty == Tfunction && !((TypeFunction *)t)->isnothrow)
                    {
                        if (mustNotThrow)
                        {
                            ne->error("%s '%s' is not nothrow",
                                ne->allocator->kind(), ne->allocator->toPrettyChars());
                        }
                        stop = true;
                    }
                }
                // See if constructor call can throw
                Type *t = ne->member->type->toBasetype();
                if (t->ty == Tfunction && !((TypeFunction *)t)->isnothrow)
                {
                    if (mustNotThrow)
                    {
                        ne->error("%s '%s' is not nothrow",
                            ne->member->kind(), ne->member->toPrettyChars());
                    }
                    stop = true;
                }
            }
            // regard storage allocation failures as not recoverable
        }

        void visit(DeleteExp *de)
        {
            Type *tb = de->e1->type->toBasetype();
            AggregateDeclaration *ad = NULL;
            switch (tb->ty)
            {
            case Tclass:
                ad = ((TypeClass *)tb)->sym;
                break;

            case Tpointer:
                tb = ((TypePointer *)tb)->next->toBasetype();
                if (tb->ty == Tstruct)
                    ad = ((TypeStruct *)tb)->sym;
                break;

            case Tarray:
            {
                Type *tv = tb->nextOf()->baseElemOf();
                if (tv->ty == Tstruct)
                {
                    ad = ((TypeStruct *)tv)->sym;
                    break;
                }
            }

            default:
                break;
            }
            if (!ad)
                return;

            if (ad->dtor)
            {
                Type *t = ad->dtor->type->toBasetype();
                if (t->ty == Tfunction && !((TypeFunction *)t)->isnothrow)
                {
                    if (mustNotThrow)
                    {
                        de->error("%s '%s' is not nothrow",
                            ad->dtor->kind(), ad->dtor->toPrettyChars());
                    }
                    stop = true;
                }
            }
            if (ad->aggDelete && tb->ty != Tarray)
            {
                Type *t = ad->aggDelete->type;
                if (t->ty == Tfunction && !((TypeFunction *)t)->isnothrow)
                {
                    if (mustNotThrow)
                    {
                        de->error("%s '%s' is not nothrow",
                            ad->aggDelete->kind(), ad->aggDelete->toPrettyChars());
                    }
                    stop = true;
                }
            }
        }

        void visit(AssignExp *ae)
        {
            // blit-init cannot throw
            if (ae->op == TOKblit)
                return;

            /* Element-wise assignment could invoke postblits.
             */
            Type *t;
            if (ae->type->toBasetype()->ty == Tsarray)
            {
                if (!ae->e2->isLvalue())
                    return;
                t = ae->type;
            }
            else if (ae->e1->op == TOKslice)
                t = ((SliceExp *)ae->e1)->e1->type;
            else
                return;

            Type *tv = t->baseElemOf();
            if (tv->ty != Tstruct)
                return;
            StructDeclaration *sd = ((TypeStruct *)tv)->sym;
            if (!sd->postblit || sd->postblit->type->ty != Tfunction)
                return;

            if (((TypeFunction *)sd->postblit->type)->isnothrow)
                ;
            else
            {
                if (mustNotThrow)
                {
                    ae->error("%s '%s' is not nothrow",
                        sd->postblit->kind(), sd->postblit->toPrettyChars());
                }
                stop = true;
            }
        }

        void visit(NewAnonClassExp *)
        {
            assert(0);          // should have been lowered by semantic()
        }
    };

    CanThrow ct(func, mustNotThrow);
    return walkPostorder(e, &ct);
}

/**************************************
 * Does symbol, when initialized, throw?
 * Mirrors logic in Dsymbol_toElem().
 */

bool Dsymbol_canThrow(Dsymbol *s, FuncDeclaration *func, bool mustNotThrow)
{
    AttribDeclaration *ad;
    VarDeclaration *vd;
    TemplateMixin *tm;
    TupleDeclaration *td;

    //printf("Dsymbol_toElem() %s\n", s->toChars());
    ad = s->isAttribDeclaration();
    if (ad)
    {
        Dsymbols *decl = ad->include(NULL, NULL);
        if (decl && decl->dim)
        {
            for (size_t i = 0; i < decl->dim; i++)
            {
                s = (*decl)[i];
                if (Dsymbol_canThrow(s, func, mustNotThrow))
                    return true;
            }
        }
    }
    else if ((vd = s->isVarDeclaration()) != NULL)
    {
        s = s->toAlias();
        if (s != vd)
            return Dsymbol_canThrow(s, func, mustNotThrow);
        if (vd->storage_class & STCmanifest)
            ;
        else if (vd->isStatic() || vd->storage_class & (STCextern | STCtls | STCgshared))
            ;
        else
        {
            if (vd->_init)
            {
                ExpInitializer *ie = vd->_init->isExpInitializer();
                if (ie && canThrow(ie->exp, func, mustNotThrow))
                    return true;
            }
            if (vd->needsScopeDtor())
                return canThrow(vd->edtor, func, mustNotThrow);
        }
    }
    else if ((tm = s->isTemplateMixin()) != NULL)
    {
        //printf("%s\n", tm->toChars());
        if (tm->members)
        {
            for (size_t i = 0; i < tm->members->dim; i++)
            {
                Dsymbol *sm = (*tm->members)[i];
                if (Dsymbol_canThrow(sm, func, mustNotThrow))
                    return true;
            }
        }
    }
    else if ((td = s->isTupleDeclaration()) != NULL)
    {
        for (size_t i = 0; i < td->objects->dim; i++)
        {
            RootObject *o = (*td->objects)[i];
            if (o->dyncast() == DYNCAST_EXPRESSION)
            {
                Expression *eo = (Expression *)o;
                if (eo->op == TOKdsymbol)
                {
                    DsymbolExp *se = (DsymbolExp *)eo;
                    if (Dsymbol_canThrow(se->s, func, mustNotThrow))
                        return true;
                }
            }
        }
    }
    return false;
}
