blob: c3fa90d7f791093bfb73e68698ff9cbd9e148f15 [file] [log] [blame]
/**
* Checks whether member access or array casting is allowed in `@safe` code.
*
* Specification: $(LINK2 https://dlang.org/spec/function.html#function-safety, Function Safety)
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/safe.d, _safe.d)
* Documentation: https://dlang.org/phobos/dmd_safe.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/safe.d
*/
module dmd.safe;
import core.stdc.stdio;
import dmd.aggregate;
import dmd.astenums;
import dmd.dclass;
import dmd.declaration;
import dmd.dscope;
import dmd.expression;
import dmd.id;
import dmd.identifier;
import dmd.mtype;
import dmd.target;
import dmd.tokens;
import dmd.func : setUnsafe, setUnsafePreview;
/*************************************************************
* 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)
{
//printf("checkUnsafeAccess(e: '%s', readonly: %d, printmsg: %d)\n", e.toChars(), readonly, printmsg);
if (e.op != EXP.dotVariable)
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.isMember2();
if (!ad)
return false;
import dmd.globals : global;
if (v.isSystem())
{
if (sc.setUnsafePreview(global.params.systemVariables, !printmsg, e.loc,
"cannot access `@system` field `%s.%s` in `@safe` code", ad, v))
return true;
}
// needed to set v.overlapped and v.overlapUnsafe
if (ad.sizeok != Sizeok.done)
ad.determineSize(ad.loc);
const hasPointers = v.type.hasPointers();
if (hasPointers)
{
if (v.overlapped)
{
if (sc.setUnsafe(!printmsg, e.loc,
"field `%s.%s` cannot access pointers in `@safe` code that overlap other fields", ad, v))
return true;
}
}
if (v.type.hasInvariant())
{
if (v.overlapped)
{
if (sc.setUnsafe(!printmsg, e.loc,
"field `%s.%s` cannot access structs with invariants in `@safe` code that overlap other fields",
ad, v))
return true;
}
}
if (readonly || !e.type.isMutable())
return false;
if (hasPointers && v.type.toBasetype().ty != Tstruct)
{
if ((!ad.type.alignment.isDefault() && ad.type.alignment.get() < target.ptrsize ||
(v.offset & (target.ptrsize - 1))))
{
if (sc.setUnsafe(!printmsg, e.loc,
"field `%s.%s` cannot modify misaligned pointers in `@safe` code", ad, v))
return true;
}
}
if (v.overlapUnsafe)
{
if (sc.setUnsafe(!printmsg, e.loc,
"field `%s.%s` cannot modify fields in `@safe` code that overlap fields with other storage classes",
ad, v))
{
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 && tfromb.ty == Tclass)
{
ClassDeclaration cdfrom = tfromb.isClassHandle();
ClassDeclaration cdto = ttob.isClassHandle();
int offset;
if (!cdfrom.isBaseOf(cdto, &offset) &&
!((cdfrom.isInterfaceDeclaration() || cdto.isInterfaceDeclaration())
&& cdfrom.classKind == ClassKind.d && cdto.classKind == ClassKind.d))
return false;
if (cdfrom.isCPPinterface() || cdto.isCPPinterface())
return false;
if (!MODimplicitConv(tfromb.mod, ttob.mod))
return false;
return true;
}
if (ttob.ty == Tarray && tfromb.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502
tfromb = tfromb.nextOf().arrayOf();
if (ttob.ty == Tarray && tfromb.ty == Tarray ||
ttob.ty == Tpointer && tfromb.ty == Tpointer)
{
Type ttobn = ttob.nextOf().toBasetype();
Type tfromn = tfromb.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 == EXP.arrayLiteral)
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;
}
/*************************************************
* Check for unsafe use of `.ptr` or `.funcptr`
* Params:
* sc = context
* e = expression for error messages
* id = `ptr` or `funcptr`
* flag = DotExpFlag
* Returns:
* true if error
*/
bool checkUnsafeDotExp(Scope* sc, Expression e, Identifier id, int flag)
{
if (!(flag & DotExpFlag.noDeref)) // this use is attempting a dereference
{
if (id == Id.ptr)
return sc.setUnsafe(false, e.loc, "`%s.ptr` cannot be used in `@safe` code, use `&%s[0]` instead", e, e);
else
return sc.setUnsafe(false, e.loc, "`%s.%s` cannot be used in `@safe` code", e, id);
}
return false;
}