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 new ErrorExp();
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 }