2 * Checks that a function marked `@nogc` does not invoke the Garbage Collector.
4 * Specification: $(LINK2 https://dlang.org/spec/function.html#nogc-functions, No-GC Functions)
6 * Copyright: Copyright (C) 1999-2024 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
16 import core
.stdc
.stdio
;
20 import dmd
.declaration
;
22 import dmd
.dtemplate
: isDsymbol
;
24 import dmd
.expression
;
29 import dmd
.postordervisitor
;
33 /**************************************
34 * Look for GC-allocations
36 extern (C
++) final class NOGCVisitor
: StoppableVisitor
38 alias visit
= typeof(super).visit
;
41 bool checkOnly
; // don't print errors
44 extern (D
) this(FuncDeclaration f
) scope @safe
49 void doCond(Expression exp
)
52 walkPostorder(exp
, this);
55 override void visit(Expression e
)
59 override void visit(DeclarationExp e
)
61 // Note that, walkPostorder does not support DeclarationExp today.
62 VarDeclaration v
= e
.declaration
.isVarDeclaration();
63 if (v
&& !(v
.storage_class
& STC
.manifest
) && !v
.isDataseg() && v
._init
)
65 if (ExpInitializer ei
= v
._init
.isExpInitializer())
73 * Register that expression `e` requires the GC
75 * e = expression that uses GC
76 * format = error message when `e` is used in a `@nogc` function.
77 * Must contain format strings "`@nogc` %s `%s`" referring to the function.
78 * Returns: `true` if `err` was set, `false` if it's not in a `@nogc` and not checkonly (-betterC)
80 private bool setGC(Expression e
, const(char)* format
)
87 if (f
.setGC(e
.loc
, format
))
89 error(e
.loc
, format
, f
.kind(), f
.toPrettyChars());
96 override void visit(CallExp e
)
99 import core
.stdc
.stdio
: printf
;
103 // Treat lowered hook calls as their original expressions.
104 auto fd
= stripHookTraceImpl(e
.f
);
105 if (fd
.ident
== Id
._d_arraysetlengthT
)
107 if (setGC(e
, "setting `length` in `@nogc` %s `%s` may cause a GC allocation"))
109 f
.printGCUsage(e
.loc
, "setting `length` may cause a GC allocation");
113 override void visit(ArrayLiteralExp e
)
115 if (e
.type
.ty
!= Tarray ||
!e
.elements ||
!e
.elements
.length || e
.onstack
)
117 if (setGC(e
, "array literal in `@nogc` %s `%s` may cause a GC allocation"))
119 f
.printGCUsage(e
.loc
, "array literal may cause a GC allocation");
122 override void visit(AssocArrayLiteralExp e
)
126 if (setGC(e
, "associative array literal in `@nogc` %s `%s` may cause a GC allocation"))
128 f
.printGCUsage(e
.loc
, "associative array literal may cause a GC allocation");
131 override void visit(NewExp e
)
133 if (e
.member
&& !e
.member
.isNogc() && f
.setGC(e
.loc
, null))
135 // @nogc-ness is already checked in NewExp::semantic
140 if (global
.params
.ehnogc
&& e
.thrownew
)
141 return; // separate allocator is called for this, not the GC
143 if (setGC(e
, "cannot use `new` in `@nogc` %s `%s`"))
145 f
.printGCUsage(e
.loc
, "`new` causes a GC allocation");
148 override void visit(DeleteExp e
)
150 if (VarExp ve
= e
.e1
.isVarExp())
152 VarDeclaration v
= ve
.var
.isVarDeclaration();
154 return; // delete for scope allocated class object
157 // Semantic should have already handled this case.
161 override void visit(IndexExp e
)
163 Type t1b
= e
.e1
.type
.toBasetype();
164 if (e
.modifiable
&& t1b
.ty
== Taarray
)
166 if (setGC(e
, "assigning an associative array element in `@nogc` %s `%s` may cause a GC allocation"))
168 f
.printGCUsage(e
.loc
, "assigning an associative array element may cause a GC allocation");
172 override void visit(AssignExp e
)
174 if (e
.e1
.op
== EXP
.arrayLength
)
176 if (setGC(e
, "setting `length` in `@nogc` %s `%s` may cause a GC allocation"))
178 f
.printGCUsage(e
.loc
, "setting `length` may cause a GC allocation");
182 override void visit(CatAssignExp e
)
189 if (setGC(e
, "cannot use operator `~=` in `@nogc` %s `%s`"))
191 f
.printGCUsage(e
.loc
, "operator `~=` may cause a GC allocation");
194 override void visit(CatExp e
)
196 if (setGC(e
, "cannot use operator `~` in `@nogc` %s `%s`"))
198 f
.printGCUsage(e
.loc
, "operator `~` may cause a GC allocation");
202 Expression
checkGC(Scope
* sc
, Expression e
)
204 if (sc
.flags
& SCOPE
.ctfeBlock
) // ignore GC in ctfe blocks
207 /* If betterC, allow GC to happen in non-CTFE code.
208 * Just don't generate code for it.
209 * Detect non-CTFE use of the GC in betterC code.
211 const betterC
= !global
.params
.useGC
;
212 FuncDeclaration f
= sc
.func
;
213 if (e
&& e
.op
!= EXP
.error
&& f
&& sc
.intypeof
!= 1 &&
214 (!(sc
.flags
& SCOPE
.ctfe
) || betterC
) &&
215 (f
.type
.ty
== Tfunction
&&
216 (cast(TypeFunction
)f
.type
).isnogc || f
.nogcInprocess || global
.params
.v
.gc
) &&
217 !(sc
.flags
& SCOPE
.debug_
))
219 scope NOGCVisitor gcv
= new NOGCVisitor(f
);
220 gcv
.checkOnly
= betterC
;
221 walkPostorder(e
, gcv
);
226 /* Allow ctfe to use the gc code, but don't let it into the runtime
228 f
.skipCodegen
= true;
231 return ErrorExp
.get();
238 * Removes `_d_HookTraceImpl` if found from `fd`.
239 * This is needed to be able to find hooks that are called though the hook's `*Trace` wrapper.
241 * fd = The function declaration to remove `_d_HookTraceImpl` from
243 private FuncDeclaration
stripHookTraceImpl(FuncDeclaration fd
)
246 import dmd
.dsymbol
: Dsymbol
;
247 import dmd
.rootobject
: RootObject
, DYNCAST
;
249 if (fd
.ident
!= Id
._d_HookTraceImpl
)
252 // Get the Hook from the second template parameter
253 auto templateInstance
= fd
.parent
.isTemplateInstance
;
254 RootObject hook
= (*templateInstance
.tiargs
)[1];
255 Dsymbol s
= hook
.isDsymbol();
256 assert(s
, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!");
257 return s
.isFuncDeclaration
;