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

wurstscript / WurstScript / 254

24 Jun 2025 03:56PM UTC coverage: 62.245% (-0.03%) from 62.277%
254

push

circleci

Frotty
Ignore defunct test

17265 of 27737 relevant lines covered (62.25%)

0.62 hits per line

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

53.24
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java
1
package de.peeeq.wurstscript.intermediatelang.optimizer;
2

3
import com.google.common.collect.LinkedHashMultimap;
4
import com.google.common.collect.LinkedListMultimap;
5
import com.google.common.collect.Multimap;
6
import de.peeeq.datastructures.TransitiveClosure;
7
import de.peeeq.wurstscript.jassIm.*;
8
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
9

10
import java.util.Collection;
11
import java.util.LinkedHashSet;
12
import java.util.Set;
13
import java.util.stream.Collectors;
14
import java.util.stream.Stream;
15

16
/**
17
 * Analyzes a program for side-effects
18
 */
19
public class SideEffectAnalyzer {
20

21
    private final ImProg prog;
22
    // f -> set of functions directly called by f
23
    private Multimap<ImFunction, ImFunction> callRelation;
24
    // f -> set of functions directly and transitively called by f
25
    private TransitiveClosure<ImFunction> callRelationTr;
26
    // f -> global variables directly used in f
27
    private Multimap<ImFunction, ImVar> usedGlobals;
28

29
    public SideEffectAnalyzer(ImProg prog) {
1✔
30
        this.prog = prog;
1✔
31
    }
1✔
32

33
    /**
34
     * checks if this expression might have side effects
35
     * (does not do a deep analysis, all function calls and statements are considered to have side effects)
36
     */
37
    public static boolean quickcheckHasSideeffects(ImExpr expr) {
38
        return expr.match(new ImExpr.Matcher<Boolean>() {
1✔
39
            @Override
40
            public Boolean case_ImFunctionCall(ImFunctionCall imFunctionCall) {
41
                return true;
1✔
42
            }
43

44
            @Override
45
            public Boolean case_ImTypeIdOfClass(ImTypeIdOfClass imTypeIdOfClass) {
46
                return false;
×
47
            }
48

49
            @Override
50
            public Boolean case_ImVarArrayAccess(ImVarArrayAccess e) {
51
                return e.getIndexes().stream().anyMatch(SideEffectAnalyzer::quickcheckHasSideeffects);
1✔
52
            }
53

54
            @Override
55
            public Boolean case_ImRealVal(ImRealVal imRealVal) {
56
                return false;
×
57
            }
58

59
            @Override
60
            public Boolean case_ImTupleSelection(ImTupleSelection e) {
61
                return quickcheckHasSideeffects(e.getTupleExpr());
×
62
            }
63

64
            @Override
65
            public Boolean case_ImInstanceof(ImInstanceof e) {
66
                return quickcheckHasSideeffects(e.getObj());
×
67
            }
68

69
            @Override
70
            public Boolean case_ImDealloc(ImDealloc imDealloc) {
71
                return true;
×
72
            }
73

74
            @Override
75
            public Boolean case_ImMemberAccess(ImMemberAccess e) {
76
                return quickcheckHasSideeffects(e.getReceiver());
×
77
            }
78

79
            @Override
80
            public Boolean case_ImBoolVal(ImBoolVal imBoolVal) {
81
                return false;
×
82
            }
83

84
            @Override
85
            public Boolean case_ImTupleExpr(ImTupleExpr e) {
86
                return e.getExprs().stream().anyMatch(SideEffectAnalyzer::quickcheckHasSideeffects);
×
87
            }
88

89
            @Override
90
            public Boolean case_ImNull(ImNull imNull) {
91
                return false;
×
92
            }
93

94
            @Override
95
            public Boolean case_ImGetStackTrace(ImGetStackTrace imGetStackTrace) {
96
                return true;
×
97
            }
98

99
            @Override
100
            public Boolean case_ImTypeVarDispatch(ImTypeVarDispatch imTypeVarDispatch) {
101
                return true;
×
102
            }
103

104
            @Override
105
            public Boolean case_ImOperatorCall(ImOperatorCall e) {
106
                return e.getArguments().stream().anyMatch(SideEffectAnalyzer::quickcheckHasSideeffects);
1✔
107
            }
108

109
            @Override
110
            public Boolean case_ImStringVal(ImStringVal imStringVal) {
111
                return false;
×
112
            }
113

114
            @Override
115
            public Boolean case_ImMethodCall(ImMethodCall imMethodCall) {
116
                return true;
×
117
            }
118

119
            @Override
120
            public Boolean case_ImAlloc(ImAlloc imAlloc) {
121
                return true;
×
122
            }
123

124
            @Override
125
            public Boolean case_ImCast(ImCast imCast) {
126
                return quickcheckHasSideeffects(imCast.getExpr());
1✔
127
            }
128

129
            @Override
130
            public Boolean case_ImCompiletimeExpr(ImCompiletimeExpr imCompiletimeExpr) {
131
                return true;
×
132
            }
133

134
            @Override
135
            public Boolean case_ImTypeIdOfObj(ImTypeIdOfObj e) {
136
                return quickcheckHasSideeffects(e.getObj());
×
137
            }
138

139
            @Override
140
            public Boolean case_ImVarAccess(ImVarAccess imVarAccess) {
141
                return false;
1✔
142
            }
143

144
            @Override
145
            public Boolean case_ImIntVal(ImIntVal imIntVal) {
146
                return false;
1✔
147
            }
148

149
            @Override
150
            public Boolean case_ImFuncRef(ImFuncRef imFuncRef) {
151
                return false;
×
152
            }
153

154
            @Override
155
            public Boolean case_ImStatementExpr(ImStatementExpr imStatementExpr) {
156
                return true;
×
157
            }
158
        });
159
    }
160

161
    /**
162
     * @return f -> set of functions directly called by f
163
     */
164
    public Multimap<ImFunction, ImFunction> getCallRelation() {
165
        if (callRelation != null) {
1✔
166
            return callRelation;
×
167
        }
168
        callRelation = LinkedListMultimap.create();
1✔
169
        for (ImFunction caller : ImHelper.calculateFunctionsOfProg(prog)) {
1✔
170
            callRelation.putAll(caller, directlyCalledFunctions(caller));
1✔
171
        }
1✔
172
        return callRelation;
1✔
173
    }
174

175
    /**
176
     * @return f -> set of functions directly and transitively called by f
177
     */
178
    public TransitiveClosure<ImFunction> getCallRelationTr() {
179
        if (callRelationTr != null) {
1✔
180
            return callRelationTr;
1✔
181
        }
182
        callRelationTr = new TransitiveClosure<>(getCallRelation());
1✔
183
        return callRelationTr;
1✔
184
    }
185

186
    /**
187
     * @return f -> global variables directly used in f
188
     */
189
    public Multimap<ImFunction, ImVar> getUsedGlobals() {
190
        if (usedGlobals != null) {
1✔
191
            return usedGlobals;
×
192
        }
193
        usedGlobals = LinkedHashMultimap.create();
1✔
194
        for (ImFunction function : ImHelper.calculateFunctionsOfProg(prog)) {
1✔
195
            for (ImVar v : directlyUsedVariables(function)) {
1✔
196
                if (v.isGlobal()) {
1✔
197
                    usedGlobals.put(function, v);
1✔
198
                }
199
            }
1✔
200
        }
1✔
201
        return usedGlobals;
1✔
202
    }
203

204

205
    /**
206
     * Functions directly or indirectly called in e
207
     */
208
    public Set<ImFunction> calledFunctions(Element e) {
209
        return calledFunctionsStream(e)
×
210
                .collect(Collectors.toSet());
×
211
    }
212

213
    /**
214
     * Functions directly or indirectly called in e
215
     */
216
    private Stream<ImFunction> calledFunctionsStream(Element e) {
217
        return directlyCalledFunctions(e).stream()
1✔
218
                .flatMap(f -> Stream.concat(Stream.of(f), getCallRelationTr().get(f)));
1✔
219
    }
220

221
    /**
222
     * Natives directly or indirectly called in e
223
     */
224
    public Set<ImFunction> calledNatives(Element e) {
225
        return calledFunctionsStream(e)
1✔
226
                .filter(ImFunction::isNative)
1✔
227
                .collect(Collectors.toSet());
1✔
228
    }
229

230
    /**
231
     * Variables directly or indirectly (via called functions) used in e
232
     * Does not consider variables used because of natives doing stuff (e.g. ForGroup callback)
233
     */
234
    public Set<ImVar> usedVariables(Element e) {
235
        Stream<ImVar> indirectGlobals = calledFunctionsStream(e)
1✔
236
                .flatMap(f -> getUsedGlobals().get(f).stream());
1✔
237
        return Stream.concat(indirectGlobals, directlyUsedVariables(e).stream())
1✔
238
                .collect(Collectors.toSet());
1✔
239
    }
240

241

242
    /**
243
     * Functions directly called in e
244
     */
245
    public Set<ImFunction> directlyCalledFunctions(Element e) {
246
        Set<ImFunction> calledFunctions = new LinkedHashSet<>();
1✔
247
        e.accept(new ImFunction.DefaultVisitor() {
1✔
248

249
            @Override
250
            public void visit(ImFunctionCall c) {
251
                super.visit(c);
1✔
252
                calledFunctions.add(c.getFunc());
1✔
253
            }
1✔
254

255
            @Override
256
            public void visit(ImMethodCall c) {
257
                super.visit(c);
×
258
                calledFunctions.add(c.getMethod().getImplementation());
×
259
            }
×
260
        });
261
        return calledFunctions;
1✔
262

263
    }
264

265
    /**
266
     * Variables directly used in e
267
     */
268
    public Set<ImVar> directlyUsedVariables(Element e) {
269
        Set<ImVar> imVars = new LinkedHashSet<>();
1✔
270
        e.accept(new ImStmt.DefaultVisitor() {
1✔
271

272
            @Override
273
            public void visit(ImVarAccess va) {
274
                super.visit(va);
1✔
275
                imVars.add(va.getVar());
1✔
276
            }
1✔
277

278
            @Override
279
            public void visit(ImVarArrayAccess va) {
280
                super.visit(va);
1✔
281
                imVars.add(va.getVar());
1✔
282
            }
1✔
283

284
            @Override
285
            public void visit(ImMemberAccess va) {
286
                super.visit(va);
×
287
                imVars.add(va.getVar());
×
288
            }
×
289

290
            @Override
291
            public void visit(ImSet va) {
292
                super.visit(va);
1✔
293
                ImLExpr assignable = va.getLeft();
1✔
294
                collectVars(imVars, assignable);
1✔
295

296
            }
1✔
297

298
            @Override
299
            public void visit(ImVarargLoop va) {
300
                super.visit(va);
×
301
                imVars.add(va.getLoopVar());
×
302
            }
×
303

304
        });
305
        return imVars;
1✔
306
    }
307

308
    private void collectVars(Collection<ImVar> imVars, ImLExpr assignable) {
309
        assignable.match(new ImLExpr.MatcherVoid() {
1✔
310
            @Override
311
            public void case_ImVarAccess(ImVarAccess v) {
312
                imVars.add(v.getVar());
1✔
313
            }
1✔
314

315
            @Override
316
            public void case_ImStatementExpr(ImStatementExpr imStatementExpr) {
317
                throw new RuntimeException("TODO"); // TODO
×
318
            }
319

320
            @Override
321
            public void case_ImTupleSelection(ImTupleSelection v) {
322
                collectVars(imVars, (ImLExpr) v.getTupleExpr());
×
323
            }
×
324

325
            @Override
326
            public void case_ImVarArrayAccess(ImVarArrayAccess v) {
327
                imVars.add(v.getVar());
1✔
328
            }
1✔
329

330
            @Override
331
            public void case_ImMemberAccess(ImMemberAccess v) {
332
                imVars.add(v.getVar());
×
333
            }
×
334

335
            @Override
336
            public void case_ImTupleExpr(ImTupleExpr te) {
337
                for (ImExpr e : te.getExprs()) {
×
338
                    ((ImLExpr) e).match(this);
×
339
                }
×
340
            }
×
341
        });
342
    }
1✔
343

344

345
    /**
346
     * Variables directly used in e
347
     */
348
    public Set<ImVar> directlyAccessedVariables(Element e) {
349
        Set<ImVar> imVars = new LinkedHashSet<>();
×
350
        e.accept(new ImStmt.DefaultVisitor() {
×
351

352
            @Override
353
            public void visit(ImVarAccess va) {
354
                super.visit(va);
×
355
                imVars.add(va.getVar());
×
356
            }
×
357

358
            @Override
359
            public void visit(ImVarArrayAccess va) {
360
                super.visit(va);
×
361
                imVars.add(va.getVar());
×
362
            }
×
363

364
            @Override
365
            public void visit(ImMemberAccess va) {
366
                super.visit(va);
×
367
                imVars.add(va.getVar());
×
368
            }
×
369

370
        });
371
        return imVars;
×
372
    }
373

374
    /**
375
     * Variables directly used in e
376
     */
377
    public Set<ImVar> directlySetVariables(Element e) {
378
        Set<ImVar> imVars = new LinkedHashSet<>();
×
379
        e.accept(new ImStmt.DefaultVisitor() {
×
380

381
            @Override
382
            public void visit(ImSet va) {
383
                super.visit(va);
×
384
                collectVars(imVars, va.getLeft());
×
385
            }
×
386

387
            @Override
388
            public void visit(ImVarargLoop va) {
389
                super.visit(va);
×
390
                imVars.add(va.getLoopVar());
×
391
            }
×
392

393
        });
394
        return imVars;
×
395
    }
396

397

398
    /**
399
     * Checks if two statements might affect each other.
400
     * When this returns true, it is certain that it does not matter whether stmt1 or stmt2 are called first
401
     * <p>
402
     * The only difference between executing stmt1; stmt2 vs stmt2; stmt1 would be if one of the statement
403
     * crashes and thus the second statement would not be executed.
404
     * But for optimizations, we assume the program already is correct and thus we can ignore crashes.
405
     */
406
    public boolean mightAffect(ImStmt stmt1, ImStmt stmt2) {
407
        if (!calledNatives(stmt1).isEmpty() || !calledNatives(stmt2).isEmpty()) {
1✔
408
            // there are natives that can affect other natives
409
            // be safe
410
            return true;
1✔
411
        }
412
        Set<ImVar> used1 = usedVariables(stmt1);
1✔
413
        Set<ImVar> used2 = usedVariables(stmt2);
1✔
414

415
        // check that there are no variables, that both use
416
        return used1.stream().anyMatch(used2::contains);
1✔
417
    }
418

419
    /**
420
     * Checks if the given statement cannot use the variable v
421
     */
422
    public boolean cannotUseVar(ImStmt s, ImVar v) {
423
        if (v.isGlobal()) {
1✔
424
            Set<ImVar> imVars = usedVariables(s);
1✔
425
            Set<ImFunction> imFunctions = calledNatives(s);
1✔
426
            return !imVars.contains(v) && imFunctions.isEmpty();
1✔
427
        } else {
428
            // local variables
429
            Set<ImVar> imVars = directlyUsedVariables(s);
1✔
430
            return !imVars.contains(v);
1✔
431
        }
432
    }
433

434
    /**
435
     * Checks if the given function calls any functions or modifies and variable
436
     */
437
    public boolean hasSideEffects(Element elem) {
438
        Set<ImFunction> natives = calledNatives(elem);
×
439
        Set<ImFunction> directFuncs = calledFunctions(elem);
×
440
        Set<ImVar> imVars = directlySetVariables(elem);
×
441
        return natives.size() + directFuncs.size() + imVars.size() > 0;
×
442
    }
443
}
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