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-2023 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 https://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.astenums;
20 import dmd.dclass;
21 import dmd.declaration;
22 import dmd.dscope;
23 import dmd.expression;
24 import dmd.id;
25 import dmd.identifier;
26 import dmd.mtype;
27 import dmd.target;
28 import dmd.tokens;
29 import dmd.func : setUnsafe, setUnsafePreview;
30 
31 /*************************************************************
32  * Check for unsafe access in @safe code:
33  * 1. read overlapped pointers
34  * 2. write misaligned pointers
35  * 3. write overlapped storage classes
36  * Print error if unsafe.
37  * Params:
38  *      sc = scope
39  *      e = expression to check
40  *      readonly = if access is read-only
41  *      printmsg = print error message if true
42  * Returns:
43  *      true if error
44  */
45 
46 bool checkUnsafeAccess(Scope* sc, Expression e, bool readonly, bool printmsg)
47 {
48     //printf("checkUnsafeAccess(e: '%s', readonly: %d, printmsg: %d)\n", e.toChars(), readonly, printmsg);
49     if (e.op != EXP.dotVariable)
50         return false;
51     DotVarExp dve = cast(DotVarExp)e;
52     if (VarDeclaration v = dve.var.isVarDeclaration())
53     {
54         if (!sc.func)
55             return false;
56         auto ad = v.isMember2();
57         if (!ad)
58             return false;
59 
60         import dmd.globals : global;
61         if (v.isSystem())
62         {
63             if (sc.setUnsafePreview(global.params.systemVariables, !printmsg, e.loc,
64                 "cannot access `@system` field `%s.%s` in `@safe` code", ad, v))
65                 return true;
66         }
67 
68         // This branch shouldn't be here, but unfortunately calling `ad.determineSize`
69         // breaks code with circular reference errors. Specifically, test23589.d fails
70         if (ad.sizeok != Sizeok.done && !sc.func.isSafeBypassingInference())
71             return false;
72 
73         // needed to set v.overlapped and v.overlapUnsafe
74         if (ad.sizeok != Sizeok.done)
75             ad.determineSize(ad.loc);
76 
77         const hasPointers = v.type.hasPointers();
78         if (hasPointers)
79         {
80             if (v.overlapped)
81             {
82                 if (sc.func.isSafeBypassingInference() && sc.setUnsafe(!printmsg, e.loc,
83                     "field `%s.%s` cannot access pointers in `@safe` code that overlap other fields", ad, v))
84                 {
85                     return true;
86                 }
87                 else
88                 {
89                     import dmd.globals : FeatureState;
90                     // @@@DEPRECATED_2.116@@@
91                     // https://issues.dlang.org/show_bug.cgi?id=20655
92                     // Inferring `@system` because of union access breaks code,
93                     // so make it a deprecation safety violation as of 2.106
94                     // To turn into an error, remove `isSafeBypassingInference` check in the
95                     // above if statement and remove the else branch
96                     sc.setUnsafePreview(FeatureState.default_, !printmsg, e.loc,
97                         "field `%s.%s` cannot access pointers in `@safe` code that overlap other fields", ad, v);
98                 }
99             }
100         }
101 
102         if (v.type.hasInvariant())
103         {
104             if (v.overlapped)
105             {
106                 if (sc.setUnsafe(!printmsg, e.loc,
107                     "field `%s.%s` cannot access structs with invariants in `@safe` code that overlap other fields",
108                     ad, v))
109                     return true;
110             }
111         }
112 
113         if (readonly || !e.type.isMutable())
114             return false;
115 
116         if (hasPointers && v.type.toBasetype().ty != Tstruct)
117         {
118             if ((!ad.type.alignment.isDefault() && ad.type.alignment.get() < target.ptrsize ||
119                  (v.offset & (target.ptrsize - 1))))
120             {
121                 if (sc.setUnsafe(!printmsg, e.loc,
122                     "field `%s.%s` cannot modify misaligned pointers in `@safe` code", ad, v))
123                     return true;
124             }
125         }
126 
127         if (v.overlapUnsafe)
128         {
129             if (sc.setUnsafe(!printmsg, e.loc,
130                 "field `%s.%s` cannot modify fields in `@safe` code that overlap fields with other storage classes",
131                 ad, v))
132             {
133                 return true;
134             }
135         }
136     }
137     return false;
138 }
139 
140 
141 /**********************************************
142  * Determine if it is @safe to cast e from tfrom to tto.
143  * Params:
144  *      e = expression to be cast
145  *      tfrom = type of e
146  *      tto = type to cast e to
147  * Returns:
148  *      true if @safe
149  */
150 bool isSafeCast(Expression e, Type tfrom, Type tto)
151 {
152     // Implicit conversions are always safe
153     if (tfrom.implicitConvTo(tto))
154         return true;
155 
156     if (!tto.hasPointers())
157         return true;
158 
159     auto tfromb = tfrom.toBasetype();
160     auto ttob = tto.toBasetype();
161 
162     if (ttob.ty == Tclass && tfromb.ty == Tclass)
163     {
164         ClassDeclaration cdfrom = tfromb.isClassHandle();
165         ClassDeclaration cdto = ttob.isClassHandle();
166 
167         int offset;
168         if (!cdfrom.isBaseOf(cdto, &offset) &&
169             !((cdfrom.isInterfaceDeclaration() || cdto.isInterfaceDeclaration())
170                 && cdfrom.classKind == ClassKind.d && cdto.classKind == ClassKind.d))
171             return false;
172 
173         if (cdfrom.isCPPinterface() || cdto.isCPPinterface())
174             return false;
175 
176         if (!MODimplicitConv(tfromb.mod, ttob.mod))
177             return false;
178         return true;
179     }
180 
181     if (ttob.ty == Tarray && tfromb.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502
182         tfromb = tfromb.nextOf().arrayOf();
183 
184     if (ttob.ty == Tarray   && tfromb.ty == Tarray ||
185         ttob.ty == Tpointer && tfromb.ty == Tpointer)
186     {
187         Type ttobn = ttob.nextOf().toBasetype();
188         Type tfromn = tfromb.nextOf().toBasetype();
189 
190         /* From void[] to anything mutable is unsafe because:
191          *  int*[] api;
192          *  void[] av = api;
193          *  int[] ai = cast(int[]) av;
194          *  ai[0] = 7;
195          *  *api[0] crash!
196          */
197         if (tfromn.ty == Tvoid && ttobn.isMutable())
198         {
199             if (ttob.ty == Tarray && e.op == EXP.arrayLiteral)
200                 return true;
201             return false;
202         }
203 
204         // If the struct is opaque we don't know about the struct members then the cast becomes unsafe
205         if (ttobn.ty == Tstruct && !(cast(TypeStruct)ttobn).sym.members ||
206             tfromn.ty == Tstruct && !(cast(TypeStruct)tfromn).sym.members)
207             return false;
208 
209         const frompointers = tfromn.hasPointers();
210         const topointers = ttobn.hasPointers();
211 
212         if (frompointers && !topointers && ttobn.isMutable())
213             return false;
214 
215         if (!frompointers && topointers)
216             return false;
217 
218         if (!topointers &&
219             ttobn.ty != Tfunction && tfromn.ty != Tfunction &&
220             (ttob.ty == Tarray || ttobn.size() <= tfromn.size()) &&
221             MODimplicitConv(tfromn.mod, ttobn.mod))
222         {
223             return true;
224         }
225     }
226     return false;
227 }
228 
229 /*************************************************
230  * Check for unsafe use of `.ptr` or `.funcptr`
231  * Params:
232  *      sc = context
233  *      e = expression for error messages
234  *      id = `ptr` or `funcptr`
235  *      flag = DotExpFlag
236  * Returns:
237  *      true if error
238  */
239 bool checkUnsafeDotExp(Scope* sc, Expression e, Identifier id, int flag)
240 {
241     if (!(flag & DotExpFlag.noDeref)) // this use is attempting a dereference
242     {
243         if (id == Id.ptr)
244             return sc.setUnsafe(false, e.loc, "`%s.ptr` cannot be used in `@safe` code, use `&%s[0]` instead", e, e);
245         else
246             return sc.setUnsafe(false, e.loc, "`%s.%s` cannot be used in `@safe` code", e, id);
247     }
248     return false;
249 }