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

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

There is a newer version: 24.beta3
Show newest version
/*
 * Copyright (c) 2022, 2024, Oracle and/or its affiliates. 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;

public class BytecodeHelpers {

    private BytecodeHelpers() {
    }

    public static Opcode loadOpcode(TypeKind tk, int slot) {
        return switch (tk) {
            case IntType, ShortType, ByteType, CharType, BooleanType -> 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;
            };
            case LongType -> 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;
            };
            case DoubleType -> 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;
            };
            case FloatType -> 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;
            };
            case ReferenceType -> 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;
            };
            case VoidType -> throw new IllegalArgumentException("void");
        };
    }

    public static Opcode storeOpcode(TypeKind tk, int slot) {
        return switch (tk) {
            case IntType, ShortType, ByteType, CharType, BooleanType -> 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;
            };
            case LongType -> 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;
            };
            case DoubleType -> 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;
            };
            case FloatType -> 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;
            };
            case ReferenceType -> 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;
            };
            case VoidType -> throw new IllegalArgumentException("void");
        };
    }

    public static Opcode returnOpcode(TypeKind tk) {
        return switch (tk) {
            case ByteType, ShortType, IntType, CharType, BooleanType -> Opcode.IRETURN;
            case FloatType -> Opcode.FRETURN;
            case LongType -> Opcode.LRETURN;
            case DoubleType -> Opcode.DRETURN;
            case ReferenceType -> Opcode.ARETURN;
            case VoidType -> Opcode.RETURN;
        };
    }

    public static Opcode arrayLoadOpcode(TypeKind tk) {
        return switch (tk) {
            case ByteType, BooleanType -> Opcode.BALOAD;
            case ShortType -> Opcode.SALOAD;
            case IntType -> Opcode.IALOAD;
            case FloatType -> Opcode.FALOAD;
            case LongType -> Opcode.LALOAD;
            case DoubleType -> Opcode.DALOAD;
            case ReferenceType -> Opcode.AALOAD;
            case CharType -> Opcode.CALOAD;
            case VoidType -> throw new IllegalArgumentException("void not an allowable array type");
        };
    }

    public static Opcode arrayStoreOpcode(TypeKind tk) {
        return switch (tk) {
            case ByteType, BooleanType -> Opcode.BASTORE;
            case ShortType -> Opcode.SASTORE;
            case IntType -> Opcode.IASTORE;
            case FloatType -> Opcode.FASTORE;
            case LongType -> Opcode.LASTORE;
            case DoubleType -> Opcode.DASTORE;
            case ReferenceType -> Opcode.AASTORE;
            case CharType -> Opcode.CASTORE;
            case VoidType -> 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 new IllegalArgumentException("Unknown branch instruction: " + op);
        };
    }

    public static Opcode convertOpcode(TypeKind from, TypeKind to) {
        return switch (from) {
            case IntType ->
                    switch (to) {
                        case LongType -> Opcode.I2L;
                        case FloatType -> Opcode.I2F;
                        case DoubleType -> Opcode.I2D;
                        case ByteType -> Opcode.I2B;
                        case CharType -> Opcode.I2C;
                        case ShortType -> Opcode.I2S;
                        default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to));
                    };
            case LongType ->
                    switch (to) {
                        case FloatType -> Opcode.L2F;
                        case DoubleType -> Opcode.L2D;
                        case IntType -> Opcode.L2I;
                        default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to));
                    };
            case DoubleType ->
                    switch (to) {
                        case FloatType -> Opcode.D2F;
                        case LongType -> Opcode.D2L;
                        case IntType -> Opcode.D2I;
                        default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to));
                    };
            case FloatType ->
                    switch (to) {
                        case LongType -> Opcode.F2L;
                        case DoubleType -> Opcode.F2D;
                        case IntType -> Opcode.F2I;
                        default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to));
                    };
            default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to));
        };
    }

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

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

    static void validateSipush(ConstantDesc d) {
        if (d instanceof Integer iVal) {
            validateSipush(iVal.longValue());
        } else if (d instanceof Long lVal) {
            validateSipush(lVal.longValue());
        } else {
            throw new IllegalArgumentException("SIPUSH: not an integral number: ".concat(d.toString()));
        }
    }

    static void validateBipush(ConstantDesc d) {
        if (d instanceof Integer iVal) {
            validateBipush(iVal.longValue());
        } else if (d instanceof Long lVal) {
            validateBipush(lVal.longValue());
        } else {
            throw new IllegalArgumentException("BIPUSH: not an integral number: ".concat(d.toString()));
        }
    }

    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 void validateValue(Opcode opcode, ConstantDesc v) {
        switch (opcode) {
            case ACONST_NULL -> {
                if (v != null && v != ConstantDescs.NULL)
                    throw new IllegalArgumentException("value must be null or ConstantDescs.NULL with opcode ACONST_NULL");
            }
            case SIPUSH ->
                    validateSipush(v);
            case BIPUSH ->
                    validateBipush(v);
            case LDC, LDC_W, LDC2_W -> {
                if (v == null)
                    throw new IllegalArgumentException("`null` must use ACONST_NULL");
            }
            default -> {
                var exp = opcode.constantValue();
                if (exp == null)
                    throw new IllegalArgumentException("Can not use Opcode: " + opcode + " with constant()");
                if (v == null || !(v.equals(exp) || (exp instanceof Long l && v.equals(l.intValue())))) {
                    var t = (exp instanceof Long) ? "L" : (exp instanceof Float) ? "f" : (exp instanceof Double) ? "d" : "";
                    throw new IllegalArgumentException("value must be " + exp + t + " with opcode " + opcode.name());
                }
            }
        }
    }

    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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy