|
19 | 19 | */ |
20 | 20 | package org.sonar.javascript.checks; |
21 | 21 |
|
22 | | -import com.google.common.collect.HashMultimap; |
23 | 22 | 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; |
30 | 23 | import java.util.Set; |
31 | | -import javax.annotation.CheckForNull; |
32 | | -import javax.annotation.Nullable; |
33 | 24 | import org.sonar.check.Priority; |
34 | 25 | import org.sonar.check.Rule; |
35 | 26 | import org.sonar.javascript.cfg.CfgBlock; |
36 | 27 | import org.sonar.javascript.cfg.ControlFlowGraph; |
| 28 | +import org.sonar.javascript.se.LiveVariableAnalysis; |
| 29 | +import org.sonar.javascript.se.LiveVariableAnalysis.Usages; |
37 | 30 | import org.sonar.javascript.tree.symbols.Scope; |
38 | 31 | import org.sonar.plugins.javascript.api.symbols.Symbol; |
39 | 32 | import org.sonar.plugins.javascript.api.symbols.Usage; |
|
43 | 36 | import org.sonar.plugins.javascript.api.tree.declaration.FunctionTree; |
44 | 37 | import org.sonar.plugins.javascript.api.tree.declaration.MethodDeclarationTree; |
45 | 38 | import org.sonar.plugins.javascript.api.tree.expression.ArrowFunctionTree; |
46 | | -import org.sonar.plugins.javascript.api.tree.expression.AssignmentExpressionTree; |
47 | 39 | import org.sonar.plugins.javascript.api.tree.expression.FunctionExpressionTree; |
48 | | -import org.sonar.plugins.javascript.api.tree.expression.IdentifierTree; |
49 | 40 | import org.sonar.plugins.javascript.api.tree.statement.BlockTree; |
50 | 41 | import org.sonar.plugins.javascript.api.visitors.DoubleDispatchVisitorCheck; |
51 | 42 | import org.sonar.squidbridge.annotations.ActivatedByDefault; |
52 | 43 | import org.sonar.squidbridge.annotations.SqaleConstantRemediation; |
53 | 44 |
|
| 45 | +import static org.sonar.javascript.se.LiveVariableAnalysis.isRead; |
| 46 | +import static org.sonar.javascript.se.LiveVariableAnalysis.isWrite; |
| 47 | + |
54 | 48 | @Rule( |
55 | 49 | key = "S1854", |
56 | 50 | name = "Dead Stores should be removed", |
@@ -97,215 +91,50 @@ private void checkFunction(FunctionTree functionTree) { |
97 | 91 | // Identifying dead stores is basically "live variable analysis". |
98 | 92 | // See https://en.wikipedia.org/wiki/Live_variable_analysis |
99 | 93 | 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(); |
103 | 97 |
|
104 | | - for (BlockLiveness blockLiveness : livenesses) { |
105 | | - checkBlock(blockLiveness, usages); |
106 | | - } |
| 98 | + for (CfgBlock cfgBlock : cfg.blocks()) { |
| 99 | + Set<Symbol> live = lva.getLiveOutSymbols(cfgBlock); |
107 | 100 |
|
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); |
112 | 105 | } |
113 | 106 | } |
114 | 107 | } |
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); |
122 | 108 |
|
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); |
135 | 110 | } |
136 | 111 |
|
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(); |
140 | 114 |
|
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); |
163 | 118 | } |
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); |
178 | 120 |
|
179 | | - private static boolean isWrite(Usage usage) { |
180 | | - if (usage == null) { |
181 | | - return false; |
| 121 | + } else if (isRead(usage)) { |
| 122 | + liveSymbols.add(symbol); |
182 | 123 | } |
183 | | - return usage.kind() == Usage.Kind.WRITE || usage.kind() == Usage.Kind.DECLARATION_WRITE; |
184 | 124 | } |
185 | 125 |
|
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()) { |
218 | 129 | if (isWrite(usage)) { |
219 | | - liveIn.remove(symbol(usage)); |
220 | | - } else if (isRead(usage)) { |
221 | | - liveIn.add(symbol(usage)); |
| 130 | + addIssue(usage.identifierTree(), symbol); |
222 | 131 | } |
223 | 132 | } |
224 | | - |
225 | | - return !oldIn.equals(liveIn); |
226 | 133 | } |
227 | 134 | } |
228 | 135 |
|
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())); |
309 | 138 | } |
310 | 139 |
|
311 | 140 | } |
0 commit comments