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-2020 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/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 dmd.aggregate; 17 import dmd.apply; 18 import dmd.declaration; 19 import dmd.dscope; 20 import dmd.expression; 21 import dmd.func; 22 import dmd.globals; 23 import dmd.init; 24 import dmd.mtype; 25 import dmd.tokens; 26 import dmd.visitor; 27 28 /************************************** 29 * Look for GC-allocations 30 */ 31 extern (C++) final class NOGCVisitor : StoppableVisitor 32 { 33 alias visit = typeof(super).visit; 34 public: 35 FuncDeclaration f; 36 bool err; 37 38 extern (D) this(FuncDeclaration f) 39 { 40 this.f = f; 41 } 42 43 void doCond(Expression exp) 44 { 45 if (exp) 46 walkPostorder(exp, this); 47 } 48 49 override void visit(Expression e) 50 { 51 } 52 53 override void visit(DeclarationExp e) 54 { 55 // Note that, walkPostorder does not support DeclarationExp today. 56 VarDeclaration v = e.declaration.isVarDeclaration(); 57 if (v && !(v.storage_class & STC.manifest) && !v.isDataseg() && v._init) 58 { 59 if (ExpInitializer ei = v._init.isExpInitializer()) 60 { 61 doCond(ei.exp); 62 } 63 } 64 } 65 66 override void visit(CallExp e) 67 { 68 import dmd.id : Id; 69 import core.stdc.stdio : printf; 70 if (!e.f) 71 return; 72 73 auto fd = stripHookTraceImpl(e.f); 74 if (fd.ident == Id._d_arraysetlengthT) 75 { 76 if (f.setGC()) 77 { 78 e.error("setting `length` in `@nogc` %s `%s` may cause a GC allocation", 79 f.kind(), f.toPrettyChars()); 80 err = true; 81 return; 82 } 83 f.printGCUsage(e.loc, "setting `length` may cause a GC allocation"); 84 } 85 } 86 87 override void visit(ArrayLiteralExp e) 88 { 89 if (e.type.ty != Tarray || !e.elements || !e.elements.dim) 90 return; 91 if (f.setGC()) 92 { 93 e.error("array literal in `@nogc` %s `%s` may cause a GC allocation", 94 f.kind(), f.toPrettyChars()); 95 err = true; 96 return; 97 } 98 f.printGCUsage(e.loc, "array literal may cause a GC allocation"); 99 } 100 101 override void visit(AssocArrayLiteralExp e) 102 { 103 if (!e.keys.dim) 104 return; 105 if (f.setGC()) 106 { 107 e.error("associative array literal in `@nogc` %s `%s` may cause a GC allocation", 108 f.kind(), f.toPrettyChars()); 109 err = true; 110 return; 111 } 112 f.printGCUsage(e.loc, "associative array literal may cause a GC allocation"); 113 } 114 115 override void visit(NewExp e) 116 { 117 if (e.member && !e.member.isNogc() && f.setGC()) 118 { 119 // @nogc-ness is already checked in NewExp::semantic 120 return; 121 } 122 if (e.onstack) 123 return; 124 if (e.allocator) 125 return; 126 if (global.params.ehnogc && e.thrownew) 127 return; // separate allocator is called for this, not the GC 128 if (f.setGC()) 129 { 130 e.error("cannot use `new` in `@nogc` %s `%s`", 131 f.kind(), f.toPrettyChars()); 132 err = true; 133 return; 134 } 135 f.printGCUsage(e.loc, "`new` causes a GC allocation"); 136 } 137 138 override void visit(DeleteExp e) 139 { 140 if (e.e1.op == TOK.variable) 141 { 142 VarDeclaration v = (cast(VarExp)e.e1).var.isVarDeclaration(); 143 if (v && v.onstack) 144 return; // delete for scope allocated class object 145 } 146 147 Type tb = e.e1.type.toBasetype(); 148 AggregateDeclaration ad = null; 149 switch (tb.ty) 150 { 151 case Tclass: 152 ad = (cast(TypeClass)tb).sym; 153 break; 154 155 case Tpointer: 156 tb = (cast(TypePointer)tb).next.toBasetype(); 157 if (tb.ty == Tstruct) 158 ad = (cast(TypeStruct)tb).sym; 159 break; 160 161 default: 162 break; 163 } 164 165 if (f.setGC()) 166 { 167 e.error("cannot use `delete` in `@nogc` %s `%s`", 168 f.kind(), f.toPrettyChars()); 169 err = true; 170 return; 171 } 172 f.printGCUsage(e.loc, "`delete` requires the GC"); 173 } 174 175 override void visit(IndexExp e) 176 { 177 Type t1b = e.e1.type.toBasetype(); 178 if (t1b.ty == Taarray) 179 { 180 if (f.setGC()) 181 { 182 e.error("indexing an associative array in `@nogc` %s `%s` may cause a GC allocation", 183 f.kind(), f.toPrettyChars()); 184 err = true; 185 return; 186 } 187 f.printGCUsage(e.loc, "indexing an associative array may cause a GC allocation"); 188 } 189 } 190 191 override void visit(AssignExp e) 192 { 193 if (e.e1.op == TOK.arrayLength) 194 { 195 if (f.setGC()) 196 { 197 e.error("setting `length` in `@nogc` %s `%s` may cause a GC allocation", 198 f.kind(), f.toPrettyChars()); 199 err = true; 200 return; 201 } 202 f.printGCUsage(e.loc, "setting `length` may cause a GC allocation"); 203 } 204 } 205 206 override void visit(CatAssignExp e) 207 { 208 if (f.setGC()) 209 { 210 e.error("cannot use operator `~=` in `@nogc` %s `%s`", 211 f.kind(), f.toPrettyChars()); 212 err = true; 213 return; 214 } 215 f.printGCUsage(e.loc, "operator `~=` may cause a GC allocation"); 216 } 217 218 override void visit(CatExp e) 219 { 220 if (f.setGC()) 221 { 222 e.error("cannot use operator `~` in `@nogc` %s `%s`", 223 f.kind(), f.toPrettyChars()); 224 err = true; 225 return; 226 } 227 f.printGCUsage(e.loc, "operator `~` may cause a GC allocation"); 228 } 229 } 230 231 Expression checkGC(Scope* sc, Expression e) 232 { 233 FuncDeclaration f = sc.func; 234 if (e && e.op != TOK.error && f && sc.intypeof != 1 && !(sc.flags & SCOPE.ctfe) && 235 (f.type.ty == Tfunction && 236 (cast(TypeFunction)f.type).isnogc || (f.flags & FUNCFLAG.nogcInprocess) || global.params.vgc) && 237 !(sc.flags & SCOPE.debug_)) 238 { 239 scope NOGCVisitor gcv = new NOGCVisitor(f); 240 walkPostorder(e, gcv); 241 if (gcv.err) 242 return ErrorExp.get(); 243 } 244 return e; 245 } 246 247 /** 248 * Removes `_d_HookTraceImpl` if found from `fd`. 249 * This is needed to be able to find hooks that are called though the hook's `*Trace` wrapper. 250 * Parameters: 251 * fd = The function declaration to remove `_d_HookTraceImpl` from 252 */ 253 private FuncDeclaration stripHookTraceImpl(FuncDeclaration fd) 254 { 255 import dmd.id : Id; 256 import dmd.dsymbol : Dsymbol; 257 import dmd.root.rootobject : RootObject, DYNCAST; 258 259 if (fd.ident != Id._d_HookTraceImpl) 260 return fd; 261 262 // Get the Hook from the second template parameter 263 auto templateInstance = fd.parent.isTemplateInstance; 264 RootObject hook = (*templateInstance.tiargs)[1]; 265 assert(hook.dyncast() == DYNCAST.dsymbol, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!"); 266 return (cast(Dsymbol)hook).isFuncDeclaration; 267 }