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 }