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-2022 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
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
checkUnsafeAccess(Scope * sc,Expression e,bool readonly,bool printmsg)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.intypeof || !sc.func || !sc.func.isSafeBypassingInference())
55 return false;
56 auto ad = v.toParent2().isAggregateDeclaration();
57 if (!ad)
58 return false;
59
60 // needed to set v.overlapped and v.overlapUnsafe
61 if (ad.sizeok != Sizeok.done)
62 ad.determineSize(ad.loc);
63
64 const hasPointers = v.type.hasPointers();
65 if (hasPointers)
66 {
67 if (v.overlapped && sc.func.setUnsafe())
68 {
69 if (printmsg)
70 e.error("field `%s.%s` cannot access pointers in `@safe` code that overlap other fields",
71 ad.toChars(), v.toChars());
72 return true;
73 }
74 }
75
76 if (v.type.hasInvariant())
77 {
78 if (v.overlapped && sc.func.setUnsafe())
79 {
80 if (printmsg)
81 e.error("field `%s.%s` cannot access structs with invariants in `@safe` code that overlap other fields",
82 ad.toChars(), v.toChars());
83 return true;
84 }
85 }
86
87 if (readonly || !e.type.isMutable())
88 return false;
89
90 if (hasPointers && v.type.toBasetype().ty != Tstruct)
91 {
92 if ((!ad.type.alignment.isDefault() && ad.type.alignment.get() < target.ptrsize ||
93 (v.offset & (target.ptrsize - 1))) &&
94 sc.func.setUnsafe())
95 {
96 if (printmsg)
97 e.error("field `%s.%s` cannot modify misaligned pointers in `@safe` code",
98 ad.toChars(), v.toChars());
99 return true;
100 }
101 }
102
103 if (v.overlapUnsafe && sc.func.setUnsafe())
104 {
105 if (printmsg)
106 e.error("field `%s.%s` cannot modify fields in `@safe` code that overlap fields with other storage classes",
107 ad.toChars(), v.toChars());
108 return true;
109 }
110 }
111 return false;
112 }
113
114
115 /**********************************************
116 * Determine if it is @safe to cast e from tfrom to tto.
117 * Params:
118 * e = expression to be cast
119 * tfrom = type of e
120 * tto = type to cast e to
121 * Returns:
122 * true if @safe
123 */
isSafeCast(Expression e,Type tfrom,Type tto)124 bool isSafeCast(Expression e, Type tfrom, Type tto)
125 {
126 // Implicit conversions are always safe
127 if (tfrom.implicitConvTo(tto))
128 return true;
129
130 if (!tto.hasPointers())
131 return true;
132
133 auto tfromb = tfrom.toBasetype();
134 auto ttob = tto.toBasetype();
135
136 if (ttob.ty == Tclass && tfromb.ty == Tclass)
137 {
138 ClassDeclaration cdfrom = tfromb.isClassHandle();
139 ClassDeclaration cdto = ttob.isClassHandle();
140
141 int offset;
142 if (!cdfrom.isBaseOf(cdto, &offset) &&
143 !((cdfrom.isInterfaceDeclaration() || cdto.isInterfaceDeclaration())
144 && cdfrom.classKind == ClassKind.d && cdto.classKind == ClassKind.d))
145 return false;
146
147 if (cdfrom.isCPPinterface() || cdto.isCPPinterface())
148 return false;
149
150 if (!MODimplicitConv(tfromb.mod, ttob.mod))
151 return false;
152 return true;
153 }
154
155 if (ttob.ty == Tarray && tfromb.ty == Tsarray) // https://issues.dlang.org/show_bug.cgi?id=12502
156 tfromb = tfromb.nextOf().arrayOf();
157
158 if (ttob.ty == Tarray && tfromb.ty == Tarray ||
159 ttob.ty == Tpointer && tfromb.ty == Tpointer)
160 {
161 Type ttobn = ttob.nextOf().toBasetype();
162 Type tfromn = tfromb.nextOf().toBasetype();
163
164 /* From void[] to anything mutable is unsafe because:
165 * int*[] api;
166 * void[] av = api;
167 * int[] ai = cast(int[]) av;
168 * ai[0] = 7;
169 * *api[0] crash!
170 */
171 if (tfromn.ty == Tvoid && ttobn.isMutable())
172 {
173 if (ttob.ty == Tarray && e.op == EXP.arrayLiteral)
174 return true;
175 return false;
176 }
177
178 // If the struct is opaque we don't know about the struct members then the cast becomes unsafe
179 if (ttobn.ty == Tstruct && !(cast(TypeStruct)ttobn).sym.members ||
180 tfromn.ty == Tstruct && !(cast(TypeStruct)tfromn).sym.members)
181 return false;
182
183 const frompointers = tfromn.hasPointers();
184 const topointers = ttobn.hasPointers();
185
186 if (frompointers && !topointers && ttobn.isMutable())
187 return false;
188
189 if (!frompointers && topointers)
190 return false;
191
192 if (!topointers &&
193 ttobn.ty != Tfunction && tfromn.ty != Tfunction &&
194 (ttob.ty == Tarray || ttobn.size() <= tfromn.size()) &&
195 MODimplicitConv(tfromn.mod, ttobn.mod))
196 {
197 return true;
198 }
199 }
200 return false;
201 }
202
203 /*************************************************
204 * Check for unsafe use of `.ptr` or `.funcptr`
205 * Params:
206 * sc = context
207 * e = expression for error messages
208 * id = `ptr` or `funcptr`
209 * flag = DotExpFlag
210 * Returns:
211 * true if error
212 */
checkUnsafeDotExp(Scope * sc,Expression e,Identifier id,int flag)213 bool checkUnsafeDotExp(Scope* sc, Expression e, Identifier id, int flag)
214 {
215 if (!(flag & DotExpFlag.noDeref) && // this use is attempting a dereference
216 sc.func && // inside a function
217 !sc.intypeof && // allow unsafe code in typeof expressions
218 !(sc.flags & SCOPE.debug_) && // allow unsafe code in debug statements
219 sc.func.setUnsafe()) // infer this function to be unsafe
220 {
221 if (id == Id.ptr)
222 e.error("`%s.ptr` cannot be used in `@safe` code, use `&%s[0]` instead", e.toChars(), e.toChars());
223 else
224 e.error("`%s.%s` cannot be used in `@safe` code", e.toChars(), id.toChars());
225 return true;
226 }
227 return false;
228 }
229