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_VALUE_TYPE = 'Q';
151     private static final char JVM_SIGNATURE_ENDCLASS = ';';
152     private static final char JVM_SIGNATURE_ENUM = 'E';
153     private static final char JVM_SIGNATURE_FLOAT = 'F';
154     private static final char JVM_SIGNATURE_DOUBLE = 'D';
155     private static final char JVM_SIGNATURE_FUNC = '(';
156     private static final char JVM_SIGNATURE_ENDFUNC = ')';
157     private static final char JVM_SIGNATURE_INT = 'I';
158     private static final char JVM_SIGNATURE_LONG = 'J';
159     private static final char JVM_SIGNATURE_SHORT = 'S';
160     private static final char JVM_SIGNATURE_VOID = 'V';
161     private static final char JVM_SIGNATURE_BOOLEAN = 'Z';
162 
163     /**
164      * Validates that the characters at [start, end) within the provided string
165      * describe a valid field type descriptor.
166      * @param descriptor the descriptor string
167      * @param start the starting index into the string
168      * @param end the ending index within the string
169      * @param voidOK is void acceptable?
170      * @return the length of the descriptor, or 0 if it is not a descriptor
171      * @throws IllegalArgumentException if the descriptor string is not valid
172      */
173     @SuppressWarnings("fallthrough")
174     static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) {
175         int arrayDim = 0;
176         int index = start;
177         while (index < end) {
178             switch (descriptor.charAt(index)) {
179                 case JVM_SIGNATURE_VOID: if (!voidOK) { return index; }
180                 case JVM_SIGNATURE_BOOLEAN:
181                 case JVM_SIGNATURE_BYTE:
182                 case JVM_SIGNATURE_CHAR:
183                 case JVM_SIGNATURE_SHORT:
184                 case JVM_SIGNATURE_INT:
185                 case JVM_SIGNATURE_FLOAT:
186                 case JVM_SIGNATURE_LONG:
187                 case JVM_SIGNATURE_DOUBLE:
188                     return index - start + 1;
189                 case JVM_SIGNATURE_CLASS:
190                 case JVM_SIGNATURE_VALUE_TYPE:
191                     // Skip leading 'L' or 'Q' and ignore first appearance of ';'
192                     index++;
193                     int indexOfSemi = descriptor.indexOf(';', index);
194                     if (indexOfSemi != -1) {
195                         String unqualifiedName = descriptor.substring(index, indexOfSemi);
196                         boolean legal = verifyUnqualifiedClassName(unqualifiedName);
197                         if (!legal) {
198                             return 0;
199                         }
200                         return index - start + unqualifiedName.length() + 1;
201                     }
202                     return 0;
203                 case JVM_SIGNATURE_ARRAY:
204                     arrayDim++;
205                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
206                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
207                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
208                     }
209                     // The rest of what's there better be a legal descriptor
210                     index++;
211                     voidOK = false;
212                     break;
213                 default:
214                     return 0;
215             }
216         }
217         return 0;
218     }
219 
220     /**
221      * Returns the basic type of the given descriptor.  If {@code verifyClassName}
222      * is true, then this method will validate that the characters at [start, end)
223      * within the given string describe a valid field type descriptor.
224      *
225      * @return the character represents the basic type that the descriptor string
226      * references
227      * @throws IllegalArgumentException if the descriptor string is not valid
228      */
229     static char basicType(String descriptor, int start, int end, boolean verifyClassName) {
230         int arrayDim = 0;
231         int index = start;
232         while (index < end) {
233             char c = descriptor.charAt(index);
234             switch (c) {
235                 case JVM_SIGNATURE_VOID:
236                 case JVM_SIGNATURE_BOOLEAN:
237                 case JVM_SIGNATURE_BYTE:
238                 case JVM_SIGNATURE_CHAR:
239                 case JVM_SIGNATURE_SHORT:
240                 case JVM_SIGNATURE_INT:
241                 case JVM_SIGNATURE_FLOAT:
242                 case JVM_SIGNATURE_LONG:
243                 case JVM_SIGNATURE_DOUBLE:
244                     return c;
245                 case JVM_SIGNATURE_CLASS:
246                 case JVM_SIGNATURE_VALUE_TYPE:
247                     index++;
248                     int indexOfSemi = descriptor.indexOf(';', index);
249                     if (indexOfSemi != -1) {
250                         if (verifyClassName) {
251                             String unqualifiedName = descriptor.substring(index, indexOfSemi);
252                             boolean legal = verifyUnqualifiedClassName(unqualifiedName);
253                             if (!legal) {
254                                 throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
255                             }
256                         }
257                         return c;
258                     }
259                     throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
260                 case JVM_SIGNATURE_ARRAY:
261                     arrayDim++;
262                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
263                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
264                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
265                     }
266                     // The rest of what's there better be a legal descriptor
267                     index++;
268                     break;
269                 default:
270                     throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
271             }
272         }
273         throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
274     }
275 
276     static boolean verifyUnqualifiedClassName(String name) {
277         for (int index = 0; index < name.length(); index++) {
278             char ch = name.charAt(index);
279             if (ch < 128) {
280                 if (ch == '.' || ch == ';' || ch == '[' ) {
281                     return false;   // do not permit '.', ';', or '['
282                 }
283                 if (ch == '/') {
284                     // check for '//' or leading or trailing '/' which are not legal
285                     // unqualified name must not be empty
286                     if (index == 0 || index + 1 >= name.length() || name.charAt(index + 1) == '/') {
287                         return false;
288                     }
289                 }
290             } else {
291                 index ++;
292             }
293         }
294         return true;
295     }
296 }