
// Copyright (c) 1999-2006 by Digital Mars
// All Rights Reserved
// written by Walter Bright
// http://www.digitalmars.com
// License for redistribution is by either the Artistic License
// in artistic.txt, or the GNU General Public License in gnu.txt.
// See the included readme.txt for details.


#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "root.h"
#include "rmem.h"

#include "enum.h"
#include "aggregate.h"
#include "init.h"
#include "attrib.h"
#include "scope.h"
#include "id.h"
#include "mtype.h"
#include "declaration.h"
#include "aggregate.h"
#include "expression.h"
#include "module.h"

#define LOG 0

/* Code to do access checks
 */

int hasPackageAccess(Scope *sc, Dsymbol *s);

/****************************************
 * Return PROT access for Dsymbol smember in this declaration.
 */

enum PROT AggregateDeclaration::getAccess(Dsymbol *smember)
{
    return PROTpublic;
}

enum PROT StructDeclaration::getAccess(Dsymbol *smember)
{
    enum PROT access_ret = PROTnone;

#if LOG
    printf("+StructDeclaration::getAccess(this = '%s', smember = '%s')\n",
        toChars(), smember->toChars());
#endif
    if (smember->toParent() == this)
    {
        access_ret = smember->prot();
    }
    else if (smember->isDeclaration()->isStatic())
    {
        access_ret = smember->prot();
    }
    return access_ret;
}

enum PROT ClassDeclaration::getAccess(Dsymbol *smember)
{
    enum PROT access_ret = PROTnone;

#if LOG
    printf("+ClassDeclaration::getAccess(this = '%s', smember = '%s')\n",
        toChars(), smember->toChars());
#endif
    if (smember->toParent() == this)
    {
        access_ret = smember->prot();
    }
    else
    {
        enum PROT access;
        int i;

        if (smember->isDeclaration()->isStatic())
        {
            access_ret = smember->prot();
        }

        for (i = 0; i < baseclasses->dim; i++)
        {   BaseClass *b = (BaseClass *)baseclasses->data[i];

            access = b->base->getAccess(smember);
            switch (access)
            {
                case PROTnone:
                    break;

                case PROTprivate:
                    access = PROTnone;  // private members of base class not accessible
                    break;

                case PROTpackage:
                case PROTprotected:
                case PROTpublic:
                case PROTexport:
                    // If access is to be tightened
                    if (b->protection < access)
                        access = b->protection;

                    // Pick path with loosest access
                    if (access > access_ret)
                        access_ret = access;
                    break;

                default:
                    assert(0);
            }
        }
    }
#if LOG
    printf("-ClassDeclaration::getAccess(this = '%s', smember = '%s') = %d\n",
        toChars(), smember->toChars(), access_ret);
#endif
    return access_ret;
}

/********************************************************
 * Helper function for ClassDeclaration::accessCheck()
 * Returns:
 *      0       no access
 *      1       access
 */

static int accessCheckX(
        Dsymbol *smember,
        Dsymbol *sfunc,
        AggregateDeclaration *dthis,
        AggregateDeclaration *cdscope)
{
    assert(dthis);

#if 0
    printf("accessCheckX for %s.%s in function %s() in scope %s\n",
        dthis->toChars(), smember->toChars(),
        sfunc ? sfunc->toChars() : "NULL",
        cdscope ? cdscope->toChars() : "NULL");
#endif
    if (dthis->hasPrivateAccess(sfunc) ||
        dthis->isFriendOf(cdscope))
    {
        if (smember->toParent() == dthis)
            return 1;
        else
        {
            ClassDeclaration *cdthis = dthis->isClassDeclaration();
            if (cdthis)
            {
                for (int i = 0; i < cdthis->baseclasses->dim; i++)
                {   BaseClass *b = (BaseClass *)cdthis->baseclasses->data[i];
                    enum PROT access;

                    access = b->base->getAccess(smember);
                    if (access >= PROTprotected ||
                        accessCheckX(smember, sfunc, b->base, cdscope)
                       )
                        return 1;

                }
            }
        }
    }
    else
    {
        if (smember->toParent() != dthis)
        {
            ClassDeclaration *cdthis = dthis->isClassDeclaration();
            if (cdthis)
            {
                for (int i = 0; i < cdthis->baseclasses->dim; i++)
                {   BaseClass *b = (BaseClass *)cdthis->baseclasses->data[i];

                    if (accessCheckX(smember, sfunc, b->base, cdscope))
                        return 1;
                }
            }
        }
    }
    return 0;
}

/*******************************
 * Do access check for member of this class, this class being the
 * type of the 'this' pointer used to access smember.
 */

