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

wurstscript / WurstScript / 271

29 Sep 2025 12:12PM UTC coverage: 64.649% (+2.4%) from 62.222%
271

Pull #1096

circleci

Frotty
Merge branch 'perf-improvements' of https://github.com/wurstscript/WurstScript into perf-improvements
Pull Request #1096: Perf improvements

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

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

86.43
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/DataflowAnomalyAnalysis.java
1
package de.peeeq.wurstscript.validation.controlflow;
2

3
import com.google.common.collect.ImmutableMap;
4
import com.google.common.collect.ImmutableMap.Builder;
5
import com.google.common.collect.ImmutableSet;
6
import com.google.common.collect.ImmutableSetMultimap;
7
import com.google.common.collect.Sets;
8
import de.peeeq.immutablecollections.ImmutableList;
9
import de.peeeq.wurstscript.ast.*;
10
import de.peeeq.wurstscript.attributes.names.NameLink;
11
import de.peeeq.wurstscript.types.WurstTypeArray;
12
import de.peeeq.wurstscript.utils.Utils;
13
import org.eclipse.jdt.annotation.Nullable;
14

15
import java.util.*;
16
import java.util.Map.Entry;
17

18
//w(11)->{r(17), r(19)} & w(19)->{r(17), r(19)} & w(22)
19
class VarStates {
20
    final ImmutableMap<LocalVarDef, VState> states;
21
    final boolean thisDestroyed;
22

23
    public VarStates(ImmutableMap<LocalVarDef, VState> states, boolean thisDestroyed) {
1✔
24
        this.states = states;
1✔
25
        this.thisDestroyed = thisDestroyed;
1✔
26
    }
1✔
27

28
    VarStates merge(VarStates other) {
29
        ImmutableMap<LocalVarDef, VState> merged = Utils.mergeMaps(states, other.states, VState::merge);
1✔
30
        return new VarStates(merged, thisDestroyed || other.thisDestroyed);
1✔
31
    }
32

33
    @Override
34
    public boolean equals(Object o) {
35
        if (this == o) return true;
1✔
36
        if (o == null || getClass() != o.getClass()) return false;
1✔
37
        VarStates varStates = (VarStates) o;
1✔
38
        return thisDestroyed == varStates.thisDestroyed &&
1✔
39
                Objects.equals(states, varStates.states);
1✔
40
    }
41

42
    @Override
43
    public int hashCode() {
44
        return Objects.hash(states, thisDestroyed);
×
45
    }
46

47
    public static VarStates initial(Set<LocalVarDef> r) {
48
        ImmutableMap.Builder<LocalVarDef, VState> s = ImmutableMap.builder();
1✔
49
        for (LocalVarDef v : r) {
1✔
50
            s.put(v, VState.initial);
1✔
51
        }
1✔
52
        return new VarStates(s.build(), false);
1✔
53
    }
54

55
    public boolean destroyed(NameDef v) {
56
        VState s = states.get(v);
1✔
57
        return s != null && s.mightBeDestroyed;
1✔
58
    }
59

60
    public boolean uninitialized(NameDef v) {
61
        VState s = states.get(v);
1✔
62
        return s != null && s.mightBeUninitialized;
1✔
63
    }
64

65
    public VarStates addRead(LocalVarDef v, Element r) {
66
        VState s = getVarState(v);
1✔
67
        if (s == null) {
1✔
68
            s = VState.initialDefined;
1✔
69
        }
70
        s = s.addRead(r);
1✔
71
        Builder<LocalVarDef, VState> builder = ImmutableMap.builder();
1✔
72
        for (Entry<LocalVarDef, VState> e : states.entrySet()) {
1✔
73
            if (e.getKey() != v) {
1✔
74
                builder.put(e);
1✔
75
            }
76
        }
1✔
77
        ImmutableMap<LocalVarDef, VState> rs = builder
1✔
78
                .put(v, s)
1✔
79
                .build();
1✔
80
        return new VarStates(rs, thisDestroyed);
1✔
81
    }
82

83
    public ImmutableSet<WStatement> getUnreadWrites(NameDef var) {
84
        ImmutableSet.Builder<WStatement> res = ImmutableSet.builder();
1✔
85
        VState vState = states.get(var);
1✔
86
        if (vState == null) return ImmutableSet.of();
1✔
87
        for (WStatement wr : vState.allWrites) {
1✔
88
            if (vState.writesAndReads.get(wr).isEmpty()) {
1✔
89
                res.add(wr);
1✔
90
            }
91
        }
1✔
92

93
        return res.build();
1✔
94
    }
95

96
    public VarStates addWrite(LocalVarDef var, WStatement s) {
97
        ImmutableMap.Builder<LocalVarDef, VState> res = ImmutableMap.builder();
1✔
98
        for (Entry<LocalVarDef, VState> e : states.entrySet()) {
1✔
99
            if (e.getKey() != var) {
1✔
100
                res.put(e);
1✔
101
            }
102
        }
1✔
103
        VState vState = getVarState(var);
1✔
104
        if (vState == null) {
1✔
105
            vState = VState.initialDefined;
1✔
106
        }
107
        vState = vState.addWrite(s);
1✔
108
        res.put(var, vState);
1✔
109
        return new VarStates(res.build(), thisDestroyed);
1✔
110
    }
111

112
    public VarStates addDestroy(LocalVarDef var) {
113
        ImmutableMap.Builder<LocalVarDef, VState> res = ImmutableMap.builder();
1✔
114
        for (Entry<LocalVarDef, VState> e : states.entrySet()) {
1✔
115
            if (e.getKey() != var) {
1✔
116
                res.put(e);
1✔
117
            }
118
        }
1✔
119
        res.put(var, VState.destroyed);
1✔
120
        return new VarStates(res.build(), thisDestroyed);
1✔
121
    }
122

123

124

125
    private @Nullable VState getVarState(LocalVarDef var) {
126
        return states.get(var);
1✔
127
    }
128

129
    @Override
130
    public String toString() {
131
        StringBuilder sb = new StringBuilder("VarStates [");
×
132
        for (Entry<LocalVarDef, VState> e : this.states.entrySet()) {
×
133
            sb.append("\n\t");
×
134
            sb.append(e.getKey().getName()).append(" -> ").append(e.getValue());
×
135

136
        }
×
137
        sb.append("]");
×
138
        return sb.toString();
×
139
    }
140

141
    public boolean isThisDestroyed() {
142
        return thisDestroyed;
×
143
    }
144

145
    public VarStates withThisDestroyed(boolean thisDestroyed) {
146
        return new VarStates(states, thisDestroyed);
1✔
147
    }
148

149

150
}
151

