org.sonar.java.bytecode.cfg.BytecodeCFGConstructionTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.bytecode.cfg.BytecodeCFGConstructionTest.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2018 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.cfg;

import com.google.common.collect.ImmutableList;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.Printer;
import org.sonar.java.bytecode.cfg.Instruction.FieldOrMethod;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DSTORE;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FSTORE;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFGE;
import static org.objectweb.asm.Opcodes.IFGT;
import static org.objectweb.asm.Opcodes.IFLE;
import static org.objectweb.asm.Opcodes.IFLT;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static org.objectweb.asm.Opcodes.IF_ACMPNE;
import static org.objectweb.asm.Opcodes.IF_ICMPEQ;
import static org.objectweb.asm.Opcodes.IF_ICMPGE;
import static org.objectweb.asm.Opcodes.IF_ICMPGT;
import static org.objectweb.asm.Opcodes.IF_ICMPLE;
import static org.objectweb.asm.Opcodes.IF_ICMPLT;
import static org.objectweb.asm.Opcodes.IF_ICMPNE;
import static org.objectweb.asm.Opcodes.IINC;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INSTANCEOF;
import static org.objectweb.asm.Opcodes.INVOKEDYNAMIC;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.LDC;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LOOKUPSWITCH;
import static org.objectweb.asm.Opcodes.LSTORE;
import static org.objectweb.asm.Opcodes.MULTIANEWARRAY;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.NEWARRAY;
import static org.objectweb.asm.Opcodes.NOP;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RET;
import static org.objectweb.asm.Opcodes.SIPUSH;
import static org.objectweb.asm.Opcodes.TABLESWITCH;

@RunWith(Parameterized.class)
public class BytecodeCFGConstructionTest {

    public static final String JAVA_LANG_OBJECT = "java.lang.Object";

