/* * 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.sonar.java.ast.parser.JavaParser; import org.sonar.java.resolve.SemanticModel; import org.sonar.java.se.checks.NullDereferenceCheck; import org.sonar.java.se.constraint.BooleanConstraint; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.Tree; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SymbolicExecutionVisitorTest { @Test public void method_behavior_cache_should_be_filled() { SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/resources/se/MethodBehavior.java"); assertThat(sev.behaviorCache.entrySet()).hasSize(6); assertThat(sev.behaviorCache.values().stream().filter(mb -> mb != null).count()).isEqualTo(6); // check order of method exploration : last is the topMethod as it requires the other to get its behavior. // Then, as we explore fully a path before switching to another one (see the LIFO in EGW) : qix is handled before foo. assertThat(sev.behaviorCache.keySet().stream().map(Symbol.MethodSymbol::name).collect(Collectors.toList())) .containsSequence("topMethod", "bar", "qix", "foo", "independent", "nativeMethod"); MethodBehavior nativeMethod = sev.behaviorCache.keySet().stream() .filter(s -> s.name().equals("nativeMethod")) .map(s -> sev.behaviorCache.get(s)) .findFirst() .orElseThrow(IllegalStateException::new); assertThat(nativeMethod.yields()).isEmpty(); } @Test public void method_behavior_yields() { SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/resources/se/MethodYields.java"); MethodBehavior mb = getMethodBehavior(sev, "method"); List<MethodYield> yields = mb.yields(); assertThat(yields).hasSize(3); List<MethodYield> trueResults = yields.stream().filter(my -> BooleanConstraint.TRUE.equals(my.resultConstraint)).collect(Collectors.toList()); assertThat(trueResults).hasSize(1); MethodYield trueResult = trueResults.get(0); // 'a' has constraint "null" assertThat(trueResult.parametersConstraints[0].isNull()).isTrue(); // no constraint on 'b' assertThat(trueResult.parametersConstraints[1]).isNull(); // result SV is a different SV than 'a' and 'b' assertThat(trueResult.resultIndex).isEqualTo(-1); List<MethodYield> falseResults = yields.stream().filter(my -> BooleanConstraint.FALSE.equals(my.resultConstraint)).collect(Collectors.toList()); assertThat(falseResults).hasSize(2); // for both "False" results, 'a' has the constraint "not null" assertThat(falseResults.stream().filter(my -> !my.parametersConstraints[0].isNull()).count()).isEqualTo(2); // 1) 'b' has constraint "false", result is 'b' assertThat(falseResults.stream().filter(my -> BooleanConstraint.FALSE.equals(my.parametersConstraints[1]) && my.resultIndex == 1).count()).isEqualTo(1); // 2) 'b' is "true", result is a different SV than 'a' and 'b' assertThat(falseResults.stream().filter(my -> BooleanConstraint.TRUE.equals(my.parametersConstraints[1]) && my.resultIndex == -1).count()).isEqualTo(1); } @Test public void method_behavior_handling_finally() { SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/resources/se/ReturnAndFinally.java"); assertThat(sev.behaviorCache.entrySet()).hasSize(3); MethodBehavior foo = getMethodBehavior(sev, "foo"); assertThat(foo.yields()).hasSize(4); assertThat(foo.yields().stream().filter(y -> !y.exception).count()).isEqualTo(2); assertThat(foo.yields().stream().filter(y -> y.exception).count()).isEqualTo(2); MethodBehavior qix = getMethodBehavior(sev, "qix"); List<MethodYield> qixYield = qix.yields(); assertThat(qixYield.stream() .filter(y -> !y.parametersConstraints[0].isNull()) .allMatch(y -> y.exception)).isTrue(); assertThat(qixYield.stream() .filter(y -> y.parametersConstraints[0].isNull() && y.exception) .count()).isEqualTo(2); assertThat(qixYield.stream() .filter(y -> !y.exception) .allMatch(y -> y.parametersConstraints[0].isNull())).isTrue(); } @Test public void explore_method_with_recursive_call() throws Exception { SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/resources/se/RecursiveCall.java"); assertThat(sev.behaviorCache.entrySet()).hasSize(1); assertThat(sev.behaviorCache.keySet().iterator().next().name()).isEqualTo("foo"); } @Test public void clear_stack_when_taking_exceptional_path_from_method_invocation() throws Exception { SymbolicExecutionVisitor sev = createSymbolicExecutionVisitor("src/test/files/se/CleanStackWhenRaisingException.java"); List<MethodYield> yields = sev.behaviorCache.values().iterator().next().yields(); assertThat(yields).hasSize(5); yields.stream().map(y -> y.resultConstraint).filter(Objects::nonNull).forEach(c -> assertThat(c.isNull()).isFalse()); assertThat(yields.stream().filter(y -> !y.exception).count()).isEqualTo(2); List<MethodYield> exceptionalYields = yields.stream().filter(y -> y.exception).collect(Collectors.toList()); assertThat(exceptionalYields).hasSize(3); assertThat(exceptionalYields.stream().filter(y -> y.exceptionType == null)).hasSize(1); // exception thrown by System.getProperty() assertThat(exceptionalYields.stream() .filter(y -> y.exceptionType != null) .map(y -> y.exceptionType.fullyQualifiedName())) .containsOnly("java.lang.SecurityException", "java.lang.IllegalArgumentException"); } 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 MethodBehavior getMethodBehavior(SymbolicExecutionVisitor sev, String methodName) { Optional<MethodBehavior> mb = sev.behaviorCache.entrySet().stream() .filter(e -> methodName.equals(e.getKey().name())) .map(Map.Entry::getValue) .findFirst(); assertThat(mb.isPresent()).isTrue(); return mb.get(); } }