152
class VState {
153

154
    public static final VState initialDefined = new VState(false, false, ImmutableSetMultimap.of()
1✔
155
            , ImmutableSet.of(), ImmutableSet.of());
1✔
156
    public static final VState initial = new VState(true, false, ImmutableSetMultimap.of()
1✔
157
            , ImmutableSet.of(), ImmutableSet.of());
1✔
158
    public static final VState destroyed = new VState(false, true, ImmutableSetMultimap.of()
1✔
159
            , ImmutableSet.of(), ImmutableSet.of());
1✔
160

161
    final boolean mightBeUninitialized;
162
    final boolean mightBeDestroyed;
163
    final ImmutableSetMultimap<WStatement, Element> writesAndReads;
164
    final ImmutableSet<WStatement> activeWrites;
165
    final ImmutableSet<WStatement> allWrites;
166

167
    public VState(boolean mightBeUninitialized, boolean mightBeDestroyed, ImmutableSetMultimap<WStatement, Element> writesAndReads, ImmutableSet<WStatement> activeWrites, ImmutableSet<WStatement> allWrites) {
1✔
168
        this.mightBeUninitialized = mightBeUninitialized;
1✔
169
        this.mightBeDestroyed = mightBeDestroyed;
1✔
170
        this.writesAndReads = writesAndReads;
1✔
171
        this.activeWrites = activeWrites;
1✔
172
        this.allWrites = allWrites;
1✔
173
    }
1✔
174

175
    public VState addWrite(WStatement s) {
176
        ImmutableSet.Builder<WStatement> wr = ImmutableSet.builder();
1✔
177
        wr.addAll(allWrites);
1✔
178
        wr.add(s);
1✔
179
        return new VState(false, false, writesAndReads, ImmutableSet.of(s), wr.build());
1✔
180
    }
181

182
    public VState addRead(Element r) {
183
        ImmutableSetMultimap.Builder<WStatement, Element> builder = ImmutableSetMultimap.builder();
1✔
184
        builder.putAll(writesAndReads);
1✔
185
        for (WStatement s : this.activeWrites) {
1✔
186
            builder.put(s, r);
1✔
187
        }
1✔
188
        return new VState(mightBeUninitialized, mightBeDestroyed, builder.build(), activeWrites, allWrites);
1✔
189
    }
190

191
    public VState merge(VState other) {
192
        return new VState(mightBeUninitialized || other.mightBeUninitialized,
1✔
193
                mightBeDestroyed || other.mightBeDestroyed,
194
                Utils.mergeMultiMaps(writesAndReads, other.writesAndReads),
1✔
195
                Utils.mergeSets(activeWrites, other.activeWrites),
1✔
196
                Utils.mergeSets(allWrites, other.allWrites));
1✔
197
    }
198

199
    @Override
200
    public int hashCode() {
201
        final int prime = 31;
×
202
        int result = 1;
×
203
        result = prime * result + activeWrites.hashCode();
×
204
        result = prime * result + allWrites.hashCode();
×
205
        result = prime * result + (mightBeUninitialized ? 1231 : 1237);
×
206
        result = prime * result + (mightBeDestroyed ? 1231 : 1237);
×
207
        result = prime * result + writesAndReads.hashCode();
×
208
        return result;
×
209
    }
210

211
    @Override
212
    public boolean equals(@Nullable Object obj) {
213
        if (this == obj)
1✔
214
            return true;
1✔
215
        if (obj == null)
1✔
216
            return false;
×
217
        if (getClass() != obj.getClass())
1✔
218
            return false;
×
219
        VState other = (VState) obj;
1✔
220
        if (!activeWrites.equals(other.activeWrites))
1✔
221
            return false;
1✔
222
        if (!allWrites.equals(other.allWrites))
1✔
223
            return false;
1✔
224
        if (mightBeUninitialized != other.mightBeUninitialized)
1✔
225
            return false;
1✔
226
        if (mightBeDestroyed != other.mightBeDestroyed)
1✔
227
            return false;
1✔
228
        return writesAndReads.equals(other.writesAndReads);
1✔
229
    }
230

231
    @Override
232
    public String toString() {
233
        StringBuilder sb = new StringBuilder("VState [ " + this.mightBeUninitialized + ", " + this.mightBeDestroyed + ", ");
×
234
        for (Entry<WStatement, Element> e : this.writesAndReads.entries()) {
×
235
            sb.append("\n\t\t");
×
236
            sb.append(e.getKey().attrSource().getLine()).append(" -> ").append(e.getValue().attrSource().getLine());
×
237
        }
×
238
        sb.append("]");
×
239
        return sb.toString();
×
240
    }
241

242

243
}
244

