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