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

io.github.dmlloyd.classfile.impl.BytecodeHelpers Maven / Gradle / Ivy

/*
 * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package io.github.dmlloyd.classfile.impl;

import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandleInfo;
import java.util.ArrayList;
import java.util.List;

import io.github.dmlloyd.classfile.BootstrapMethodEntry;
import io.github.dmlloyd.classfile.constantpool.ClassEntry;
import io.github.dmlloyd.classfile.constantpool.ConstantDynamicEntry;
import io.github.dmlloyd.classfile.constantpool.ConstantPoolBuilder;
import io.github.dmlloyd.classfile.Opcode;
import io.github.dmlloyd.classfile.TypeKind;
import io.github.dmlloyd.classfile.constantpool.LoadableConstantEntry;
import io.github.dmlloyd.classfile.constantpool.MemberRefEntry;
import io.github.dmlloyd.classfile.constantpool.MethodHandleEntry;
import io.github.dmlloyd.classfile.constantpool.NameAndTypeEntry;

import static io.github.dmlloyd.classfile.ClassFile.*;

/**
 * Note: This class switches on opcode.bytecode for code size
 */
public class BytecodeHelpers {

    private BytecodeHelpers() {
    }

    public static IllegalArgumentException cannotConvertException(TypeKind from, TypeKind to) {
        return new IllegalArgumentException(String.format("convert %s -> %s", from, to));
    }

    public static Opcode loadOpcode(TypeKind tk, int slot) {
        return switch (tk) {
            case INT, SHORT, BYTE, CHAR, BOOLEAN
                           -> iload(slot);
            case LONG      -> lload(slot);
            case DOUBLE    -> dload(slot);
            case FLOAT     -> fload(slot);
            case REFERENCE -> aload(slot);
            case VOID      -> throw new IllegalArgumentException("void");
        };
    }

    public static Opcode aload(int slot) {
        return switch (slot) {
            case 0 -> Opcode.ALOAD_0;
            case 1 -> Opcode.ALOAD_1;
            case 2 -> Opcode.ALOAD_2;
            case 3 -> Opcode.ALOAD_3;
            default -> (slot < 256) ? Opcode.ALOAD : Opcode.ALOAD_W;
        };
    }

    public static Opcode fload(int slot) {
        return switch (slot) {
            case 0 -> Opcode.FLOAD_0;
            case 1 -> Opcode.FLOAD_1;
            case 2 -> Opcode.FLOAD_2;
            case 3 -> Opcode.FLOAD_3;
            default -> (slot < 256) ? Opcode.FLOAD : Opcode.FLOAD_W;
        };
    }

    public static Opcode dload(int slot) {
        return switch (slot) {
            case 0 -> Opcode.DLOAD_0;
            case 1 -> Opcode.DLOAD_1;
            case 2 -> Opcode.DLOAD_2;
            case 3 -> Opcode.DLOAD_3;
            default -> (slot < 256) ? Opcode.DLOAD : Opcode.DLOAD_W;
        };
    }

    public static Opcode lload(int slot) {
        return switch (slot) {
            case 0 -> Opcode.LLOAD_0;
            case 1 -> Opcode.LLOAD_1;
            case 2 -> Opcode.LLOAD_2;
            case 3 -> Opcode.LLOAD_3;
            default -> (slot < 256) ? Opcode.LLOAD : Opcode.LLOAD_W;
        };
    }

    public static Opcode iload(int slot) {
        return switch (slot) {
            case 0 -> Opcode.ILOAD_0;
            case 1 -> Opcode.ILOAD_1;
            case 2 -> Opcode.ILOAD_2;
            case 3 -> Opcode.ILOAD_3;
            default -> (slot < 256) ? Opcode.ILOAD : Opcode.ILOAD_W;
        };
    }

