/* * 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; import com.google.common.collect.Maps; import org.sonar.java.cfg.CFG; 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.semantic.Symbol; import org.sonar.plugins.java.api.tree.Tree; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; public class ExplodedGraph { private Map<Node, Node> nodes = Maps.newHashMap(); /** * Returns node associated with given (programPoint,programState) pair. If no node for this pair exists, it is created. */ Node getNode(ProgramPoint programPoint, @Nullable ProgramState programState) { Node result = new Node(programPoint, programState); Node cached = nodes.get(result); if (cached != null) { cached.isNew = false; return cached; } result.isNew = true; nodes.put(result, result); return result; } public Map<Node, Node> getNodes() { return nodes; } public static class ProgramPoint { private int hashcode; final CFG.Block block; final int i; public ProgramPoint(CFG.Block block, int i) { this.block = block; this.i = i; } @Override public int hashCode() { if (hashcode == 0) { hashcode = block.id() * 31 + i; } return hashcode; } @Override public boolean equals(Object obj) { if (obj instanceof ProgramPoint) { ProgramPoint other = (ProgramPoint) obj; return this.block.id() == other.block.id() && this.i == other.i; } return false; } @Override public String toString() { String tree = ""; if (i < block.elements().size()) { tree = "" + block.elements().get(i).kind() + block.elements().get(i).firstToken().line(); } return "B" + block.id() + "." + i + " " + tree; } public Tree syntaxTree() { if (block.elements().isEmpty()) { return block.terminator(); } return block.elements().get(Math.min(i, block.elements().size() - 1)); } } public static class Node { boolean isNew; boolean exitPath = false; boolean happyPath = true; /** * Execution location. Currently only pre-statement, but tomorrow we might add post-statement. */ public final ProgramPoint programPoint; @Nullable public final ProgramState programState; private final List<Node> parents; private final List<LearnedConstraint> learnedConstraints; private final List<LearnedValue> learnedSymbols; public Node(ProgramPoint programPoint, @Nullable ProgramState programState) { this.programPoint = programPoint; this.programState = programState; learnedConstraints = new ArrayList<>(); learnedSymbols = new ArrayList<>(); parents = new ArrayList<>(); } public void setParent(@Nullable Node parent) { if (parent != null) { if (parents.isEmpty()) { programState.constraints.forEach((sv, c) -> { if (parent.programState.getConstraint(sv) != c) { addConstraint(sv, c); } }); programState.values.forEach((s, sv) -> { if (parent.programState.getValue(s) != sv) { learnedSymbols.add(new LearnedValue(sv, s)); } }); } parents.add(parent); } } private void addConstraint(SymbolicValue sv, @Nullable Constraint constraint) { // FIXME : this might end up adding twice the same SV in learned constraints. Safe because of find first in SECheck.flows if (sv instanceof BinarySymbolicValue) { BinarySymbolicValue binarySymbolicValue = (BinarySymbolicValue) sv; addConstraint(binarySymbolicValue.getLeftOp(), null); addConstraint(binarySymbolicValue.getRightOp(), null); } learnedConstraints.add(new LearnedConstraint(sv, constraint)); } public void addParent(Node node) { parents.add(node); } @Nullable public Node parent() { return parents.isEmpty() ? null : parents.get(0); } public List<Node> getParents() { return parents; } public List<LearnedConstraint> getLearnedConstraints() { return learnedConstraints; } public List<LearnedValue> getLearnedSymbols() { return learnedSymbols; } public static class LearnedConstraint { final SymbolicValue sv; @Nullable final Constraint constraint; public LearnedConstraint(SymbolicValue sv, @Nullable Constraint constraint) { this.sv = sv; this.constraint = constraint; } public SymbolicValue getSv() { return sv; } @CheckForNull public Constraint getConstraint() { return constraint; } @Override public String toString() { return sv + " - " + constraint; } } public static class LearnedValue { final SymbolicValue sv; final Symbol symbol; public LearnedValue(SymbolicValue sv, Symbol symbol) { this.sv = sv; this.symbol = symbol; } public Symbol getSymbol() { return symbol; } public SymbolicValue getSv() { return sv; } @Override public String toString() { return sv + " - " + symbol.name(); } } @Override public int hashCode() { return programPoint.hashCode() * 31 + (programState == null ? 0 : programState.hashCode()); } @Override public boolean equals(Object obj) { if (obj instanceof Node) { Node other = (Node) obj; return this.programPoint.equals(other.programPoint) && Objects.equals(this.programState, other.programState); } return false; } @Override public String toString() { return "B" + programPoint.block.id() + "." + programPoint.i + ": " + programState; } } }