245

246
public class DataflowAnomalyAnalysis extends ForwardMethod<VarStates, AstElementWithBody> {
247

248

249
    private final boolean jassCode;
250

251
    public DataflowAnomalyAnalysis(boolean jassCode) {
1✔
252
        this.jassCode = jassCode;
1✔
253
    }
1✔
254

255
    @Override
256
    VarStates calculate(WStatement s, VarStates incoming) {
257
        if (s instanceof StartFunctionStatement) {
1✔
258
            // initially all vars are uninitialized
259
            final Set<LocalVarDef> r = Sets.newHashSet();
1✔
260
            collectLocalVars(r, getFuncDef());
1✔
261
            return VarStates.initial(r);
1✔
262
        }
263

264

265
        if (s instanceof CompoundStatement) {
1✔
266
            // for a compound statement check only the expressions in the statement
267
            for (int i = 0; i < s.size(); i++) {
1✔
268
                if (s.get(i) instanceof Expr) {
1✔
269
                    Expr expr = (Expr) s.get(i);
1✔
270
                    incoming = handleExprInCompound(incoming, expr);
1✔
271
                }
272
            }
273
            if (s instanceof SwitchStmt) {
1✔
274
                SwitchStmt swi = (SwitchStmt) s;
1✔
275
                // switch statement must be handled separately, because expressions are not direct children:
276
                for (SwitchCase switchCase : swi.getCases()) {
1✔
277
                    for (Expr switchCaseExpr : switchCase.getExpressions()) {
1✔
278
                        incoming = handleExprInCompound(incoming, switchCaseExpr);
1✔
279
                    }
1✔
280
                }
1✔
281
            }
1✔
282
        } else {
283
            checkIfVarsInitialized(s, incoming);
1✔
284
            for (NameDef v : s.attrReadVariables()) {
1✔
285
                if (isLocalVarDef(v)) {
1✔
286
                    incoming = incoming.addRead((LocalVarDef) v, s);
1✔
287
                }
288
            }
1✔
289
            if (incoming.thisDestroyed) {
1✔
290
                checkNoAccessToThis(s);
1✔
291
            }
292
        }
293

294
        if (s instanceof ExprDestroy) {
1✔
295
            ExprDestroy destr = (ExprDestroy) s;
1✔
296
            if (destr.getDestroyedObj() instanceof ExprVarAccess) {
1✔
297
                ExprVarAccess destroyed = (ExprVarAccess) destr.getDestroyedObj();
1✔
298
                NameDef destroyedVar = destroyed.attrNameDef();
1✔
299
                if (isLocalVarDef(destroyedVar)) {
1✔
300
                    return incoming.addDestroy((LocalVarDef) destroyedVar);
1✔
301
                }
302
            } else if (destr.getDestroyedObj() instanceof ExprThis) {
1✔
303
                return incoming.withThisDestroyed(true);
1✔
304
            }
305
        }
306

307
        NameDef n = getInitializedVar(s);
1✔
308
        if (n != null) {
1✔
309
//                        ImmutableSet<WStatement> unreadWrites = incoming.getUnreadWrites(n);
310
//                        for (WStatement wr : unreadWrites) {
311
//                                Element parent = s.getParent();
312
//                                if (isNested(wr, parent)) {
313
////                                        s.addError("Assigning to " + Utils.printElement(n) + " overrides previous assignment in line " + wr.attrSource().getLine());
314
//                                }
315
//                        }
316
            if (isLocalVarDef(n)) {
1✔
317
                LocalVarDef lv = (LocalVarDef) n;
1✔
318
                return incoming.addWrite(lv, s);
1✔
319
            }
320
        }
321
        return incoming;
1✔
322
    }
323

324
    /** checks that no expression in s uses 'this'; adds an error and returns true if it finds someting*/
325
    private boolean checkNoAccessToThis(Element s) {
326
        for (int i = 0; i < s.size(); i++) {
1✔
327
            if (checkNoAccessToThis(s.get(i))) {
1✔
328
                return true;
×
329
            }
330
        }
331
        if (s instanceof ExprThis) {
1✔
332
            reportError(s, "Cannot access 'this' because it might already have been destroyed.");
×
333
            return true;
×
334
        } if (s instanceof FunctionCall) {
1✔
335
            if (((FunctionCall) s).attrImplicitParameter() instanceof ExprThis) {
1✔
336
                reportError(s, "Cannot access 'this' because it might already have been destroyed.");
×
337
                return true;
×
338
            }
339
        } else if (s instanceof NameRef) {
1✔
340
            if (((NameRef) s).attrImplicitParameter() instanceof ExprThis) {
1✔
341
                reportError(s, "Cannot access 'this' because it might already have been destroyed.");
×
342
                return true;
×
343
            }
344
        }
345
        return false;
1✔
346
    }
347

348
    private VarStates handleExprInCompound(VarStates incoming, Expr expr) {
349
        checkIfVarsInitialized(expr, incoming);
1✔
350
        for (NameDef v : expr.attrReadVariables()) {
1✔
351
            if (isLocalVarDef(v)) {
1✔
352
                incoming = incoming.addRead((LocalVarDef) v, expr);
1✔
353
            }
354
        }
1✔
355
        return incoming;
1✔
356
    }
357

358
    private @Nullable NameDef getInitializedVar(WStatement s) {
359
        NameDef n = null;
1✔
360
        if (s instanceof StmtSet) {
1✔
361
            StmtSet s2 = (StmtSet) s;
1✔
362
            NameLink link = s2.getUpdatedExpr().attrNameLink();
1✔
363
            if (link != null) {
1✔
364
                n = link.getDef();
1✔
365
            }
366

367
        } else if (isLocalVarDef(s)) {
1✔
368
            LocalVarDef l = (LocalVarDef) s;
1✔
369
            if (l.getInitialExpr() instanceof Expr) {
1✔
370
                n = l;
1✔
371
            }
372
        } else if (s instanceof LoopStatementWithVarDef) {
1✔
373
            LoopStatementWithVarDef s2 = (LoopStatementWithVarDef) s;
1✔
374
            n = s2.getLoopVar();
1✔
375
        }
376
        return n;
1✔
377
    }
378

379
    private void collectLocalVars(Set<LocalVarDef> out, Element root) {
380
        ArrayDeque<Element> stack = new ArrayDeque<>();
1✔
381
        stack.push(root);
1✔
382

383
        while (!stack.isEmpty()) {
1✔
384
            Element e = stack.pop();
1✔
385

386
            if (isLocalVarDef(e)) {
1✔
387
                out.add((LocalVarDef) e);
1✔
388
            }
389

390
            // visit children left→right: push in reverse
391
            for (int i = e.size() - 1; i >= 0; i--) {
1✔
392
                Element c = e.get(i);
1✔
393
                if (!(c instanceof ExprClosure) && !(c instanceof ExprStatementsBlock)) {
1✔
394
                    stack.push(c);
1✔
395
                }
396
            }
397
        }
1✔
398
    }
1✔
399

400
    /**
401
     * checks if this is a local variable and not an array
402
     */
403
    private boolean isLocalVarDef(Element e) {
404
        if (e instanceof LocalVarDef) {
1✔
405
            LocalVarDef l = (LocalVarDef) e;
1✔
406
            return !l.attrTyp().isArray();
1✔
407
        }
408
        return false;
1✔
409
    }
410

411
    private void checkIfVarsInitialized(HasReadVariables s, VarStates incoming) {
412
        ImmutableList<NameDef> readVars = s.attrReadVariables();
1✔
413
        for (NameDef v : readVars) {
1✔
414
            if (v.attrTyp() instanceof WurstTypeArray) {
1✔
415
                continue;
1✔
416
            }
417

418
            if (incoming.uninitialized(v) || incoming.destroyed(v)) {
1✔
419
                Element readingExpr = findRead(s, v);
1✔
420
                if (readingExpr == null) {
1✔
421
                    readingExpr = s;
×
422
                }
423
                String error = "Variable " + v.getName();
1✔
424
                if (incoming.destroyed(v)) {
1✔
425
                    error += " may have been destroyed already";
1✔
426
                } else {
427
                    error += " may not have been initialized";
1✔
428
                }
429
                reportError(readingExpr, error);
1✔
430
            }
431
        }
1✔
432
    }
1✔
433

434
    private void reportError(Element location, String error) {
435
        if (jassCode) {
1✔
436
            location.addWarning(error);
1✔
437
        } else {
438
            location.addError(error);
×
439
        }
440
    }
1✔
441

442
    private @Nullable HasReadVariables findRead(Element e, NameDef v) {
443
        HasReadVariables result = null;
1✔
444
        if (e instanceof HasReadVariables) {
1✔
445
            HasReadVariables r = (HasReadVariables) e;
1✔
446
            if (!r.attrReadVariables().contains(v)) {
1✔
447
                return null;
1✔
448
            }
449
            result = r;
1✔
450
        }
451
        for (int i = 0; i < e.size(); i++) {
1✔
452
            HasReadVariables r = findRead(e.get(i), v);
1✔
453
            if (r != null) {
1✔
454
                if (isLeftOfStmtSet(r)) {
1✔
455
                    continue;
1✔
456
                }
457

458
                return r;
1✔
459
            }
460
        }
461
        return result;
1✔
462
    }
463

464
    private boolean isLeftOfStmtSet(HasReadVariables r) {
465
        Element parent = r.getParent();
1✔
466
        if (parent instanceof StmtSet) {
1✔
467
            StmtSet stmtSet = (StmtSet) parent;
1✔
468
            return stmtSet.getUpdatedExpr() == r;
1✔
469
        }
470
        return false;
1✔
471
    }
472

473
    @Override
474
    VarStates merge(Collection<VarStates> values) {
475
        Iterator<VarStates> it = values.iterator();
1✔
476
        VarStates r = it.next();
1✔
477
        while (it.hasNext()) {
1✔
478
            r = r.merge(it.next());
1✔
479
        }
480
        return r;
1✔
481
    }
482

483
    @Override
484
    String print(VarStates t) {
485
        if (t == null) {
×
486
            return "null";
×
487
        }
488
        return t.toString();
×
489
    }
490

491
    @Override
492
    void checkFinal(VarStates fin) {
493
        for (LocalVarDef var : fin.states.keySet()) {
1✔
494
            if (var.getName().startsWith("_")) {
1✔
495
                // ignore warning, if name starts with "_"
496
                continue;
1✔
497
            }
498
            for (WStatement ur : fin.getUnreadWrites(var)) {
1✔
499
                if (ur instanceof LoopStatement) {
1✔
500
                    // no warnings for loop variables
501
                    continue;
1✔
502
                }
503

504
                Element errorPos = ur;
1✔
505
                if (ur instanceof StmtSet) {
1✔
506
                    errorPos = ((StmtSet) ur).getUpdatedExpr();
1✔
507
                }
508
                errorPos.addWarning("The assignment to " + Utils.printElement(var) + " is never read.");
1✔
509
            }
1✔
510
        }
1✔
511
    }
1✔
512

513
    @Override
514
    public VarStates startValue() {
515
        return VarStates.initial(Collections.emptySet());
1✔
516
    }
517

518

519
}
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

© 2025 Coveralls, Inc