org.sonar.java.se.symbolicvalues.RelationalSymbolicValueTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.se.symbolicvalues.RelationalSymbolicValueTest.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2017 SonarSource SA
 * mailto:info 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.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.sonar.java.collections.PCollections;
import org.sonar.java.collections.PMap;
import org.sonar.java.model.InternalSyntaxToken;
import org.sonar.java.model.expression.BinaryExpressionTreeImpl;
import org.sonar.java.se.JavaCheckVerifier;
import org.sonar.java.se.ProgramState;
import org.sonar.java.se.checks.DivisionByZeroCheck;
import org.sonar.java.se.checks.NullDereferenceCheck;
import org.sonar.java.se.constraint.BooleanConstraint;
import org.sonar.java.se.constraint.Constraint;
import org.sonar.java.se.constraint.ConstraintManager;
import org.sonar.java.se.constraint.ObjectConstraint;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.Tree;

import javax.annotation.Nullable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.sonar.java.se.symbolicvalues.RelationState.FULFILLED;
import static org.sonar.java.se.symbolicvalues.RelationState.UNFULFILLED;
import static org.sonar.java.se.symbolicvalues.RelationalSymbolicValue.Kind.EQUAL;
import static org.sonar.java.se.symbolicvalues.RelationalSymbolicValue.Kind.METHOD_EQUALS;

public class RelationalSymbolicValueTest {

    ConstraintManager constraintManager = new ConstraintManager();
    SymbolicValue a = new SymbolicValue() {
        @Override
        public String toString() {
            return "SV_1";
        }
    };
    SymbolicValue b = new SymbolicValue() {
        @Override
        public String toString() {
            return "SV_2";
        }
    };
    SymbolicValue c = new SymbolicValue() {
        @Override
        public String toString() {
            return "SV_3";
        }
    };
    SymbolicValue d = new SymbolicValue();
    SymbolicValue e = new SymbolicValue();

    private final List<Tree.Kind> operators = Arrays.asList(Tree.Kind.EQUAL_TO, Tree.Kind.NOT_EQUAL_TO,
            Tree.Kind.GREATER_THAN, Tree.Kind.GREATER_THAN_OR_EQUAL_TO, Tree.Kind.LESS_THAN,
            Tree.Kind.LESS_THAN_OR_EQUAL_TO);

    @Test
    public void test_normalization() throws Exception {
        assertThat(relationalSV(Tree.Kind.EQUAL_TO, b, a)).hasToString("SV_1==SV_2");
        assertThat(relationalSV(Tree.Kind.NOT_EQUAL_TO, b, a)).hasToString("SV_1!=SV_2");
        assertThat(relationalSV(Tree.Kind.GREATER_THAN, b, a)).hasToString("SV_2<SV_1");
        assertThat(relationalSV(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, b, a)).hasToString("SV_1>=SV_2");
        assertThat(relationalSV(Tree.Kind.LESS_THAN, b, a)).hasToString("SV_1<SV_2");
        assertThat(relationalSV(Tree.Kind.LESS_THAN_OR_EQUAL_TO, b, a)).hasToString("SV_2>=SV_1");
    }

    private RelationalSymbolicValue relationalSV(Tree.Kind kind, SymbolicValue... computedFrom) {
        return (RelationalSymbolicValue) constraintManager
                .createBinarySymbolicValue(
                        new BinaryExpressionTreeImpl(kind, mock(ExpressionTree.class),
                                mock(InternalSyntaxToken.class), mock(ExpressionTree.class)),
                        Arrays.asList(computedFrom));
    }

    @Test
    public void test_same_operand() {
        assertThat(sameOperandResolution(Tree.Kind.EQUAL_TO)).isEqualTo(FULFILLED);
        RelationalSymbolicValue eq = new RelationalSymbolicValue(METHOD_EQUALS, a, a);
        assertThat(eq.resolveRelationState(Collections.emptySet())).isEqualTo(FULFILLED);
        assertThat(sameOperandResolution(Tree.Kind.LESS_THAN_OR_EQUAL_TO)).isEqualTo(FULFILLED);
        assertThat(sameOperandResolution(Tree.Kind.GREATER_THAN_OR_EQUAL_TO)).isEqualTo(FULFILLED);

        assertThat(sameOperandResolution(Tree.Kind.NOT_EQUAL_TO)).isEqualTo(UNFULFILLED);
        assertThat(eq.inverse().resolveRelationState(Collections.emptySet())).isEqualTo(UNFULFILLED);
        assertThat(sameOperandResolution(Tree.Kind.LESS_THAN)).isEqualTo(UNFULFILLED);
        assertThat(sameOperandResolution(Tree.Kind.GREATER_THAN)).isEqualTo(UNFULFILLED);
    }

