Skip to content

Commit 42ab9c4

Browse files
vilchik-elenapynicolas
authored andcommitted
SONARJS-709 Use live variable analysis to avoid execution duplicated paths (#282)
* SONARJS-709 Extract LVA from DeadStoreCheck * SONARJS-709 Use live variable analysis to avoid execution duplicated paths * SONARJS-709 Add unit test for LVA * SONARJS-709 Clean program state with live IN symbols * Fix some quality flaws * Keep constraint for top expression stack element
1 parent a40bf5e commit 42ab9c4

File tree

18 files changed

+442
-208
lines changed

18 files changed

+442
-208
lines changed

its/ruling/src/test/expected/javascript-S2259.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
{
2+
'project:dojo-1.8.1/dijit/_editor/RichText.js.uncompressed.js':[
3+
2577,
4+
],
5+
'project:dojo-1.8.1/dijit/dijit-all.js.uncompressed.js':[
6+
14184,
7+
],
28
'project:dojo-1.8.1/dijit/tree/dndSource.js.uncompressed.js':[
39
440,
410
],
@@ -154,6 +160,9 @@
154160
'project:open-layers-2.12/OpenLayers/Map.js':[
155161
1877,
156162
],
163+
'project:open-layers-2.12/OpenLayers/Renderer/SVG.js':[
164+
338,
165+
],
157166
'project:open-layers-2.12/OpenLayers/Renderer/VML.js':[
158167
297,
159168
],
@@ -165,6 +174,7 @@
165174
7633,
166175
],
167176
'project:processing-1.3.6.js':[
177+
2072,
168178
2871,
169179
2879,
170180
2999,

its/ruling/src/test/expected/javascript-S2583.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
4811,
44
8964,
55
],
6+
'project:dojo-1.8.1/dijit/_TemplatedMixin.js.uncompressed.js':[
7+
202,
8+
],
69
'project:dojo-1.8.1/dijit/_base/focus.js.uncompressed.js':[
710
146,
811
],
@@ -13,6 +16,7 @@
1316
'project:dojo-1.8.1/dijit/dijit.js.uncompressed.js':[
1417
2523,
1518
4931,
19+
5406,
1620
5736,
1721
6770,
1822
],
@@ -214,6 +218,7 @@
214218
14477,
215219
14483,
216220
15461,
221+
16164,
217222
16520,
218223
],
219224
'project:dojo-1.8.1/dojox/collections/BinaryTree.js.uncompressed.js':[
@@ -284,6 +289,12 @@
284289
'project:dojo-1.8.1/dojox/fx/split.js.uncompressed.js':[
285290
520,
286291
],
292+
'project:dojo-1.8.1/dojox/gantt/GanttProjectControl.js.uncompressed.js':[
293+
735,
294+
750,
295+
794,
296+
812,
297+
],
287298
'project:dojo-1.8.1/dojox/gantt/GanttTaskControl.js.uncompressed.js':[
288299
1254,
289300
],
@@ -322,6 +333,7 @@
322333
'project:dojo-1.8.1/dojox/grid/DataGrid.js.uncompressed.js':[
323334
2885,
324335
6522,
336+
7812,
325337
12288,
326338
13871,
327339
],
@@ -580,26 +592,37 @@
580592
],
581593
'project:yui-3.7.3/simpleyui/simpleyui.js':[
582594
1056,
595+
5595,
583596
6597,
584597
12788,
598+
19860,
599+
],
600+
'project:yui-3.7.3/stylesheet/stylesheet.js':[
601+
136,
585602
],
586603
'project:yui-3.7.3/widget-modality/widget-modality.js':[
587604
518,
588605
],
589606
'project:yui-3.7.3/yui-base/yui-base.js':[
590607
1056,
608+
5595,
591609
],
592610
'project:yui-3.7.3/yui-core/yui-core.js':[
593611
1056,
594612
],
613+
'project:yui-3.7.3/yui-log/yui-log.js':[
614+
54,
615+
],
595616
'project:yui-3.7.3/yui-nodejs/yui-nodejs.js':[
596617
1056,
618+
4516,
597619
6477,
598620
6719,
599621
6754,
600622
],
601623
'project:yui-3.7.3/yui/yui.js':[
602624
1056,
625+
5595,
603626
7476,
604627
7718,
605628
7753,

javascript-checks/src/main/java/org/sonar/javascript/checks/DeadStoreCheck.java

Lines changed: 29 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,14 @@
1919
*/
2020
package org.sonar.javascript.checks;
2121

22-
import com.google.common.collect.HashMultimap;
2322
import com.google.common.collect.Lists;
24-
import com.google.common.collect.SetMultimap;
25-
import java.util.ArrayDeque;
26-
import java.util.Deque;
27-
import java.util.HashMap;
28-
import java.util.HashSet;
29-
import java.util.Map;
3023
import java.util.Set;
31-
import javax.annotation.CheckForNull;
32-
import javax.annotation.Nullable;
3324
import org.sonar.check.Priority;
3425
import org.sonar.check.Rule;
3526
import org.sonar.javascript.cfg.CfgBlock;
3627
import org.sonar.javascript.cfg.ControlFlowGraph;
28+
import org.sonar.javascript.se.LiveVariableAnalysis;
29+
import org.sonar.javascript.se.LiveVariableAnalysis.Usages;
3730
import org.sonar.javascript.tree.symbols.Scope;
3831
import org.sonar.plugins.javascript.api.symbols.Symbol;
3932
import org.sonar.plugins.javascript.api.symbols.Usage;
@@ -43,14 +36,15 @@
4336
import org.sonar.plugins.javascript.api.tree.declaration.FunctionTree;
4437
import org.sonar.plugins.javascript.api.tree.declaration.MethodDeclarationTree;
4538
import org.sonar.plugins.javascript.api.tree.expression.ArrowFunctionTree;
46-
import org.sonar.plugins.javascript.api.tree.expression.AssignmentExpressionTree;
4739
import org.sonar.plugins.javascript.api.tree.expression.FunctionExpressionTree;
48-
import org.sonar.plugins.javascript.api.tree.expression.IdentifierTree;
4940
import org.sonar.plugins.javascript.api.tree.statement.BlockTree;
5041
import org.sonar.plugins.javascript.api.visitors.DoubleDispatchVisitorCheck;
5142
import org.sonar.squidbridge.annotations.ActivatedByDefault;
5243
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
5344

45+
import static org.sonar.javascript.se.LiveVariableAnalysis.isRead;
46+
import static org.sonar.javascript.se.LiveVariableAnalysis.isWrite;
47+
5448
@Rule(
5549
key = "S1854",
5650
name = "Dead Stores should be removed",
@@ -97,215 +91,50 @@ private void checkFunction(FunctionTree functionTree) {
9791
// Identifying dead stores is basically "live variable analysis".
9892
// See https://en.wikipedia.org/wiki/Live_variable_analysis
9993
private void checkCFG(ControlFlowGraph cfg, FunctionTree functionTree) {
100-
Usages usages = new Usages(functionTree);
101-
Set<BlockLiveness> livenesses = new HashSet<>();
102-
buildUsagesAndLivenesses(cfg, usages, livenesses);
94+
Scope scope = getContext().getSymbolModel().getScope(functionTree);
95+
LiveVariableAnalysis lva = LiveVariableAnalysis.create(cfg, scope);
96+
Usages usages = lva.getUsages();
10397

104-
for (BlockLiveness blockLiveness : livenesses) {
105-
checkBlock(blockLiveness, usages);
106-
}
98+
for (CfgBlock cfgBlock : cfg.blocks()) {
99+
Set<Symbol> live = lva.getLiveOutSymbols(cfgBlock);
107100

108-
for (Symbol symbol : usages.neverReadSymbols()) {
109-
for (Usage usage : symbol.usages()) {
110-
if (isWrite(usage)) {
111-
addIssue(usage.identifierTree(), symbol);
101+
for (Tree element : Lists.reverse(cfgBlock.elements())) {
102+
Usage usage = usages.getUsage(element);
103+
if (usage != null) {
104+
checkUsage(usage, live, usages);
112105
}
113106
}
114107
}
115-
}
116-
117-
private void checkBlock(BlockLiveness blockLiveness, Usages usages) {
118-
Set<Symbol> live = blockLiveness.liveOut;
119-
120-
for (Tree element : Lists.reverse(blockLiveness.block.elements())) {
121-
Usage usage = usages.getUsage(element);
122108

123-
if (isWrite(usage)) {
124-
125-
Symbol symbol = symbol(usage);
126-
if (!live.contains(symbol) && !usages.hasUsagesInNestedFunctions(symbol) && !usages.isNeverRead(symbol)) {
127-
addIssue(usage.identifierTree(), symbol);
128-
}
129-
live.remove(symbol);
130-
131-
} else if (isRead(usage)) {
132-
live.add(symbol(usage));
133-
}
134-
}
109+
raiseIssuesForNeverReadSymbols(usages);
135110
}
136111

137-
private void addIssue(Tree element, Symbol symbol) {
138-
addIssue(element, String.format(MESSAGE, symbol.name()));
139-
}
112+
private void checkUsage(Usage usage, Set<Symbol> liveSymbols, Usages usages) {
113+
Symbol symbol = usage.symbol();
140114

141-
private void buildUsagesAndLivenesses(ControlFlowGraph cfg, Usages usages, Set<BlockLiveness> livenesses) {
142-
Map<CfgBlock, BlockLiveness> livenessPerBlock = new HashMap<>();
143-
for (CfgBlock block : cfg.blocks()) {
144-
BlockLiveness blockLiveness = new BlockLiveness(block, usages);
145-
livenessPerBlock.put(block, blockLiveness);
146-
livenesses.add(blockLiveness);
147-
}
148-
149-
// To compute the set of variables which are live OUT of a block, we need to have the
150-
// set of variables which are live IN its successors.
151-
// As the CFG may contain cycles between blocks (that's the case for loops), we use a queue
152-
// to keep track of blocks which may need to be updated.
153-
// See "worklist algorithm" in http://www.cs.cornell.edu/courses/cs4120/2013fa/lectures/lec26-fa13.pdf
154-
Deque<CfgBlock> queue = new ArrayDeque(cfg.blocks());
155-
while (!queue.isEmpty()) {
156-
CfgBlock block = queue.pop();
157-
BlockLiveness blockLiveness = livenessPerBlock.get(block);
158-
boolean changed = blockLiveness.updateLiveInAndOut(cfg, livenessPerBlock);
159-
if (changed) {
160-
for (CfgBlock predecessor : block.predecessors()) {
161-
queue.push(predecessor);
162-
}
115+
if (isWrite(usage)) {
116+
if (!liveSymbols.contains(symbol) && !usages.hasUsagesInNestedFunctions(symbol) && !usages.neverReadSymbols().contains(symbol)) {
117+
addIssue(usage.identifierTree(), symbol);
163118
}
164-
}
165-
}
166-
167-
@CheckForNull
168-
public static Symbol symbol(Usage usage) {
169-
return usage.identifierTree().symbol();
170-
}
171-
172-
private static boolean isRead(Usage usage) {
173-
if (usage == null) {
174-
return false;
175-
}
176-
return usage.kind() == Usage.Kind.READ || usage.kind() == Usage.Kind.READ_WRITE;
177-
}
119+
liveSymbols.remove(symbol);
178120

179-
private static boolean isWrite(Usage usage) {
180-
if (usage == null) {
181-
return false;
121+
} else if (isRead(usage)) {
122+
liveSymbols.add(symbol);
182123
}
183-
return usage.kind() == Usage.Kind.WRITE || usage.kind() == Usage.Kind.DECLARATION_WRITE;
184124
}
185125

186-
private static class BlockLiveness {
187-
188-
private final CfgBlock block;
189-
private final Usages usages;
190-
private final Set<Symbol> liveOut = new HashSet<>();
191-
private Set<Symbol> liveIn = new HashSet<>();
192-
193-
public BlockLiveness(CfgBlock block, Usages usages) {
194-
this.usages = usages;
195-
this.block = block;
196-
197-
for (Tree element : block.elements()) {
198-
if (element instanceof IdentifierTree) {
199-
usages.add((IdentifierTree) element);
200-
}
201-
if (element.is(Kind.ASSIGNMENT)) {
202-
usages.addAssignment((AssignmentExpressionTree) element);
203-
}
204-
}
205-
}
206-
207-
public boolean updateLiveInAndOut(ControlFlowGraph cfg, Map<CfgBlock, BlockLiveness> livenessPerBlock) {
208-
liveOut.clear();
209-
for (CfgBlock successor : block.successors()) {
210-
liveOut.addAll(livenessPerBlock.get(successor).liveIn);
211-
}
212-
213-
Set<Symbol> oldIn = liveIn;
214-
liveIn = new HashSet<>(liveOut);
215-
216-
for (Tree element : Lists.reverse(block.elements())) {
217-
Usage usage = usages.getUsage(element);
126+
private void raiseIssuesForNeverReadSymbols(Usages usages) {
127+
for (Symbol symbol : usages.neverReadSymbols()) {
128+
for (Usage usage : symbol.usages()) {
218129
if (isWrite(usage)) {
219-
liveIn.remove(symbol(usage));
220-
} else if (isRead(usage)) {
221-
liveIn.add(symbol(usage));
130+
addIssue(usage.identifierTree(), symbol);
222131
}
223132
}
224-
225-
return !oldIn.equals(liveIn);
226133
}
227134
}
228135

229-
private class Usages {
230-
231-
private final Scope functionScope;
232-
private final Set<Symbol> symbols = new HashSet<>();
233-
private final Map<IdentifierTree, Usage> localVariableUsages = new HashMap<>();
234-
private final Set<Symbol> neverReadSymbols = new HashSet<>();
235-
private final SetMultimap<Symbol, Usage> usagesInCFG = HashMultimap.create();
236-
private final Set<Tree> assignmentVariables = new HashSet<>();
237-
238-
public Usages(FunctionTree function) {
239-
this.functionScope = getContext().getSymbolModel().getScope(function);
240-
}
241-
242-
public Usage getUsage(Tree element) {
243-
if (assignmentVariables.contains(element)) {
244-
return null;
245-
}
246-
if (element.is(Kind.ASSIGNMENT)) {
247-
return localVariableUsages.get(((AssignmentExpressionTree) element).variable());
248-
}
249-
return localVariableUsages.get(element);
250-
}
251-
252-
public boolean hasUsagesInNestedFunctions(Symbol symbol) {
253-
return usagesInCFG.get(symbol).size() != symbol.usages().size();
254-
}
255-
256-
@CheckForNull
257-
public Usage add(IdentifierTree identifier) {
258-
addSymbol(identifier.symbol());
259-
Usage usage = localVariableUsages.get(identifier);
260-
if (usage != null) {
261-
usagesInCFG.put(identifier.symbol(), usage);
262-
}
263-
return usage;
264-
}
265-
266-
private void addSymbol(@Nullable Symbol symbol) {
267-
if (symbol == null || symbols.contains(symbol)) {
268-
return;
269-
}
270-
271-
symbols.add(symbol);
272-
273-
if (isLocalVariable(symbol)) {
274-
boolean readAtLeastOnce = false;
275-
for (Usage usage : symbol.usages()) {
276-
localVariableUsages.put(usage.identifierTree(), usage);
277-
if (isRead(usage)) {
278-
readAtLeastOnce = true;
279-
}
280-
}
281-
if (!readAtLeastOnce) {
282-
neverReadSymbols.add(symbol);
283-
}
284-
}
285-
}
286-
287-
private boolean isLocalVariable(Symbol symbol) {
288-
Scope scope = symbol.scope();
289-
while (!scope.isGlobal()) {
290-
if (scope.equals(functionScope)) {
291-
return true;
292-
}
293-
scope = scope.outer();
294-
}
295-
return false;
296-
}
297-
298-
public Set<Symbol> neverReadSymbols() {
299-
return neverReadSymbols;
300-
}
301-
302-
public boolean isNeverRead(Symbol symbol) {
303-
return neverReadSymbols.contains(symbol);
304-
}
305-
306-
public void addAssignment(AssignmentExpressionTree tree) {
307-
assignmentVariables.add(tree.variable());
308-
}
136+
private void addIssue(Tree element, Symbol symbol) {
137+
addIssue(element, String.format(MESSAGE, symbol.name()));
309138
}
310139

311140
}

0 commit comments

Comments
 (0)