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