1 /** 2 * Checks that a function marked `@nogc` does not invoke the Garbage Collector. 3 * 4 * Specification: $(LINK2 https://dlang.org/spec/function.html#nogc-functions, No-GC Functions) 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/nogc.d, _nogc.d) 10 * Documentation: https://dlang.org/phobos/dmd_nogc.html 11 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/nogc.d 12 */ 13 14 module dmd.nogc; 15 16 import core.stdc.stdio; 17 18 import dmd.aggregate; 19 import dmd.astenums; 20 import dmd.declaration; 21 import dmd.dscope; 22 import dmd.dtemplate : isDsymbol; 23 import dmd.errors; 24 import dmd.expression; 25 import dmd.func; 26 import dmd.globals; 27 import dmd.init; 28 import dmd.mtype; 29 import dmd.postordervisitor; 30 import dmd.tokens; 31 import dmd.visitor; 32 33 /************************************** 34 * Look for GC-allocations 35 */ 36 extern (C++) final class NOGCVisitor : StoppableVisitor 37 { 38 alias visit = typeof(super).visit; 39 public: 40 FuncDeclaration f; 41 bool checkOnly; // don't print errors 42 bool err; 43 44 extern (D) this(FuncDeclaration f) scope @safe 45 { 46 this.f = f; 47 } 48 49 void doCond(Expression exp) 50 { 51 if (exp) 52 walkPostorder(exp, this); 53 } 54 55 override void visit(Expression e) 56 { 57 } 58 59 override void visit(DeclarationExp e) 60 { 61 // Note that, walkPostorder does not support DeclarationExp today. 62 VarDeclaration v = e.declaration.isVarDeclaration(); 63 if (v && !(v.storage_class & STC.manifest) && !v.isDataseg() && v._init) 64 { 65 if (ExpInitializer ei = v._init.isExpInitializer()) 66 { 67 doCond(ei.exp); 68 } 69 } 70 } 71 72 /** 73 * Register that expression `e` requires the GC 74 * Params: 75 * e = expression that uses GC 76 * format = error message when `e` is used in a `@nogc` function. 77 * Must contain format strings "`@nogc` %s `%s`" referring to the function. 78 * Returns: `true` if `err` was set, `false` if it's not in a `@nogc` and not checkonly (-betterC) 79 */ 80 private bool setGC(Expression e, const(char)* format) 81 { 82 if (checkOnly) 83 { 84 err = true; 85 return true; 86 } 87 if (f.setGC(e.loc, format)) 88 { 89 error(e.loc, format, f.kind(), f.toPrettyChars()); 90 err = true; 91 return true; 92 } 93 return false; 94 } 95 96 override void visit(CallExp e) 97 { 98 import dmd.id : Id; 99 import core.stdc.stdio : printf; 100 if (!e.f) 101 return; 102 103 // Treat lowered hook calls as their original expressions. 104 auto fd = stripHookTraceImpl(e.f); 105 if (fd.ident == Id._d_arraysetlengthT) 106 { 107 if (setGC(e, "setting `length` in `@nogc` %s `%s` may cause a GC allocation")) 108 return; 109 f.printGCUsage(e.loc, "setting `length` may cause a GC allocation"); 110 } 111 else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendcTX) 112 { 113 if (setGC(e, "cannot use operator `~=` in `@nogc` %s `%s`")) 114 return; 115 f.printGCUsage(e.loc, "operator `~=` may cause a GC allocation"); 116 } 117 } 118 119 override void visit(ArrayLiteralExp e) 120 { 121 if (e.type.ty != Tarray || !e.elements || !e.elements.length || e.onstack) 122 return; 123 if (setGC(e, "array literal in `@nogc` %s `%s` may cause a GC allocation")) 124 return; 125 f.printGCUsage(e.loc, "array literal may cause a GC allocation"); 126 } 127 128 override void visit(AssocArrayLiteralExp e) 129 { 130 if (!e.keys.length) 131 return; 132 if (setGC(e, "associative array literal in `@nogc` %s `%s` may cause a GC allocation")) 133 return; 134 f.printGCUsage(e.loc, "associative array literal may cause a GC allocation"); 135 } 136 137 override void visit(NewExp e) 138 { 139 if (e.member && !e.member.isNogc() && f.setGC(e.loc, null)) 140 { 141 // @nogc-ness is already checked in NewExp::semantic 142 return; 143 } 144 if (e.onstack) 145 return; 146 if (global.params.ehnogc && e.thrownew) 147 return; // separate allocator is called for this, not the GC 148 149 if (setGC(e, "cannot use `new` in `@nogc` %s `%s`")) 150 return; 151 f.printGCUsage(e.loc, "`new` causes a GC allocation"); 152 } 153 154 override void visit(DeleteExp e) 155 { 156 if (VarExp ve = e.e1.isVarExp()) 157 { 158 VarDeclaration v = ve.var.isVarDeclaration(); 159 if (v && v.onstack) 160 return; // delete for scope allocated class object 161 } 162 163 // Semantic should have already handled this case. 164 assert(0); 165 } 166 167 override void visit(IndexExp e) 168 { 169 Type t1b = e.e1.type.toBasetype(); 170 if (e.modifiable && t1b.ty == Taarray) 171 { 172 if (setGC(e, "assigning an associative array element in `@nogc` %s `%s` may cause a GC allocation")) 173 return; 174 f.printGCUsage(e.loc, "assigning an associative array element may cause a GC allocation"); 175 } 176 } 177 178 override void visit(AssignExp e) 179 { 180 if (e.e1.op == EXP.arrayLength) 181 { 182 if (setGC(e, "setting `length` in `@nogc` %s `%s` may cause a GC allocation")) 183 return; 184 f.printGCUsage(e.loc, "setting `length` may cause a GC allocation"); 185 } 186 } 187 188 override void visit(CatAssignExp e) 189 { 190 /* CatAssignExp will exist in `__traits(compiles, ...)` and in the `.e1` branch of a `__ctfe ? :` CondExp. 191 * The other branch will be `_d_arrayappendcTX(e1, 1), e1[$-1]=e2` which will generate the warning about 192 * GC usage. See visit(CallExp). 193 */ 194 if (checkOnly) 195 { 196 err = true; 197 return; 198 } 199 if (f.setGC(e.loc, null)) 200 { 201 err = true; 202 return; 203 } 204 } 205 206 override void visit(CatExp e) 207 { 208 if (setGC(e, "cannot use operator `~` in `@nogc` %s `%s`")) 209 return; 210 f.printGCUsage(e.loc, "operator `~` may cause a GC allocation"); 211 } 212 } 213 214 Expression checkGC(Scope* sc, Expression e) 215 { 216 if (sc.flags & SCOPE.ctfeBlock) // ignore GC in ctfe blocks 217 return e; 218 219 /* If betterC, allow GC to happen in non-CTFE code. 220 * Just don't generate code for it. 221 * Detect non-CTFE use of the GC in betterC code. 222 */ 223 const betterC = !global.params.useGC; 224 FuncDeclaration f = sc.func; 225 if (e && e.op != EXP.error && f && sc.intypeof != 1 && 226 (!(sc.flags & SCOPE.ctfe) || betterC) && 227 (f.type.ty == Tfunction && 228 (cast(TypeFunction)f.type).isnogc || f.nogcInprocess || global.params.v.gc) && 229 !(sc.flags & SCOPE.debug_)) 230 { 231 scope NOGCVisitor gcv = new NOGCVisitor(f); 232 gcv.checkOnly = betterC; 233 walkPostorder(e, gcv); 234 if (gcv.err) 235 { 236 if (betterC) 237 { 238 /* Allow ctfe to use the gc code, but don't let it into the runtime 239 */ 240 f.skipCodegen = true; 241 } 242 else 243 return ErrorExp.get(); 244 } 245 } 246 return e; 247 } 248 249 /** 250 * Removes `_d_HookTraceImpl` if found from `fd`. 251 * This is needed to be able to find hooks that are called though the hook's `*Trace` wrapper. 252 * Parameters: 253 * fd = The function declaration to remove `_d_HookTraceImpl` from 254 */ 255 private FuncDeclaration stripHookTraceImpl(FuncDeclaration fd) 256 { 257 import dmd.id : Id; 258 import dmd.dsymbol : Dsymbol; 259 import dmd.rootobject : RootObject, DYNCAST; 260 261 if (fd.ident != Id._d_HookTraceImpl) 262 return fd; 263 264 // Get the Hook from the second template parameter 265 auto templateInstance = fd.parent.isTemplateInstance; 266 RootObject hook = (*templateInstance.tiargs)[1]; 267 Dsymbol s = hook.isDsymbol(); 268 assert(s, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!"); 269 return s.isFuncDeclaration; 270 }