/*
* 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.base.Charsets;
import com.google.common.collect.Lists;
import com.sonar.sslr.api.typed.ActionParser;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.java.ast.parser.JavaParser;
import org.sonar.java.resolve.JavaSymbol;
import org.sonar.java.resolve.SemanticModel;
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.ObjectConstraint;
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.semantic.Symbol.MethodSymbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.Tree;
import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MethodYieldTest {
@Test
public void test_creation_of_states() throws Exception {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/XProcYields.java");
Map.Entry<MethodSymbol, MethodBehavior> entry = getMethodBehavior(sev, "foo");
Symbol.MethodSymbol methodSymbol = entry.getKey();
List<MethodYield> yields = entry.getValue().yields();
ProgramState ps = ProgramState.EMPTY_STATE;
SymbolicValue sv1 = new SymbolicValue(41);
SymbolicValue sv2 = new SymbolicValue(42);
SymbolicValue sv3 = new SymbolicValue(43);
Symbol sym = new JavaSymbol.VariableJavaSymbol(0, "myVar", (JavaSymbol) methodSymbol);
ps = ps.put(sym, sv1);
MethodYield methodYield = yields.get(0);
Stream<ProgramState> generatedStatesFromFirstYield = methodYield.statesAfterInvocation(Lists.newArrayList(sv1, sv2), Lists.newArrayList(), ps, () -> sv3);
assertThat(generatedStatesFromFirstYield).hasSize(1);
}
@Test
public void test_toString() throws Exception {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/XProcYields.java");
Set<String> yieldsToString = getMethodBehavior(sev, "bar").getValue().yields().stream().map(MethodYield::toString).collect(Collectors.toSet());
assertThat(yieldsToString).contains(
"{params: [TRUE, NOT_NULL], result: null (-1), exceptional: false}",
"{params: [FALSE, null], result: null (-1), exceptional: false}");
}
private enum YieldStatus implements ObjectConstraint.Status {
A, B
}
@Test
public void all_constraints_should_be_valid_to_generate_a_new_state() throws Exception {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/XProcYields.java");
Map.Entry<MethodSymbol, MethodBehavior> entry = getMethodBehavior(sev, "bar");
Symbol.MethodSymbol methodSymbol = entry.getKey();
List<MethodYield> yields = entry.getValue().yields();
MethodYield trueYield = yields.stream()
.filter(y -> y.parametersConstraints[0] instanceof BooleanConstraint && ((BooleanConstraint) y.parametersConstraints[0]).isTrue())
.findFirst().get();
// force status of the arg1 to be B
trueYield.parametersConstraints[1] = ((ObjectConstraint<YieldStatus>) trueYield.parametersConstraints[1]).withStatus(YieldStatus.B);
ProgramState ps = ProgramState.EMPTY_STATE;
SymbolicValue sv1 = new SymbolicValue(41);
SymbolicValue sv2 = new SymbolicValue(42);
SymbolicValue sv3 = new SymbolicValue(43);
Symbol myBoolean = new JavaSymbol.VariableJavaSymbol(0, "myBoolean", (JavaSymbol) methodSymbol);
ps = ps.put(myBoolean, sv1);
ps = ps.addConstraint(sv1, BooleanConstraint.TRUE);
Symbol myVar = new JavaSymbol.VariableJavaSymbol(0, "myVar", (JavaSymbol) methodSymbol);
ps = ps.put(myVar, sv2);
ps = ps.addConstraint(sv2, new ObjectConstraint(false, false, YieldStatus.A));
// status of sv2 should be changed from A to B
Collection<ProgramState> generatedStatesFromFirstYield = trueYield.statesAfterInvocation(Lists.newArrayList(sv1, sv2), Lists.newArrayList(), ps, () -> sv3)
.collect(Collectors.toList());
assertThat(generatedStatesFromFirstYield).hasSize(1);
assertThat(generatedStatesFromFirstYield.iterator().next().getConstraintWithStatus(sv2, YieldStatus.B)).isNotNull();
}
@Test
public void test_yield_equality() {
MethodYield yield = new MethodYield(1, false);
MethodYield otherYield;
assertThat(yield).isNotEqualTo(null);
assertThat(yield).isNotEqualTo(new Object());
// same instance
assertThat(yield).isEqualTo(yield);
// same constraints, same nb of parameters, same exceptional aspect
assertThat(yield).isEqualTo(new MethodYield(1, false));
// arity is taken into account
assertThat(yield).isNotEqualTo(new MethodYield(0, false));
// varargs is taken into account
assertThat(yield).isNotEqualTo(new MethodYield(1, true));
// same arity and constraints but exceptional path
otherYield = new MethodYield(1, false);
otherYield.exception = true;
assertThat(yield).isNotEqualTo(otherYield);
// same arity and constraints but different return value
otherYield = new MethodYield(1, false);
otherYield.resultIndex = 0;
assertThat(yield).isNotEqualTo(otherYield);
// same arity but different return constraint
otherYield = new MethodYield(1, false);
otherYield.resultConstraint = ObjectConstraint.notNull();
assertThat(yield).isNotEqualTo(otherYield);
// same return constraint
yield.resultConstraint = ObjectConstraint.notNull();
otherYield = new MethodYield(1, false);
otherYield.resultConstraint = ObjectConstraint.notNull();
assertThat(yield).isEqualTo(otherYield);
// exceptional yields
MethodYield exceptionalYield = new MethodYield(0, false);
exceptionalYield.exception = true;
otherYield = new MethodYield(0, false);
otherYield.exception = false;
assertThat(exceptionalYield).isNotEqualTo(otherYield);
otherYield.exception = true;
assertThat(exceptionalYield).isEqualTo(otherYield);
Type exceptionType = Mockito.mock(Type.class);
otherYield.exceptionType = exceptionType;
assertThat(exceptionalYield).isNotEqualTo(otherYield);
exceptionalYield.exceptionType = exceptionType;
assertThat(exceptionalYield).isEqualTo(otherYield);
}
@Test
public void test_hashCode() {
MethodYield methodYield = new MethodYield(0, true);
MethodYield other = new MethodYield(0, true);
// same values for same yields
assertThat(methodYield.hashCode()).isEqualTo(other.hashCode());
// different values for different yields
other.exception = true;
assertThat(methodYield.hashCode()).isNotEqualTo(other.hashCode());
}
@Test
public void exceptional_yields() {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/ExceptionalYields.java");
List<MethodYield> yields = getMethodBehavior(sev, "myMethod").getValue().yields();
assertThat(yields).hasSize(4);
List<MethodYield> exceptionalYields = yields.stream().filter(y -> y.exception).collect(Collectors.toList());
assertThat(exceptionalYields).hasSize(3);
// runtime exception
Optional<MethodYield> runtimeException = exceptionalYields.stream().filter(y -> y.exceptionType == null).findFirst();
assertThat(runtimeException.isPresent()).isTrue();
MethodYield runtimeExceptionYield = runtimeException.get();
assertThat(runtimeExceptionYield.resultIndex).isEqualTo(-1);
assertThat(runtimeExceptionYield.resultConstraint).isNull();
assertThat(runtimeExceptionYield.parametersConstraints[0]).isEqualTo(BooleanConstraint.FALSE);
// exception from other method call
Optional<MethodYield> implicitException = exceptionalYields.stream().filter(y -> y.exceptionType != null && y.exceptionType.is("org.foo.MyException2")).findFirst();
assertThat(implicitException.isPresent()).isTrue();
MethodYield implicitExceptionYield = implicitException.get();
assertThat(implicitExceptionYield.resultIndex).isEqualTo(-1);
assertThat(implicitExceptionYield.resultConstraint).isNull();
assertThat(implicitExceptionYield.parametersConstraints[0]).isEqualTo(BooleanConstraint.FALSE);
// explicitly thrown exception
Optional<MethodYield> explicitException = exceptionalYields.stream().filter(y -> y.exceptionType != null && y.exceptionType.is("org.foo.MyException1")).findFirst();
assertThat(explicitException.isPresent()).isTrue();
MethodYield explicitExceptionYield = explicitException.get();
assertThat(explicitExceptionYield.resultIndex).isEqualTo(-1);
assertThat(explicitExceptionYield.resultConstraint).isNull();
assertThat(explicitExceptionYield.parametersConstraints[0]).isEqualTo(BooleanConstraint.TRUE);
}
@Test
public void exceptional_yields_void_method() {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/ExceptionalYieldsVoidMethod.java");
List<MethodYield> yields = getMethodBehavior(sev, "myVoidMethod").getValue().yields();
assertThat(yields).hasSize(4);
List<MethodYield> exceptionalYields = yields.stream().filter(y -> y.exception).collect(Collectors.toList());
assertThat(exceptionalYields).hasSize(3);
assertThat(exceptionalYields.stream().filter(y -> y.exceptionType == null).count()).isEqualTo(1);
MethodYield explicitExceptionYield = exceptionalYields.stream().filter(y -> y.exceptionType != null && y.exceptionType.is("org.foo.MyException1")).findAny().get();
assertThat(explicitExceptionYield.resultIndex).isEqualTo(-1);
assertThat(explicitExceptionYield.resultConstraint).isNull();
assertThat(explicitExceptionYield.parametersConstraints[0]).isEqualTo(ObjectConstraint.nullConstraint());
MethodYield implicitExceptionYield = exceptionalYields.stream().filter(y -> y.exceptionType != null && y.exceptionType.is("org.foo.MyException2")).findAny().get();
assertThat(implicitExceptionYield.resultIndex).isEqualTo(-1);
assertThat(implicitExceptionYield.resultConstraint).isNull();
assertThat(implicitExceptionYield.parametersConstraints[0]).isEqualTo(ObjectConstraint.notNull());
}
@Test
public void constraints_on_varargs() throws Exception {
SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/VarArgsYields.java");
Map.Entry<MethodSymbol, MethodBehavior> entry = getMethodBehavior(sev, "varArgMethod");
Symbol.MethodSymbol methodSymbol = entry.getKey();
List<MethodYield> yields = entry.getValue().yields();
assertThat(yields).hasSize(3);
assertThat(yields.stream().filter(y -> y.exception).count()).isEqualTo(2);
MethodYield yield = yields.stream().filter(y -> !y.exception).findFirst().get();
// check that we have NOT_NULL constraint on the first argument
assertThat(yield.parametersConstraints[0].isNull()).isFalse();
// check that we have NOT_NULL constraint on the variadic argument
assertThat(yield.parametersConstraints[1].isNull()).isFalse();
List<IdentifierTree> usages = methodSymbol.usages();
assertThat(usages).hasSize(6);
List<List<Type>> arguments = usages.stream()
.map(MethodYieldTest::getMethodIncoationArgumentsTypes)
.collect(Collectors.toList());
ProgramState ps = ProgramState.EMPTY_STATE;
ProgramState psResult;
SymbolicValue svFirstArg = new SymbolicValue(41);
SymbolicValue svVarArg1 = new SymbolicValue(42);
SymbolicValue svVarArg2 = new SymbolicValue(43);
SymbolicValue svResult = new SymbolicValue(43);
// apply constraint NotNull to parameter
Collection<ProgramState> arrayOfA = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg, svVarArg1), arguments.get(0), ps, () -> svResult).collect(Collectors.toList());
assertThat(arrayOfA).hasSize(1);
psResult = arrayOfA.iterator().next();
assertThat(psResult.getConstraint(svFirstArg).isNull()).isFalse();
assertThat(psResult.getConstraint(svVarArg1).isNull()).isFalse();
// apply constraint NotNull to parameter (B[] is a subtype of A[])
Collection<ProgramState> arrayOfB = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg, svVarArg1), arguments.get(1), ps, () -> svResult).collect(Collectors.toList());
assertThat(arrayOfB).hasSize(1);
psResult = arrayOfB.iterator().next();
assertThat(psResult.getConstraint(svFirstArg).isNull()).isFalse();
assertThat(psResult.getConstraint(svVarArg1).isNull()).isFalse();
// no constraint, as 'a' is not an array
Collection<ProgramState> objectA = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg, svVarArg1), arguments.get(2), ps, () -> svResult).collect(Collectors.toList());
assertThat(objectA).hasSize(1);
psResult = objectA.iterator().next();
assertThat(psResult.getConstraint(svFirstArg).isNull()).isFalse();
assertThat(psResult.getConstraint(svVarArg1)).isNull();
// no constraint, as 'a' and 'b' can not receive the constraint of the array
Collection<ProgramState> objectsAandB = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg, svVarArg1, svVarArg2), arguments.get(3), ps, () -> svResult).collect(Collectors.toList());
assertThat(objectsAandB).hasSize(1);
psResult = objectsAandB.iterator().next();
assertThat(psResult.getConstraint(svFirstArg).isNull()).isFalse();
assertThat(psResult.getConstraint(svVarArg1)).isNull();
assertThat(psResult.getConstraint(svVarArg2)).isNull();
// no param, we only learn something about the argument which is not variadic
Collection<ProgramState> noParam = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg), arguments.get(4), ps, () -> svResult).collect(Collectors.toList());
assertThat(noParam).hasSize(1);
psResult = noParam.iterator().next();
assertThat(psResult.getConstraint(svFirstArg).isNull()).isFalse();
// null param, contradiction, no resulting program state
ps = ProgramState.EMPTY_STATE.addConstraint(svFirstArg, ObjectConstraint.nullConstraint());
Collection<ProgramState> nullParam = yield.statesAfterInvocation(Lists.newArrayList(svFirstArg, svVarArg1), arguments.get(5), ps, () -> svResult).collect(Collectors.toList());
assertThat(nullParam).isEmpty();
}
private static List<Type> getMethodIncoationArgumentsTypes(IdentifierTree identifier) {
Tree tree = identifier;
while(!tree.is(Tree.Kind.METHOD_INVOCATION)) {
tree = tree.parent();
}
return ((MethodInvocationTree) tree).arguments().stream().map(ExpressionTree::symbolType).collect(Collectors.toList());
}
@Test
public void native_methods_behavior_should_not_be_used() throws Exception {
Map<Symbol.MethodSymbol, MethodBehavior> behaviorCache = createSymbolicExecutionVisitor("src/test/files/se/XProcNativeMethods.java").behaviorCache;
behaviorCache.entrySet().stream().filter(e -> e.getKey().name().equals("foo")).forEach(e -> assertThat(e.getValue().yields()).hasSize(2));
}
@Test
public void catch_class_cast_exception() throws Exception {
Map<Symbol.MethodSymbol, MethodBehavior> behaviorCache = createSymbolicExecutionVisitor("src/test/files/se/XProcCatchClassCastException.java").behaviorCache;
assertThat(behaviorCache.values()).hasSize(1);
MethodBehavior methodBehavior = behaviorCache.values().iterator().next();
assertThat(methodBehavior.yields()).hasSize(2);
MethodYield[] expected = new MethodYield[] {
buildMethodYield(0, null),
buildMethodYield(-1, ObjectConstraint.nullConstraint())};
assertThat(methodBehavior.yields()).contains(expected);
}
private MethodYield buildMethodYield(int resultIndex, @Nullable ObjectConstraint resultConstraint) {
MethodYield methodYield = new MethodYield(1, false);
methodYield.resultIndex = resultIndex;
methodYield.parametersConstraints = new Constraint[] {null};
methodYield.exception = false;
methodYield.resultConstraint = resultConstraint;
return methodYield;
}
private static SymbolicExecutionVisitor createSymbolicExecutionVisitor(String fileName) {
ActionParser<Tree> p = JavaParser.createParser(Charsets.UTF_8);
CompilationUnitTree cut = (CompilationUnitTree) p.parse(new File(fileName));
SemanticModel semanticModel = SemanticModel.createFor(cut, new ArrayList<>());
SymbolicExecutionVisitor sev = new SymbolicExecutionVisitor(Lists.newArrayList(new NullDereferenceCheck()));
JavaFileScannerContext context = mock(JavaFileScannerContext.class);
when(context.getTree()).thenReturn(cut);
when(context.getSemanticModel()).thenReturn(semanticModel);
sev.scanFile(context);
return sev;
}
private static Map.Entry<Symbol.MethodSymbol, MethodBehavior> getMethodBehavior(SymbolicExecutionVisitor sev, String methodName) {
Optional<Map.Entry<MethodSymbol, MethodBehavior>> mb = sev.behaviorCache.entrySet().stream()
.filter(e -> methodName.equals(e.getKey().name()))
.findFirst();
assertThat(mb.isPresent()).isTrue();
return mb.get();
}
}