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 1
    extern (D) this(FuncDeclaration f)
39
    {
40 1
        this.f = f;
41
    }
42

43
    void doCond(Expression exp)
44
    {
45 1
        if (exp)
46 1
            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 1
        VarDeclaration v = e.declaration.isVarDeclaration();
57 1
        if (v && !(v.storage_class & STC.manifest) && !v.isDataseg() && v._init)
58
        {
59 1
            if (ExpInitializer ei = v._init.isExpInitializer())
60
            {
61 1
                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 1
        if (!e.f)
71 1
            return;
72

73 1
        auto fd = stripHookTraceImpl(e.f);
74 1
        if (fd.ident == Id._d_arraysetlengthT)
75
        {
76 1
            if (f.setGC())
77
            {
78 1
                e.error("setting `length` in `@nogc` %s `%s` may cause a GC allocation",
79
                    f.kind(), f.toPrettyChars());
80 1
                err = true;
81 1
                return;
82
            }
83 1
            f.printGCUsage(e.loc, "setting `length` may cause a GC allocation");
84
        }
85
    }
86

87
    override void visit(ArrayLiteralExp e)
88
    {
89 1
        if (e.type.ty != Tarray || !e.elements || !e.elements.dim)
90 1
            return;
91 1
        if (f.setGC())
92
        {
93 1
            e.error("array literal in `@nogc` %s `%s` may cause a GC allocation",
94
                f.kind(), f.toPrettyChars());
95 1
            err = true;
96 1
            return;
97
        }
98 1
        f.printGCUsage(e.loc, "array literal may cause a GC allocation");
99
    }
100

101
    override void visit(AssocArrayLiteralExp e)
102
    {
103 1
        if (!e.keys.dim)
104 0
            return;
105 1
        if (f.setGC())
106
        {
107 1
            e.error("associative array literal in `@nogc` %s `%s` may cause a GC allocation",
108
                f.kind(), f.toPrettyChars());
109 1
            err = true;
110 1
            return;
111
        }
112 1
        f.printGCUsage(e.loc, "associative array literal may cause a GC allocation");
113
    }
114

115
    override void visit(NewExp e)
116
    {
117 1
        if (e.member && !e.member.isNogc() && f.setGC())
118
        {
119
            // @nogc-ness is already checked in NewExp::semantic
120 1
            return;
121
        }
122 1
        if (e.onstack)
123 1
            return;
124 1
        if (e.allocator)
125 0
            return;
126 1
        if (global.params.ehnogc && e.thrownew)
127 1
            return;                     // separate allocator is called for this, not the GC
128 1
        if (f.setGC())
129
        {
130 1
            e.error("cannot use `new` in `@nogc` %s `%s`",
131
                f.kind(), f.toPrettyChars());
132 1
            err = true;
133 1
            return;
134
        }
135 1
        f.printGCUsage(e.loc, "`new` causes a GC allocation");
136
    }
137

138
    override void visit(DeleteExp e)
139
    {
140 1
        if (e.e1.op == TOK.variable)
141
        {
142 1
            VarDeclaration v = (cast(VarExp)e.e1).var.isVarDeclaration();
143 1
            if (v && v.onstack)
144 1
                return; // delete for scope allocated class object
145
        }
146

147 1
        Type tb = e.e1.type.toBasetype();
148 1
        AggregateDeclaration ad = null;
149 1
        switch (tb.ty)
150
        {
151 1
        case Tclass:
152 1
            ad = (cast(TypeClass)tb).sym;
153 1
            break;
154

155 1
        case Tpointer:
156 1
            tb = (cast(TypePointer)tb).next.toBasetype();
157 1
            if (tb.ty == Tstruct)
158 1
                ad = (cast(TypeStruct)tb).sym;
159 1
            break;
160

161 0
        default:
162 0
            break;
163
        }
164

165 1
        if (f.setGC())
166
        {
167 1
            e.error("cannot use `delete` in `@nogc` %s `%s`",
168
                f.kind(), f.toPrettyChars());
169 1
            err = true;
170 1
            return;
171
        }
172 1
        f.printGCUsage(e.loc, "`delete` requires the GC");
173
    }
174

175
    override void visit(IndexExp e)
176
    {
177 1
        Type t1b = e.e1.type.toBasetype();
178 1
        if (t1b.ty == Taarray)
179
        {
180 1
            if (f.setGC())
181
            {
182 1
                e.error("indexing an associative array in `@nogc` %s `%s` may cause a GC allocation",
183
                    f.kind(), f.toPrettyChars());
184 1
                err = true;
185 1
                return;
186
            }
187 1
            f.printGCUsage(e.loc, "indexing an associative array may cause a GC allocation");
188
        }
189
    }
190

191
    override void visit(AssignExp e)
192
    {
193 1
        if (e.e1.op == TOK.arrayLength)
194
        {
195 0
            if (f.setGC())
196
            {
197 0
                e.error("setting `length` in `@nogc` %s `%s` may cause a GC allocation",
198
                    f.kind(), f.toPrettyChars());
199 0
                err = true;
200 0
                return;
201
            }
202 0
            f.printGCUsage(e.loc, "setting `length` may cause a GC allocation");
203
        }
204
    }
205

206
    override void visit(CatAssignExp e)
207
    {
208 1
        if (f.setGC())
209
        {
210 1
            e.error("cannot use operator `~=` in `@nogc` %s `%s`",
211
                f.kind(), f.toPrettyChars());
212 1
            err = true;
213 1
            return;
214
        }
215 1
        f.printGCUsage(e.loc, "operator `~=` may cause a GC allocation");
216
    }
217

218
    override void visit(CatExp e)
219
    {
220 1
        if (f.setGC())
221
        {
222 1
            e.error("cannot use operator `~` in `@nogc` %s `%s`",
223
                f.kind(), f.toPrettyChars());
224 1
            err = true;
225 1
            return;
226
        }
227 1
        f.printGCUsage(e.loc, "operator `~` may cause a GC allocation");
228
    }
229
}
230

231
Expression checkGC(Scope* sc, Expression e)
232
{
233 1
    FuncDeclaration f = sc.func;
234 1
    if (e && e.op != TOK.error && f && sc.intypeof != 1 && !(sc.flags & SCOPE.ctfe) &&
235 1
           (f.type.ty == Tfunction &&
236 1
            (cast(TypeFunction)f.type).isnogc || (f.flags & FUNCFLAG.nogcInprocess) || global.params.vgc) &&
237 1
           !(sc.flags & SCOPE.debug_))
238
    {
239 1
        scope NOGCVisitor gcv = new NOGCVisitor(f);
240 1
        walkPostorder(e, gcv);
241 1
        if (gcv.err)
242 1
            return ErrorExp.get();
243
    }
244 1
    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 1
    if (fd.ident != Id._d_HookTraceImpl)
260 1
        return fd;
261

262
    // Get the Hook from the second template parameter
263 0
    auto templateInstance = fd.parent.isTemplateInstance;
264 0
    RootObject hook = (*templateInstance.tiargs)[1];
265 0
    assert(hook.dyncast() == DYNCAST.dsymbol, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!");
266 0
    return (cast(Dsymbol)hook).isFuncDeclaration;
267
}

Read our documentation on viewing source code .

Loading