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

io.github.dmlloyd.classfile.extras.constant.ConstantUtils Maven / Gradle / Ivy

There is a newer version: 24.cr2
Show newest version
/*
 * Copyright (c) 2018, 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.extras.constant;

//import jdk.internal.vm.annotation.Stable;
//import sun.invoke.util.Wrapper;

import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodType;
import java.util.Set;

import static java.lang.constant.ConstantDescs.*;

/**
 * Helper methods for the implementation of {@code java.lang.constant}.
 */
public final class ConstantUtils {
    /** an empty constant descriptor */
    public static final ConstantDesc[] EMPTY_CONSTANTDESC = new ConstantDesc[0];
    public static final ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0];
    public static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
    public static final ClassDesc CD_module_info = binaryNameToDesc("module-info");
    public static ClassDesc CD_Object_array; // set from ConstantDescs, avoid circular initialization

    private static final Set pointyNames = Set.of(ExtraConstantDescs.INIT_NAME, ExtraConstantDescs.CLASS_INIT_NAME);

    /** No instantiation */
    private ConstantUtils() {}

    // Note:
    // Non-JDK users should create their own utilities that wrap
    // {@code .describeConstable().orElseThrow()} calls;
    // these xxDesc methods has undefined and unsafe exceptional
    // behavior, so they are not suitable as public APIs.

    /**
     * Creates a {@linkplain ClassDesc} from a pre-validated binary name
     * for a class or interface type. Validated version of {@link
     * ClassDesc#of(String)}.
     *
     * @param binaryName a binary name
     */
    public static ClassDesc binaryNameToDesc(String binaryName) {
        return ClassDesc.of(binaryName);
    }

    /**
     * Creates a {@linkplain ClassDesc} from a pre-validated internal name
     * for a class or interface type. Validated version of {@link
     * ClassDesc#ofInternalName(String)}.
     *
     * @param internalName a binary name
     */
    public static ClassDesc internalNameToDesc(String internalName) {
        return ClassDesc.ofDescriptor(concat("L", internalName, ";"));
    }

    /**
     * Creates a ClassDesc from a Class object, requires that this class
     * can always be described nominally, i.e. this class is not a
     * hidden class or interface or an array with a hidden component
     * type.
     */
    public static ClassDesc classDesc(Class type) {
        return type.describeConstable().orElseThrow();
    }

    /**
     * Creates a ClassDesc from a Class object representing a non-hidden
     * class or interface or an array type with a non-hidden component type.
     */
    public static ClassDesc referenceClassDesc(Class type) {
        return type.describeConstable().orElseThrow();
    }

    /**
     * Creates a {@linkplain ClassDesc} from a pre-validated descriptor string
     * for a class or interface type or an array type.
     *
     * @param descriptor a field descriptor string for a class or interface type
     * @jvms 4.3.2 Field Descriptors
     */
    public static ClassDesc referenceClassDesc(String descriptor) {
        return ClassDesc.ofDescriptor(descriptor);
    }

    /**
     * Creates a MethodTypeDesc from a MethodType object, requires that
     * the type can be described nominally, i.e. all of its return
     * type and parameter types can be described nominally.
     */
    public static MethodTypeDesc methodTypeDesc(MethodType type) {
        return type.describeConstable().orElseThrow();
    }

    /**
     * Creates a MethodTypeDesc from return class and parameter
     * class objects, requires that all of them can be described nominally.
     * This version is mainly useful for working with Method objects.
     */
    public static MethodTypeDesc methodTypeDesc(Class returnType, Class[] parameterTypes) {
        return MethodType.methodType(returnType, parameterTypes).describeConstable().orElseThrow();
    }

    /**
     * Validates the correctness of a class or interface name or a package name.
     * In particular checks for the presence of invalid characters,
     * consecutive, leading, or trailing separator char, for both non-internal
     * and internal forms, and the empty string for class or interface names.
     *
     * @param name the name
     * @param slashSeparator {@code true} means {@code /} is the separator char
     *     (internal form); otherwise {@code .} is the separator char
     * @param allowEmpty {@code true} means the empty string is a valid name
     * @return the name passed if valid
     * @throws IllegalArgumentException if the name is invalid
     * @throws NullPointerException if name is {@code null}
     */
    private static String validateClassOrPackageName(String name, boolean slashSeparator, boolean allowEmpty) {
        int len = name.length();  // implicit null check
        // empty name special rule
        if (allowEmpty && len == 0)
            return name;
        // state variable for detection of illegal states of
        // empty name, consecutive, leading, or trailing separators
        int afterSeparator = 0;
        for (int i = 0; i < len; i++) {
            char ch = name.charAt(i);
            // reject ';' or '['
            if (ch == ';' || ch == '[')
                throw invalidClassName(name);
            // encounter a separator
            boolean foundSlash = ch == '/';
            if (foundSlash || ch == '.') {
                // reject the other separator char
                // reject consecutive or leading separators
                if (foundSlash != slashSeparator || i == afterSeparator)
                    throw invalidClassName(name);
                afterSeparator = i + 1;
            }
        }
        // reject empty name or trailing separators
        if (len == afterSeparator)
            throw invalidClassName(name);
        return name;
    }

    /**
     * Validates the correctness of a binary class name.
     * In particular checks for the presence of invalid characters, empty
     * name, consecutive, leading, or trailing {@code .}.
     *
     * @param name the class name
     * @return the class name passed if valid
     * @throws IllegalArgumentException if the class name is invalid
     * @throws NullPointerException if class name is {@code null}
     */
    public static String validateBinaryClassName(String name) {
        return validateClassOrPackageName(name, false, false);
    }

    /**
     * Validates the correctness of an internal class name.
     * In particular checks for the presence of invalid characters, empty
     * name, consecutive, leading, or trailing {@code /}.
     *
     * @param name the class name
     * @return the class name passed if valid
     * @throws IllegalArgumentException if the class name is invalid
     * @throws NullPointerException if class name is {@code null}
     */
    public static String validateInternalClassName(String name) {
        return validateClassOrPackageName(name, true, false);
    }

    /**
     * Validates the correctness of a binary package name.
     * In particular checks for the presence of invalid characters, consecutive,
     * leading, or trailing {@code .}.  Allows empty strings for the unnamed package.
     *
     * @param name the package name
     * @return the package name passed if valid
     * @throws IllegalArgumentException if the package name is invalid
     * @throws NullPointerException if the package name is {@code null}
     */
    public static String validateBinaryPackageName(String name) {
        return validateClassOrPackageName(name, false, true);
    }

    /**
     * Validates the correctness of an internal package name.
     * In particular checks for the presence of invalid characters, consecutive,
     * leading, or trailing {@code /}.  Allows empty strings for the unnamed package.
     *
     * @param name the package name
     * @return the package name passed if valid
     * @throws IllegalArgumentException if the package name is invalid
     * @throws NullPointerException if the package name is {@code null}
     */
    public static String validateInternalPackageName(String name) {
        return validateClassOrPackageName(name, true, true);
    }

    /**
     * Validates the correctness of a module name.
     * In particular checks for the presence of invalid characters in the name.
     * Empty module name is allowed.
     *
     * {@jvms 4.2.3} Module and Package Names
     *
     * @param name the module name
     * @return the module name passed if valid
     * @throws IllegalArgumentException if the module name is invalid
     * @throws NullPointerException if the module name is {@code null}
     */
    public static String validateModuleName(String name) {
        for (int i = name.length() - 1; i >= 0; i--) {
            char ch = name.charAt(i);
            if ((ch >= '\u0000' && ch <= '\u001F')
            || ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\')))
                throw new IllegalArgumentException("Invalid module name: " + name);
        }
        return name;
    }

    /**
     * Validates a member name
     *
     * @param name the name of the member
     * @return the name passed if valid
     * @throws IllegalArgumentException if the member name is invalid
     * @throws NullPointerException if the member name is {@code null}
     */
    public static String validateMemberName(String name, boolean method) {
        int len = name.length();
        if (len == 0)
            throw new IllegalArgumentException("zero-length member name");
        for (int i = 0; i < len; i++) {
            char ch = name.charAt(i);
            // common case fast-path
            if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
                continue;
            if (ch == '.' || ch == ';' || ch == '[' || ch == '/')
                throw new IllegalArgumentException("Invalid member name: " + name);
            if (method && (ch == '<' || ch == '>')) {
                if (!pointyNames.contains(name))
                    throw new IllegalArgumentException("Invalid member name: " + name);
            }
        }
        return name;
    }

    public static void validateClassOrInterface(ClassDesc classDesc) {
        if (!classDesc.isClassOrInterface())
            throw new IllegalArgumentException("not a class or interface type: " + classDesc);
    }

    public static void validateArrayRank(int rank) {
        // array rank must be representable with u1 and nonzero
        if (rank == 0 || (rank & ~0xFF) != 0) {
            throw new IllegalArgumentException(invalidArrayRankMessage(rank));
        }
    }

    /**
     * Retrieves the array depth on a trusted descriptor.
     * Uses a simple loop with the assumption that most descriptors have
     * 0 or very low array depths.
     */
    public static int arrayDepth(String descriptorString, int off) {
        int depth = 0;
        while (descriptorString.charAt(off) == '[') {
            depth++;
            off++;
        }
        return depth;
    }

    public static String binaryToInternal(String name) {
        return name.replace('.', '/');
    }

    public static String internalToBinary(String name) {
        return name.replace('/', '.');
    }

    public static String dropFirstAndLastChar(String s) {
        return s.substring(1, s.length() - 1);
    }

    public static ClassDesc forPrimitiveType(String descriptor, int offset) {
        return switch (descriptor.charAt(offset)) {
            case JVM_SIGNATURE_BYTE    -> CD_byte;
            case JVM_SIGNATURE_CHAR    -> CD_char;
            case JVM_SIGNATURE_FLOAT   -> CD_float;
            case JVM_SIGNATURE_DOUBLE  -> CD_double;
            case JVM_SIGNATURE_INT     -> CD_int;
            case JVM_SIGNATURE_LONG    -> CD_long;
            case JVM_SIGNATURE_SHORT   -> CD_short;
            case JVM_SIGNATURE_VOID    -> CD_void;
            case JVM_SIGNATURE_BOOLEAN -> CD_boolean;
            default -> throw badMethodDescriptor(descriptor);
        };
    }

    static String invalidArrayRankMessage(int rank) {
        return "Array rank must be within [1, 255]: " + rank;
    }

    static IllegalArgumentException invalidClassName(String className) {
        return new IllegalArgumentException("Invalid class name: ".concat(className));
    }

    static IllegalArgumentException badMethodDescriptor(String descriptor) {
        return new IllegalArgumentException("Bad method descriptor: " + descriptor);
    }

    private static final char JVM_SIGNATURE_ARRAY = '[';
    private static final char JVM_SIGNATURE_BYTE = 'B';
    private static final char JVM_SIGNATURE_CHAR = 'C';
    private static final char JVM_SIGNATURE_CLASS = 'L';
    private static final char JVM_SIGNATURE_FLOAT = 'F';
    private static final char JVM_SIGNATURE_DOUBLE = 'D';
    private static final char JVM_SIGNATURE_INT = 'I';
    private static final char JVM_SIGNATURE_LONG = 'J';
    private static final char JVM_SIGNATURE_SHORT = 'S';
    private static final char JVM_SIGNATURE_VOID = 'V';
    private static final char JVM_SIGNATURE_BOOLEAN = 'Z';

    /**
     * Validates that the characters at [start, end) within the provided string
     * describe a valid field type descriptor.
     * @param descriptor the descriptor string
     * @param start the starting index into the string
     * @param end the ending index within the string
     * @return the length of the descriptor, or 0 if it is not a descriptor
     * @throws IllegalArgumentException if the descriptor string is not valid
     */
    static int skipOverFieldSignature(String descriptor, int start, int end) {
        int arrayDim = 0;
        int index = start;
        if (index < end) {
            char ch;
            while ((ch = descriptor.charAt(index++)) == JVM_SIGNATURE_ARRAY) {
                arrayDim++;
            }
            if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
                throw maxArrayTypeDescDimensions();
            }

            switch (ch) {
                case JVM_SIGNATURE_BOOLEAN:
                case JVM_SIGNATURE_BYTE:
                case JVM_SIGNATURE_CHAR:
                case JVM_SIGNATURE_SHORT:
                case JVM_SIGNATURE_INT:
                case JVM_SIGNATURE_FLOAT:
                case JVM_SIGNATURE_LONG:
                case JVM_SIGNATURE_DOUBLE:
                    return index - start;
                case JVM_SIGNATURE_CLASS:
                    // state variable for detection of illegal states of
                    // empty name, '//', leading '/', or trailing '/'
                    int afterSeparator = index + 1; // start of internal name
                    while (index < end) {
                        ch = descriptor.charAt(index++);
                        if (ch == ';')
                            // reject empty name or trailing '/'
                            return index == afterSeparator ? 0 : index - start;
                        // reject '.' or '['
                        if (ch == '.' || ch == '[')
                            return 0;
                        if (ch == '/') {
                            // reject '//' or leading '/'
                            if (index == afterSeparator)
                                return 0;
                            afterSeparator = index + 1;
                        }
                    }
                    break;
                default:
                    break;
            }
        }
        return 0;
    }

    private static IllegalArgumentException maxArrayTypeDescDimensions() {
        return new IllegalArgumentException(String.format(
                        "Cannot create an array type descriptor with more than %d dimensions",
                        ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
    }

    public static String concat(String prefix, Object value, String suffix) {
        return prefix + value + suffix;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy