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 
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         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 
visit(ArrayLiteralExp e)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 
visit(AssocArrayLiteralExp e)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 
visit(NewExp e)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 
visit(DeleteExp e)137     override void visit(DeleteExp e)
138     {
139         if (e.e1.op == TOK.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 
visit(IndexExp e)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 
visit(AssignExp e)190     override void visit(AssignExp e)
191     {
192         if (e.e1.op == TOK.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 
visit(CatAssignExp e)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 
visit(CatExp e)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 
checkGC(Scope * sc,Expression e)230 Expression checkGC(Scope* sc, Expression e)
231 {
232     FuncDeclaration f = sc.func;
233     if (e && e.op != TOK.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  */
stripHookTraceImpl(FuncDeclaration fd)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 }
267