/* * 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.symbolicvalues; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import org.sonar.java.se.ExplodedGraphWalker; import org.sonar.java.se.ProgramState; import org.sonar.java.se.constraint.BooleanConstraint; import org.sonar.java.se.constraint.Constraint; import org.sonar.java.se.constraint.ObjectConstraint; import org.sonar.java.se.constraint.TypedConstraint; import org.sonar.plugins.java.api.semantic.Type; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Objects; public class SymbolicValue { public static final SymbolicValue NULL_LITERAL = new SymbolicValue(0) { @Override public String toString() { return super.toString() + "_NULL"; } }; public static final SymbolicValue TRUE_LITERAL = new SymbolicValue(1) { @Override public String toString() { return super.toString() + "_TRUE"; } }; public static final SymbolicValue FALSE_LITERAL = new SymbolicValue(2) { @Override public String toString() { return super.toString() + "_FALSE"; } }; public static final List<SymbolicValue> PROTECTED_SYMBOLIC_VALUES = ImmutableList.of( NULL_LITERAL, TRUE_LITERAL, FALSE_LITERAL ); private final int id; public SymbolicValue(int id) { this.id = id; } public int id() { return id; } public static boolean isDisposable(SymbolicValue symbolicValue) { if (symbolicValue instanceof NotSymbolicValue) { NotSymbolicValue notSV = (NotSymbolicValue) symbolicValue; return !(notSV.operand instanceof RelationalSymbolicValue); } return !PROTECTED_SYMBOLIC_VALUES.contains(symbolicValue) && !(symbolicValue instanceof RelationalSymbolicValue); } public boolean references(SymbolicValue other) { return false; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SymbolicValue that = (SymbolicValue) o; return id == that.id; } @Override public int hashCode() { return id; } @Override public String toString() { return "SV_" + id; } public void computedFrom(List<SymbolicValue> symbolicValues) { // no op in general case } public List<ProgramState> setConstraint(ProgramState programState, ObjectConstraint nullConstraint) { Object data = programState.getConstraint(this); if (data instanceof ObjectConstraint) { ObjectConstraint nc = (ObjectConstraint) data; if (nc.isNull() ^ nullConstraint.isNull()) { // setting null where value is known to be non null or the contrary return ImmutableList.of(); } else if (hasAnyStatus(nc) && hasNoStatus(nullConstraint)) { return ImmutableList.of(programState); } } if (data instanceof BooleanConstraint) { return nullConstraint.isNull() ? ImmutableList.of() : ImmutableList.of(programState); } if (data == null || !data.equals(nullConstraint)) { return ImmutableList.of(programState.addConstraint(this, nullConstraint)); } return ImmutableList.of(programState); } private static boolean hasAnyStatus(ObjectConstraint constraint) { return !hasNoStatus(constraint); } private static boolean hasNoStatus(ObjectConstraint constraint) { return constraint.hasStatus(null); } public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { Object data = programState.getConstraint(this); // update program state only for a different constraint if (data instanceof BooleanConstraint && !data.equals(booleanConstraint)) { // setting null where value is known to be non null or the contrary return ImmutableList.of(); } if ((data == null || isNonNullConstraint(data)) && programState.canReach(this)) { // store constraint only if symbolic value can be reached by a symbol. return ImmutableList.of(programState.addConstraint(this, booleanConstraint)); } return ImmutableList.of(programState); } private static boolean isNonNullConstraint(Object data) { return data instanceof ObjectConstraint && !((ObjectConstraint) data).isNull(); } public ProgramState setSingleConstraint(ProgramState programState, ObjectConstraint nullConstraint) { final List<ProgramState> states = setConstraint(programState, nullConstraint); if (states.size() != 1) { throw new IllegalStateException("Only a single program state is expected at this location"); } return states.get(0); } public SymbolicValue wrappedValue() { return this; } public abstract static class UnarySymbolicValue extends SymbolicValue { protected SymbolicValue operand; public UnarySymbolicValue(int id) { super(id); } @Override public boolean references(SymbolicValue other) { return operand.equals(other) || operand.references(other); } @Override public void computedFrom(List<SymbolicValue> symbolicValues) { Preconditions.checkArgument(symbolicValues.size() == 1); this.operand = symbolicValues.get(0); } } public static class ExceptionalSymbolicValue extends SymbolicValue { @Nullable private final Type exceptionType; public ExceptionalSymbolicValue(int id, @Nullable Type exceptionType) { super(id); this.exceptionType = exceptionType; } @CheckForNull public Type exceptionType() { return exceptionType; } @Override public String toString() { return super.toString() + "_" + (exceptionType == null ? "!unknownException" : exceptionType.fullyQualifiedName()) + "!"; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } return Objects.equals(exceptionType, ((ExceptionalSymbolicValue) o).exceptionType); } } public static class NotSymbolicValue extends UnarySymbolicValue { public NotSymbolicValue(int id) { super(id); } @Override public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { return operand.setConstraint(programState, booleanConstraint.inverse()); } @Override public String toString() { return "!" + operand; } } public static class InstanceOfSymbolicValue extends UnarySymbolicValue { public InstanceOfSymbolicValue(int id) { super(id); } @Override public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { if (booleanConstraint.isTrue()) { Constraint constraint = programState.getConstraint(operand); if (constraint !=null && constraint.isNull()) { // irrealizable constraint : instance of true if operand is null return ImmutableList.of(); } // if instanceof is true then we know for sure that expression is not null. List<ProgramState> ps = operand.setConstraint(programState, ObjectConstraint.notNull()); if (ps.size() == 1 && ps.get(0).equals(programState)) { // FIXME we already know that operand is NOT NULL, so we add a different constraint to distinguish program state. Typed Constraint // should store the deduced type. return ImmutableList.of(programState.addConstraint(this, new TypedConstraint())); } return ps; } return ImmutableList.of(programState); } } public abstract static class BooleanExpressionSymbolicValue extends BinarySymbolicValue { protected BooleanExpressionSymbolicValue(int id) { super(id); } @Override public BooleanConstraint shouldNotInverse() { return BooleanConstraint.TRUE; } protected static void addStates(List<ProgramState> states, List<ProgramState> newStates) { if (states.size() > ExplodedGraphWalker.MAX_NESTED_BOOLEAN_STATES || newStates.size() > ExplodedGraphWalker.MAX_NESTED_BOOLEAN_STATES) { throw new ExplodedGraphWalker.TooManyNestedBooleanStatesException(); } states.addAll(newStates); } } public static class AndSymbolicValue extends BooleanExpressionSymbolicValue { public AndSymbolicValue(int id) { super(id); } @Override public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { final List<ProgramState> states = new ArrayList<>(); if (booleanConstraint.isFalse()) { List<ProgramState> falseFirstOp = leftOp.setConstraint(programState, BooleanConstraint.FALSE); for (ProgramState ps : falseFirstOp) { addStates(states, rightOp.setConstraint(ps, BooleanConstraint.TRUE)); addStates(states, rightOp.setConstraint(ps, BooleanConstraint.FALSE)); } } List<ProgramState> trueFirstOp = leftOp.setConstraint(programState, BooleanConstraint.TRUE); for (ProgramState ps : trueFirstOp) { addStates(states, rightOp.setConstraint(ps, booleanConstraint)); } return states; } @Override public String toString() { return leftOp + " & " + rightOp; } } public static class OrSymbolicValue extends BooleanExpressionSymbolicValue { public OrSymbolicValue(int id) { super(id); } @Override public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { List<ProgramState> states = new ArrayList<>(); if (booleanConstraint.isTrue()) { List<ProgramState> trueFirstOp = leftOp.setConstraint(programState, BooleanConstraint.TRUE); for (ProgramState ps : trueFirstOp) { addStates(states, rightOp.setConstraint(ps, BooleanConstraint.TRUE)); addStates(states, rightOp.setConstraint(ps, BooleanConstraint.FALSE)); } } List<ProgramState> falseFirstOp = leftOp.setConstraint(programState, BooleanConstraint.FALSE); for (ProgramState ps : falseFirstOp) { addStates(states, rightOp.setConstraint(ps, booleanConstraint)); } return states; } @Override public String toString() { return leftOp + " | " + rightOp; } } public static class XorSymbolicValue extends BooleanExpressionSymbolicValue { public XorSymbolicValue(int id) { super(id); } @Override public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) { List<ProgramState> states = new ArrayList<>(); List<ProgramState> trueFirstOp = leftOp.setConstraint(programState, BooleanConstraint.TRUE); for (ProgramState ps : trueFirstOp) { addStates(states, rightOp.setConstraint(ps, booleanConstraint.inverse())); } List<ProgramState> falseFirstOp = leftOp.setConstraint(programState, BooleanConstraint.FALSE); for (ProgramState ps : falseFirstOp) { addStates(states, rightOp.setConstraint(ps, booleanConstraint)); } return states; } @Override public String toString() { return leftOp + " ^ " + rightOp; } } @CheckForNull public BinaryRelation binaryRelation() { return null; } }