1 /*
  2  * Copyright (c) 2018, 2023, 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 ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0];
 40     static final Constable[] EMPTY_CONSTABLE = new Constable[0];
 41     static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
 42 
 43     private static final Set<String> pointyNames = Set.of(ConstantDescs.INIT_NAME, ConstantDescs.VNEW_NAME, ConstantDescs.CLASS_INIT_NAME);
 44 
 45     /**
 46      * Validates the correctness of a binary class name. In particular checks for the presence of
 47      * invalid characters in the name.
 48      *
 49      * @param name the class name
 50      * @return the class name passed if valid
 51      * @throws IllegalArgumentException if the class name is invalid
 52      */
 53     static String validateBinaryClassName(String name) {
 54         for (int i=0; i<name.length(); i++) {
 55             char ch = name.charAt(i);
 56             if (ch == ';' || ch == '[' || ch == '/')
 57                 throw new IllegalArgumentException("Invalid class name: " + name);
 58         }
 59         return name;
 60     }
 61 
 62     /**
 63       * Validates the correctness of an internal class name.
 64       * In particular checks for the presence of invalid characters in the name.
 65       *
 66       * @param name the class name
 67       * @return the class name passed if valid
 68       * @throws IllegalArgumentException if the class name is invalid
 69       */
 70      static String validateInternalClassName(String name) {
 71          for (int i=0; i<name.length(); i++) {
 72              char ch = name.charAt(i);
 73              if (ch == ';' || ch == '[' || ch == '.')
 74                  throw new IllegalArgumentException("Invalid class name: " + name);
 75          }
 76          return name;
 77      }
 78 
 79     /**
 80      * Validates the correctness of a binary package name.
 81      * In particular checks for the presence of invalid characters in the name.
 82      * Empty package name is allowed.
 83      *
 84      * @param name the package name
 85      * @return the package name passed if valid
 86      * @throws IllegalArgumentException if the package name is invalid
 87      * @throws NullPointerException if the package name is {@code null}
 88      */
 89     public static String validateBinaryPackageName(String name) {
 90         for (int i=0; i<name.length(); i++) {
 91             char ch = name.charAt(i);
 92             if (ch == ';' || ch == '[' || ch == '/')
 93                 throw new IllegalArgumentException("Invalid package name: " + name);
 94         }
 95         return name;
 96     }
 97 
 98     /**
 99      * Validates the correctness of an internal package name.
100      * In particular checks for the presence of invalid characters in the name.
101      * Empty package name is allowed.
102      *
103      * @param name the package name
104      * @return the package name passed if valid
105      * @throws IllegalArgumentException if the package name is invalid
106      * @throws NullPointerException if the package name is {@code null}
107      */
108     public static String validateInternalPackageName(String name) {
109         for (int i=0; i<name.length(); i++) {
110             char ch = name.charAt(i);
111             if (ch == ';' || ch == '[' || ch == '.')
112                 throw new IllegalArgumentException("Invalid package name: " + name);
113         }
114         return name;
115     }
116 
117     /**
118      * Validates the correctness of a module name.
119      * In particular checks for the presence of invalid characters in the name.
120      * Empty module name is allowed.
121      *
122      * {@jvms 4.2.3} Module and Package Names
123      *
124      * @param name the module name
125      * @return the module name passed if valid
126      * @throws IllegalArgumentException if the module name is invalid
127      * @throws NullPointerException if the module name is {@code null}
128      */
129     public static String validateModuleName(String name) {
130         for (int i=name.length() - 1; i >= 0; i--) {
131             char ch = name.charAt(i);
132             if ((ch >= '\u0000' && ch <= '\u001F')
133             || ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\')))
134                 throw new IllegalArgumentException("Invalid module name: " + name);
135         }
136         return name;
137     }
138 
139     /**
140      * Validates a member name
141      *
142      * @param name the name of the member
143      * @return the name passed if valid
144      * @throws IllegalArgumentException if the member name is invalid
145      */
146     public static String validateMemberName(String name, boolean method) {
147         requireNonNull(name);
148         if (name.length() == 0)
149             throw new IllegalArgumentException("zero-length member name");
150         for (int i=0; i<name.length(); i++) {
151             char ch = name.charAt(i);
152             if (ch == '.' || ch == ';' || ch == '[' || ch == '/')
153                 throw new IllegalArgumentException("Invalid member name: " + name);
154             if (method && (ch == '<' || ch == '>')) {
155                 if (!pointyNames.contains(name))
156                     throw new IllegalArgumentException("Invalid member name: " + name);
157             }
158         }
159         return name;
160     }
161 
162     static void validateClassOrInterface(ClassDesc classDesc) {
163         if (!classDesc.isClassOrInterface())
164             throw new IllegalArgumentException("not a class or interface type: " + classDesc);
165     }
166 
167     static int arrayDepth(String descriptorString) {
168         int depth = 0;
169         while (descriptorString.charAt(depth) == '[')
170             depth++;
171         return depth;
172     }
173 
174     static String binaryToInternal(String name) {
175         return name.replace('.', '/');
176     }
177 
178     static String internalToBinary(String name) {
179         return name.replace('/', '.');
180     }
181 
182     static String dropLastChar(String s) {
183         return s.substring(0, s.length() - 1);
184     }
185 
186     static String dropFirstAndLastChar(String s) {
187         return s.substring(1, s.length() - 1);
188     }
189 
190     /**
191      * Parses a method descriptor string, and return a list of field descriptor
192      * strings, return type first, then parameter types
193      *
194      * @param descriptor the descriptor string
195      * @return the list of types
196      * @throws IllegalArgumentException if the descriptor string is not valid
197      */
198     static List<String> parseMethodDescriptor(String descriptor) {
199         int cur = 0, end = descriptor.length();
200         ArrayList<String> ptypes = new ArrayList<>();
201         ptypes.add(null); //placeholder for return type
202 
203         if (cur >= end || descriptor.charAt(cur) != '(')
204             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
205 
206         ++cur;  // skip '('
207         while (cur < end && descriptor.charAt(cur) != ')') {
208             int len = skipOverFieldSignature(descriptor, cur, end, false);
209             if (len == 0)
210                 throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
211             ptypes.add(descriptor.substring(cur, cur + len));
212             cur += len;
213         }
214         if (cur >= end)
215             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
216         ++cur;  // skip ')'
217 
218         int rLen = skipOverFieldSignature(descriptor, cur, end, true);
219         if (rLen == 0 || cur + rLen != end)
220             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
221         ptypes.set(0, descriptor.substring(cur, cur + rLen));
222         return ptypes;
223     }
224 
225     private static final char JVM_SIGNATURE_ARRAY = '[';
226     private static final char JVM_SIGNATURE_BYTE = 'B';
227     private static final char JVM_SIGNATURE_CHAR = 'C';
228     private static final char JVM_SIGNATURE_CLASS = 'L';
229     private static final char JVM_SIGNATURE_VALUE_TYPE = 'Q';
230     private static final char JVM_SIGNATURE_ENDCLASS = ';';
231     private static final char JVM_SIGNATURE_ENUM = 'E';
232     private static final char JVM_SIGNATURE_FLOAT = 'F';
233     private static final char JVM_SIGNATURE_DOUBLE = 'D';
234     private static final char JVM_SIGNATURE_FUNC = '(';
235     private static final char JVM_SIGNATURE_ENDFUNC = ')';
236     private static final char JVM_SIGNATURE_INT = 'I';
237     private static final char JVM_SIGNATURE_LONG = 'J';
238     private static final char JVM_SIGNATURE_SHORT = 'S';
239     private static final char JVM_SIGNATURE_VOID = 'V';
240     private static final char JVM_SIGNATURE_BOOLEAN = 'Z';
241 
242     /**
243      * Validates that the characters at [start, end) within the provided string
244      * describe a valid field type descriptor.
245      * @param descriptor the descriptor string
246      * @param start the starting index into the string
247      * @param end the ending index within the string
248      * @param voidOK is void acceptable?
249      * @return the length of the descriptor, or 0 if it is not a descriptor
250      * @throws IllegalArgumentException if the descriptor string is not valid
251      */
252     @SuppressWarnings("fallthrough")
253     static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) {
254         int arrayDim = 0;
255         int index = start;
256         while (index < end) {
257             switch (descriptor.charAt(index)) {
258                 case JVM_SIGNATURE_VOID: if (!voidOK) { return 0; }
259                 case JVM_SIGNATURE_BOOLEAN:
260                 case JVM_SIGNATURE_BYTE:
261                 case JVM_SIGNATURE_CHAR:
262                 case JVM_SIGNATURE_SHORT:
263                 case JVM_SIGNATURE_INT:
264                 case JVM_SIGNATURE_FLOAT:
265                 case JVM_SIGNATURE_LONG:
266                 case JVM_SIGNATURE_DOUBLE:
267                     return index - start + 1;
268                 case JVM_SIGNATURE_CLASS:
269                 case JVM_SIGNATURE_VALUE_TYPE:
270                     // state variable for detection of illegal states, such as:
271                     // empty unqualified name, '//', leading '/', or trailing '/'
272                     boolean legal = false;
273                     while (++index < end) {
274                         switch (descriptor.charAt(index)) {
275                             case ';' -> {
276                                 // illegal state on parser exit indicates empty unqualified name or trailing '/'
277                                 return legal ? index - start + 1 : 0;
278                             }
279                             case '.', '[' -> {
280                                 // do not permit '.' or '['
281                                 return 0;
282                             }
283                             case '/' -> {
284                                 // illegal state when received '/' indicates '//' or leading '/'
285                                 if (!legal) return 0;
286                                 legal = false;
287                             }
288                             default ->
289                                 legal = true;
290                         }
291                     }
292                     return 0;
293                 case JVM_SIGNATURE_ARRAY:
294                     arrayDim++;
295                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
296                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
297                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
298                     }
299                     // The rest of what's there better be a legal descriptor
300                     index++;
301                     voidOK = false;
302                     break;
303                 default:
304                     return 0;
305             }
306         }
307         return 0;
308     }
309 
310     static boolean verifyUnqualifiedClassName(String name) {
311         for (int index = 0; index < name.length(); index++) {
312             char ch = name.charAt(index);
313             if (ch < 128) {
314                 if (ch == '.' || ch == ';' || ch == '[' ) {
315                     return false;   // do not permit '.', ';', or '['
316                 }
317                 if (ch == '/') {
318                     // check for '//' or leading or trailing '/' which are not legal
319                     // unqualified name must not be empty
320                     if (index == 0 || index + 1 >= name.length() || name.charAt(index + 1) == '/') {
321                         return false;
322                     }
323                 }
324             } else {
325                 index ++;
326             }
327         }
328         return true;
329     }
330 
331     /**
332      * Returns the basic type of the given descriptor.  If {@code verifyClassName}
333      * is true, then this method will validate that the characters at [start, end)
334      * within the given string describe a valid field type descriptor.
335      *
336      * @return the character represents the basic type that the descriptor string
337      * references
338      * @throws IllegalArgumentException if the descriptor string is not valid
339      */
340     static char basicType(String descriptor, int start, int end, boolean verifyClassName) {
341         int arrayDim = 0;
342         int index = start;
343         while (index < end) {
344             char c = descriptor.charAt(index);
345             switch (c) {
346                 case JVM_SIGNATURE_VOID:
347                 case JVM_SIGNATURE_BOOLEAN:
348                 case JVM_SIGNATURE_BYTE:
349                 case JVM_SIGNATURE_CHAR:
350                 case JVM_SIGNATURE_SHORT:
351                 case JVM_SIGNATURE_INT:
352                 case JVM_SIGNATURE_FLOAT:
353                 case JVM_SIGNATURE_LONG:
354                 case JVM_SIGNATURE_DOUBLE:
355                     return c;
356                 case JVM_SIGNATURE_CLASS:
357                 case JVM_SIGNATURE_VALUE_TYPE:
358                     index++;
359                     int indexOfSemi = descriptor.indexOf(';', index);
360                     if (indexOfSemi != -1) {
361                         if (verifyClassName) {
362                             String unqualifiedName = descriptor.substring(index, indexOfSemi);
363                             boolean legal = verifyUnqualifiedClassName(unqualifiedName);
364                             if (!legal) {
365                                 throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
366                             }
367                         }
368                         return c;
369                     }
370                     throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
371                 case JVM_SIGNATURE_ARRAY:
372                     arrayDim++;
373                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
374                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
375                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
376                     }
377                     // The rest of what's there better be a legal descriptor
378                     index++;
379                     break;
380                 default:
381                     throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
382             }
383         }
384         throw new IllegalArgumentException(String.format("not a valid type descriptor: %s", descriptor));
385     }
386 
387 }