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-2021 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.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 != TOK.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() < 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 == TOK.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