    @Parameters(name = "{0}")
    public static Collection<Object[]> data() {
        ImmutableList.Builder<Object[]> testData = ImmutableList.builder();

        // Instructions without operand
        testData.addAll(Instructions.NO_OPERAND_INSN.stream()
                .map(opcode -> new Object[] { new TestInput(opcode), new Instruction(opcode) })
                .collect(Collectors.toList()));

        // Instructions with int operand
        testData.add(new Object[] { intOp(BIPUSH, 1), inst(BIPUSH, 1) });
        testData.add(new Object[] { intOp(SIPUSH, 1), inst(SIPUSH, 1) });
        testData.add(new Object[] { intOp(NEWARRAY, 1), inst(NEWARRAY, 1) });

        // LOAD STORE
        testData.add(new Object[] { intOp(ILOAD, 42), inst(ILOAD, 42) });
        testData.add(new Object[] { intOp(LLOAD, 42), inst(LLOAD, 42) });
        testData.add(new Object[] { intOp(FLOAD, 42), inst(FLOAD, 42) });
        testData.add(new Object[] { intOp(DLOAD, 42), inst(DLOAD, 42) });
        testData.add(new Object[] { intOp(ALOAD, 42), inst(ALOAD, 42) });
        testData.add(new Object[] { intOp(ISTORE, 42), inst(ISTORE, 42) });
        testData.add(new Object[] { intOp(LSTORE, 42), inst(LSTORE, 42) });
        testData.add(new Object[] { intOp(FSTORE, 42), inst(FSTORE, 42) });
        testData.add(new Object[] { intOp(DSTORE, 42), inst(DSTORE, 42) });
        testData.add(new Object[] { intOp(ASTORE, 42), inst(ASTORE, 42) });
        testData.add(new Object[] { intOp(RET, 42), inst(RET, 42) });

        // Instructions with type argument
        testData.add(new Object[] { new TestInput(NEW, JAVA_LANG_OBJECT), inst(NEW, JAVA_LANG_OBJECT) });
        testData.add(
                new Object[] { new TestInput(ANEWARRAY, JAVA_LANG_OBJECT), inst(ANEWARRAY, JAVA_LANG_OBJECT) });
        testData.add(
                new Object[] { new TestInput(CHECKCAST, JAVA_LANG_OBJECT), inst(CHECKCAST, JAVA_LANG_OBJECT) });
        testData.add(
                new Object[] { new TestInput(INSTANCEOF, JAVA_LANG_OBJECT), inst(INSTANCEOF, JAVA_LANG_OBJECT) });

        // Instructions with field argument
        testData.add(new Object[] { new TestInput(GETSTATIC, JAVA_LANG_OBJECT, "field", ""),
                inst(GETSTATIC, JAVA_LANG_OBJECT, "field", "") });
        testData.add(new Object[] { new TestInput(PUTSTATIC, JAVA_LANG_OBJECT, "field", ""),
                inst(PUTSTATIC, JAVA_LANG_OBJECT, "field", "") });
        testData.add(new Object[] { new TestInput(GETFIELD, JAVA_LANG_OBJECT, "field", ""),
                inst(GETFIELD, JAVA_LANG_OBJECT, "field", "") });
        testData.add(new Object[] { new TestInput(PUTFIELD, JAVA_LANG_OBJECT, "field", ""),
                inst(PUTFIELD, JAVA_LANG_OBJECT, "field", "") });

        // Instructions with method argument
        testData.add(new Object[] { new TestInput(INVOKESPECIAL, JAVA_LANG_OBJECT, "hashCode", "()I", false),
                inst(INVOKESPECIAL, JAVA_LANG_OBJECT, "hashCode", "()I", false) });
        testData.add(new Object[] { new TestInput(INVOKESTATIC, JAVA_LANG_OBJECT, "hashCode", "()I", false),
                inst(INVOKESTATIC, JAVA_LANG_OBJECT, "hashCode", "()I", false) });
        testData.add(new Object[] { new TestInput(INVOKEVIRTUAL, JAVA_LANG_OBJECT, "hashCode", "()I", false),
                inst(INVOKEVIRTUAL, JAVA_LANG_OBJECT, "hashCode", "()I", false) });
        testData.add(new Object[] { new TestInput(INVOKEINTERFACE, JAVA_LANG_OBJECT, "hashCode", "()I", false),
                inst(INVOKEINTERFACE, JAVA_LANG_OBJECT, "hashCode", "()I", false) });

        // Jump instructions
        testData.add(new Object[] { new TestInput(IFEQ), null });
        testData.add(new Object[] { new TestInput(IFNE), null });
        testData.add(new Object[] { new TestInput(IFLT), null });
        testData.add(new Object[] { new TestInput(IFGE), null });
        testData.add(new Object[] { new TestInput(IFGT), null });
        testData.add(new Object[] { new TestInput(IFLE), null });
        testData.add(new Object[] { new TestInput(IF_ICMPEQ), null });
        testData.add(new Object[] { new TestInput(IF_ICMPNE), null });
        testData.add(new Object[] { new TestInput(IF_ICMPLT), null });
        testData.add(new Object[] { new TestInput(IF_ICMPGE), null });
        testData.add(new Object[] { new TestInput(IF_ICMPGT), null });
        testData.add(new Object[] { new TestInput(IF_ICMPLE), null });
        testData.add(new Object[] { new TestInput(IF_ACMPEQ), null });
        testData.add(new Object[] { new TestInput(IF_ACMPNE), null });
        testData.add(new Object[] { new TestInput(GOTO), null });
        testData.add(new Object[] { new TestInput(IFNULL), null });
        testData.add(new Object[] { new TestInput(IFNONNULL), null });

        // The rest
        testData.add(new Object[] { new TestInput(LDC), new Instruction.LdcInsn("a") });
        testData.add(new Object[] { new TestInput(IINC, 2), inst(IINC, 2) });
        testData.add(new Object[] { new TestInput(INVOKEDYNAMIC),
                new Instruction.InvokeDynamicInsn("()Ljava/util/function/Supplier;") });
        testData.add(new Object[] { new TestInput(TABLESWITCH), null });
        testData.add(new Object[] { new TestInput(LOOKUPSWITCH), null });
        testData.add(new Object[] { new TestInput(MULTIANEWARRAY), new Instruction.MultiANewArrayInsn("B", 2) });

        return testData.build();
    }

    private static TestInput intOp(int opcode, int operand) {
        return new TestInput(opcode, operand);
    }

