1 /**
2  * Checks whether member access or array casting is allowed in `@safe` code.
3  *
4  * Specification: $(LINK2 https://dlang.org/spec/function.html#function-safety, Function Safety)
5  *
6  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/safe.d, _safe.d)
10  * Documentation:  https://dlang.org/phobos/dmd_safe.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/safe.d
12  */
13 
14 module dmd.safe;
15 
16 import core.stdc.stdio;
17 
18 import dmd.aggregate;
19 import dmd.dclass;
20 import dmd.declaration;
21 import dmd.dscope;
22 import dmd.expression;
23 import dmd.mtype;
24 import dmd.target;
25 import dmd.tokens;
26 
27 
28 /*************************************************************
29  * Check for unsafe access in @safe code:
30  * 1. read overlapped pointers
31  * 2. write misaligned pointers
32  * 3. write overlapped storage classes
33  * Print error if unsafe.
34  * Params:
35  *      sc = scope
36  *      e = expression to check
37  *      readonly = if access is read-only
38  *      printmsg = print error message if true
39  * Returns:
40  *      true if error
41  */
42 
43 bool checkUnsafeAccess(Scope* sc, Expression e, bool readonly, bool printmsg)
44 {
45     //printf("checkUnsafeAccess(e: '%s', readonly: %d, printmsg: %d)\n", e.toChars(), readonly, printmsg);
46     if (e.op != TOK.dotVariable)
47         return false;
48     DotVarExp dve = cast(DotVarExp)e;
49     if (VarDeclaration v = dve.var.isVarDeclaration())
50     {
51         if (sc.intypeof || !sc.func || !sc.func.isSafeBypassingInference())
52             return false;
53         auto ad = v.toParent2().isAggregateDeclaration();
54         if (!ad)
55             return false;
56 
57         // needed to set v.overlapped and v.overlapUnsafe
58         if (ad.sizeok != Sizeok.done)
59             ad.determineSize(ad.loc);
60 
61         const hasPointers = v.type.hasPointers();
62         if (hasPointers)
63         {
64 
65             if (v.overlapped && sc.func.setUnsafe())
66             {
67                 if (printmsg)
68                     e.error("field `%s.%s` cannot access pointers in `@safe` code that overlap other fields",
69                         ad.toChars(), v.toChars());
70                 return true;
71             }
72         }
73 
74         if (readonly || !e.type.isMutable())
75             return false;
76 
77         if (hasPointers && v.type.toBasetype().ty != Tstruct)
78         {
79             if ((ad.type.alignment() < target.ptrsize ||
80                  (v.offset & (target.ptrsize - 1))) &&
81                 sc.func.setUnsafe())
82             {
83                 if (printmsg)
84                     e.error("field `%s.%s` cannot modify misaligned pointers in `@safe` code",
85                         ad.toChars(), v.toChars());
86                 return true;
87             }
88         }
89 
90         if (v.overlapUnsafe && sc.func.setUnsafe())
91         {
92              if (printmsg)
93                  e.error("field `%s.%s` cannot modify fields in `@safe` code that overlap fields with other storage classes",
94                     ad.toChars(), v.toChars());
95              return true;
96         }
97     }
98     return false;
99 }
100 
101 
102 /**********************************************
103  * Determine if it is @safe to cast e from tfrom to tto.
104  * Params:
105  *      e = expression to be cast
106  *      tfrom = type of e
107  *      tto = type to cast e to
108  * Returns:
109  *      true if @safe
110  */
111 bool isSafeCast(Expression e, Type tfrom, Type tto)
112 {
113     // Implicit conversions are always safe
114     if (tfrom.implicitConvTo(tto))
115         return true;
116 
117     if (!tto.hasPointers())
118         return true;
119 
120     auto tfromb = tfrom.toBasetype();
121     auto ttob = tto.toBasetype();
122 
123     if (ttob.ty == Tclass && tfromb.ty == Tclass)
124     {
125         ClassDeclaration cdfrom = tfromb.isClassHandle();
126         ClassDeclaration cdto = ttob.isClassHandle();
127 
128         int offset;
129         if (!cdfrom.isBaseOf(cdto, &offset) &&
130             !((cdfrom.isInterfaceDeclaration() || cdto.isInterfaceDeclaration())
131                 && cdfrom.classKind == ClassKind.d && cdto.classKind == ClassKind.d))
132             return false;
133 
134         if (cdfrom.isCPPinterface() || cdto.isCPPinterface())
135             return false;
136 
137         if (!MODimplicitConv(tfromb.mod, ttob.mod))
138             return false;
139         return true;
140     }
141 
142     if (ttob.ty == Tarray && tfromb.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502
143         tfromb = tfromb.nextOf().arrayOf();
144 
145     if (ttob.ty == Tarray   && tfromb.ty == Tarray ||
146         ttob.ty == Tpointer && tfromb.ty == Tpointer)
147     {
148         Type ttobn = ttob.nextOf().toBasetype();
149         Type tfromn = tfromb.nextOf().toBasetype();
150 
151         /* From void[] to anything mutable is unsafe because:
152          *  int*[] api;
153          *  void[] av = api;
154          *  int[] ai = cast(int[]) av;
155          *  ai[0] = 7;
156          *  *api[0] crash!
157          */
158         if (tfromn.ty == Tvoid && ttobn.isMutable())
159         {
160             if (ttob.ty == Tarray && e.op == TOK.arrayLiteral)
161                 return true;
162             return false;
163         }
164 
165         // If the struct is opaque we don't know about the struct members then the cast becomes unsafe
166         if (ttobn.ty == Tstruct && !(cast(TypeStruct)ttobn).sym.members ||
167             tfromn.ty == Tstruct && !(cast(TypeStruct)tfromn).sym.members)
168             return false;
169 
170         const frompointers = tfromn.hasPointers();
171         const topointers = ttobn.hasPointers();
172 
173         if (frompointers && !topointers && ttobn.isMutable())
174             return false;
175 
176         if (!frompointers && topointers)
177             return false;
178 
179         if (!topointers &&
180             ttobn.ty != Tfunction && tfromn.ty != Tfunction &&
181             (ttob.ty == Tarray || ttobn.size() <= tfromn.size()) &&
182             MODimplicitConv(tfromn.mod, ttobn.mod))
183         {
184             return true;
185         }
186     }
187     return false;
188 }
189