    private RelationState sameOperandResolution(Tree.Kind kind) {
        return relationalSV(kind, a, a).resolveRelationState(Collections.emptySet());
    }

    @Test
    public void test_different_operand() throws Exception {
        RelationalSymbolicValue ab = new RelationalSymbolicValue(EQUAL, a, b);
        RelationalSymbolicValue bc = new RelationalSymbolicValue(EQUAL, b, c);
        assertThat(ab.differentOperand(bc)).isEqualTo(a);
        assertThat(bc.differentOperand(ab)).isEqualTo(c);
    }

    @Test
    public void test_direct_deduction() throws Exception {
        List<String> actual = new ArrayList<>();
        for (Tree.Kind operator : operators) {
            actual.addAll(resolveRelationStateForAllKinds(relationalSV(operator, b, a),
                    () -> relationToString(operator, a, b)));
            actual.addAll(resolveRelationStateForAllKinds(relationalSV(operator, a, b),
                    () -> relationToString(operator, b, a)));
        }
        RelationalSymbolicValue eqAB = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, a,
                b);
        RelationalSymbolicValue eqBA = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, b,
                a);
        Stream.of(eqAB, eqBA, eqAB.inverse(), eqBA.inverse())
                .forEach(rel -> actual.addAll(resolveRelationStateForAllKinds(rel, rel::toString)));
        List<String> expected = IOUtils.readLines(getClass().getResourceAsStream("/relations/direct.txt"));
        assertThat(actual).isEqualTo(expected);
    }

    private List<String> resolveRelationStateForAllKinds(RelationalSymbolicValue known,
            Supplier<String> knownAsString) {
        List<String> actual = new ArrayList<>();
        for (Tree.Kind operator : operators) {
            RelationalSymbolicValue test = relationalSV(operator, b, a);
            RelationState relationState = test.resolveRelationState(Collections.singleton(known));
            actual.add(String.format("given %s when %s -> %s", knownAsString.get(),
                    relationToString(operator, a, b), relationState));
        }
        RelationalSymbolicValue eq = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, a, b);
        Stream.of(eq, eq.inverse()).forEach(rel -> {
            RelationState relationState = rel.resolveRelationState(Collections.singleton(known));
            actual.add(String.format("given %s when %s -> %s", knownAsString.get(), rel, relationState));
        });
        return actual;
    }

    private ProgramState stateWithRelations(RelationalSymbolicValue... known) {
        ProgramState ps = ProgramState.EMPTY_STATE;
        for (RelationalSymbolicValue rel : known) {
            ps = Iterables.getOnlyElement(rel.setConstraint(ps, BooleanConstraint.TRUE));
        }
        return ps;
    }

    @Test
    public void test_conjuction_equal() throws Exception {
        RelationalSymbolicValue aLEb = relationalSV(Tree.Kind.LESS_THAN_OR_EQUAL_TO, a, b);
        RelationalSymbolicValue bLEa = relationalSV(Tree.Kind.LESS_THAN_OR_EQUAL_TO, b, a);
        RelationalSymbolicValue aEb = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        ProgramState state = Iterables
                .getOnlyElement(aEb.setConstraint(stateWithRelations(aLEb, bLEa), BooleanConstraint.TRUE));
        assertThat(state.getConstraint(aEb, BooleanConstraint.class)).isEqualTo(BooleanConstraint.TRUE);
    }

    @Test
    public void test_transitive_GE() throws Exception {
        RelationalSymbolicValue ab = relationalSV(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, a, b);
        RelationalSymbolicValue bc = relationalSV(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, b, c);
        RelationalSymbolicValue deduced = ab.deduceTransitiveOrSimplified(bc);
        assertThat(deduced).isEqualTo(relationalSV(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, a, c));
    }

    @Test
    public void test_transitive_method_equals() throws Exception {
        RelationalSymbolicValue equalAB = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        RelationalSymbolicValue methodEqualBC = new RelationalSymbolicValue(METHOD_EQUALS, b, c);
        RelationalSymbolicValue deduced = equalAB.deduceTransitiveOrSimplified(methodEqualBC);
        RelationalSymbolicValue expected = new RelationalSymbolicValue(METHOD_EQUALS, a, c);
        assertThat(deduced).isEqualTo(expected);
        deduced = methodEqualBC.deduceTransitiveOrSimplified(equalAB);
        assertThat(deduced).isEqualTo(expected);
    }

    @Test
    public void test_chained_transitivity() throws Exception {
        // create chain of relations in the form sv1 < sv2 < ... < sv45
        // and test if relation sv1 < sv45 is deduced
        int chainLength = 45;
        SymbolicValue[] sv = new SymbolicValue[chainLength];
        RelationalSymbolicValue[] given = new RelationalSymbolicValue[chainLength - 1];
        sv[0] = new SymbolicValue();
        for (int i = 1; i < chainLength; i++) {
            sv[i] = new SymbolicValue();
            given[i - 1] = relationalSV(Tree.Kind.LESS_THAN, sv[i - 1], sv[i]);
        }
        RelationalSymbolicValue firstLessThanLast = relationalSV(Tree.Kind.LESS_THAN, sv[0], sv[chainLength - 1]);
        ProgramState programState = Iterables
                .getOnlyElement(firstLessThanLast.setConstraint(stateWithRelations(given), BooleanConstraint.TRUE));
        assertThat(programState.getConstraint(firstLessThanLast, BooleanConstraint.class))
                .isEqualTo(BooleanConstraint.TRUE);
    }

    @Test
    public void test_not_equals_is_not_transitive() throws Exception {
        RelationalSymbolicValue aNEb = relationalSV(Tree.Kind.NOT_EQUAL_TO, a, b);
        RelationalSymbolicValue bNEc = relationalSV(Tree.Kind.NOT_EQUAL_TO, b, c);
        RelationalSymbolicValue relation = aNEb.deduceTransitiveOrSimplified(bNEc);
        assertThat(relation).isNull();
    }

    @Test
    public void test_transitive_constraint_copy() throws Exception {
        SymbolicValue aNEb = relationalSV(Tree.Kind.NOT_EQUAL_TO, b, a);
        SymbolicValue bNEc = relationalSV(Tree.Kind.NOT_EQUAL_TO, c, b);
        ProgramState programState = ProgramState.EMPTY_STATE;
        List<ProgramState> programStates = aNEb.setConstraint(programState, BooleanConstraint.TRUE);
        programState = Iterables.getOnlyElement(programStates);
        programStates = bNEc.setConstraint(programState, BooleanConstraint.TRUE);
        programState = Iterables.getOnlyElement(programStates);

        SymbolicValue aNEc = relationalSV(Tree.Kind.NOT_EQUAL_TO, c, a);
        programStates = aNEc.setConstraint(programState, BooleanConstraint.FALSE);
        assertThat(programStates).hasSize(1);
        programStates = aNEc.setConstraint(programState, BooleanConstraint.TRUE);
        assertThat(programStates).hasSize(1);
    }

    @Test
    public void test_equals_hashCode() throws Exception {
        SymbolicValue ab = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        SymbolicValue ba = relationalSV(Tree.Kind.EQUAL_TO, b, a);
        assertThat(ab).isEqualTo(ba);
        assertThat(ab.hashCode()).isEqualTo(ba.hashCode());

        ab = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, a, b);
        ba = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, b, a);
        assertThat(ab).isEqualTo(ba);
        assertThat(ab.hashCode()).isEqualTo(ba.hashCode());

        ab = relationalSV(Tree.Kind.LESS_THAN, a, b);
        ba = relationalSV(Tree.Kind.LESS_THAN, b, a);
        assertThat(ab).isNotEqualTo(ba);

        SymbolicValue eq = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        SymbolicValue eq1 = relationalSV(Tree.Kind.EQUAL_TO, b, b);
        SymbolicValue eq2 = relationalSV(Tree.Kind.EQUAL_TO, a, a);
        SymbolicValue neq = relationalSV(Tree.Kind.NOT_EQUAL_TO, b, a);
        assertThat(eq).isNotEqualTo(neq);
        assertThat(eq).isEqualTo(eq);
        assertThat(eq).isNotEqualTo(eq1);
        assertThat(eq).isNotEqualTo(eq2);
        assertThat(eq).isNotEqualTo(null);
        assertThat(eq).isNotEqualTo(new Object());

        SymbolicValue ab1 = relationalSV(Tree.Kind.LESS_THAN, a, b);
        SymbolicValue ab2 = relationalSV(Tree.Kind.LESS_THAN, a, b);
        SymbolicValue ab3 = relationalSV(Tree.Kind.LESS_THAN, a, new SymbolicValue());
        SymbolicValue ab4 = relationalSV(Tree.Kind.LESS_THAN, new SymbolicValue(), b);
        assertThat(ab1).isEqualTo(ab2);
        assertThat(ab1).isNotEqualTo(ab3);
        assertThat(ab1).isNotEqualTo(ab4);
    }

    @Test
    public void test_constraint_copy() throws Exception {
        ProgramState ps = ProgramState.EMPTY_STATE;
        SymbolicValue a = new SymbolicValue();
        SymbolicValue b = new SymbolicValue();
        List<ProgramState> newProgramStates = a.setConstraint(ps, DivisionByZeroCheck.ZeroConstraint.ZERO);
        ps = Iterables.getOnlyElement(newProgramStates);
        // 0 >= b
        SymbolicValue aGEb = relationalSV(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, b, a);
        newProgramStates = aGEb.setConstraint(ps, BooleanConstraint.TRUE);
        ps = Iterables.getOnlyElement(newProgramStates);

        // Zero constraint should stay when Zero is >= to SV without any constraint
        assertThat(ps.getConstraint(a, DivisionByZeroCheck.ZeroConstraint.class))
                .isEqualTo(DivisionByZeroCheck.ZeroConstraint.ZERO);
        assertThat(ps.getConstraint(b, DivisionByZeroCheck.ZeroConstraint.class)).isNull();
    }

    @Test
    public void test_setting_operands() throws Exception {
        RelationalSymbolicValue relSV = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.EQUAL, a, b);
        assertThatThrownBy(() -> relSV.computedFrom(Arrays.asList(b, a))).isInstanceOf(IllegalStateException.class)
                .hasMessage("Operands already set!");

        assertThatThrownBy(() -> relSV.computedFrom(Arrays.asList(a, b, a)))
                .isInstanceOf(IllegalArgumentException.class);
    }

    @Test
    public void test_transitive_deduction() throws Exception {
        List<String> actual = new ArrayList<>();
        for (Tree.Kind r : operators) {
            RelationalSymbolicValue first = relationalSV(r, b, a);
            actual.addAll(combineWithAll(first, () -> relationToString(r, a, b)));
        }
        RelationalSymbolicValue eqAB = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, a,
                b);
        actual.addAll(combineWithAll(eqAB, eqAB::toString));
        RelationalSymbolicValue neqAB = eqAB.inverse();
        actual.addAll(combineWithAll(neqAB, neqAB::toString));

        List<String> expected = IOUtils.readLines(getClass().getResourceAsStream("/relations/transitive.txt"));
        assertThat(actual).isEqualTo(expected);
    }

    private List<String> combineWithAll(RelationalSymbolicValue relation, Supplier<String> relationAsString) {
        List<String> actual = new ArrayList<>();
        for (Tree.Kind r : operators) {
            actual.add(String.format("%s && %s => %s", relationAsString.get(), relationToString(r, b, c),
                    nullableToCollection(relation.deduceTransitiveOrSimplified(relationalSV(r, c, b)))));
        }
        RelationalSymbolicValue eq = new RelationalSymbolicValue(RelationalSymbolicValue.Kind.METHOD_EQUALS, b, c);
        Stream.of(eq, eq.inverse()).forEach(rel -> actual.add(String.format("%s && %s => %s",
                relationAsString.get(), rel, nullableToCollection(relation.deduceTransitiveOrSimplified(rel)))));
        return actual;
    }

    private Collection<SymbolicValue> nullableToCollection(@Nullable RelationalSymbolicValue symbolicValue) {
        return symbolicValue == null ? Collections.emptySet() : Collections.singleton(symbolicValue);
    }

    @Test
    public void test_all_transitive_relations_are_computed() throws Exception {
        RelationalSymbolicValue ab = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        RelationalSymbolicValue bc = relationalSV(Tree.Kind.EQUAL_TO, b, c);
        RelationalSymbolicValue cd = relationalSV(Tree.Kind.EQUAL_TO, c, d);
        Set<RelationalSymbolicValue> transitive = ab.transitiveRelations(ImmutableSet.of(ab, bc, cd));
        assertThat(transitive).containsOnly(relationalSV(Tree.Kind.EQUAL_TO, a, c),
                relationalSV(Tree.Kind.EQUAL_TO, b, d), relationalSV(Tree.Kind.EQUAL_TO, a, d));
    }

    @Test
    public void test_constraints_are_copied_over_transitive_relations() throws Exception {
        ProgramState ps = ProgramState.EMPTY_STATE;
        ps = Iterables.getOnlyElement(a.setConstraint(ps, ObjectConstraint.NULL));
        RelationalSymbolicValue ab = relationalSV(Tree.Kind.EQUAL_TO, a, b);
        ps = setTrue(ps, ab);
        assertNullConstraint(ps, b);

        RelationalSymbolicValue cd = relationalSV(Tree.Kind.EQUAL_TO, c, d);
        ps = setTrue(ps, cd);
        RelationalSymbolicValue de = relationalSV(Tree.Kind.EQUAL_TO, d, e);
        ps = setTrue(ps, de);
        assertNoConstraints(ps, c);
        assertNoConstraints(ps, d);
        assertNoConstraints(ps, e);
        assertThat(ps.getConstraint(relationalSV(Tree.Kind.EQUAL_TO, c, e), BooleanConstraint.class))
                .isEqualTo(BooleanConstraint.TRUE);

        // this relation will connect two distinct groups of relations (ab) -- (cde)
        RelationalSymbolicValue bc = relationalSV(Tree.Kind.EQUAL_TO, b, c);
        ps = setTrue(ps, bc);
        // we assert that NULL was copied over to all values
        assertNullConstraint(ps, b);
        assertNullConstraint(ps, c);
        assertNullConstraint(ps, d);
        assertNullConstraint(ps, e);
    }

    @Test
    public void relationships_transitivity_should_take_known_relationships_into_account() throws Exception {
        // Testcase in that file can fail with a stackoverflow if known relations in program state are not taken into account.
        JavaCheckVerifier.verifyNoIssue("src/test/files/se/InifiniteTransitiveRelationshipConstraintCopy.java",
                new NullDereferenceCheck());
    }

    private void assertNullConstraint(ProgramState ps, SymbolicValue sv) {
        assertThat(ps.getConstraint(sv, ObjectConstraint.class)).isEqualTo(ObjectConstraint.NULL);
    }

    private void assertNoConstraints(ProgramState ps, SymbolicValue sv) {
        PMap<Class<? extends Constraint>, Constraint> constraints = ps.getConstraints(sv);
        if (constraints != null) {
            assertThat(constraints).isEqualTo(PCollections.emptyMap());
        }
    }

    private ProgramState setTrue(ProgramState ps, RelationalSymbolicValue ab) {
        return Iterables.getOnlyElement(ab.setConstraint(ps, BooleanConstraint.TRUE));
    }

    private String relationToString(Tree.Kind kind, SymbolicValue leftOp, SymbolicValue rightOp) {
        return leftOp.toString() + operatorToString(kind) + rightOp.toString();
    }

    private String operatorToString(Tree.Kind kind) {
        switch (kind) {
        case EQUAL_TO:
            return "==";
        case NOT_EQUAL_TO:
            return "!=";
        case GREATER_THAN:
            return ">";
        case GREATER_THAN_OR_EQUAL_TO:
            return ">=";
        case LESS_THAN:
            return "<";
        case LESS_THAN_OR_EQUAL_TO:
            return "<=";
        default:
            throw new IllegalStateException();
        }
    }
}