    private static Instruction inst(int opcode) {
        return new Instruction(opcode);
    }

    private static Instruction inst(int opcode, int operand) {
        return new Instruction(opcode, operand);
    }

    private static Instruction inst(int opcode, String type) {
        return new Instruction(opcode, type);
    }

    private static Instruction inst(int opcode, String owner, String name, String desc) {
        return new Instruction(opcode, new FieldOrMethod(owner, name, desc));
    }

    private static Instruction inst(int opcode, String owner, String name, String desc, boolean itf) {
        return new Instruction(opcode, new FieldOrMethod(owner, name, desc, itf));
    }

    static class TestInput {
        int opcode;
        int operandOrVar;
        String type;
        FieldOrMethod fieldOrMethod;

        @Override
        public String toString() {
            return Printer.OPCODES[opcode];
        }

        TestInput(int opcode) {
            this.opcode = opcode;
        }

        TestInput(int opcode, int operandOrVar) {
            this.opcode = opcode;
            this.operandOrVar = operandOrVar;
        }

        TestInput(int opcode, String type) {
            this.opcode = opcode;
            this.type = type;
        }

        TestInput(int opcode, String owner, String name, String desc) {
            this(opcode, owner, name, desc, false);
        }

        TestInput(int opcode, String owner, String name, String desc, boolean itf) {
            this.opcode = opcode;
            this.fieldOrMethod = new FieldOrMethod(owner, name, desc, itf);
        }
    }

    private TestInput testInput;
    private Instruction expected;

    @BeforeClass
    public static void verifyTestData() {
        List<Integer> opcodes = data().stream().map(data -> ((TestInput) data[0]).opcode)
                .collect(Collectors.toList());
        assertThat(opcodes).containsAll(Instructions.OPCODES);
    }

    public BytecodeCFGConstructionTest(TestInput testInput, Instruction expected) {
        this.testInput = testInput;
        this.expected = expected;
    }

    @Test
    public void test() throws Exception {
        if (isJumpInstruction(testInput.opcode)) {
            test_jumps();
            return;
        }
        BytecodeCFG cfg = new Instructions().cfg(testInput.opcode, testInput.operandOrVar, testInput.type,
                testInput.fieldOrMethod);
        assertThat(cfg.blocks.size()).isEqualTo(2);
        if (expected == null) {
            expected = inst(testInput.opcode);
        }
        Instruction actual = cfg.blocks.get(1).instructions.get(0);
        assertThat(actual).isEqualTo(expected);
    }

    private static boolean isJumpInstruction(int opcode) {
        return Opcodes.IFEQ <= opcode && opcode <= LOOKUPSWITCH && opcode != RET || opcode == IFNULL
                || opcode == IFNONNULL;
    }

    private void test_jumps() {
        BytecodeCFG cfg = new Instructions().cfg(testInput.opcode);
        assertThat(expected).isNull();
        if (testInput.opcode == TABLESWITCH) {
            assertThat(cfg.blocks.size()).isEqualTo(5);
        } else if (testInput.opcode == LOOKUPSWITCH) {
            assertThat(cfg.blocks.size()).isEqualTo(5);
        } else {
            // exit block, jump block, jump-to block, other block
            BytecodeCFG.Block block1 = cfg.blocks.get(1);
            assertThat(block1.instructions).isEmpty();
            assertThat(block1.terminator().opcode()).isEqualTo(testInput.opcode);
            if (testInput.opcode == GOTO) {
                assertThat(cfg.blocks.size()).isEqualTo(3);
                assertThat(block1.successors).hasSize(1);
                assertThat(block1.trueSuccessor()).isNull();
                assertThat(block1.falseSuccessor()).isNull();
                assertThat(cfg.blocks.get(2).instructions).hasSize(2);
            } else {
                assertThat(cfg.blocks.size()).isEqualTo(4);
                assertThat(cfg.blocks.get(2).instructions.get(0).opcode()).isEqualTo(ICONST_0);
                assertThat(cfg.blocks.get(2).instructions.get(1).opcode()).isEqualTo(NOP);
                assertThat(cfg.blocks.get(3).instructions).isEmpty();
            }
        }
    }
}