/*
* 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.se.checks;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.sonar.java.se.ExplodedGraph;
import org.sonar.java.se.constraint.Constraint;
import org.sonar.java.se.symbolicvalues.BinarySymbolicValue;
import org.sonar.java.se.symbolicvalues.SymbolicValue;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.Tree;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class FlowComputation {
private final Predicate<Constraint> addToFlow;
private final Predicate<Constraint> terminateTraversal;
private final List<JavaFileScannerContext.Location> flow = new ArrayList<>();
private final SymbolicValue symbolicValue;
private final Set<ExplodedGraph.Node> visited = new HashSet<>();
private FlowComputation(SymbolicValue symbolicValue, Predicate<Constraint> addToFlow, Predicate<Constraint> terminateTraversal) {
this.addToFlow = addToFlow;
this.terminateTraversal = terminateTraversal;
this.symbolicValue = symbolicValue;
}
public static List<JavaFileScannerContext.Location> flow(ExplodedGraph.Node currentNode, SymbolicValue currentVal) {
return flow(currentNode, currentVal, constraint -> true);
}
public static List<JavaFileScannerContext.Location> flow(ExplodedGraph.Node currentNode, SymbolicValue currentVal, Predicate<Constraint> addToFlow) {
return flow(currentNode, currentVal, addToFlow, c -> false);
}
public static List<JavaFileScannerContext.Location> flow(ExplodedGraph.Node currentNode, SymbolicValue currentVal, Predicate<Constraint> addToFlow,
Predicate<Constraint> terminateTraversal) {
FlowComputation flowComputation = new FlowComputation(currentVal, addToFlow, terminateTraversal);
Symbol trackSymbol = currentNode.programState.getLastEvaluated();
if (currentVal instanceof BinarySymbolicValue) {
Set<JavaFileScannerContext.Location> binSVFlow = flowComputation.flowFromBinarySV(currentNode, (BinarySymbolicValue) currentVal, trackSymbol);
flowComputation.flow.addAll(binSVFlow);
}
flowComputation.run(currentNode, trackSymbol);
return flowComputation.flow;
}
private Set<JavaFileScannerContext.Location> flowFromBinarySV(ExplodedGraph.Node currentNode, BinarySymbolicValue binarySV, Symbol trackSymbol) {
HashSet<JavaFileScannerContext.Location> binSVFlow = new HashSet<>();
FlowComputation left = fork(binarySV.getLeftOp());
left.run(currentNode.parent(), trackSymbol);
binSVFlow.addAll(left.flow);
FlowComputation right = fork(binarySV.getRightOp());
right.run(currentNode.parent(), trackSymbol);
binSVFlow.addAll(right.flow);
return binSVFlow;
}
private FlowComputation fork(SymbolicValue symbolicValue) {
return new FlowComputation(symbolicValue, addToFlow, terminateTraversal);
}
private void run(@Nullable final ExplodedGraph.Node currentNode, @Nullable final Symbol trackSymbol) {
if (currentNode == null || visited.contains(currentNode)) {
return;
}
visited.add(currentNode);
Symbol newTrackSymbol = trackSymbol;
if (currentNode.programPoint.syntaxTree() != null) {
newTrackSymbol = flowFromLearnedSymbols(currentNode, trackSymbol);
List<Constraint> learnedConstraints = flowFromLearnedConstraints(currentNode);
if (learnedConstraints.stream().anyMatch(terminateTraversal)) {
return;
}
}
for (ExplodedGraph.Node parent : currentNode.getParents()) {
run(parent, newTrackSymbol);
}
}
private List<Constraint> flowFromLearnedConstraints(ExplodedGraph.Node currentNode) {
List<Constraint> learnedConstraints = currentNode.getLearnedConstraints().stream()
.filter(lc -> lc.getSv().equals(symbolicValue))
.map(ExplodedGraph.Node.LearnedConstraint::getConstraint)
.collect(Collectors.toList());
if (learnedConstraints.stream().anyMatch(addToFlow)) {
flow.add(location(currentNode.parent()));
}
return learnedConstraints;
}
@Nullable
private Symbol flowFromLearnedSymbols(ExplodedGraph.Node currentNode, @Nullable Symbol trackSymbol) {
ExplodedGraph.Node parent = currentNode.parent();
if (trackSymbol == null || parent == null) {
return null;
}
Optional<ExplodedGraph.Node.LearnedValue> learnedValue = currentNode.getLearnedSymbols().stream()
.filter(lv -> lv.getSymbol().equals(trackSymbol))
.findFirst();
if (learnedValue.isPresent()) {
ExplodedGraph.Node.LearnedValue lv = learnedValue.get();
Constraint constraint = parent.programState.getConstraint(lv.getSv());
JavaFileScannerContext.Location location = constraint == null ? location(parent) :
location(parent, lv.getSymbol().name() + " is assigned " + constraint.valueAsString());
flow.add(location);
return parent.programState.getLastEvaluated();
}
return trackSymbol;
}
private static JavaFileScannerContext.Location location(ExplodedGraph.Node node) {
return location(node, "...");
}
private static JavaFileScannerContext.Location location(ExplodedGraph.Node node, String message) {
return new JavaFileScannerContext.Location(message, node.programPoint.syntaxTree());
}
static Set<List<JavaFileScannerContext.Location>> singleton(String msg, Tree tree) {
return ImmutableSet.of(ImmutableList.of(new JavaFileScannerContext.Location(msg, tree)));
}
}