All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eolang.jeo.representation.bytecode.BytecodeInstruction Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2024 Objectionary.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.eolang.jeo.representation.bytecode;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.eolang.jeo.representation.directives.DirectivesInstruction;
import org.eolang.jeo.representation.directives.OpcodeName;
import org.eolang.jeo.representation.xmir.AllLabels;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.xembly.Directive;

/**
 * Bytecode instruction.
 * @since 0.1
 * @checkstyle FileLengthCheck (2000 lines)
 */
@ToString
@EqualsAndHashCode
@SuppressWarnings({"PMD.ExcessiveClassLength", "PMD.GodClass", "PMD.TooManyMethods"})
public final class BytecodeInstruction implements BytecodeEntry {

    /**
     * Opcode.
     */
    private final int opcode;

    /**
     * Arguments.
     */
    private final List args;

    /**
     * All labels.
     */
    private final AllLabels labels;

    /**
     * Constructor.
     * @param opcode Opcode.
     * @param args Arguments.
     */
    public BytecodeInstruction(final int opcode, final Object... args) {
        this(opcode, Arrays.asList(args));
    }

    /**
     * Constructor.
     * @param opcode Opcode.
     * @param args Arguments.
     */
    public BytecodeInstruction(final int opcode, final List args) {
        this(new AllLabels(), opcode, args);
    }

    /**
     * Constructor.
     *
     * @param labels All labels.
     * @param opcode Opcode.
     * @param args Arguments.
     */
    BytecodeInstruction(
        final AllLabels labels,
        final int opcode,
        final Object... args
    ) {
        this(labels, opcode, Arrays.asList(args));
    }

    /**
     * Constructor.
     * @param labels All labels.
     * @param opcode Opcode.
     * @param args Arguments.
     */
    BytecodeInstruction(
        final AllLabels labels,
        final int opcode,
        final List args
    ) {
        this.labels = labels;
        this.opcode = opcode;
        this.args = args;
    }

    @Override
    public void writeTo(final MethodVisitor visitor) {
        Instruction.find(this.opcode).generate(visitor, this.args);
    }

    @Override
    public Iterable directives(final boolean counting) {
        return new DirectivesInstruction(this.opcode, counting, this.args.toArray());
    }

    @Override
    public boolean isLabel() {
        return false;
    }

    @Override
    public boolean isOpcode() {
        return true;
    }

    @ToString.Include
    @Override
    public String testCode() {
        return String.format(
            ".opcode(%s)",
            Stream.concat(
                Stream.of(String.format("Opcodes.%s", new OpcodeName(this.opcode).simplified())),
                this.args.stream().map(
                    arg -> {
                        final String result;
                        if (arg instanceof String) {
                            result = String.format("\"%s\"", arg);
                        } else if (arg instanceof Label) {
                            result = String.format(
                                "labels.label(\"%s\")",
                                this.labels.uid((Label) arg)
                            );
                        } else {
                            result = String.valueOf(arg);
                        }
                        return result;
                    }
                )
            ).collect(Collectors.joining(", "))
        );
    }

