xref: /netbsd-src/external/gpl3/gcc/dist/gcc/d/dmd/nogc.d (revision b1e838363e3c6fc78a55519254d99869742dd33c)
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