d: Merge upstream dmd, druntime 26f049fb26, phobos 330d6a4fd.
[official-gcc.git] / gcc / d / dmd / nogc.d
blobd7a28209b3eb4196fd40d1f65352f64618fa779e
1 /**
2 * Checks that a function marked `@nogc` does not invoke the Garbage Collector.
4 * Specification: $(LINK2 https://dlang.org/spec/function.html#nogc-functions, No-GC Functions)
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
14 module dmd.nogc;
16 import core.stdc.stdio;
18 import dmd.aggregate;
19 import dmd.astenums;
20 import dmd.declaration;
21 import dmd.dscope;
22 import dmd.errors;
23 import dmd.expression;
24 import dmd.func;
25 import dmd.globals;
26 import dmd.init;
27 import dmd.mtype;
28 import dmd.postordervisitor;
29 import dmd.tokens;
30 import dmd.visitor;
32 /**************************************
33 * Look for GC-allocations
35 extern (C++) final class NOGCVisitor : StoppableVisitor
37 alias visit = typeof(super).visit;
38 public:
39 FuncDeclaration f;
40 bool checkOnly; // don't print errors
41 bool err;
43 extern (D) this(FuncDeclaration f) scope
45 this.f = f;
48 void doCond(Expression exp)
50 if (exp)
51 walkPostorder(exp, this);
54 override void visit(Expression e)
58 override void visit(DeclarationExp e)
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)
64 if (ExpInitializer ei = v._init.isExpInitializer())
66 doCond(ei.exp);
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)
79 private bool setGC(Expression e, const(char)* format)
81 if (checkOnly)
83 err = true;
84 return true;
86 if (f.setGC(e.loc, format))
88 e.error(format, f.kind(), f.toPrettyChars());
89 err = true;
90 return true;
92 return false;
95 override void visit(CallExp e)
97 import dmd.id : Id;
98 import core.stdc.stdio : printf;
99 if (!e.f)
100 return;
102 // Treat lowered hook calls as their original expressions.
103 auto fd = stripHookTraceImpl(e.f);
104 if (fd.ident == Id._d_arraysetlengthT)
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");
110 else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendcTX)
112 if (setGC(e, "cannot use operator `~=` in `@nogc` %s `%s`"))
113 return;
114 f.printGCUsage(e.loc, "operator `~=` may cause a GC allocation");
118 override void visit(ArrayLiteralExp e)
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");
127 override void visit(AssocArrayLiteralExp e)
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");
136 override void visit(NewExp e)
138 if (e.member && !e.member.isNogc() && f.setGC(e.loc, null))
140 // @nogc-ness is already checked in NewExp::semantic
141 return;
143 if (e.onstack)
144 return;
145 if (global.params.ehnogc && e.thrownew)
146 return; // separate allocator is called for this, not the GC
148 if (setGC(e, "cannot use `new` in `@nogc` %s `%s`"))
149 return;
150 f.printGCUsage(e.loc, "`new` causes a GC allocation");
153 override void visit(DeleteExp e)
155 if (VarExp ve = e.e1.isVarExp())
157 VarDeclaration v = ve.var.isVarDeclaration();
158 if (v && v.onstack)
159 return; // delete for scope allocated class object
162 // Semantic should have already handled this case.
163 assert(0);
166 override void visit(IndexExp e)
168 Type t1b = e.e1.type.toBasetype();
169 if (e.modifiable && t1b.ty == Taarray)
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");
177 override void visit(AssignExp e)
179 if (e.e1.op == EXP.arrayLength)
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");
187 override void visit(CatAssignExp e)
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).
193 if (checkOnly)
195 err = true;
196 return;
198 if (f.setGC(e.loc, null))
200 err = true;
201 return;
205 override void visit(CatExp e)
207 if (setGC(e, "cannot use operator `~` in `@nogc` %s `%s`"))
208 return;
209 f.printGCUsage(e.loc, "operator `~` may cause a GC allocation");
213 Expression checkGC(Scope* sc, Expression e)
215 if (sc.flags & SCOPE.ctfeBlock) // ignore GC in ctfe blocks
216 return e;
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.
222 const betterC = !global.params.useGC;
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_))
230 scope NOGCVisitor gcv = new NOGCVisitor(f);
231 gcv.checkOnly = betterC;
232 walkPostorder(e, gcv);
233 if (gcv.err)
235 if (betterC)
237 /* Allow ctfe to use the gc code, but don't let it into the runtime
239 f.skipCodegen = true;
241 else
242 return ErrorExp.get();
245 return e;
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
254 private FuncDeclaration stripHookTraceImpl(FuncDeclaration fd)
256 import dmd.id : Id;
257 import dmd.dsymbol : Dsymbol;
258 import dmd.root.rootobject : RootObject, DYNCAST;
260 if (fd.ident != Id._d_HookTraceImpl)
261 return fd;
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;