    public static Opcode storeOpcode(TypeKind tk, int slot) {
        return switch (tk) {
            case INT, SHORT, BYTE, CHAR, BOOLEAN
                           -> istore(slot);
            case LONG      -> lstore(slot);
            case DOUBLE    -> dstore(slot);
            case FLOAT     -> fstore(slot);
            case REFERENCE -> astore(slot);
            case VOID      -> throw new IllegalArgumentException("void");
        };
    }

    public static Opcode astore(int slot) {
        return switch (slot) {
            case 0 -> Opcode.ASTORE_0;
            case 1 -> Opcode.ASTORE_1;
            case 2 -> Opcode.ASTORE_2;
            case 3 -> Opcode.ASTORE_3;
            default -> (slot < 256) ? Opcode.ASTORE : Opcode.ASTORE_W;
        };
    }

    public static Opcode fstore(int slot) {
        return switch (slot) {
            case 0 -> Opcode.FSTORE_0;
            case 1 -> Opcode.FSTORE_1;
            case 2 -> Opcode.FSTORE_2;
            case 3 -> Opcode.FSTORE_3;
            default -> (slot < 256) ? Opcode.FSTORE : Opcode.FSTORE_W;
        };
    }

    public static Opcode dstore(int slot) {
        return switch (slot) {
            case 0 -> Opcode.DSTORE_0;
            case 1 -> Opcode.DSTORE_1;
            case 2 -> Opcode.DSTORE_2;
            case 3 -> Opcode.DSTORE_3;
            default -> (slot < 256) ? Opcode.DSTORE : Opcode.DSTORE_W;
        };
    }

    public static Opcode lstore(int slot) {
        return switch (slot) {
            case 0 -> Opcode.LSTORE_0;
            case 1 -> Opcode.LSTORE_1;
            case 2 -> Opcode.LSTORE_2;
            case 3 -> Opcode.LSTORE_3;
            default -> (slot < 256) ? Opcode.LSTORE : Opcode.LSTORE_W;
        };
    }

    public static Opcode istore(int slot) {
        return switch (slot) {
            case 0 -> Opcode.ISTORE_0;
            case 1 -> Opcode.ISTORE_1;
            case 2 -> Opcode.ISTORE_2;
            case 3 -> Opcode.ISTORE_3;
            default -> (slot < 256) ? Opcode.ISTORE : Opcode.ISTORE_W;
        };
    }

    public static Opcode returnOpcode(TypeKind tk) {
        return switch (tk) {
            case BYTE, SHORT, INT, CHAR, BOOLEAN -> Opcode.IRETURN;
            case FLOAT -> Opcode.FRETURN;
            case LONG -> Opcode.LRETURN;
            case DOUBLE -> Opcode.DRETURN;
            case REFERENCE -> Opcode.ARETURN;
            case VOID -> Opcode.RETURN;
        };
    }

    public static Opcode arrayLoadOpcode(TypeKind tk) {
        return switch (tk) {
            case BYTE, BOOLEAN -> Opcode.BALOAD;
            case SHORT -> Opcode.SALOAD;
            case INT -> Opcode.IALOAD;
            case FLOAT -> Opcode.FALOAD;
            case LONG -> Opcode.LALOAD;
            case DOUBLE -> Opcode.DALOAD;
            case REFERENCE -> Opcode.AALOAD;
            case CHAR -> Opcode.CALOAD;
            case VOID -> throw new IllegalArgumentException("void not an allowable array type");
        };
    }

    public static Opcode arrayStoreOpcode(TypeKind tk) {
        return switch (tk) {
            case BYTE, BOOLEAN -> Opcode.BASTORE;
            case SHORT -> Opcode.SASTORE;
            case INT -> Opcode.IASTORE;
            case FLOAT -> Opcode.FASTORE;
            case LONG -> Opcode.LASTORE;
            case DOUBLE -> Opcode.DASTORE;
            case REFERENCE -> Opcode.AASTORE;
            case CHAR -> Opcode.CASTORE;
            case VOID -> throw new IllegalArgumentException("void not an allowable array type");
        };
    }

