• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zeek / zeek / 5593807640592384

31 Mar 2026 12:21PM UTC coverage: 66.953% (-0.01%) from 66.964%
5593807640592384

push

cirrus

evantypanski
Merge remote-tracking branch 'origin/topic/etyp/deprecate-weird'

* origin/topic/etyp/deprecate-weird:
  Deprecate `Weird::weird`

135552 of 202457 relevant lines covered (66.95%)

2295846.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.36
/src/script_opt/Inline.cc
1
// See the file "COPYING" in the main distribution directory for copyright.
2

3
#include "zeek/script_opt/Inline.h"
4

5
#include "zeek/EventRegistry.h"
6
#include "zeek/module_util.h"
7
#include "zeek/script_opt/Expr.h"
8
#include "zeek/script_opt/FuncInfo.h"
9
#include "zeek/script_opt/ProfileFunc.h"
10
#include "zeek/script_opt/ScriptOpt.h"
11
#include "zeek/script_opt/StmtOptInfo.h"
12
#include "zeek/script_opt/ZAM/Support.h"
13

14
namespace zeek::detail {
15

16
constexpr int MAX_INLINE_SIZE = 1000;
17

18
void Inliner::Analyze() {
35✔
19
    // Locate self- and indirectly recursive functions.
20

21
    // Maps each function to any functions that it calls, either
22
    // directly or (ultimately) indirectly.
23
    std::unordered_map<const Func*, std::unordered_set<const Func*>> call_set;
35✔
24

25
    // Prime the call set for each function with the functions it
26
    // directly calls.
27
    for ( auto& f : funcs ) {
11,967✔
28
        // For any function explicitly known to the event engine, it can
29
        // be hard to analyze whether there's a possibility that when
30
        // executing the function, doing so will tickle the event engine
31
        // into calling it recursively. So we remove these up front.
32
        //
33
        // We deal with cases where these defaults are overridden to refer
34
        // to some other function below, when we go through indirect functions.
35
        if ( is_special_script_func(f.Func()->GetName()) )
11,862✔
36
            continue;
142✔
37

38
        // If ZAM can replace the script, don't inline it, so its usage
39
        // remains visible during the AST reduction process.
40
        if ( is_ZAM_replaceable_script_func(f.Func()->GetName()) )
11,722✔
41
            continue;
2✔
42

43
        std::unordered_set<const Func*> cs;
11,720✔
44

45
        // Aspirational ....
46
        non_recursive_funcs.insert(f.Func());
11,720✔
47

48
        for ( auto& func : f.Profile()->ScriptCalls() ) {
40,308✔
49
            cs.insert(func);
5,148✔
50

51
            if ( func == f.Func() ) {
5,148✔
52
                if ( report_recursive )
8✔
53
                    printf("%s is directly recursive\n", func->GetName().c_str());
×
54

55
                non_recursive_funcs.erase(func);
8✔
56
            }
57
        }
58

59
        call_set[f.Func()] = cs;
11,720✔
60

61
        for ( auto& ind_func : f.Profile()->IndirectFuncs() ) {
35,306✔
62
            auto& v = ind_func->GetVal();
146✔
63
            if ( ! v )
146✔
64
                // Global doesn't correspond to an actual function body.
65
                continue;
72✔
66

67
            auto vf = v->AsFunc();
74✔
68
            if ( vf->GetKind() != BuiltinFunc::SCRIPT_FUNC )
74✔
69
                // Not of analysis interest.
70
                continue;
×
71

72
            auto sf = static_cast<const ScriptFunc*>(vf);
74✔
73

74
            // If we knew transitively that the function lead to any
75
            // indirect calls, nor calls to unsafe BiFs that themselves
76
            // might do so, then we could know that this function isn't
77
            // recursive via indirection. It's not clear, however, that
78
            // identifying such cases is worth the trouble, other than
79
            // for cutting down noise from the following recursion report.
80

81
            if ( report_recursive )
74✔
82
                printf("%s is used indirectly, and thus potentially recursively\n", sf->GetName().c_str());
×
83

84
            non_recursive_funcs.erase(sf);
74✔
85
        }
86
    }
11,720✔
87

88
    // Transitive closure.  If we had any self-respect, we'd implement
89
    // Warshall's algorithm.  What we do here is feasible though since
90
    // Zeek call graphs tend not to be super-deep.  (We could also save
91
    // cycles by only analyzing non-[direct-or-indirect] leaves, as
92
    // was computed by the previous version of this code.  But in
93
    // practice, the execution time for this is completely dwarfed
94
    // by the expense of compiling inlined functions, so we keep it
95
    // simple.)
96

97
    // Whether a change has occurred.
98
    bool did_addition = true;
35✔
99
    while ( did_addition ) {
108✔
100
        did_addition = false;
73✔
101

102
        // Loop over all the functions of interest.
103
        for ( auto& c : call_set ) {
22,742✔
104
            // For each of them, loop over the set of functions
105
            // they call.
106

107
            std::unordered_set<const Func*> addls;
22,523✔
108

109
            for ( auto& cc : c.second ) {
79,314✔
110
                if ( cc == c.first )
11,745✔
111
                    // Don't loop over ourselves.
112
                    continue;
33✔
113

114
                // For each called function, pull up *its*
115
                // set of called functions.
116
                for ( auto& ccc : call_set[cc] ) {
41,472✔
117
                    // For each of those, if we don't
118
                    // already have it, add it.
119
                    if ( c.second.contains(ccc) )
6,336✔
120
                        // We already have it.
121
                        continue;
4,613✔
122

123
                    addls.insert(ccc);
1,723✔
124

125
                    if ( ccc != c.first )
1,723✔
126
                        // Non-recursive.
127
                        continue;
1,720✔
128

129
                    if ( report_recursive )
3✔
130
                        printf("%s is indirectly recursive, called by %s\n", c.first->GetName().c_str(),
3✔
131
                               cc->GetName().c_str());
×
132

133
                    non_recursive_funcs.erase(c.first);
3✔
134
                    non_recursive_funcs.erase(cc);
3✔
135
                }
136
            }
137

138
            if ( ! addls.empty() ) {
22,523✔
139
                did_addition = true;
787✔
140

141
                for ( auto& a : addls )
3,739✔
142
                    c.second.insert(a);
1,378✔
143
            }
144
        }
22,523✔
145
    }
146

147
    for ( auto& f : funcs ) {
11,967✔
148
        if ( f.ShouldSkip() )
11,862✔
149
            continue;
2,402✔
150

151
        const auto& func_ptr = f.FuncPtr();
9,460✔
152
        const auto& func = func_ptr.get();
18,920✔
153
        const auto& body = f.Body();
9,460✔
154

155
        // Candidates are non-event, non-hook, non-recursive,
156
        // non-compiled functions ...
157
        if ( func->Flavor() != FUNC_FLAVOR_FUNCTION )
9,460✔
158
            continue;
2,793✔
159

160
        if ( ! non_recursive_funcs.contains(func) )
6,667✔
161
            continue;
179✔
162

163
        if ( ! is_ZAM_compilable(f.Profile()) )
6,488✔
164
            continue;
×
165

166
        inline_ables[func] = f.Profile();
12,976✔
167
    }
168

169
    if ( ! analysis_options.no_eh_coalescence )
35✔
170
        CoalesceEventHandlers();
35✔
171

172
    for ( auto& f : funcs )
12,168✔
173
        if ( f.ShouldAnalyze() )
24,126✔
174
            InlineFunction(&f);
9,661✔
175
}
35✔
176

177
void Inliner::CoalesceEventHandlers() {
35✔
178
    std::unordered_map<ScriptFunc*, size_t> event_handlers;
35✔
179
    BodyInfo body_to_info;
35✔
180
    for ( size_t i = 0U; i < funcs.size(); ++i ) {
11,897✔
181
        auto& f = funcs[i];
11,862✔
182
        if ( ! f.ShouldAnalyze() )
11,862✔
183
            continue;
10,328✔
184

185
        auto& func_ptr = f.FuncPtr();
9,460✔
186
        const auto& func = func_ptr.get();
9,460✔
187
        const auto& func_type = func->GetType();
9,460✔
188

189
        if ( func_type->AsFuncType()->Flavor() != FUNC_FLAVOR_EVENT )
9,460✔
190
            continue;
6,789✔
191

192
        // Special-case: zeek_init both has tons of event handlers (even
193
        // with -b), such that it inevitably blows out the inlining budget,
194
        // *and* only runs once, such that even if we could inline it, if
195
        // it takes more time to compile it than to just run it via the
196
        // interpreter, it's a lose.
197
        static std::string zeek_init_name = "zeek_init";
2,671✔
198
        if ( func->GetName() == zeek_init_name )
2,671✔
199
            continue;
1,137✔
200

201
        const auto& body = f.Body();
1,534✔
202

203
        if ( func->GetKind() == Func::SCRIPT_FUNC && func->GetBodies().size() > 1 && body->Tag() != STMT_CPP ) {
1,534✔
204
            ++event_handlers[func];
441✔
205
            ASSERT(! body_to_info.contains(body.get()));
441✔
206
            body_to_info[body.get()] = i;
441✔
207
        }
208
    }
209

210
    for ( auto& e : event_handlers ) {
306✔
211
        auto func = e.first;
201✔
212
        auto& bodies = func->GetBodies();
201✔
213
        if ( bodies.size() != e.second )
201✔
214
            // It's potentially unsound to inline some-but-not-all event
215
            // handlers, because doing so may violate &priority's. We
216
            // could do the work of identifying such instances and only
217
            // skipping those, but given that ZAM is feature-complete
218
            // the mismatch here should only arise when using restrictions
219
            // like --optimize-file, which likely aren't the common case.
220
            continue;
×
221

222
        CoalesceEventHandlers({NewRef{}, func}, bodies, body_to_info);
402✔
223
    }
224
}
70✔
225

226
void Inliner::CoalesceEventHandlers(ScriptFuncPtr func, const std::vector<Func::Body>& bodies,
201✔
227
                                    const BodyInfo& body_to_info) {
228
    // We pattern the new (alternate) body off of the first body.
229
    auto& b0 = func->GetBodies()[0].stmts;
201✔
230
    auto merged_body = with_location_of(make_intrusive<StmtList>(), b0);
402✔
231
    auto oi = merged_body->GetOptInfo();
201✔
232

233
    auto& params = func->GetType()->Params();
201✔
234
    auto nparams = params->NumFields();
201✔
235
    size_t init_frame_size = static_cast<size_t>(nparams);
201✔
236

237
    PreInline(oi, init_frame_size);
201✔
238

239
    auto b0_info = body_to_info.find(b0.get());
201✔
240
    ASSERT(b0_info != body_to_info.end());
402✔
241
    auto& info0 = funcs[b0_info->second];
201✔
242
    auto& scope0 = info0.Scope();
201✔
243
    auto& vars = scope0->OrderedVars();
201✔
244

245
    // We need to create a new Scope. Otherwise, when inlining the first
246
    // body the analysis of identifiers gets confused regarding whether
247
    // a given identifier represents the outer instance or the inner.
248
    auto empty_attrs = std::make_unique<std::vector<AttrPtr>>();
201✔
249
    push_scope(scope0->GetID(), std::move(empty_attrs));
603✔
250

251
    std::vector<IDPtr> param_ids;
201✔
252

253
    for ( auto i = 0; i < nparams; ++i ) {
680✔
254
        auto& vi = vars[i];
479✔
255
        // We use a special scope name so that when debugging issues we can
256
        // see that a given variable came from coalescing event handlers.
257
        auto p = install_ID(vi->Name(), "<event>", false, false);
479✔
258
        p->SetType(vi->GetType());
1,437✔
259
        param_ids.push_back(std::move(p));
479✔
260
    }
479✔
261

262
    auto new_scope = pop_scope();
201✔
263

264
    // Build up the calling arguments.
265
    auto args = with_location_of(make_intrusive<ListExpr>(), b0);
402✔
266
    for ( auto& p : param_ids )
1,082✔
267
        args->Append(with_location_of(make_intrusive<NameExpr>(p), b0));
958✔
268

269
    for ( auto& b : bodies ) {
1,044✔
270
        auto bp = b.stmts;
441✔
271
        auto bi_find = body_to_info.find(bp.get());
441✔
272
        ASSERT(bi_find != body_to_info.end());
882✔
273
        auto& bi = funcs[bi_find->second];
441✔
274
        auto ie = DoInline(func, bp, args, bi.Scope(), bi.Profile());
3,969✔
275

276
        if ( ! ie )
441✔
277
            // Failure presumably occurred due to hitting the maximum
278
            // AST complexity for inlining. We can give up by simply
279
            // returning, as at this point we haven't made any actual
280
            // changes other than the function's scope.
281
            return;
×
282

283
        auto ie_s = with_location_of(make_intrusive<ExprStmt>(ie), bp);
882✔
284
        merged_body->Stmts().emplace_back(std::move(ie_s));
441✔
285
    }
1,323✔
286

287
    // The groups are empty here because CoalescedScriptFunc handles the
288
    // case of groups turning elements on/off.
289
    Func::Body new_body = {.stmts = merged_body};
402✔
290

291
    auto inlined_func = make_intrusive<CoalescedScriptFunc>(new_body, new_scope, func);
201✔
292
    inlined_func->SetScope(new_scope);
603✔
293

294
    // Replace the function for that EventHandler with the delegating one.
295
    auto* eh = event_registry->Lookup(func->GetName());
402✔
296
    ASSERT(eh);
201✔
297
    eh->SetFunc(inlined_func);
603✔
298

299
    // Likewise, replace the value of the identifier.
300
    auto fid = lookup_ID(func->GetName().c_str(), GLOBAL_MODULE_NAME, false, false, false);
201✔
301
    ASSERT(fid);
201✔
302
    fid->SetVal(make_intrusive<FuncVal>(inlined_func));
402✔
303

304
    PostInline(oi, inlined_func);
603✔
305

306
    // We don't need to worry about event groups because the CoalescedScriptFunc
307
    // wrapper checks at run-time for whether any handlers have been disabled,
308
    // and if so skips coalesced execution.
309
    Func::Body body{.stmts = merged_body};
402✔
310
    funcs.emplace_back(inlined_func, new_scope, std::move(body));
201✔
311

312
    auto pf = std::make_shared<ProfileFunc>(inlined_func.get(), merged_body, true);
201✔
313
    funcs.back().SetProfile(std::move(pf));
402✔
314
}
1,206✔
315

316
void Inliner::InlineFunction(FuncInfo* f) {
9,661✔
317
    auto oi = f->Body()->GetOptInfo();
9,661✔
318
    PreInline(oi, f->Scope()->Length());
9,661✔
319
    f->Body()->Inline(this);
9,661✔
320
    PostInline(oi, f->FuncPtr());
28,983✔
321
}
9,661✔
322

323
void Inliner::PreInline(StmtOptInfo* oi, size_t frame_size) {
9,862✔
324
    max_inlined_frame_size = 0;
9,862✔
325
    curr_frame_size = frame_size;
9,862✔
326
    num_stmts = oi->num_stmts;
9,862✔
327
    num_exprs = oi->num_exprs;
9,862✔
328
}
9,862✔
329

330
void Inliner::PostInline(StmtOptInfo* oi, ScriptFuncPtr f) {
9,862✔
331
    oi->num_stmts = num_stmts;
9,862✔
332
    oi->num_exprs = num_exprs;
9,862✔
333

334
    int new_frame_size = curr_frame_size + max_inlined_frame_size;
9,862✔
335

336
    if ( new_frame_size > f->FrameSize() )
9,862✔
337
        f->SetFrameSize(new_frame_size);
2,215✔
338
}
9,862✔
339

340
ExprPtr Inliner::CheckForInlining(CallExprPtr c) {
26,687✔
341
    auto f = c->Func();
26,687✔
342

343
    if ( f->Tag() != EXPR_NAME )
26,687✔
344
        // We don't inline indirect calls.
345
        return c;
224✔
346

347
    auto n = f->AsNameExpr();
26,463✔
348
    auto func = n->Id();
26,463✔
349

350
    if ( ! func->IsGlobal() )
26,463✔
351
        return c;
30✔
352

353
    const auto& func_v = func->GetVal();
26,433✔
354
    if ( ! func_v )
26,433✔
355
        return c;
×
356

357
    auto function = func_v->AsFuncVal()->AsFuncPtr();
26,433✔
358

359
    if ( function->GetKind() != Func::SCRIPT_FUNC )
26,433✔
360
        return c;
20,318✔
361

362
    auto func_vf = cast_intrusive<ScriptFunc>(function);
12,230✔
363

364
    auto ia = inline_ables.find(func_vf.get());
6,115✔
365
    if ( ia == inline_ables.end() )
12,230✔
366
        return c;
206✔
367

368
    if ( c->IsInWhen() ) {
5,909✔
369
        // Don't inline these, as doing so requires propagating
370
        // the in-when attribute to the inlined function body.
371
        skipped_inlining.insert(func_vf.get());
4✔
372
        return c;
4✔
373
    }
374

375
    // Check for mismatches in argument count due to single-arg-of-type-any
376
    // loophole used for variadic BiFs.  (The issue isn't calls to the
377
    // BiFs, which won't happen here, but instead to script functions that
378
    // are misusing/abusing the loophole.)
379
    if ( function->GetType()->Params()->NumFields() == 1 && c->Args()->Exprs().size() != 1 ) {
5,905✔
380
        skipped_inlining.insert(func_vf.get());
×
381
        return c;
×
382
    }
383

384
    // We're going to inline the body, unless it's too large.
385
    auto body = func_vf->GetBodies()[0].stmts; // there's only 1 body
5,905✔
386

387
    if ( body->Tag() == STMT_CPP )
5,905✔
388
        return c;
×
389

390
    auto scope = func_vf->GetScope();
5,905✔
391
    auto ie = DoInline(func_vf, body, c->ArgsPtr(), scope, ia->second);
53,145✔
392

393
    if ( ie ) {
5,905✔
394
        ie->SetLocationInfo(c->GetLocationInfo());
11,620✔
395
        did_inline.insert(func_vf.get());
5,810✔
396
    }
397

398
    return ie;
5,905✔
399
}
50,263✔
400

401
ExprPtr Inliner::DoInline(ScriptFuncPtr sf, StmtPtr body, ListExprPtr args, ScopePtr scope, const ProfileFunc* pf) {
6,346✔
402
    // Inline the body, unless it's too large.
403
    auto oi = body->GetOptInfo();
6,346✔
404

405
    if ( num_stmts + oi->num_stmts + num_exprs + oi->num_exprs > MAX_INLINE_SIZE ) {
6,346✔
406
        skipped_inlining.insert(sf.get());
95✔
407
        return nullptr; // signals "stop inlining"
95✔
408
    }
409

410
    num_stmts += oi->num_stmts;
6,251✔
411
    num_exprs += oi->num_exprs;
6,251✔
412

413
    auto body_dup = body->Duplicate();
6,251✔
414
    body_dup->GetOptInfo()->num_stmts = oi->num_stmts;
6,251✔
415
    body_dup->GetOptInfo()->num_exprs = oi->num_exprs;
6,251✔
416

417
    // Getting the names of the parameters is tricky.  It's tempting
418
    // to take them from the function's type declaration, but alas
419
    // Zeek allows forward-declaring a function with one set of parameter
420
    // names and then defining a later instance of it with different
421
    // names, as long as the types match.  So we have to glue together
422
    // the type declaration, which gives us the number of parameters,
423
    // with the scope, which gives us all the variables declared in
424
    // the function, *using the knowledge that the parameters are
425
    // declared first*.
426
    auto& vars = scope->OrderedVars();
6,251✔
427
    int nparam = sf->GetType()->Params()->NumFields();
6,251✔
428

429
    std::vector<IDPtr> params;
6,251✔
430
    std::vector<bool> param_is_modified;
6,251✔
431

432
    for ( int i = 0; i < nparam; ++i ) {
16,985✔
433
        auto& vi = vars[i];
10,734✔
434
        params.emplace_back(vi);
10,734✔
435
        param_is_modified.emplace_back((pf->Assignees().contains(vi)));
10,734✔
436
    }
437

438
    // Recursively inline the body.  This is safe to do because we've
439
    // ensured there are no recursive loops ... but we have to be
440
    // careful in accounting for the frame sizes.
441
    int frame_size = sf->FrameSize();
6,251✔
442

443
    int hold_curr_frame_size = curr_frame_size;
6,251✔
444
    curr_frame_size = frame_size;
6,251✔
445

446
    int hold_max_inlined_frame_size = max_inlined_frame_size;
6,251✔
447
    max_inlined_frame_size = 0;
6,251✔
448

449
    body_dup->Inline(this);
6,251✔
450

451
    curr_frame_size = hold_curr_frame_size;
6,251✔
452

453
    int new_frame_size = frame_size + max_inlined_frame_size;
6,251✔
454
    if ( new_frame_size > hold_max_inlined_frame_size )
6,251✔
455
        max_inlined_frame_size = new_frame_size;
4,282✔
456
    else
457
        max_inlined_frame_size = hold_max_inlined_frame_size;
1,969✔
458

459
    auto t = scope->GetReturnType();
6,251✔
460

461
    ASSERT(params.size() == args->Exprs().size());
6,251✔
462
    return with_location_of(make_intrusive<InlineExpr>(sf, args, params, param_is_modified, body_dup, curr_frame_size,
6,251✔
463
                                                       t),
464
                            body);
12,502✔
465
}
12,502✔
466

467
} // namespace zeek::detail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc