Java tutorial
/* * SonarQube Java * Copyright (C) 2012-2019 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.bytecode.se; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.util.Printer; import org.sonar.java.ast.parser.JavaParser; import org.sonar.java.bytecode.cfg.BytecodeCFG; import org.sonar.java.bytecode.cfg.Instruction; import org.sonar.java.bytecode.cfg.Instructions; import org.sonar.java.bytecode.loader.SquidClassLoader; import org.sonar.java.cfg.CFG; import org.sonar.java.resolve.SemanticModel; import org.sonar.java.se.ProgramPoint; import org.sonar.java.se.ProgramState; import org.sonar.java.se.SETestUtils; import org.sonar.java.se.checks.DivisionByZeroCheck; import org.sonar.java.se.constraint.BooleanConstraint; import org.sonar.java.se.constraint.Constraint; import org.sonar.java.se.constraint.ConstraintsByDomain; import org.sonar.java.se.constraint.ObjectConstraint; import org.sonar.java.se.constraint.TypedConstraint; import org.sonar.java.se.symbolicvalues.BinarySymbolicValue; import org.sonar.java.se.symbolicvalues.RelationalSymbolicValue; import org.sonar.java.se.symbolicvalues.SymbolicValue; import org.sonar.java.se.xproc.BehaviorCache; import org.sonar.java.se.xproc.HappyPathYield; import org.sonar.java.se.xproc.MethodBehavior; import org.sonar.java.se.xproc.MethodYield; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.objectweb.asm.Opcodes.ASTORE; import static org.objectweb.asm.Opcodes.DADD; import static org.objectweb.asm.Opcodes.DDIV; import static org.objectweb.asm.Opcodes.DMUL; import static org.objectweb.asm.Opcodes.DREM; import static org.objectweb.asm.Opcodes.DSUB; import static org.objectweb.asm.Opcodes.GOTO; import static org.objectweb.asm.Opcodes.ICONST_0; import static org.objectweb.asm.Opcodes.ICONST_1; import static org.objectweb.asm.Opcodes.ICONST_2; import static org.objectweb.asm.Opcodes.ICONST_3; import static org.objectweb.asm.Opcodes.ICONST_4; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.ISTORE; import static org.objectweb.asm.Opcodes.LADD; import static org.objectweb.asm.Opcodes.LAND; import static org.objectweb.asm.Opcodes.LDIV; import static org.objectweb.asm.Opcodes.LMUL; import static org.objectweb.asm.Opcodes.LOR; import static org.objectweb.asm.Opcodes.LREM; import static org.objectweb.asm.Opcodes.LSHL; import static org.objectweb.asm.Opcodes.LSHR; import static org.objectweb.asm.Opcodes.LSUB; import static org.objectweb.asm.Opcodes.LUSHR; import static org.objectweb.asm.Opcodes.LXOR; import static org.objectweb.asm.Opcodes.RETURN; public class BytecodeEGWalkerExecuteTest { private static final Set<Integer> LONG_OPCODE = ImmutableSet.of(LADD, LSUB, LMUL, LDIV, LAND, LOR, LXOR, LREM, LSHL, LSHR, LUSHR, DADD, DSUB, DMUL, DDIV, DREM); private static final String TRY_CATCH_SIGNATURE = BytecodeEGWalkerExecuteTest.class.getCanonicalName() + "#tryCatch(Z)V"; private static final String TRY_WRONG_CATCH_SIGNATURE = BytecodeEGWalkerExecuteTest.class.getCanonicalName() + "#tryWrongCatch(Z)V"; static SemanticModel semanticModel; private BytecodeEGWalker walker; private static SquidClassLoader squidClassLoader; @BeforeClass public static void initializeClassLoaderAndSemanticModel() { List<File> files = new ArrayList<>( FileUtils.listFiles(new File("target/test-jars"), new String[] { "jar", "zip" }, true)); files.add(new File("target/classes")); files.add(new File("target/test-classes")); squidClassLoader = new SquidClassLoader(files); File file = new File("src/test/java/org/sonar/java/bytecode/se/BytecodeEGWalkerExecuteTest.java"); CompilationUnitTree tree = (CompilationUnitTree) JavaParser.createParser().parse(file); semanticModel = SemanticModel.createFor(tree, squidClassLoader); } @Before public void initializeWalker() { BehaviorCache behaviorCache = new BehaviorCache(squidClassLoader); behaviorCache.setFileContext(null, semanticModel); walker = new BytecodeEGWalker(behaviorCache, semanticModel); } @Test public void athrow_should_not_be_linked_to_next_label() throws Exception { CompilationUnitTree tree = (CompilationUnitTree) JavaParser.createParser().parse("class A {int field;}"); SquidClassLoader classLoader = new SquidClassLoader(Collections.singletonList(new File("src/test/JsrRet"))); SemanticModel semanticModel = SemanticModel.createFor(tree, classLoader); BehaviorCache behaviorCache = new BehaviorCache(classLoader); behaviorCache.setFileContext(null, semanticModel); BytecodeEGWalker walker = new BytecodeEGWalker(behaviorCache, semanticModel); MethodBehavior methodBehavior = walker.getMethodBehavior( "org.apache.commons.io.FileUtils#readFileToString(Ljava/io/File;)Ljava/lang/String;", classLoader); assertThat(methodBehavior.happyPathYields().collect(Collectors.toList())).hasSize(1); assertThat(methodBehavior.exceptionalPathYields().collect(Collectors.toList())).hasSize(2); } @Test public void behavior_with_no_yield_should_stack_value() throws Exception { BehaviorCache behaviorCache = new BehaviorCache(squidClassLoader); MethodBehavior methodBehavior = behaviorCache.get("org.mypackage.MyClass#MyMethod()Ljava/lang/Exception;"); methodBehavior.completed(); BytecodeEGWalker walker = new BytecodeEGWalker(behaviorCache, semanticModel); walker.programState = ProgramState.EMPTY_STATE; CFG.IBlock block = mock(CFG.IBlock.class); when(block.successors()).thenReturn(Collections.emptySet()); walker.programPosition = new ProgramPoint(block); walker.workList.clear(); walker.executeInstruction(new Instruction(INVOKESTATIC, new Instruction.FieldOrMethod("org.mypackage.MyClass", "MyMethod", "()Ljava/lang/Exception;"))); assertThat(walker.workList.getFirst().programState.peekValue()).isNotNull(); } @Test public void test_nop() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.NOP)); assertThat(programState).isEqualTo(ProgramState.EMPTY_STATE); } @Test public void test_ldc() throws Exception { ProgramState programState = execute(new Instruction.LdcInsn("a")); assertStack(programState, ObjectConstraint.NOT_NULL); SymbolicValue sv = programState.peekValue(); assertThat(isDoubleOrLong(programState, sv)).isFalse(); programState = execute(new Instruction.LdcInsn(1L)); sv = programState.peekValue(); assertThat(isDoubleOrLong(programState, sv)).isTrue(); programState = execute(new Instruction.LdcInsn(1D)); sv = programState.peekValue(); assertThat(isDoubleOrLong(programState, sv)).isTrue(); } @Test public void test_aconst_null() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.ACONST_NULL)); assertStack(programState, ObjectConstraint.NULL); } @Test public void test_xReturn() throws Exception { SymbolicValue returnValue = new SymbolicValue(); int[] opcodes = { Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN }; for (int opcode : opcodes) { ProgramState programState = execute(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(returnValue)); assertThat(programState.peekValue()).isNull(); assertThat(programState.exitValue()).isEqualTo(returnValue); } } @Test public void test_return() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.RETURN), ProgramState.EMPTY_STATE); assertThat(programState.peekValue()).isNull(); assertThat(programState.exitValue()).isNull(); } @Test public void test_iconst() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.ICONST_0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, BooleanConstraint.FALSE, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.ICONST_1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, BooleanConstraint.TRUE, ObjectConstraint.NOT_NULL } }); int[] opCodesConst = new int[] { Opcodes.ICONST_M1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5 }; for (int opcode : opCodesConst) { programState = execute(new Instruction(opcode)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL } }); } } @Test public void test_lconst() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.LCONST_0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, BooleanConstraint.FALSE, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.LCONST_1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, BooleanConstraint.TRUE, ObjectConstraint.NOT_NULL } }); } @Test public void test_fconst() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.FCONST_0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, BooleanConstraint.FALSE, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.FCONST_1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, BooleanConstraint.TRUE, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.FCONST_2)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL } }); } @Test public void test_bipush() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.BIPUSH, 42)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.BIPUSH, 1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL, BooleanConstraint.TRUE } }); programState = execute(new Instruction(Opcodes.BIPUSH, 0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, ObjectConstraint.NOT_NULL, BooleanConstraint.FALSE } }); } @Test public void test_sipush() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.SIPUSH, 42)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.SIPUSH, 1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, ObjectConstraint.NOT_NULL, BooleanConstraint.TRUE } }); programState = execute(new Instruction(Opcodes.SIPUSH, 0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, ObjectConstraint.NOT_NULL, BooleanConstraint.FALSE } }); } @Test public void test_array_load() throws Exception { int[] loadRefOpcodes = new int[] { Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD }; SymbolicValue array = new SymbolicValue(); SymbolicValue index = new SymbolicValue(); ProgramState initState = ProgramState.EMPTY_STATE.stackValue(array).stackValue(index); for (int opcode : loadRefOpcodes) { ProgramState programState = execute(new Instruction(opcode), initState); if (opcode != Opcodes.AALOAD) { assertStack(programState, ObjectConstraint.NOT_NULL); } ProgramState.Pop result = programState.unstackValue(1); assertThat(result.values).hasSize(1); assertThat(result.values.get(0)).isNotEqualTo(array); assertThat(result.values.get(0)).isNotEqualTo(index); if (opcode == Opcodes.DALOAD || opcode == Opcodes.LALOAD) { assertThat(isDoubleOrLong(programState, result.values.get(0))).isTrue(); } } } @Test public void test_array_store() throws Exception { int[] storeArrayOpcodes = new int[] { Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE }; SymbolicValue array = new SymbolicValue(); SymbolicValue index = new SymbolicValue(); SymbolicValue value = new SymbolicValue(); ProgramState initState = ProgramState.EMPTY_STATE.stackValue(array).stackValue(index).stackValue(value); for (int opcode : storeArrayOpcodes) { ProgramState ps = execute(new Instruction(opcode), initState); assertEmptyStack(ps); } } @Test public void test_dconst() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.DCONST_0)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.ZERO, BooleanConstraint.FALSE, ObjectConstraint.NOT_NULL } }); programState = execute(new Instruction(Opcodes.DCONST_1)); assertStack(programState, new Constraint[][] { { DivisionByZeroCheck.ZeroConstraint.NON_ZERO, BooleanConstraint.TRUE, ObjectConstraint.NOT_NULL } }); } @Test public void test_load() throws Exception { int[] loadRefOpcodes = new int[] { Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD }; for (int loadRefOpcode : loadRefOpcodes) { SymbolicValue loadRef = new SymbolicValue(); ProgramState programState = execute(new Instruction(loadRefOpcode, 0), ProgramState.EMPTY_STATE.put(0, loadRef)); assertThat(programState.peekValue()).isEqualTo(loadRef); // no SV indexed should failed assertThatThrownBy(() -> execute(new Instruction(loadRefOpcode, 0), ProgramState.EMPTY_STATE)) .hasMessage("Loading a symbolic value unindexed"); } } @Test public void test_pop() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.POP), ProgramState.EMPTY_STATE.stackValue(sv1).stackValue(sv2)); assertThat(programState.peekValue()).isEqualTo(sv1); programState = execute(new Instruction(Opcodes.POP)); assertEmptyStack(programState); } @Test public void test_pop2() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.POP2), ProgramState.EMPTY_STATE.stackValue(sv1).stackValue(sv2).stackValue(sv3)); assertThat(programState.peekValue()).isEqualTo(sv1); assertThatThrownBy(() -> execute(new Instruction(Opcodes.POP2))).hasMessage("POP2 on empty stack"); } @Test public void test_pop2_long_double() throws Exception { SymbolicValue normalSv = new SymbolicValue(); SymbolicValue longSv = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(normalSv).stackValue(longSv); startingState = setDoubleOrLong(startingState, longSv, true); ProgramState programState = execute(new Instruction(Opcodes.POP2), startingState); assertThat(programState.peekValue()).isEqualTo(normalSv); } @Test public void test_new() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.NEW, "java.lang.Object")); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL, new TypedConstraint("java.lang.Object") } }); } @Test public void test_dup() throws Exception { SymbolicValue sv = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP), ProgramState.EMPTY_STATE.stackValue(sv)); ProgramState.Pop pop = programState.unstackValue(2); assertThat(pop.values).containsOnly(sv); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.DUP))).hasMessage("DUP on empty stack"); } @Test public void test_dup_x1() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP_X1), ProgramState.EMPTY_STATE.stackValue(sv3).stackValue(sv2).stackValue(sv1)); ProgramState.Pop pop = programState.unstackValue(4); assertThat(pop.values).containsExactly(sv1, sv2, sv1, sv3); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.DUP_X1), ProgramState.EMPTY_STATE.stackValue(sv1))) .hasMessage("DUP_X1 needs 2 values on stack"); } @Test public void test_dup_x2() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP_X2), ProgramState.EMPTY_STATE.stackValue(sv3).stackValue(sv2).stackValue(sv1)); ProgramState.Pop pop = programState.unstackValue(4); assertThat(pop.values).containsExactly(sv1, sv2, sv3, sv1); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.DUP_X2), ProgramState.EMPTY_STATE.stackValue(sv1).stackValue(sv2))) .hasMessage("DUP_X2 needs 3 values on stack"); } @Test public void test_dup_x2_long_double() throws Exception { SymbolicValue normalSv = new SymbolicValue(); SymbolicValue longSv = new SymbolicValue(); SymbolicValue another = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(another).stackValue(longSv) .stackValue(normalSv); startingState = setDoubleOrLong(startingState, longSv, true); ProgramState programState = execute(new Instruction(Opcodes.DUP_X2), startingState); ProgramState.Pop pop = programState.unstackValue(4); assertThat(pop.values).containsExactly(normalSv, longSv, normalSv, another); } @Test public void test_dup2() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP2), ProgramState.EMPTY_STATE.stackValue(sv2).stackValue(sv1)); ProgramState.Pop pop = programState.unstackValue(4); assertThat(pop.values).containsExactly(sv1, sv2, sv1, sv2); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.DUP2))) .hasMessage("DUP2 needs at least 1 value on stack"); } @Test public void test_dup2_long_double() throws Exception { SymbolicValue longSv = new SymbolicValue(); SymbolicValue another = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(another).stackValue(longSv); startingState = setDoubleOrLong(startingState, longSv, true); ProgramState programState = execute(new Instruction(Opcodes.DUP2), startingState); ProgramState.Pop pop = programState.unstackValue(4); assertThat(pop.values).containsExactly(longSv, longSv, another); } @Test public void test_dup2_x1() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X1), ProgramState.EMPTY_STATE.stackValue(sv3).stackValue(sv2).stackValue(sv1)); ProgramState.Pop pop = programState.unstackValue(5); assertThat(pop.values).containsExactly(sv1, sv2, sv3, sv1, sv2); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy( () -> execute(new Instruction(Opcodes.DUP2_X1), ProgramState.EMPTY_STATE.stackValue(sv1))) .hasMessage("DUP2_X1 needs 3 values on stack"); } @Test public void test_dup2_x1_long_double() throws Exception { SymbolicValue normalSv = new SymbolicValue(); SymbolicValue longSv = new SymbolicValue(); SymbolicValue another = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(another).stackValue(normalSv) .stackValue(longSv); startingState = setDoubleOrLong(startingState, longSv, true); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X1), startingState); ProgramState.Pop pop = programState.unstackValue(5); assertThat(pop.values).containsExactly(longSv, normalSv, longSv, another); } @Test public void test_swap() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.SWAP), ProgramState.EMPTY_STATE.stackValue(sv1).stackValue(sv2)); ProgramState.Pop pop = programState.unstackValue(2); assertThat(pop.values).containsExactly(sv1, sv2); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.SWAP), ProgramState.EMPTY_STATE.stackValue(sv1))) .hasMessage("SWAP needs 2 values on stack"); } @Test public void test_dup2_x2() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); SymbolicValue sv4 = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X2), ProgramState.EMPTY_STATE.stackValue(sv4).stackValue(sv3).stackValue(sv2).stackValue(sv1)); ProgramState.Pop pop = programState.unstackValue(6); assertThat(pop.values).containsExactly(sv1, sv2, sv3, sv4, sv1, sv2); assertThat(pop.state).isEqualTo(ProgramState.EMPTY_STATE); assertThatThrownBy(() -> execute(new Instruction(Opcodes.DUP2_X2), ProgramState.EMPTY_STATE.stackValue(sv1).stackValue(sv2).stackValue(sv3))) .hasMessage("DUP2_X2 needs 4 values on stack"); } // see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.dup2_x2 @Test public void test_dup2_x2_form2() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); SymbolicValue sv4 = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(sv4).stackValue(sv3).stackValue(sv2) .stackValue(sv1); startingState = setDoubleOrLong(startingState, sv1, true); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X2), startingState); ProgramState.Pop pop = programState.unstackValue(6); assertThat(pop.values).containsExactly(sv1, sv2, sv3, sv1, sv4); } @Test public void test_dup2_x2_form3() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); SymbolicValue sv4 = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(sv4).stackValue(sv3).stackValue(sv2) .stackValue(sv1); startingState = setDoubleOrLong(startingState, sv3, true); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X2), startingState); ProgramState.Pop pop = programState.unstackValue(6); assertThat(pop.values).containsExactly(sv1, sv2, sv3, sv1, sv2, sv4); } @Test public void test_dup2_x2_form4() throws Exception { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); SymbolicValue sv3 = new SymbolicValue(); SymbolicValue sv4 = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(sv4).stackValue(sv3).stackValue(sv2) .stackValue(sv1); startingState = setDoubleOrLong(startingState, sv1, true); startingState = setDoubleOrLong(startingState, sv2, true); ProgramState programState = execute(new Instruction(Opcodes.DUP2_X2), startingState); ProgramState.Pop pop = programState.unstackValue(6); assertThat(pop.values).containsExactly(sv1, sv2, sv1, sv3, sv4); } @Test public void test_add_sub_mul_div_rem() throws Exception { int[] opcodes = new int[] { Opcodes.IADD, Opcodes.LADD, Opcodes.FADD, Opcodes.DADD, Opcodes.ISUB, Opcodes.LSUB, Opcodes.FSUB, Opcodes.DSUB, Opcodes.IMUL, Opcodes.LMUL, Opcodes.FMUL, Opcodes.DMUL, Opcodes.IDIV, Opcodes.LDIV, Opcodes.FDIV, Opcodes.DDIV, Opcodes.IREM, Opcodes.LREM, Opcodes.FREM, Opcodes.DREM, }; assertConsume2produceNotNull(opcodes); assertThrowWhenInvalidStack(opcodes, " needs 2 values on stack"); } @Test public void test_neg() throws Exception { SymbolicValue sv = new SymbolicValue(); int[] negOpcodes = new int[] { Opcodes.INEG, Opcodes.LNEG, Opcodes.FNEG, Opcodes.DNEG }; ProgramState initState = ProgramState.EMPTY_STATE.stackValue(sv); for (int negOpcode : negOpcodes) { ProgramState programState = execute(new Instruction(negOpcode), initState); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL } }); assertThat(programState.peekValue()).isNotEqualTo(sv); } for (int opcode : negOpcodes) { assertThatThrownBy(() -> execute(new Instruction(opcode), ProgramState.EMPTY_STATE)) .hasMessage(Printer.OPCODES[opcode] + " needs 1 values on stack"); } } @Test public void test_shift() throws Exception { int[] shiftOpcodes = new int[] { Opcodes.ISHL, Opcodes.LSHL, Opcodes.ISHR, Opcodes.LSHR, Opcodes.IUSHR, Opcodes.LUSHR }; assertConsume2produceNotNull(shiftOpcodes); assertThrowWhenInvalidStack(shiftOpcodes, " needs 2 values on stack"); } @Test public void test_and() throws Exception { int[] opcodes = new int[] { Opcodes.IAND, Opcodes.LAND }; assertBinarySymbolicValue(opcodes, SymbolicValue.AndSymbolicValue.class); assertThrowWhenInvalidStack(opcodes, " needs 2 values on stack"); } private void assertThrowWhenInvalidStack(int[] opcodes, String message) { for (int opcode : opcodes) { assertThatThrownBy(() -> execute(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()))) .hasMessage(Printer.OPCODES[opcode] + message); } } private void assertBinarySymbolicValue(int[] opcodes, Class<? extends BinarySymbolicValue> binarySvClass) { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); ProgramState initState = ProgramState.EMPTY_STATE.stackValue(sv2).stackValue(sv1); for (int opcode : opcodes) { ProgramState programState = execute(new Instruction(opcode), initState); ProgramState.Pop pop = programState.unstackValue(1); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL } }); SymbolicValue result = pop.values.get(0); assertThat(result).isNotEqualTo(sv1); assertThat(result).isNotEqualTo(sv2); assertThat(result).isInstanceOf(binarySvClass); assertThat(isDoubleOrLong(programState, result)).isEqualTo(LONG_OPCODE.contains(opcode)); BinarySymbolicValue andSv = (BinarySymbolicValue) result; assertThat(andSv.getRightOp()).isEqualTo(sv1); assertThat(andSv.getLeftOp()).isEqualTo(sv2); } } @Test public void test_or() throws Exception { int[] opcodes = new int[] { Opcodes.IOR, Opcodes.LOR }; assertBinarySymbolicValue(opcodes, SymbolicValue.OrSymbolicValue.class); assertThrowWhenInvalidStack(opcodes, " needs 2 values on stack"); } @Test public void test_xor() throws Exception { int[] opcodes = new int[] { Opcodes.IXOR, Opcodes.LXOR }; assertBinarySymbolicValue(opcodes, SymbolicValue.XorSymbolicValue.class); assertThrowWhenInvalidStack(opcodes, " needs 2 values on stack"); } @Test public void test_iinc() throws Exception { SymbolicValue sv = new SymbolicValue(); ProgramState programState = ProgramState.EMPTY_STATE.put(2, sv); programState = execute(new Instruction(Opcodes.IINC, 2), programState); SymbolicValue result = programState.getValue(2); assertThat(result).isNotEqualTo(sv); assertThat(programState.getConstraint(result, ObjectConstraint.class)).isEqualTo(ObjectConstraint.NOT_NULL); assertEmptyStack(programState); assertThatThrownBy(() -> execute(new Instruction(Opcodes.IINC, 1), ProgramState.EMPTY_STATE)) .hasMessage("Local variable 1 not found"); } @Test public void test_cmp() throws Exception { int[] opcodes = new int[] { Opcodes.LCMP, Opcodes.FCMPG, Opcodes.FCMPL, Opcodes.DCMPG, Opcodes.FCMPL }; assertConsume2produceNotNull(opcodes); assertThrowWhenInvalidStack(opcodes, " needs 2 values on stack"); } private void assertConsume2produceNotNull(int... opcodes) { SymbolicValue sv1 = new SymbolicValue(); SymbolicValue sv2 = new SymbolicValue(); ProgramState initState = ProgramState.EMPTY_STATE.stackValue(sv2).stackValue(sv1); for (int opcode : opcodes) { ProgramState programState = execute(new Instruction(opcode), initState); ProgramState.Pop pop = programState.unstackValue(1); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL } }); SymbolicValue result = pop.values.get(0); assertThat(result).isNotEqualTo(sv1); assertThat(result).isNotEqualTo(sv2); assertThat(isDoubleOrLong(programState, result)).isEqualTo(LONG_OPCODE.contains(opcode)); } } @Test public void test_invoke_instance_method() throws Exception { int[] opcodes = new int[] { Opcodes.INVOKESPECIAL, Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE }; for (int opcode : opcodes) { SymbolicValue thisSv = new SymbolicValue(); ProgramState stateWithThis = ProgramState.EMPTY_STATE.stackValue(thisSv); ProgramState programState = execute(invokeMethod(opcode, "methodWithoutArgument", "()V"), stateWithThis); assertEmptyStack(programState); assertThat(programState.getConstraints(thisSv).get(ObjectConstraint.class)) .isEqualTo(ObjectConstraint.NOT_NULL); programState = execute(invokeMethod(opcode, "finalVoid", "()V"), stateWithThis); assertEmptyStack(programState); assertThat(programState.getConstraints(thisSv).get(ObjectConstraint.class)) .isEqualTo(ObjectConstraint.NOT_NULL); programState = execute(invokeMethod(opcode, "booleanMethod", "()Z"), stateWithThis); assertStack(programState, new Constraint[] { null }); assertThat(isDoubleOrLong(programState, programState.peekValue())).isFalse(); SymbolicValue arg = new SymbolicValue(); programState = execute(invokeMethod(opcode, "intMethodWithIntArgument", "(I)I"), stateWithThis.stackValue(arg)); assertStack(programState, new Constraint[] { null }); assertThat(programState.peekValue()).isNotEqualTo(arg); programState = execute(invokeMethod(opcode, "methodWithIntIntArgument", "(II)V"), stateWithThis.stackValue(arg).stackValue(arg)); assertEmptyStack(programState); assertThatThrownBy( () -> execute(invokeMethod(opcode, "methodWithIntIntArgument", "(II)V"), stateWithThis)) .isInstanceOf(IllegalStateException.class); programState = execute(invokeMethod(opcode, "returningLong", "()J"), stateWithThis); assertThat(isDoubleOrLong(programState, programState.peekValue())).isTrue(); programState = execute(invokeMethod(opcode, "returningDouble", "()D"), stateWithThis); assertThat(isDoubleOrLong(programState, programState.peekValue())).isTrue(); } } @Test public void test_invoke_static() throws Exception { ProgramState programState = execute(invokeStatic("staticMethod", "()V")); assertEmptyStack(programState); programState = execute(invokeStatic("staticBooleanMethod", "()Z")); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL, BooleanConstraint.FALSE } }); assertThat(isDoubleOrLong(programState, programState.peekValue())).isFalse(); SymbolicValue arg = new SymbolicValue(); programState = execute(invokeStatic("staticIntMethodWithIntArgument", "(I)I"), ProgramState.EMPTY_STATE.stackValue(arg)); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL, DivisionByZeroCheck.ZeroConstraint.ZERO } }); assertThat(programState.peekValue()).isNotEqualTo(arg); assertThat(isDoubleOrLong(programState, programState.peekValue())).isFalse(); programState = execute(invokeStatic("staticMethodWithIntIntArgument", "(II)V"), ProgramState.EMPTY_STATE.stackValue(arg).stackValue(arg)); assertEmptyStack(programState); programState = execute(invokeStatic("staticMethodWithIntIntArgument", "(II)V"), ProgramState.EMPTY_STATE.stackValue(arg).stackValue(arg)); assertEmptyStack(programState); programState = execute(invokeStatic("staticReturningLong", "()J"), ProgramState.EMPTY_STATE); assertThat(isDoubleOrLong(programState, programState.peekValue())).isTrue(); programState = execute(invokeStatic("staticReturningDouble", "()D"), ProgramState.EMPTY_STATE); assertThat(isDoubleOrLong(programState, programState.peekValue())).isTrue(); assertThatThrownBy(() -> execute(invokeStatic("staticBooleanMethodWithIntArgument", "(I)V"))) .hasMessage("Arguments mismatch for INVOKE"); } @Test public void test_athrow() throws Exception { SymbolicValue sv = new SymbolicValue(); Type exceptionType = semanticModel.getClassType("java.lang.RuntimeException"); ProgramState initialState = ProgramState.EMPTY_STATE.stackValue(sv).addConstraint(sv, new TypedConstraint("java.lang.RuntimeException")); ProgramState programState = execute(new Instruction(Opcodes.ATHROW), initialState); SymbolicValue exception = programState.peekValue(); assertThat(exception).isInstanceOf(SymbolicValue.ExceptionalSymbolicValue.class); assertThat(((SymbolicValue.ExceptionalSymbolicValue) exception).exceptionType()).isEqualTo(exceptionType); assertThat(programState.exitValue()).isEqualTo(exception); } @Test public void test_nullness_check() throws Exception { SymbolicValue thisSv = new SymbolicValue(); ProgramState startingState = ProgramState.EMPTY_STATE.stackValue(thisSv); ProgramState programState = execute(invokeMethod(Opcodes.INVOKESPECIAL, "methodWithoutArgument", "()V"), startingState); assertThat(hasConstraint(thisSv, programState, ObjectConstraint.NOT_NULL)).isTrue(); programState = execute(invokeStatic("staticBooleanMethod", "()Z"), startingState); assertStack(programState, new Constraint[][] { { ObjectConstraint.NOT_NULL, BooleanConstraint.FALSE }, { null } }); programState = execute(invokeMethod(Opcodes.INVOKESPECIAL, "methodWithIntArgument", "(I)V"), startingState.stackValue(new SymbolicValue())); assertThat(hasConstraint(thisSv, programState, ObjectConstraint.NOT_NULL)).isTrue(); } @Test public void test_store() throws Exception { int[] storeOpcodes = new int[] { Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE }; SymbolicValue sv = new SymbolicValue(); ProgramState startState = ProgramState.EMPTY_STATE.stackValue(sv); for (int opcode : storeOpcodes) { ProgramState programState = execute(new Instruction(opcode, 67), startState); assertThat(programState.getValue(67)).isEqualTo(sv); } } @Test public void test_tableswitch() throws Exception { Instructions instr = new Instructions(); instr.visitVarInsn(ILOAD, 0); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); Label l3 = new Label(); instr.visitTableSwitchInsn(0, 2, l3, new Label[] { l0, l1, l2 }); instr.visitLabel(l0); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 1); instr.visitJumpInsn(GOTO, l3); instr.visitLabel(l1); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 2); instr.visitJumpInsn(GOTO, l3); instr.visitLabel(l2); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 3); instr.visitLabel(l3); instr.visitInsn(RETURN); BytecodeCFG cfg = instr.cfg(); CFG.IBlock<Instruction> entry = cfg.entry(); BytecodeEGWalker walker = new BytecodeEGWalker(null, null); walker.programState = ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()); walker.handleBlockExit(new ProgramPoint(entry)); assertThat(walker.workList).hasSize(entry.successors().size()); walker.workList.forEach(node -> { assertThat(node.programState.peekValue()).isNull(); assertThat(entry.successors().contains(node.programPoint.block)).isTrue(); }); } @Test public void test_lookupswitch() throws Exception { Instructions instr = new Instructions(); instr.visitVarInsn(ILOAD, 0); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); Label l3 = new Label(); instr.visitLookupSwitchInsn(l3, new int[] { 0, 1, 2, 50 }, new Label[] { l0, l1, l2, l3 }); instr.visitLabel(l0); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 1); instr.visitJumpInsn(GOTO, l3); instr.visitLabel(l1); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 2); instr.visitJumpInsn(GOTO, l3); instr.visitLabel(l2); instr.visitInsn(ICONST_0); instr.visitVarInsn(ISTORE, 3); instr.visitJumpInsn(GOTO, l3); instr.visitLabel(l3); instr.visitInsn(RETURN); BytecodeCFG cfg = instr.cfg(); CFG.IBlock<Instruction> entry = cfg.entry(); BytecodeEGWalker walker = new BytecodeEGWalker(null, null); walker.programState = ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()); walker.handleBlockExit(new ProgramPoint(entry)); assertThat(walker.workList).hasSize(entry.successors().size()); walker.workList.forEach(node -> { assertThat(node.programState.peekValue()).isNull(); assertThat(entry.successors().contains(node.programPoint.block)).isTrue(); }); } @Test public void test_getstatic() throws Exception { ProgramState programState = execute( new Instruction(Opcodes.GETSTATIC, new Instruction.FieldOrMethod("", "", "D", false))); assertThat(programState.peekValue()).isNotNull(); assertThat(isDoubleOrLong(programState, programState.peekValue())).isTrue(); programState = execute( new Instruction(Opcodes.GETSTATIC, new Instruction.FieldOrMethod("", "", "I", false))); assertThat(programState.peekValue()).isNotNull(); assertThat(isDoubleOrLong(programState, programState.peekValue())).isFalse(); } @Test public void test_putstatic() throws Exception { ProgramState programState = execute(new Instruction(Opcodes.PUTSTATIC), ProgramState.EMPTY_STATE.stackValue(new SymbolicValue())); assertThat(programState.peekValue()).isNull(); } @Test public void test_getfield() throws Exception { SymbolicValue objectRef = new SymbolicValue(); ProgramState programState = execute( new Instruction(Opcodes.GETFIELD, new Instruction.FieldOrMethod("", "", "D", false)), ProgramState.EMPTY_STATE.stackValue(objectRef)); SymbolicValue fieldValue = programState.peekValue(); assertThat(fieldValue).isNotNull(); assertThat(isDoubleOrLong(programState, fieldValue)).isTrue(); assertThat(fieldValue).isNotEqualTo(objectRef); programState = execute(new Instruction(Opcodes.GETFIELD, new Instruction.FieldOrMethod("", "", "I", false)), ProgramState.EMPTY_STATE.stackValue(objectRef)); fieldValue = programState.peekValue(); assertThat(fieldValue).isNotNull(); assertThat(isDoubleOrLong(programState, fieldValue)).isFalse(); assertThatThrownBy(() -> execute(new Instruction(Opcodes.GETFIELD))) .hasMessage("GETFIELD needs 1 values on stack"); } @Test public void test_putfield() throws Exception { SymbolicValue objectRef = new SymbolicValue(); SymbolicValue value = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.PUTFIELD), ProgramState.EMPTY_STATE.stackValue(objectRef).stackValue(value)); assertThat(programState.peekValue()).isNull(); assertThatThrownBy( () -> execute(new Instruction(Opcodes.PUTFIELD), ProgramState.EMPTY_STATE.stackValue(value))) .hasMessage("PUTFIELD needs 2 values on stack"); } @Test public void test_newarray() throws Exception { SymbolicValue size = new SymbolicValue(); int[] opcodes = { Opcodes.NEWARRAY, Opcodes.ANEWARRAY }; for (int opcode : opcodes) { ProgramState programState = execute(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(size)); assertThat(programState.peekValue()).isNotEqualTo(size); assertStack(programState, ObjectConstraint.NOT_NULL); assertThatThrownBy(() -> execute(new Instruction(opcode), ProgramState.EMPTY_STATE)) .hasMessage(Printer.OPCODES[opcode] + " needs 1 values on stack"); } } @Test public void test_arraylength() throws Exception { SymbolicValue arrayRef = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.ARRAYLENGTH), ProgramState.EMPTY_STATE.stackValue(arrayRef)); SymbolicValue length = programState.peekValue(); assertStack(programState, new Constraint[] { null }); assertThat(length).isNotEqualTo(arrayRef); assertThatThrownBy(() -> execute(new Instruction(Opcodes.ARRAYLENGTH), ProgramState.EMPTY_STATE)) .hasMessage("ARRAYLENGTH needs 1 values on stack"); } @Test public void test_checkcast() throws Exception { SymbolicValue objectRef = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.CHECKCAST), ProgramState.EMPTY_STATE.stackValue(objectRef)); assertThat(programState.peekValue()).isEqualTo(objectRef); assertThatThrownBy(() -> execute(new Instruction(Opcodes.CHECKCAST), ProgramState.EMPTY_STATE)) .hasMessage("CHECKCAST needs 1 value on stack"); } @Test public void test_instanceof() throws Exception { SymbolicValue sv = new SymbolicValue(); ProgramState programState = execute(new Instruction(Opcodes.INSTANCEOF), ProgramState.EMPTY_STATE.stackValue(sv)); SymbolicValue result = programState.peekValue(); assertThat(result).isInstanceOf(SymbolicValue.InstanceOfSymbolicValue.class); assertThat(result.computedFrom().get(0)).isEqualTo(sv); assertThatThrownBy(() -> execute(new Instruction(Opcodes.INSTANCEOF))) .hasMessage("INSTANCEOF needs 1 values on stack"); } @Test public void test_monitor_enter_exit() throws Exception { int opcodes[] = { Opcodes.MONITORENTER, Opcodes.MONITOREXIT }; for (int opcode : opcodes) { ProgramState programState = execute(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(new SymbolicValue())); assertEmptyStack(programState); assertThatThrownBy(() -> execute(new Instruction(opcode))) .hasMessage(Printer.OPCODES[opcode] + " needs 1 values on stack"); } } @Test public void test_multianewarray() throws Exception { ProgramState programState = execute(new Instruction.MultiANewArrayInsn("B", 1), ProgramState.EMPTY_STATE.stackValue(new SymbolicValue())); assertStack(programState, ObjectConstraint.NOT_NULL); programState = execute(new Instruction.MultiANewArrayInsn("B", 2), ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()).stackValue(new SymbolicValue())); assertStack(programState, ObjectConstraint.NOT_NULL); assertThatThrownBy(() -> execute(new Instruction.MultiANewArrayInsn("B", 2))) .hasMessage("MULTIANEWARRAY needs 2 values on stack"); } @Test public void test_invoke_dynamic() throws Exception { SymbolicValue lambdaArg = new SymbolicValue(); ProgramState programState = execute(new Instruction.InvokeDynamicInsn("(I)Ljava/util/function/Supplier;"), ProgramState.EMPTY_STATE.stackValue(lambdaArg)); assertStack(programState, ObjectConstraint.NOT_NULL); assertThat(programState.peekValue()).isNotEqualTo(lambdaArg); programState = execute(new Instruction.InvokeDynamicInsn("()Ljava/util/function/Supplier;"), ProgramState.EMPTY_STATE); assertStack(programState, ObjectConstraint.NOT_NULL); assertThatThrownBy(() -> execute(new Instruction.InvokeDynamicInsn("()V"), ProgramState.EMPTY_STATE)) .hasMessage("Lambda should always evaluate to target functional interface"); } @Test public void test_compare_with_zero() { SymbolicValue sv = new SymbolicValue(); int[] opcodes = { Opcodes.IFEQ, Opcodes.IFNE, Opcodes.IFLT, Opcodes.IFGE }; for (int opcode : opcodes) { ProgramState programState = walker.branchingState(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(sv)); RelationalSymbolicValue relSV = (RelationalSymbolicValue) programState.peekValue(); assertThat(relSV.getLeftOp()).isSameAs(sv); assertThat(relSV.getRightOp()).isNotSameAs(sv); assertThat(programState.getConstraints(relSV.getRightOp()) .hasConstraint(DivisionByZeroCheck.ZeroConstraint.ZERO)).isTrue(); } // these opcodes inverse operator and swap operands int[] swapOperandsOpcodes = { Opcodes.IFLE, Opcodes.IFGT }; for (int opcode : swapOperandsOpcodes) { ProgramState programState = walker.branchingState(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(sv)); RelationalSymbolicValue relSV = (RelationalSymbolicValue) programState.peekValue(); assertThat(relSV.getRightOp()).isSameAs(sv); assertThat(relSV.getLeftOp()).isNotSameAs(sv); assertThat(programState.getConstraints(relSV.getLeftOp()) .hasConstraint(DivisionByZeroCheck.ZeroConstraint.ZERO)).isTrue(); } } @Test public void test_compare_with_null() { SymbolicValue sv = new SymbolicValue(); int[] opcodes = { Opcodes.IFNULL, Opcodes.IFNONNULL }; for (int opcode : opcodes) { ProgramState programState = walker.branchingState(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(sv)); RelationalSymbolicValue relSV = (RelationalSymbolicValue) programState.peekValue(); assertThat(relSV.getLeftOp()).isSameAs(sv); assertThat(relSV.getRightOp()).isSameAs(SymbolicValue.NULL_LITERAL); } } @Test public void test_compare_instructions() { int[] opcodes = { Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE, Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE }; SymbolicValue left = new SymbolicValue(); SymbolicValue right = new SymbolicValue(); for (int opcode : opcodes) { ProgramState programState = walker.branchingState(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(left).stackValue(right)); RelationalSymbolicValue relSV = (RelationalSymbolicValue) programState.peekValue(); assertThat(relSV.getLeftOp()).isSameAs(left); assertThat(relSV.getRightOp()).isSameAs(right); } // these opcodes inverse operator and swap operands int[] swapOperandsOpcodes = { Opcodes.IF_ICMPLE, Opcodes.IF_ICMPGT }; for (int opcode : swapOperandsOpcodes) { ProgramState programState = walker.branchingState(new Instruction(opcode), ProgramState.EMPTY_STATE.stackValue(left).stackValue(right)); RelationalSymbolicValue relSV = (RelationalSymbolicValue) programState.peekValue(); assertThat(relSV.getRightOp()).isSameAs(left); assertThat(relSV.getLeftOp()).isSameAs(right); } } @Test public void test_invalid_branch_instruction() { assertThatThrownBy(() -> walker.branchingState(new Instruction(Opcodes.GOTO), ProgramState.EMPTY_STATE)) .isInstanceOf(IllegalStateException.class); } @Test public void test_conversion() throws Exception { int[] toLongOrDouble = { Opcodes.I2D, Opcodes.I2L, Opcodes.F2D, Opcodes.F2L }; for (int opcode : toLongOrDouble) { SymbolicValue sv = new SymbolicValue(); ProgramState initialPs = ProgramState.EMPTY_STATE.stackValue(sv); ProgramState ps = execute(new Instruction(opcode), initialPs); assertThat(isDoubleOrLong(ps, sv)).isTrue(); assertThatThrownBy(() -> execute(new Instruction(opcode))) .hasMessage(Printer.OPCODES[opcode] + " needs value on stack"); } int[] fromLongOrDouble = { Opcodes.D2F, Opcodes.D2I, Opcodes.L2F, Opcodes.L2I }; for (int opcode : fromLongOrDouble) { SymbolicValue sv = new SymbolicValue(); ProgramState initialPs = ProgramState.EMPTY_STATE.stackValue(sv); initialPs = setDoubleOrLong(initialPs, sv, true); ProgramState ps = execute(new Instruction(opcode), initialPs); assertThat(isDoubleOrLong(ps, sv)).isFalse(); assertThatThrownBy(() -> execute(new Instruction(opcode))) .hasMessage(Printer.OPCODES[opcode] + " needs value on stack"); } } private boolean hasConstraint(SymbolicValue sv, ProgramState ps, Constraint constraint) { ConstraintsByDomain constraints = ps.getConstraints(sv); return constraints != null && constraints.get(constraint.getClass()) == constraint; } private Instruction invokeStatic(String methodName, String desc) { return invokeMethod(Opcodes.INVOKESTATIC, methodName, desc); } private Instruction invokeMethod(int opcode, String methodName, String desc) { return new Instruction(opcode, new Instruction.FieldOrMethod( "org/sonar/java/bytecode/se/BytecodeEGWalkerExecuteTest", methodName, desc, false)); } private void assertStack(ProgramState ps, Constraint... constraints) { Constraint[][] cs = new Constraint[constraints.length][1]; int i = 0; for (Constraint constraint : constraints) { cs[i] = new Constraint[] { constraint }; i++; } assertStack(ps, cs); } private void assertStack(ProgramState ps, Constraint[]... constraints) { ProgramState.Pop pop = ps.unstackValue(constraints.length); assertEmptyStack(pop.state); assertThat(pop.valuesAndSymbols).hasSize(constraints.length); List<SymbolicValue> symbolicValues = pop.values; int idx = 0; for (SymbolicValue sv : symbolicValues) { ConstraintsByDomain constraintsByDomain = ps.getConstraints(sv); if (constraintsByDomain != null) { constraintsByDomain = constraintsByDomain .remove(BytecodeEGWalker.StackValueCategoryConstraint.class); } for (Constraint expectedConstraint : constraints[idx]) { if (expectedConstraint != null) { Class<? extends Constraint> expectedConstraintDomain = expectedConstraint.getClass(); Constraint constraint = constraintsByDomain.get(expectedConstraintDomain); assertThat(constraint).isEqualTo(expectedConstraint); constraintsByDomain = constraintsByDomain.remove(expectedConstraintDomain); } else { assertThat(constraintsByDomain).isNull(); } } if (constraintsByDomain != null) { assertThat(constraintsByDomain.isEmpty()).isTrue(); } idx++; } } private void assertEmptyStack(ProgramState programState) { assertThat(programState.peekValue()).isNull(); } private ProgramState execute(Instruction instruction) { return execute(instruction, ProgramState.EMPTY_STATE); } private ProgramState execute(Instruction instruction, ProgramState startingState) { CFG.IBlock block = mock(CFG.IBlock.class); when(block.successors()).thenReturn(Collections.emptySet()); walker.programPosition = new ProgramPoint(block); walker.programState = startingState; walker.workList.clear(); walker.executeInstruction(instruction); ProgramState programState = walker.programState; // X-PROC will enqueue new nodes if (instruction.isInvoke() && !walker.workList.isEmpty()) { programState = walker.workList.getFirst().programState; } return programState; } private static boolean isDoubleOrLong(ProgramState programState, SymbolicValue sv) { return programState.getConstraint(sv, BytecodeEGWalker.StackValueCategoryConstraint.class) == BytecodeEGWalker.StackValueCategoryConstraint.LONG_OR_DOUBLE; } private static ProgramState setDoubleOrLong(ProgramState programState, SymbolicValue sv, boolean value) { if (value) { return programState.addConstraint(sv, BytecodeEGWalker.StackValueCategoryConstraint.LONG_OR_DOUBLE); } else { return programState.removeConstraintsOnDomain(sv, BytecodeEGWalker.StackValueCategoryConstraint.class); } } @Test public void test_enqueuing_only_happy_path() { BytecodeCFG cfg = SETestUtils.bytecodeCFG(TRY_CATCH_SIGNATURE, squidClassLoader); BytecodeCFG.Block b2 = cfg.blocks().get(2); walker.workList.clear(); walker.programState = ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()); walker.handleBlockExit(new ProgramPoint(b2)); assertThat(walker.workList).hasSize(1); assertThat(walker.workList.getFirst().programPoint.block.id()).isEqualTo(3); } @Test public void test_enqueuing_exceptional_yields() { BytecodeCFG cfg = SETestUtils.bytecodeCFG(TRY_CATCH_SIGNATURE, squidClassLoader); BytecodeCFG.Block b2 = cfg.blocks().get(2); walker.programState = ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()) .stackValue(new SymbolicValue()); walker.programPosition = new ProgramPoint(b2).next().next(); walker.executeInstruction(b2.elements().get(3)); assertThat(walker.workList).hasSize(4); } @Test public void test_enqueuing_exceptional_yields2() { BytecodeCFG cfg = SETestUtils.bytecodeCFG(TRY_WRONG_CATCH_SIGNATURE, squidClassLoader); BytecodeCFG.Block b2 = cfg.blocks().get(2); walker.programState = ProgramState.EMPTY_STATE.stackValue(new SymbolicValue()) .stackValue(new SymbolicValue()); walker.programPosition = new ProgramPoint(b2).next().next(); walker.executeInstruction(b2.elements().get(3)); assertThat(walker.workList).hasSize(3); assertThat(walker.workList.pop().programState.exitValue()).isNotNull() .isInstanceOf(SymbolicValue.ExceptionalSymbolicValue.class) .extracting( sv -> ((SymbolicValue.ExceptionalSymbolicValue) sv).exceptionType().fullyQualifiedName()) .containsExactly("java.lang.IllegalStateException"); assertThat(walker.workList.pop().programState.exitValue()).isNull(); } @Test public void test_switch_enqueuing_in_trycatch() throws Exception { Instructions mv = new Instructions(); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception"); mv.visitLabel(l0); mv.visitVarInsn(ILOAD, 1); mv.visitLookupSwitchInsn(l1, new int[] { 0 }, new Label[] { l1 }); mv.visitLabel(l1); Label l3 = new Label(); mv.visitJumpInsn(GOTO, l3); mv.visitLabel(l2); mv.visitVarInsn(ASTORE, 2); mv.visitLabel(l3); mv.visitInsn(RETURN); BytecodeCFG cfg = mv.cfg(); BytecodeCFG.Block switchBlock = cfg.blocks().get(2); assertThat(switchBlock.terminator().opcode).isEqualTo(Opcodes.LOOKUPSWITCH); walker.programState = ProgramState.EMPTY_STATE; walker.handleBlockExit(new ProgramPoint(switchBlock)); assertThat(walker.workList).hasSize(1); } @Test public void test_goto_enqueuing_in_trycatch() throws Exception { Instructions mv = new Instructions(); /* void test_goto(int i) { try { switch (i) { case 0: i = 1; // GOTO within try-catch break; case 1: i = 2; break; } i = 3; } catch (Exception e) { i = 4; } } */ Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception"); mv.visitLabel(l0); mv.visitVarInsn(ILOAD, 1); Label l3 = new Label(); Label l4 = new Label(); Label l5 = new Label(); mv.visitLookupSwitchInsn(l5, new int[] { 0, 1 }, new Label[] { l3, l4 }); mv.visitLabel(l3); mv.visitInsn(ICONST_1); mv.visitVarInsn(ISTORE, 1); mv.visitJumpInsn(GOTO, l5); // tested GOTO instruction mv.visitLabel(l4); mv.visitInsn(ICONST_2); mv.visitVarInsn(ISTORE, 1); mv.visitLabel(l5); mv.visitInsn(ICONST_3); mv.visitVarInsn(ISTORE, 1); mv.visitLabel(l1); Label l6 = new Label(); mv.visitJumpInsn(GOTO, l6); mv.visitLabel(l2); mv.visitVarInsn(ASTORE, 2); mv.visitInsn(ICONST_4); mv.visitVarInsn(ISTORE, 1); mv.visitLabel(l6); mv.visitInsn(RETURN); BytecodeCFG cfg = mv.cfg(); BytecodeCFG.Block gotoBlock = cfg.blocks().get(4); assertThat(gotoBlock.terminator().opcode).isEqualTo(GOTO); walker.programState = ProgramState.EMPTY_STATE; walker.handleBlockExit(new ProgramPoint(gotoBlock)); assertThat(walker.workList).hasSize(1); } @Test public void test_analysis_failure() throws Exception { BytecodeEGWalker walkerSpy = spy(walker); IllegalStateException ex = new IllegalStateException(); doThrow(ex).when(walkerSpy).executeInstruction(any()); assertThatThrownBy(() -> walkerSpy.getMethodBehavior("java.lang.String#valueOf(Z)Ljava/lang/String;", squidClassLoader)).isInstanceOf(BytecodeEGWalker.BytecodeAnalysisException.class) .hasMessage("Failed dataflow analysis for java.lang.String#valueOf(Z)Ljava/lang/String;") .hasCause(ex); } @Test public void method_returning_new_should_have_not_null_result() { MethodBehavior mb = walker.getMethodBehavior( BytecodeEGWalkerExecuteTest.class.getCanonicalName() + "#newObject()Ljava/lang/Object;", squidClassLoader); List<MethodYield> yields = mb.yields(); assertThat(yields).hasSize(1); MethodYield yield = yields.get(0); assertThat(yield).isInstanceOf(HappyPathYield.class); ConstraintsByDomain resultConstraint = ((HappyPathYield) yield).resultConstraint(); assertThat(resultConstraint).isNotNull(); assertThat(resultConstraint.get(ObjectConstraint.class)).isEqualTo(ObjectConstraint.NOT_NULL); TypedConstraint typeConstraint = (TypedConstraint) resultConstraint.get(TypedConstraint.class); assertThat(typeConstraint.type.equals("java.lang.String")).isTrue(); } @Test public void behavior_should_have_declared_exceptions() { MethodBehavior mb = walker.getMethodBehavior( BytecodeEGWalkerExecuteTest.class.getCanonicalName() + "#throwing()V", squidClassLoader); assertThat(mb.isComplete()).isFalse(); assertThat(mb.getDeclaredExceptions()).containsExactly("java.io.IOException"); } @Test public void exceptional_paths_should_be_enqueued() { MethodBehavior mb = walker.getMethodBehavior(BytecodeEGWalkerExecuteTest.class.getCanonicalName() + "#enqueue_exceptional_paths(Lorg/sonar/java/bytecode/se/BytecodeEGWalkerExecuteTest;)Ljava/lang/Object;", squidClassLoader); assertThat(mb.yields()).hasSize(2); List<Constraint> resultConstraints = mb.yields().stream().map(y -> ((HappyPathYield) y).resultConstraint()) .map(c -> c.get(ObjectConstraint.class)).collect(Collectors.toList()); assertThat(resultConstraints).contains(ObjectConstraint.NOT_NULL, ObjectConstraint.NULL); } /** * ---------------- used by test checking methods --------- */ static void staticMethod() { } static boolean staticBooleanMethod() { return false; } static int staticIntMethodWithIntArgument(int i) { return 0; } static void staticMethodWithIntIntArgument(int i1, int i2) { } static boolean staticBooleanMethodWithIntArgument(int i) { return true; } void methodWithoutArgument() { } void methodWithIntArgument(int i) { } boolean booleanMethod() { return false; } int intMethodWithIntArgument(int i) { return 0; } void methodWithIntIntArgument(int i1, int i2) { } static long staticReturningLong() { return 1L; } static double staticReturningDouble() { return 1.0d; } long returningLong() { return 1L; } double returningDouble() { return 1.0d; } static Object returnArg(Object o) { return o; } final void finalVoid() { } static Object newObject() { return new String(); } private void tryCatch(boolean param) { try { staticBooleanMethod(); Preconditions.checkState(param); } catch (IllegalStateException e) { e.printStackTrace(); } finally { System.out.println("finally"); } } private void tryWrongCatch(boolean param) { try { staticBooleanMethod(); Preconditions.checkState(param); } catch (IllegalArgumentException e) { e.printStackTrace(); } finally { System.out.println("finally"); } } void throwing() throws IOException { } static Object enqueue_exceptional_paths(BytecodeEGWalkerExecuteTest o) { try { o.throwing(); return new Object(); } catch (IOException e) { return null; } } }