    public static Opcode reverseBranchOpcode(Opcode op) {
        return switch (op) {
            case IFEQ -> Opcode.IFNE;
            case IFNE -> Opcode.IFEQ;
            case IFLT -> Opcode.IFGE;
            case IFGE -> Opcode.IFLT;
            case IFGT -> Opcode.IFLE;
            case IFLE -> Opcode.IFGT;
            case IF_ICMPEQ -> Opcode.IF_ICMPNE;
            case IF_ICMPNE -> Opcode.IF_ICMPEQ;
            case IF_ICMPLT -> Opcode.IF_ICMPGE;
            case IF_ICMPGE -> Opcode.IF_ICMPLT;
            case IF_ICMPGT -> Opcode.IF_ICMPLE;
            case IF_ICMPLE -> Opcode.IF_ICMPGT;
            case IF_ACMPEQ -> Opcode.IF_ACMPNE;
            case IF_ACMPNE -> Opcode.IF_ACMPEQ;
            case IFNULL -> Opcode.IFNONNULL;
            case IFNONNULL -> Opcode.IFNULL;
            default -> throw Util.badOpcodeKindException(op, Opcode.Kind.BRANCH);
        };
    }

    public static Opcode convertOpcode(TypeKind from, TypeKind to) {
        return switch (from) {
            case INT ->
                    switch (to) {
                        case LONG -> Opcode.I2L;
                        case FLOAT -> Opcode.I2F;
                        case DOUBLE -> Opcode.I2D;
                        case BYTE -> Opcode.I2B;
                        case CHAR -> Opcode.I2C;
                        case SHORT -> Opcode.I2S;
                        default -> throw cannotConvertException(from, to);
                    };
            case LONG ->
                    switch (to) {
                        case FLOAT -> Opcode.L2F;
                        case DOUBLE -> Opcode.L2D;
                        case INT -> Opcode.L2I;
                        default -> throw cannotConvertException(from, to);
                    };
            case DOUBLE ->
                    switch (to) {
                        case FLOAT -> Opcode.D2F;
                        case LONG -> Opcode.D2L;
                        case INT -> Opcode.D2I;
                        default -> throw cannotConvertException(from, to);
                    };
            case FLOAT ->
                    switch (to) {
                        case LONG -> Opcode.F2L;
                        case DOUBLE -> Opcode.F2D;
                        case INT -> Opcode.F2I;
                        default -> throw cannotConvertException(from, to);
                    };
            default -> throw cannotConvertException(from, to);
        };
    }

    public static TypeKind convertFromType(Opcode opcode) {
        return switch (opcode) {
            case I2D, I2F, I2L, I2B, I2C, I2S -> TypeKind.INT;
            case L2D, L2F, L2I -> TypeKind.LONG;
            case F2D, F2I, F2L -> TypeKind.FLOAT;
            case D2F, D2I, D2L -> TypeKind.DOUBLE;
            default -> throw Util.badOpcodeKindException(opcode, Opcode.Kind.CONVERT);
        };
    }

    public static TypeKind convertToType(Opcode opcode) {
        return switch (opcode) {
            case I2B -> TypeKind.BYTE;
            case I2C -> TypeKind.CHAR;
            case I2S -> TypeKind.SHORT;
            case L2I, F2I, D2I -> TypeKind.INT;
            case I2L, F2L, D2L -> TypeKind.LONG;
            case I2F, L2F, D2F -> TypeKind.FLOAT;
            case I2D, L2D, F2D -> TypeKind.DOUBLE;
            default -> throw Util.badOpcodeKindException(opcode, Opcode.Kind.CONVERT);
        };
    }

    public static void validateSipush(int value) {
        if (value != (short) value)
            throw new IllegalArgumentException(
                    "SIPUSH: value must be within: Short.MIN_VALUE <= value <= Short.MAX_VALUE, found: "
                            .concat(Long.toString(value)));
    }