    /**
     * Impact of each instruction on the stack.
     * @return Stack impact.
     * @checkstyle CyclomaticComplexityCheck (350 lines)
     * @checkstyle MethodLengthCheck (350 lines)
     * @checkstyle JavaNCSSCheck (350 lines)
     * @checkstyle AvoidNestedBlocksCheck (350 lines)
     */
    @SuppressWarnings({"PMD.NcssCount", "PMD.ExcessiveMethodLength"})
    public int impact() {
        final int result;
        final Instruction instruction = Instruction.find(this.opcode);
        switch (instruction) {
            case LASTORE:
            case DASTORE:
                result = -4;
                break;
            case IASTORE:
            case FASTORE:
            case AASTORE:
            case BASTORE:
            case CASTORE:
            case SASTORE:
            case LCMP:
            case DCMPL:
            case DCMPG:
                result = -3;
                break;
            case LSTORE:
            case DSTORE:
            case POP2:
            case LADD:
            case LSUB:
            case LMUL:
            case LDIV:
            case LREM:
            case DADD:
            case DSUB:
            case DMUL:
            case DDIV:
            case DREM:
            case IF_ICMPEQ:
            case IF_ICMPNE:
            case IF_ICMPLT:
            case IF_ICMPGE:
            case IF_ICMPGT:
            case IF_ICMPLE:
            case IF_ACMPEQ:
            case IF_ACMPNE:
            case LOR:
            case LAND:
            case LXOR:
            case LRETURN:
            case DRETURN:
                result = -2;
                break;
            case IALOAD:
            case FALOAD:
            case AALOAD:
            case BALOAD:
            case CALOAD:
            case SALOAD:
            case IADD:
            case ISUB:
            case IMUL:
            case IDIV:
            case IREM:
            case FADD:
            case FSUB:
            case FMUL:
            case FDIV:
            case FREM:
            case LSHL:
            case LSHR:
            case LUSHR:
            case POP:
            case FCMPL:
            case FCMPG:
            case L2I:
            case L2F:
            case D2I:
            case D2F:
            case ISTORE:
            case FSTORE:
            case ASTORE:
            case IFEQ:
            case IFNE:
            case IFLT:
            case IFGE:
            case IFGT:
            case IFLE:
            case IFNULL:
            case IFNONNULL:
            case IOR:
            case IAND:
            case IXOR:
            case ISHL:
            case ISHR:
            case IUSHR:
            case IRETURN:
            case FRETURN:
            case ARETURN:
            case MONITORENTER:
            case MONITOREXIT:
            case TABLESWITCH:
            case LOOKUPSWITCH:
            case ATHROW:
                result = -1;
                break;
            case NOP:
            case SWAP:
            case I2F:
            case F2I:
            case I2B:
            case I2C:
            case I2S:
            case L2D:
            case D2L:
            case LALOAD:
            case DALOAD:
            case DNEG:
            case FNEG:
            case LNEG:
            case INEG:
            case GOTO:
            case JSR:
            case RET:
            case RETURN:
            case IINC:
            case NEWARRAY:
            case ANEWARRAY:
            case ARRAYLENGTH:
            case CHECKCAST:
            case INSTANCEOF:
                result = 0;
                break;
            case ACONST_NULL:
            case ICONST_M1:
            case ICONST_0:
            case ICONST_1:
            case ICONST_2:
            case ICONST_3:
            case ICONST_4:
            case ICONST_5:
            case FCONST_0:
            case FCONST_1:
            case FCONST_2:
            case BIPUSH:
            case SIPUSH:
            case ILOAD:
            case FLOAD:
            case ALOAD:
            case DUP:
            case DUP_X1:
            case DUP_X2:
            case I2L:
            case I2D:
            case F2L:
            case F2D:
            case NEW:
                result = 1;
                break;
            case LCONST_0:
            case LCONST_1:
            case DCONST_0:
            case DCONST_1:
            case LLOAD:
            case DLOAD:
            case DUP2:
            case DUP2_X1:
            case DUP2_X2:
                result = 2;
                break;
            case LDC: {
                final Class clazz = this.args.get(0).getClass();
                if (clazz == Long.class || clazz == Double.class) {
                    result = 2;
                    break;
                } else {
                    result = BytecodeInstruction.size(Type.getType(clazz));
                    break;
                }
            }
            case GETSTATIC:
                result = BytecodeInstruction.size(Type.getType(String.valueOf(this.args.get(2))));
                break;
            case PUTSTATIC:
                result = BytecodeInstruction.size(
                    Type.getType(String.valueOf(this.args.get(2)))
                ) * -1;
                break;
            case GETFIELD:
                result = BytecodeInstruction.size(
                    Type.getType(String.valueOf(this.args.get(2)))
                ) - 1;
                break;
            case PUTFIELD:
                result = BytecodeInstruction.size(
                    Type.getType(String.valueOf(this.args.get(2)))
                ) * -1 - 1;
                break;
            case INVOKEVIRTUAL:
            case INVOKESPECIAL:
            case INVOKEINTERFACE:
                result = BytecodeInstruction.methodImpact(String.valueOf(this.args.get(2))) - 1;
                break;
            case INVOKESTATIC:
                result = BytecodeInstruction.methodImpact(String.valueOf(this.args.get(2)));
                break;
            case INVOKEDYNAMIC:
                result = BytecodeInstruction.methodImpact(String.valueOf(this.args.get(1)));
                break;
            case MULTIANEWARRAY:
                result = -(int) (this.args.get(1)) + 1;
                break;
            default:
                throw new UnsupportedOperationException(
                    String.format(
                        "Unsupported opcode: %s", new OpcodeName(this.opcode).simplified()
                    )
                );
        }
        return result;
    }

    /**
     * Is this instruction a variable instruction?
     * @return True if it is.
     */
    boolean isVarInstruction() {
        return Instruction.find(this.opcode).isVarInstruction();
    }

    /**
     * Local variable index.
     * @return Local variable index.
     */
    int varIndex() {
        this.assertVarInstruction();
        return (int) this.args.get(0);
    }

    /**
     * Local variable size.
     * @return Local variable size.
     */
    int varSize() {
        this.assertVarInstruction();
        return Instruction.find(this.opcode).size();
    }

    /**
     * Is this instruction a jump instruction?
     * @return True if it is.
     */
    boolean isJump() {
        return Instruction.find(this.opcode) == Instruction.GOTO;
    }

    /**
     * Is this instruction a conditional branch instruction?
     * @return True if it is.
     * @checkstyle CyclomaticComplexityCheck (100 lines)
     */
    boolean isBranch() {
        final boolean result;
        switch (Instruction.find(this.opcode)) {
            case IFEQ:
            case IFNE:
            case IFLT:
            case IFGE:
            case IFGT:
            case IFLE:
            case IF_ICMPEQ:
            case IF_ICMPNE:
            case IF_ICMPLT:
            case IF_ICMPGE:
            case IF_ICMPGT:
            case IF_ICMPLE:
            case IF_ACMPEQ:
            case IF_ACMPNE:
            case IFNULL:
            case IFNONNULL:
                result = true;
                break;
            default:
                result = false;
                break;
        }
        return result;
    }

    /**
     * Is this instruction a switch instruction?
     * @return True if it is.
     */
    boolean isSwitch() {
        final boolean result;
        switch (Instruction.find(this.opcode)) {
            case TABLESWITCH:
            case LOOKUPSWITCH:
                result = true;
                break;
            default:
                result = false;
                break;
        }
        return result;
    }

    /**
     * Is this instruction a return instruction?
     * @return True if it is.
     */
    boolean isReturn() {
        final boolean result;
        switch (Instruction.find(this.opcode)) {
            case IRETURN:
            case FRETURN:
            case ARETURN:
            case LRETURN:
            case DRETURN:
            case RETURN:
                result = true;
                break;
            default:
                result = false;
                break;
        }
        return result;
    }

    /**
     * Switch offcests.
     * @return Offsets.
     */
    List