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-2023 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
;
21 import dmd
.declaration
;
24 import dmd
.expression
;
32 /**************************************
33 * Look for GC-allocations
35 extern (C
++) final class NOGCVisitor
: StoppableVisitor
37 alias visit
= typeof(super).visit
;
40 bool checkOnly
; // don't print errors
43 extern (D
) this(FuncDeclaration f
) scope
48 void doCond(Expression exp
)
51 walkPostorder(exp
, this);
54 override void visit(Expression e
)
58 override void visit(DeclarationExp e
)
60 // Note that, walkPostorder does not support DeclarationExp today.
61 VarDeclaration v
= e
.declaration
.isVarDeclaration();
62 if (v
&& !(v
.storage_class
& STC
.manifest
) && !v
.isDataseg() && v
._init
)
64 if (ExpInitializer ei
= v
._init
.isExpInitializer())
72 * Register that expression `e` requires the GC
74 * e = expression that uses GC
75 * format = error message when `e` is used in a `@nogc` function.
76 * Must contain format strings "`@nogc` %s `%s`" referring to the function.
77 * Returns: `true` if `err` was set, `false` if it's not in a `@nogc` and not checkonly (-betterC)
79 private bool setGC(Expression e
, const(char)* format
)
88 e
.error(format
, f
.kind(), f
.toPrettyChars());
95 override void visit(CallExp e
)
98 import core
.stdc
.stdio
: printf
;
102 // Treat lowered hook calls as their original expressions.
103 auto fd
= stripHookTraceImpl(e
.f
);
104 if (fd
.ident
== Id
._d_arraysetlengthT
)
106 if (setGC(e
, "setting `length` in `@nogc` %s `%s` may cause a GC allocation"))
108 f
.printGCUsage(e
.loc
, "setting `length` may cause a GC allocation");
110 else if (fd
.ident
== Id
._d_arrayappendT || fd
.ident
== Id
._d_arrayappendcTX
)
112 if (setGC(e
, "cannot use operator `~=` in `@nogc` %s `%s`"))
114 f
.printGCUsage(e
.loc
, "operator `~=` may cause a GC allocation");
118 override void visit(ArrayLiteralExp e
)
120 if (e
.type
.ty
!= Tarray ||
!e
.elements ||
!e
.elements
.length || e
.onstack
)
122 if (setGC(e
, "array literal in `@nogc` %s `%s` may cause a GC allocation"))
124 f
.printGCUsage(e
.loc
, "array literal may cause a GC allocation");
127 override void visit(AssocArrayLiteralExp e
)
131 if (setGC(e
, "associative array literal in `@nogc` %s `%s` may cause a GC allocation"))
133 f
.printGCUsage(e
.loc
, "associative array literal may cause a GC allocation");
136 override void visit(NewExp e
)
138 if (e
.member
&& !e
.member
.isNogc() && f
.setGC())
140 // @nogc-ness is already checked in NewExp::semantic
145 if (global
.params
.ehnogc
&& e
.thrownew
)
146 return; // separate allocator is called for this, not the GC
148 if (setGC(e
, "cannot use `new` in `@nogc` %s `%s`"))
150 f
.printGCUsage(e
.loc
, "`new` causes a GC allocation");
153 override void visit(DeleteExp e
)
155 if (VarExp ve
= e
.e1
.isVarExp())
157 VarDeclaration v
= ve
.var
.isVarDeclaration();
159 return; // delete for scope allocated class object
162 // Semantic should have already handled this case.
166 override void visit(IndexExp e
)
168 Type t1b
= e
.e1
.type
.toBasetype();
169 if (e
.modifiable
&& t1b
.ty
== Taarray
)
171 if (setGC(e
, "assigning an associative array element in `@nogc` %s `%s` may cause a GC allocation"))
173 f
.printGCUsage(e
.loc
, "assigning an associative array element may cause a GC allocation");
177 override void visit(AssignExp e
)
179 if (e
.e1
.op
== EXP
.arrayLength
)
181 if (setGC(e
, "setting `length` in `@nogc` %s `%s` may cause a GC allocation"))
183 f
.printGCUsage(e
.loc
, "setting `length` may cause a GC allocation");
187 override void visit(CatAssignExp e
)
189 /* CatAssignExp will exist in `__traits(compiles, ...)` and in the `.e1` branch of a `__ctfe ? :` CondExp.
190 * The other branch will be `_d_arrayappendcTX(e1, 1), e1[$-1]=e2` which will generate the warning about
191 * GC usage. See visit(CallExp).
205 override void visit(CatExp e
)
207 if (setGC(e
, "cannot use operator `~` in `@nogc` %s `%s`"))
209 f
.printGCUsage(e
.loc
, "operator `~` may cause a GC allocation");
213 Expression
checkGC(Scope
* sc
, Expression e
)
215 if (sc
.flags
& SCOPE
.ctfeBlock
) // ignore GC in ctfe blocks
218 /* If betterC, allow GC to happen in non-CTFE code.
219 * Just don't generate code for it.
220 * Detect non-CTFE use of the GC in betterC code.
222 const betterC
= global
.params
.betterC
;
223 FuncDeclaration f
= sc
.func
;
224 if (e
&& e
.op
!= EXP
.error
&& f
&& sc
.intypeof
!= 1 &&
225 (!(sc
.flags
& SCOPE
.ctfe
) || betterC
) &&
226 (f
.type
.ty
== Tfunction
&&
227 (cast(TypeFunction
)f
.type
).isnogc || f
.nogcInprocess || global
.params
.vgc
) &&
228 !(sc
.flags
& SCOPE
.debug_
))
230 scope NOGCVisitor gcv
= new NOGCVisitor(f
);
231 gcv
.checkOnly
= betterC
;
232 walkPostorder(e
, gcv
);
237 /* Allow ctfe to use the gc code, but don't let it into the runtime
239 f
.skipCodegen
= true;
242 return ErrorExp
.get();
249 * Removes `_d_HookTraceImpl` if found from `fd`.
250 * This is needed to be able to find hooks that are called though the hook's `*Trace` wrapper.
252 * fd = The function declaration to remove `_d_HookTraceImpl` from
254 private FuncDeclaration
stripHookTraceImpl(FuncDeclaration fd
)
257 import dmd
.dsymbol
: Dsymbol
;
258 import dmd
.root
.rootobject
: RootObject
, DYNCAST
;
260 if (fd
.ident
!= Id
._d_HookTraceImpl
)
263 // Get the Hook from the second template parameter
264 auto templateInstance
= fd
.parent
.isTemplateInstance
;
265 RootObject hook
= (*templateInstance
.tiargs
)[1];
266 assert(hook
.dyncast() == DYNCAST
.dsymbol
, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!");
267 return (cast(Dsymbol
)hook
).isFuncDeclaration
;