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