    public static void validateBipush(int value) {
        if (value != (byte) value)
            throw new IllegalArgumentException(
                    "BIPUSH: value must be within: Byte.MIN_VALUE <= value <= Byte.MAX_VALUE, found: "
                            .concat(Long.toString(value)));
    }

    public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder constantPool, DirectMethodHandleDesc bootstrapMethod) {
        ClassEntry bsOwner = constantPool.classEntry(bootstrapMethod.owner());
        NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(constantPool.utf8Entry(bootstrapMethod.methodName()),
                                                               constantPool.utf8Entry(bootstrapMethod.lookupDescriptor()));
        int bsRefKind = bootstrapMethod.refKind();
        MemberRefEntry bsReference = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapMethod.isOwnerInterface());

        return constantPool.methodHandleEntry(bsRefKind, bsReference);
    }

    static MemberRefEntry toBootstrapMemberRef(ConstantPoolBuilder constantPool, int bsRefKind, ClassEntry owner, NameAndTypeEntry nat, boolean isOwnerInterface) {
        return isOwnerInterface
               ? constantPool.interfaceMethodRefEntry(owner, nat)
               : bsRefKind <= MethodHandleInfo.REF_putStatic
                 ? constantPool.fieldRefEntry(owner, nat)
                 : constantPool.methodRefEntry(owner, nat);
    }

    static ConstantDynamicEntry handleConstantDescToHandleInfo(ConstantPoolBuilder constantPool, DynamicConstantDesc desc) {
        ConstantDesc[] bootstrapArgs = desc.bootstrapArgs();
        List staticArgs = new ArrayList<>(bootstrapArgs.length);
        for (ConstantDesc bootstrapArg : bootstrapArgs)
            staticArgs.add(constantPool.loadableConstantEntry(bootstrapArg));
        MethodHandleEntry methodHandleEntry = handleDescToHandleInfo(constantPool, desc.bootstrapMethod());
        BootstrapMethodEntry bme = constantPool.bsmEntry(methodHandleEntry, staticArgs);
        return constantPool.constantDynamicEntry(bme,
                                                 constantPool.nameAndTypeEntry(desc.constantName(),
                                                                       desc.constantType()));
    }

    public static Opcode ldcOpcode(LoadableConstantEntry entry) {
        return entry.typeKind().slotSize() == 2 ? Opcode.LDC2_W
                : entry.index() > 0xff ? Opcode.LDC_W
                : Opcode.LDC;
    }

    public static LoadableConstantEntry constantEntry(ConstantPoolBuilder constantPool,
                                                      ConstantDesc constantValue) {
        // this method is invoked during JVM bootstrap - cannot use pattern switch
        if (constantValue instanceof Integer value) {
            return constantPool.intEntry(value);
        }
        if (constantValue instanceof String value) {
            return constantPool.stringEntry(value);
        }
        if (constantValue instanceof ClassDesc value && !value.isPrimitive()) {
            return constantPool.classEntry(value);
        }
        if (constantValue instanceof Long value) {
            return constantPool.longEntry(value);
        }
        if (constantValue instanceof Float value) {
            return constantPool.floatEntry(value);
        }
        if (constantValue instanceof Double value) {
            return constantPool.doubleEntry(value);
        }
        if (constantValue instanceof MethodTypeDesc value) {
            return constantPool.methodTypeEntry(value);
        }
        if (constantValue instanceof DirectMethodHandleDesc value) {
            return handleDescToHandleInfo(constantPool, value);
        } if (constantValue instanceof DynamicConstantDesc value) {
            return handleConstantDescToHandleInfo(constantPool, value);
        }
        throw new UnsupportedOperationException("not yet: " + constantValue);
    }

    public static ConstantDesc intrinsicConstantValue(Opcode opcode) {
        return switch (opcode) {
            case ACONST_NULL -> ConstantDescs.NULL;
            case ICONST_M1 -> -1;
            case ICONST_0 -> 0;
            case ICONST_1 -> 1;
            case ICONST_2 -> 2;
            case ICONST_3 -> 3;
            case ICONST_4 -> 4;
            case ICONST_5 -> 5;
            case LCONST_0 -> 0L;
            case LCONST_1 -> 1L;
            case FCONST_0 -> 0F;
            case FCONST_1 -> 1F;
            case FCONST_2 -> 2F;
            case DCONST_0 -> 0D;
            case DCONST_1 -> 1D;
            default -> throw Util.badOpcodeKindException(opcode, Opcode.Kind.CONSTANT);
        };
    }

    public static TypeKind intrinsicConstantType(Opcode opcode) {
        return switch (opcode) {
            case ACONST_NULL -> TypeKind.REFERENCE;
            case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5 -> TypeKind.INT;
            case LCONST_0, LCONST_1 -> TypeKind.LONG;
            case FCONST_0, FCONST_1, FCONST_2 -> TypeKind.FLOAT;
            case DCONST_0, DCONST_1 -> TypeKind.DOUBLE;
            default -> throw Util.badOpcodeKindException(opcode, Opcode.Kind.CONSTANT);
        };
    }

    public static boolean isUnconditionalBranch(Opcode opcode) {
        return switch (opcode) {
            case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true;
            default -> opcode.kind() == Opcode.Kind.RETURN;
        };
    }

    // Must check Opcode.sizeIfFixed() == 1 before call!
    public static int intrinsicLoadSlot(Opcode loadOpcode) {
        return switch (loadOpcode) {
            case ILOAD_0, LLOAD_0, FLOAD_0, DLOAD_0, ALOAD_0 -> 0;
            case ILOAD_1, LLOAD_1, FLOAD_1, DLOAD_1, ALOAD_1 -> 1;
            case ILOAD_2, LLOAD_2, FLOAD_2, DLOAD_2, ALOAD_2 -> 2;
            case ILOAD_3, LLOAD_3, FLOAD_3, DLOAD_3, ALOAD_3 -> 3;
            default -> throw Util.badOpcodeKindException(loadOpcode, Opcode.Kind.LOAD);
        };
    }

    // Must check Opcode.sizeIfFixed() == 1 before call!
    public static int intrinsicStoreSlot(Opcode storeOpcode) {
        return switch (storeOpcode) {
            case ISTORE_0, LSTORE_0, FSTORE_0, DSTORE_0, ASTORE_0 -> 0;
            case ISTORE_1, LSTORE_1, FSTORE_1, DSTORE_1, ASTORE_1 -> 1;
            case ISTORE_2, LSTORE_2, FSTORE_2, DSTORE_2, ASTORE_2 -> 2;
            case ISTORE_3, LSTORE_3, FSTORE_3, DSTORE_3, ASTORE_3 -> 3;
            default -> throw Util.badOpcodeKindException(storeOpcode, Opcode.Kind.STORE);
        };
    }

    public static TypeKind loadType(Opcode loadOpcode) {
        // Note: 0xFF handles wide pseudo-opcodes
        return switch (loadOpcode.bytecode() & 0xFF) {
            case ILOAD, ILOAD_0, ILOAD_1, ILOAD_2, ILOAD_3 -> TypeKind.INT;
            case LLOAD, LLOAD_0, LLOAD_1, LLOAD_2, LLOAD_3 -> TypeKind.LONG;
            case FLOAD, FLOAD_0, FLOAD_1, FLOAD_2, FLOAD_3 -> TypeKind.FLOAT;
            case DLOAD, DLOAD_0, DLOAD_1, DLOAD_2, DLOAD_3 -> TypeKind.DOUBLE;
            case ALOAD, ALOAD_0, ALOAD_1, ALOAD_2, ALOAD_3 -> TypeKind.REFERENCE;
            default -> throw Util.badOpcodeKindException(loadOpcode, Opcode.Kind.LOAD);
        };
    }

    public static TypeKind storeType(Opcode storeOpcode) {
        // Note: 0xFF handles wide pseudo-opcodes
        return switch (storeOpcode.bytecode() & 0xFF) {
            case ISTORE, ISTORE_0, ISTORE_1, ISTORE_2, ISTORE_3 -> TypeKind.INT;
            case LSTORE, LSTORE_0, LSTORE_1, LSTORE_2, LSTORE_3 -> TypeKind.LONG;
            case FSTORE, FSTORE_0, FSTORE_1, FSTORE_2, FSTORE_3 -> TypeKind.FLOAT;
            case DSTORE, DSTORE_0, DSTORE_1, DSTORE_2, DSTORE_3 -> TypeKind.DOUBLE;
            case ASTORE, ASTORE_0, ASTORE_1, ASTORE_2, ASTORE_3 -> TypeKind.REFERENCE;
            default -> throw Util.badOpcodeKindException(storeOpcode, Opcode.Kind.STORE);
        };
    }

    public static TypeKind arrayLoadType(Opcode arrayLoadOpcode) {
        return switch (arrayLoadOpcode) {
            case IALOAD -> TypeKind.INT;
            case LALOAD -> TypeKind.LONG;
            case FALOAD -> TypeKind.FLOAT;
            case DALOAD -> TypeKind.DOUBLE;
            case AALOAD -> TypeKind.REFERENCE;
            case BALOAD -> TypeKind.BYTE;
            case CALOAD -> TypeKind.CHAR;
            case SALOAD -> TypeKind.SHORT;
            default -> throw Util.badOpcodeKindException(arrayLoadOpcode, Opcode.Kind.ARRAY_LOAD);
        };
    }

    public static TypeKind arrayStoreType(Opcode arrayStoreOpcode) {
        return switch (arrayStoreOpcode) {
            case IASTORE -> TypeKind.INT;
            case LASTORE -> TypeKind.LONG;
            case FASTORE -> TypeKind.FLOAT;
            case DASTORE -> TypeKind.DOUBLE;
            case AASTORE -> TypeKind.REFERENCE;
            case BASTORE -> TypeKind.BYTE;
            case CASTORE -> TypeKind.CHAR;
            case SASTORE -> TypeKind.SHORT;
            default -> throw Util.badOpcodeKindException(arrayStoreOpcode, Opcode.Kind.ARRAY_STORE);
        };
    }

    public static TypeKind returnType(Opcode returnOpcode) {
        return switch (returnOpcode) {
            case IRETURN -> TypeKind.INT;
            case LRETURN -> TypeKind.LONG;
            case FRETURN -> TypeKind.FLOAT;
            case DRETURN -> TypeKind.DOUBLE;
            case ARETURN -> TypeKind.REFERENCE;
            case RETURN -> TypeKind.VOID;
            default -> throw Util.badOpcodeKindException(returnOpcode, Opcode.Kind.RETURN);
        };
    }

    public static TypeKind operatorOperandType(Opcode operationOpcode) {
        return switch (operationOpcode) {
            case IADD, ISUB, IMUL, IDIV, IREM, INEG,
                 ISHL, ISHR, IUSHR, IAND, IOR, IXOR,
                 ARRAYLENGTH -> TypeKind.INT;
            case LADD, LSUB, LMUL, LDIV, LREM, LNEG,
                 LSHL, LSHR, LUSHR, LAND, LOR, LXOR,
                 LCMP -> TypeKind.LONG;
            case FADD, FSUB, FMUL, FDIV, FREM, FNEG,
                 FCMPL, FCMPG -> TypeKind.FLOAT;
            case DADD, DSUB, DMUL, DDIV, DREM, DNEG,
                 DCMPL, DCMPG -> TypeKind.DOUBLE;
            default -> throw Util.badOpcodeKindException(operationOpcode, Opcode.Kind.OPERATOR);
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy