/**
 * Compiler implementation of the
 * $(LINK2 http://www.dlang.org, D programming language).
 *
 * Copyright:   Copyright (c) 1999-2017 by Digital Mars, All Rights Reserved
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/ddmd/safe.d, _safe.d)
 */

module ddmd.safe;

// Online documentation: https://dlang.org/phobos/ddmd_safe.html

import core.stdc.stdio;

import ddmd.aggregate;
import ddmd.dclass;
import ddmd.declaration;
import ddmd.dscope;
import ddmd.expression;
import ddmd.mtype;
import ddmd.target;
import ddmd.tokens;


/*************************************************************
 * Check for unsafe access in @safe code:
 * 1. read overlapped pointers
 * 2. write misaligned pointers
 * 3. write overlapped storage classes
 * Print error if unsafe.
 * Params:
 *      sc = scope
 *      e = expression to check
 *      readonly = if access is read-only
 *      printmsg = print error message if true
 * Returns:
 *      true if error
 */

bool checkUnsafeAccess(Scope* sc, Expression e, bool readonly, bool printmsg)
{
    if (e.op != TOKdotvar)
        return false;
    DotVarExp dve = cast(DotVarExp)e;
    if (VarDeclaration v = dve.var.isVarDeclaration())
    {
        if (sc.intypeof || !sc.func || !sc.func.isSafeBypassingInference())
            return false;

        auto ad = v.toParent2().isAggregateDeclaration();
        if (!ad)
            return false;

        if (v.overlapped && v.type.hasPointers() && sc.func.setUnsafe())
        {
            if (printmsg)
                e.error("field %s.%s cannot access pointers in @safe code that overlap other fields",
                    ad.toChars(), v.toChars());
            return true;
        }

        if (readonly || !e.type.isMutable())
            return false;

        if (v.type.hasPointers() && v.type.toBasetype().ty != Tstruct)
        {
            if ((ad.type.alignment() < Target.ptrsize ||
                 (v.offset & (Target.ptrsize - 1))) &&
                sc.func.setUnsafe())
            {
                if (printmsg)
                    e.error("field %s.%s cannot modify misaligned pointers in @safe code",
                        ad.toChars(), v.toChars());
                return true;
            }
        }

        if (v.overlapUnsafe && sc.func.setUnsafe())
        {
             if (printmsg)
                 e.error("field %s.%s cannot modify fields in @safe code that overlap fields with other storage classes",
                    ad.toChars(), v.toChars());
             return true;
        }
    }
    return false;
}


/**********************************************
 * Determine if it is @safe to cast e from tfrom to tto.
 * Params:
 *      e = expression to be cast
 *      tfrom = type of e
 *      tto = type to cast e to
 * Returns:
 *      true if @safe
 */
bool isSafeCast(Expression e, Type tfrom, Type tto)
{
    // Implicit conversions are always safe
    if (tfrom.implicitConvTo(tto))
        return true;

    if (!tto.hasPointers())
        return true;

    auto tfromb = tfrom.toBasetype();
    auto ttob = tto.toBasetype();

    if (ttob.ty == Tclass && tfrom.ty == Tclass)
    {
        ClassDeclaration cdfrom = tfrom.isClassHandle();
        ClassDeclaration cdto = ttob.isClassHandle();

        int offset;
        if (!cdfrom.isBaseOf(cdto, &offset))
            return false;

        if (cdfrom.isCPPinterface() || cdto.isCPPinterface())
            return false;

        if (!MODimplicitConv(tfrom.mod, ttob.mod))
            return false;
        return true;
    }

    if (ttob.ty == Tarray && tfrom.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502
        tfrom = tfrom.nextOf().arrayOf();

    if (ttob.ty == Tarray   && tfrom.ty == Tarray ||
        ttob.ty == Tpointer && tfrom.ty == Tpointer)
    {
        Type ttobn = ttob.nextOf().toBasetype();
        Type tfromn = tfrom.nextOf().toBasetype();

        /* From void[] to anything mutable is unsafe because:
         *  int*[] api;
         *  void[] av = api;
         *  int[] ai = cast(int[]) av;
         *  ai[0] = 7;
         *  *api[0] crash!
         */
        if (tfromn.ty == Tvoid && ttobn.isMutable())
        {
            if (ttob.ty == Tarray && e.op == TOKarrayliteral)
                return true;
            return false;
        }

        // If the struct is opaque we don't know about the struct members then the cast becomes unsafe
        if (ttobn.ty == Tstruct && !(cast(TypeStruct)ttobn).sym.members ||
            tfromn.ty == Tstruct && !(cast(TypeStruct)tfromn).sym.members)
            return false;

        const frompointers = tfromn.hasPointers();
        const topointers = ttobn.hasPointers();

        if (frompointers && !topointers && ttobn.isMutable())
            return false;

        if (!frompointers && topointers)
            return false;

        if (!topointers &&
            ttobn.ty != Tfunction && tfromn.ty != Tfunction &&
            (ttob.ty == Tarray || ttobn.size() <= tfromn.size()) &&
            MODimplicitConv(tfromn.mod, ttobn.mod))
        {
            return true;
        }
    }
    return false;
}

