1 /*
   2  * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.lang.constant;
  26 
  27 import java.util.ArrayList;
  28 import java.util.List;
  29 import java.util.Set;
  30 
  31 import static java.util.Objects.requireNonNull;
  32 
  33 /**
  34  * Helper methods for the implementation of {@code java.lang.constant}.
  35  */
  36 class ConstantUtils {
  37     /** an empty constant descriptor */
  38     public static final ConstantDesc[] EMPTY_CONSTANTDESC = new ConstantDesc[0];
  39     static final Constable[] EMPTY_CONSTABLE = new Constable[0];
  40     static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
  41 
  42     private static final Set<String> pointyNames = Set.of("<init>", "<clinit>");
  43 
  44     /**
  45      * Validates the correctness of a binary class name. In particular checks for the presence of
  46      * invalid characters in the name.
  47      *
  48      * @param name the class name
  49      * @return the class name passed if valid
  50      * @throws IllegalArgumentException if the class name is invalid
  51      */
  52     static String validateBinaryClassName(String name) {
  53         for (int i=0; i<name.length(); i++) {
  54             char ch = name.charAt(i);
  55             if (ch == ';' || ch == '[' || ch == '/')
  56                 throw new IllegalArgumentException("Invalid class name: " + name);
  57         }
  58         return name;
  59     }
  60 
  61     /**
  62      * Validates a member name
  63      *
  64      * @param name the name of the member
  65      * @return the name passed if valid
  66      * @throws IllegalArgumentException if the member name is invalid
  67      */
  68     public static String validateMemberName(String name, boolean method) {
  69         requireNonNull(name);
  70         if (name.length() == 0)
  71             throw new IllegalArgumentException("zero-length member name");
  72         for (int i=0; i<name.length(); i++) {
  73             char ch = name.charAt(i);
  74             if (ch == '.' || ch == ';' || ch == '[' || ch == '/')
  75                 throw new IllegalArgumentException("Invalid member name: " + name);
  76             if (method && (ch == '<' || ch == '>')) {
  77                 if (!pointyNames.contains(name))
  78                     throw new IllegalArgumentException("Invalid member name: " + name);
  79             }
  80         }
  81         return name;
  82     }
  83 
  84     static void validateClassOrInterface(ClassDesc classDesc) {
  85         if (!classDesc.isClassOrInterface())
  86             throw new IllegalArgumentException("not a class or interface type: " + classDesc);
  87     }
  88 
  89     static int arrayDepth(String descriptorString) {
  90         int depth = 0;
  91         while (descriptorString.charAt(depth) == '[')
  92             depth++;
  93         return depth;
  94     }
  95 
  96     static String binaryToInternal(String name) {
  97         return name.replace('.', '/');
  98     }
  99 
 100     static String internalToBinary(String name) {
 101         return name.replace('/', '.');
 102     }
 103 
 104     static String dropLastChar(String s) {
 105         return s.substring(0, s.length() - 1);
 106     }
 107 
 108     static String dropFirstAndLastChar(String s) {
 109         return s.substring(1, s.length() - 1);
 110     }
 111 
 112     /**
 113      * Parses a method descriptor string, and return a list of field descriptor
 114      * strings, return type first, then parameter types
 115      *
 116      * @param descriptor the descriptor string
 117      * @return the list of types
 118      * @throws IllegalArgumentException if the descriptor string is not valid
 119      */
 120     static List<String> parseMethodDescriptor(String descriptor) {
 121         int cur = 0, end = descriptor.length();
 122         ArrayList<String> ptypes = new ArrayList<>();
 123 
 124         if (cur >= end || descriptor.charAt(cur) != '(')
 125             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
 126 
 127         ++cur;  // skip '('
 128         while (cur < end && descriptor.charAt(cur) != ')') {
 129             int len = skipOverFieldSignature(descriptor, cur, end, false);
 130             if (len == 0)
 131                 throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
 132             ptypes.add(descriptor.substring(cur, cur + len));
 133             cur += len;
 134         }
 135         if (cur >= end)
 136             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
 137         ++cur;  // skip ')'
 138 
 139         int rLen = skipOverFieldSignature(descriptor, cur, end, true);
 140         if (rLen == 0 || cur + rLen != end)
 141             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
 142         ptypes.add(0, descriptor.substring(cur, cur + rLen));
 143         return ptypes;
 144     }
 145 
 146     private static final char JVM_SIGNATURE_ARRAY = '[';
 147     private static final char JVM_SIGNATURE_BYTE = 'B';
 148     private static final char JVM_SIGNATURE_CHAR = 'C';
 149     private static final char JVM_SIGNATURE_CLASS = 'L';
 150     private static final char JVM_SIGNATURE_ENDCLASS = ';';
 151     private static final char JVM_SIGNATURE_ENUM = 'E';
 152     private static final char JVM_SIGNATURE_FLOAT = 'F';
 153     private static final char JVM_SIGNATURE_DOUBLE = 'D';
 154     private static final char JVM_SIGNATURE_FUNC = '(';
 155     private static final char JVM_SIGNATURE_ENDFUNC = ')';
 156     private static final char JVM_SIGNATURE_INT = 'I';
 157     private static final char JVM_SIGNATURE_LONG = 'J';
 158     private static final char JVM_SIGNATURE_SHORT = 'S';
 159     private static final char JVM_SIGNATURE_VOID = 'V';
 160     private static final char JVM_SIGNATURE_BOOLEAN = 'Z';
 161 
 162     /**
 163      * Validates that the characters at [start, end) within the provided string
 164      * describe a valid field type descriptor.
 165      * @param descriptor the descriptor string
 166      * @param start the starting index into the string
 167      * @param end the ending index within the string
 168      * @param voidOK is void acceptable?
 169      * @return the length of the descriptor, or 0 if it is not a descriptor
 170      * @throws IllegalArgumentException if the descriptor string is not valid
 171      */
 172     @SuppressWarnings("fallthrough")
 173     static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) {
 174         int arrayDim = 0;
 175         int index = start;
 176         while (index < end) {
 177             switch (descriptor.charAt(index)) {
 178                 case JVM_SIGNATURE_VOID: if (!voidOK) { return index; }
 179                 case JVM_SIGNATURE_BOOLEAN:
 180                 case JVM_SIGNATURE_BYTE:
 181                 case JVM_SIGNATURE_CHAR:
 182                 case JVM_SIGNATURE_SHORT:
 183                 case JVM_SIGNATURE_INT:
 184                 case JVM_SIGNATURE_FLOAT:
 185                 case JVM_SIGNATURE_LONG:
 186                 case JVM_SIGNATURE_DOUBLE:
 187                     return index - start + 1;
 188                 case JVM_SIGNATURE_CLASS:
 189                     // Skip leading 'L' and ignore first appearance of ';'
 190                     index++;
 191                     int indexOfSemi = descriptor.indexOf(';', index);
 192                     if (indexOfSemi != -1) {
 193                         String unqualifiedName = descriptor.substring(index, indexOfSemi);
 194                         boolean legal = verifyUnqualifiedClassName(unqualifiedName);
 195                         if (!legal) {
 196                             return 0;
 197                         }
 198                         return index - start + unqualifiedName.length() + 1;
 199                     }
 200                     return 0;
 201                 case JVM_SIGNATURE_ARRAY:
 202                     arrayDim++;
 203                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
 204                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
 205                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
 206                     }
 207                     // The rest of what's there better be a legal descriptor
 208                     index++;
 209                     voidOK = false;
 210                     break;
 211                 default:
 212                     return 0;
 213             }
 214         }
 215         return 0;
 216     }
 217 
 218     static boolean verifyUnqualifiedClassName(String name) {
 219         for (int index = 0; index < name.length(); index++) {
 220             char ch = name.charAt(index);
 221             if (ch < 128) {
 222                 if (ch == '.' || ch == ';' || ch == '[' ) {
 223                     return false;   // do not permit '.', ';', or '['
 224                 }
 225                 if (ch == '/') {
 226                     // check for '//' or leading or trailing '/' which are not legal
 227                     // unqualified name must not be empty
 228                     if (index == 0 || index + 1 >= name.length() || name.charAt(index + 1) == '/') {
 229                         return false;
 230                     }
 231                 }
 232             } else {
 233                 index ++;
 234             }
 235         }
 236         return true;
 237     }
 238 }