void AggregateDeclaration::accessCheck(Loc loc, Scope *sc, Dsymbol *smember)
{
    int result;

    FuncDeclaration *f = sc->func;
    AggregateDeclaration *cdscope = sc->getStructClassScope();
    enum PROT access;

#if LOG
    printf("AggregateDeclaration::accessCheck() for %s.%s in function %s() in scope %s\n",
        toChars(), smember->toChars(),
        f ? f->toChars() : NULL,
        cdscope ? cdscope->toChars() : NULL);
#endif

    Dsymbol *smemberparent = smember->toParent();
    if (!smemberparent || !smemberparent->isAggregateDeclaration())
    {
#if LOG
        printf("not an aggregate member\n");
#endif
        return;                         // then it is accessible
    }

    // BUG: should enable this check
    //assert(smember->parent->isBaseOf(this, NULL));

    if (smemberparent == this)
    {   enum PROT access = smember->prot();

        result = access >= PROTpublic ||
                hasPrivateAccess(f) ||
                isFriendOf(cdscope) ||
                (access == PROTpackage && hasPackageAccess(sc, this));
#if LOG
        printf("result1 = %d\n", result);
#endif
    }
    else if ((access = this->getAccess(smember)) >= PROTpublic)
    {
        result = 1;
#if LOG
        printf("result2 = %d\n", result);
#endif
    }
    else if (access == PROTpackage && hasPackageAccess(sc, this))
    {
        result = 1;
#if LOG
        printf("result3 = %d\n", result);
#endif
    }
    else
    {
        result = accessCheckX(smember, f, this, cdscope);
#if LOG
        printf("result4 = %d\n", result);
#endif
    }
    if (!result)
    {
        error(loc, "member %s is not accessible", smember->toChars());
halt();
    }
}

/****************************************
 * Determine if this is the same or friend of cd.
 */

int AggregateDeclaration::isFriendOf(AggregateDeclaration *cd)
{
#if LOG
    printf("AggregateDeclaration::isFriendOf(this = '%s', cd = '%s')\n", toChars(), cd ? cd->toChars() : "null");
#endif
    if (this == cd)
        return 1;

    // Friends if both are in the same module
    //if (toParent() == cd->toParent())
    if (cd && getModule() == cd->getModule())
    {
#if LOG
        printf("\tin same module\n");
#endif
        return 1;
    }

#if LOG
    printf("\tnot friend\n");
#endif
    return 0;
}

/****************************************
 * Determine if scope sc has package level access to s.
 */

int hasPackageAccess(Scope *sc, Dsymbol *s)
{
#if LOG
    printf("hasPackageAccess(s = '%s', sc = '%p')\n", s->toChars(), sc);
#endif

    for (; s; s = s->parent)
    {
        if (s->isPackage() && !s->isModule())
            break;
    }
#if LOG
    if (s)
        printf("\tthis is in package '%s'\n", s->toChars());
#endif

    if (s && s == sc->module->parent)
    {
#if LOG
        printf("\ts is in same package as sc\n");
#endif
        return 1;
    }


#if LOG
    printf("\tno package access\n");
#endif
    return 0;
}

/**********************************
 * Determine if smember has access to private members of this declaration.
 */

int AggregateDeclaration::hasPrivateAccess(Dsymbol *smember)
{
    if (smember)
    {   AggregateDeclaration *cd = NULL;
        Dsymbol *smemberparent = smember->toParent();
        if (smemberparent)
            cd = smemberparent->isAggregateDeclaration();

#if LOG
        printf("AggregateDeclaration::hasPrivateAccess(class %s, member %s)\n",
                toChars(), smember->toChars());
#endif

        if (this == cd)         // smember is a member of this class
        {
#if LOG
            printf("\tyes 1\n");
#endif
            return 1;           // so we get private access
        }

        // If both are members of the same module, grant access
        while (1)
        {   Dsymbol *sp = smember->toParent();
            if (sp->isFuncDeclaration() && smember->isFuncDeclaration())
                smember = sp;
            else
                break;
        }
        if (!cd && toParent() == smember->toParent())
        {
#if LOG
            printf("\tyes 2\n");
#endif
            return 1;
        }
        if (!cd && getModule() == smember->getModule())
        {
#if LOG
            printf("\tyes 3\n");
#endif
            return 1;
        }
    }
#if LOG
    printf("\tno\n");
#endif
    return 0;
}

/****************************************
 * Check access to d for expression e.d
 */

void accessCheck(Loc loc, Scope *sc, Expression *e, Declaration *d)
{
#if LOG
    if (e)
    {   printf("accessCheck(%s . %s)\n", e->toChars(), d->toChars());
        printf("\te->type = %s\n", e->type->toChars());
    }
    else
    {
        //printf("accessCheck(%s)\n", d->toChars());
    }
#endif
    if (!e)
    {
        if (d->prot() == PROTprivate && d->getModule() != sc->module ||
            d->prot() == PROTpackage && !hasPackageAccess(sc, d))

            error(loc, "%s %s.%s is not accessible from %s",
                d->kind(), d->getModule()->toChars(), d->toChars(), sc->module->toChars());
    }
    else if (e->type->ty == Tclass)
    {   // Do access check
        ClassDeclaration *cd;

        cd = (ClassDeclaration *)(((TypeClass *)e->type)->sym);
#if 1
        if (e->op == TOKsuper)
        {   ClassDeclaration *cd2;

            cd2 = sc->func->toParent()->isClassDeclaration();
            if (cd2)
                cd = cd2;
        }
#endif
        cd->accessCheck(loc, sc, d);
    }
    else if (e->type->ty == Tstruct)
    {   // Do access check
        StructDeclaration *cd;

        cd = (StructDeclaration *)(((TypeStruct *)e->type)->sym);
        cd->accessCheck(loc, sc, d);
    }
}
