/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.cfg; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodReferenceTree; import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.Tree.Kind; import org.sonar.plugins.java.api.tree.VariableTree; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.Set; public class LiveVariables { private final CFG cfg; private final Map<CFG.Block, Set<Symbol>> out = new HashMap<>(); private final Map<CFG.Block, Set<Symbol>> in = new HashMap<>(); private final boolean includeFields; private LiveVariables(CFG cfg, boolean includeFields) { this.cfg = cfg; this.includeFields = includeFields; } public Set<Symbol> getOut(CFG.Block block) { return out.get(block); } public Set<Symbol> getIn(CFG.Block block) { return in.get(block); } /** * Returns LiveVariables object with information concerning local variables and parameters */ public static LiveVariables analyze(CFG cfg) { return analyze(cfg, false); } /** * Returns LiveVariables object with information concerning local variables, parameters and fields */ public static LiveVariables analyzeWithFields(CFG cfg) { return analyze(cfg, true); } private static LiveVariables analyze(CFG cfg, boolean includeFields) { LiveVariables liveVariables = new LiveVariables(cfg, includeFields); // Generate kill/gen for each block in isolation Map<CFG.Block, Set<Symbol>> kill = new HashMap<>(); Map<CFG.Block, Set<Symbol>> gen = new HashMap<>(); for (CFG.Block block : liveVariables.cfg.reversedBlocks()) { Set<Symbol> blockKill = new HashSet<>(); Set<Symbol> blockGen = new HashSet<>(); liveVariables.processBlockElements(block, blockKill, blockGen); kill.put(block, blockKill); gen.put(block, blockGen); } liveVariables.analyzeCFG(liveVariables.in, kill, gen); // out of exit block are empty by definition. if (!liveVariables.out.get(liveVariables.cfg.reversedBlocks().get(0)).isEmpty()) { throw new IllegalStateException("Out of exit block should be empty"); } // Make things immutable. for (Map.Entry<CFG.Block, Set<Symbol>> blockSetEntry : liveVariables.out.entrySet()) { blockSetEntry.setValue(ImmutableSet.copyOf(blockSetEntry.getValue())); } return liveVariables; } private void analyzeCFG(Map<CFG.Block, Set<Symbol>> in, Map<CFG.Block, Set<Symbol>> kill, Map<CFG.Block, Set<Symbol>> gen) { Deque<CFG.Block> workList = new LinkedList<>(); workList.addAll(cfg.reversedBlocks()); while (!workList.isEmpty()) { CFG.Block block = workList.removeFirst(); Set<Symbol> blockOut = out.get(block); if (blockOut == null) { blockOut = new HashSet<>(); out.put(block, blockOut); } block.successors().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); block.exceptions().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); // in = gen and (out - kill) Set<Symbol> newIn = new HashSet<>(gen.get(block)); newIn.addAll(Sets.difference(blockOut, kill.get(block))); if (newIn.equals(in.get(block))) { continue; } in.put(block, newIn); block.predecessors().forEach(workList::addLast); } } private void processBlockElements(CFG.Block block, Set<Symbol> blockKill, Set<Symbol> blockGen) { // process elements from bottom to top Set<Tree> assignmentLHS = new HashSet<>(); for (Tree element : Lists.reverse(block.elements())) { switch (element.kind()) { case ASSIGNMENT: processAssignment((AssignmentExpressionTree) element, blockKill, blockGen, assignmentLHS); break; case IDENTIFIER: processIdentifier((IdentifierTree) element, blockGen, assignmentLHS); break; case MEMBER_SELECT: processMemberSelect((MemberSelectExpressionTree) element, assignmentLHS, blockGen); break; case VARIABLE: blockKill.add(((VariableTree) element).symbol()); blockGen.remove(((VariableTree) element).symbol()); break; case LAMBDA_EXPRESSION: blockGen.addAll(getUsedVariables(((LambdaExpressionTree) element).body(), cfg.methodSymbol())); break; case METHOD_REFERENCE: blockGen.addAll(getUsedVariables(((MethodReferenceTree) element).expression(), cfg.methodSymbol())); break; case NEW_CLASS: blockGen.addAll(getUsedVariables(((NewClassTree) element).classBody(), cfg.methodSymbol())); break; default: // Ignore other kind of elements, no change of gen/kill } } } private void processIdentifier(IdentifierTree element, Set<Symbol> blockGen, Set<Tree> assignmentLHS) { Symbol symbol = element.symbol(); if (!assignmentLHS.contains(element) && includeSymbol(symbol)) { blockGen.add(symbol); } } private void processMemberSelect(MemberSelectExpressionTree element, Set<Tree> assignmentLHS, Set<Symbol> blockGen) { Symbol symbol; if (!assignmentLHS.contains(element) && includeFields) { symbol = getField(element); if (symbol != null) { blockGen.add(symbol); } } } private void processAssignment(AssignmentExpressionTree element, Set<Symbol> blockKill, Set<Symbol> blockGen, Set<Tree> assignmentLHS) { Symbol symbol = null; ExpressionTree lhs = element.variable(); if (lhs.is(Kind.IDENTIFIER)) { symbol = ((IdentifierTree) lhs).symbol(); } else if (includeFields && lhs.is(Kind.MEMBER_SELECT)) { symbol = getField((MemberSelectExpressionTree) lhs); } if (symbol != null && includeSymbol(symbol)) { assignmentLHS.add(lhs); blockGen.remove(symbol); blockKill.add(symbol); } } private boolean includeSymbol(Symbol symbol) { return isLocalVariable(symbol) || (includeFields && isField(symbol)); } private static boolean isLocalVariable(Symbol symbol) { return symbol.owner().isMethodSymbol(); } private static boolean isField(Symbol symbol) { return symbol.owner().isTypeSymbol() && !"this".equals(symbol.name()) && symbol.isVariableSymbol(); } @CheckForNull private static Symbol getField(MemberSelectExpressionTree memberSelect) { Symbol symbol = memberSelect.identifier().symbol(); if (memberSelect.expression().is(Kind.IDENTIFIER)) { String objectName = ((IdentifierTree) memberSelect.expression()).name(); if (symbol.isStatic() || "this".equals(objectName)) { return symbol; } } else if (symbol.isStatic()) { return symbol; } return null; } private Set<Symbol> getUsedVariables(@Nullable Tree syntaxNode, Symbol.MethodSymbol owner) { if(syntaxNode == null) { return Collections.emptySet(); } VariableReadExtractor extractorFromClass = new VariableReadExtractor(owner, includeFields); syntaxNode.accept(extractorFromClass); return extractorFromClass.usedVariables(); } }