1 /*
  2  * Copyright (c) 2018, 2024, 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 jdk.internal.constant;
 26 
 27 import sun.invoke.util.Wrapper;
 28 
 29 import java.lang.constant.ClassDesc;
 30 import java.lang.constant.ConstantDesc;
 31 import java.lang.constant.ConstantDescs;
 32 import java.lang.constant.MethodTypeDesc;
 33 import java.lang.invoke.MethodType;
 34 import java.util.ArrayList;
 35 import java.util.List;
 36 import java.util.Set;
 37 
 38 /**
 39  * Helper methods for the implementation of {@code java.lang.constant}.
 40  */
 41 public final class ConstantUtils {
 42     /** an empty constant descriptor */
 43     public static final ConstantDesc[] EMPTY_CONSTANTDESC = new ConstantDesc[0];
 44     public static final ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0];
 45     public static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
 46     public static final ClassDesc CD_module_info = binaryNameToDesc("module-info");
 47 
 48     private static final Set<String> pointyNames = Set.of(ConstantDescs.INIT_NAME, ConstantDescs.CLASS_INIT_NAME);
 49 
 50     /** No instantiation */
 51     private ConstantUtils() {}
 52 
 53     // Note:
 54     // Non-JDK users should create their own utilities that wrap
 55     // {@code .describeConstable().orElseThrow()} calls;
 56     // these xxDesc methods has undefined and unsafe exceptional
 57     // behavior, so they are not suitable as public APIs.
 58 
 59     /**
 60      * Creates a {@linkplain ClassDesc} from a pre-validated binary name
 61      * for a class or interface type. Validated version of {@link
 62      * ClassDesc#of(String)}.
 63      *
 64      * @param binaryName a binary name
 65      */
 66     public static ClassDesc binaryNameToDesc(String binaryName) {
 67         return ReferenceClassDescImpl.ofValidated("L" + binaryToInternal(binaryName) + ";");
 68     }
 69 
 70     /**
 71      * Creates a ClassDesc from a Class object, requires that this class
 72      * can always be described nominally, i.e. this class is not a
 73      * hidden class or interface or an array with a hidden component
 74      * type.
 75      */
 76     public static ClassDesc classDesc(Class<?> type) {
 77         if (type.isPrimitive()) {
 78             return Wrapper.forPrimitiveType(type).basicClassDescriptor();
 79         }
 80         return referenceClassDesc(type);
 81     }
 82 
 83     /**
 84      * Creates a ClassDesc from a Class object representing a non-hidden
 85      * class or interface or an array type with a non-hidden component type.
 86      */
 87     public static ClassDesc referenceClassDesc(Class<?> type) {
 88         return ReferenceClassDescImpl.ofValidated(type.descriptorString());
 89     }
 90 
 91     /**
 92      * Creates a MethodTypeDesc from a MethodType object, requires that
 93      * the type can be described nominally, i.e. all of its return
 94      * type and parameter types can be described nominally.
 95      */
 96     public static MethodTypeDesc methodTypeDesc(MethodType type) {
 97         var returnDesc = classDesc(type.returnType());
 98         if (type.parameterCount() == 0) {
 99             return MethodTypeDescImpl.ofValidated(returnDesc, EMPTY_CLASSDESC);
100         }
101         var paramDescs = new ClassDesc[type.parameterCount()];
102         for (int i = 0; i < type.parameterCount(); i++) {
103             paramDescs[i] = classDesc(type.parameterType(i));
104         }
105         return MethodTypeDescImpl.ofValidated(returnDesc, paramDescs);
106     }
107 
108     /**
109      * Creates a MethodTypeDesc from return class and parameter
110      * class objects, requires that all of them can be described nominally.
111      * This version is mainly useful for working with Method objects.
112      */
113     public static MethodTypeDesc methodTypeDesc(Class<?> returnType, Class<?>[] parameterTypes) {
114         var returnDesc = classDesc(returnType);
115         if (parameterTypes.length == 0) {
116             return MethodTypeDescImpl.ofValidated(returnDesc, EMPTY_CLASSDESC);
117         }
118         var paramDescs = new ClassDesc[parameterTypes.length];
119         for (int i = 0; i < parameterTypes.length; i++) {
120             paramDescs[i] = classDesc(parameterTypes[i]);
121         }
122         return MethodTypeDescImpl.ofValidated(returnDesc, paramDescs);
123     }
124 
125     /**
126      * Validates the correctness of a binary class name. In particular checks for the presence of
127      * invalid characters in the name.
128      *
129      * @param name the class name
130      * @return the class name passed if valid
131      * @throws IllegalArgumentException if the class name is invalid
132      * @throws NullPointerException if class name is {@code null}
133      */
134     public static String validateBinaryClassName(String name) {
135         for (int i = 0; i < name.length(); i++) {
136             char ch = name.charAt(i);
137             if (ch == ';' || ch == '[' || ch == '/')
138                 throw new IllegalArgumentException("Invalid class name: " + name);
139         }
140         return name;
141     }
142 
143     /**
144      * Validates the correctness of an internal class name.
145      * In particular checks for the presence of invalid characters in the name.
146      *
147      * @param name the class name
148      * @return the class name passed if valid
149      * @throws IllegalArgumentException if the class name is invalid
150      * @throws NullPointerException if class name is {@code null}
151      */
152     public static String validateInternalClassName(String name) {
153         for (int i = 0; i < name.length(); i++) {
154             char ch = name.charAt(i);
155             if (ch == ';' || ch == '[' || ch == '.')
156                 throw new IllegalArgumentException("Invalid class name: " + name);
157         }
158         return name;
159     }
160 
161     /**
162      * Validates the correctness of a binary package name.
163      * In particular checks for the presence of invalid characters in the name.
164      * Empty package name is allowed.
165      *
166      * @param name the package name
167      * @return the package name passed if valid
168      * @throws IllegalArgumentException if the package name is invalid
169      * @throws NullPointerException if the package name is {@code null}
170      */
171     public static String validateBinaryPackageName(String name) {
172         for (int i = 0; i < name.length(); i++) {
173             char ch = name.charAt(i);
174             if (ch == ';' || ch == '[' || ch == '/')
175                 throw new IllegalArgumentException("Invalid package name: " + name);
176         }
177         return name;
178     }
179 
180     /**
181      * Validates the correctness of an internal package name.
182      * In particular checks for the presence of invalid characters in the name.
183      * Empty package name is allowed.
184      *
185      * @param name the package name
186      * @return the package name passed if valid
187      * @throws IllegalArgumentException if the package name is invalid
188      * @throws NullPointerException if the package name is {@code null}
189      */
190     public static String validateInternalPackageName(String name) {
191         for (int i = 0; i < name.length(); i++) {
192             char ch = name.charAt(i);
193             if (ch == ';' || ch == '[' || ch == '.')
194                 throw new IllegalArgumentException("Invalid package name: " + name);
195         }
196         return name;
197     }
198 
199     /**
200      * Validates the correctness of a module name.
201      * In particular checks for the presence of invalid characters in the name.
202      * Empty module name is allowed.
203      *
204      * {@jvms 4.2.3} Module and Package Names
205      *
206      * @param name the module name
207      * @return the module name passed if valid
208      * @throws IllegalArgumentException if the module name is invalid
209      * @throws NullPointerException if the module name is {@code null}
210      */
211     public static String validateModuleName(String name) {
212         for (int i = name.length() - 1; i >= 0; i--) {
213             char ch = name.charAt(i);
214             if ((ch >= '\u0000' && ch <= '\u001F')
215             || ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\')))
216                 throw new IllegalArgumentException("Invalid module name: " + name);
217         }
218         return name;
219     }
220 
221     /**
222      * Validates a member name
223      *
224      * @param name the name of the member
225      * @return the name passed if valid
226      * @throws IllegalArgumentException if the member name is invalid
227      * @throws NullPointerException if the member name is {@code null}
228      */
229     public static String validateMemberName(String name, boolean method) {
230         int len = name.length();
231         if (len == 0)
232             throw new IllegalArgumentException("zero-length member name");
233         for (int i = 0; i < len; i++) {
234             char ch = name.charAt(i);
235             // common case fast-path
236             if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
237                 continue;
238             if (ch == '.' || ch == ';' || ch == '[' || ch == '/')
239                 throw new IllegalArgumentException("Invalid member name: " + name);
240             if (method && (ch == '<' || ch == '>')) {
241                 if (!pointyNames.contains(name))
242                     throw new IllegalArgumentException("Invalid member name: " + name);
243             }
244         }
245         return name;
246     }
247 
248     public static void validateClassOrInterface(ClassDesc classDesc) {
249         if (!classDesc.isClassOrInterface())
250             throw new IllegalArgumentException("not a class or interface type: " + classDesc);
251     }
252 
253     public static int arrayDepth(String descriptorString) {
254         int depth = 0;
255         while (descriptorString.charAt(depth) == '[')
256             depth++;
257         return depth;
258     }
259 
260     public static String binaryToInternal(String name) {
261         return name.replace('.', '/');
262     }
263 
264     public static String internalToBinary(String name) {
265         return name.replace('/', '.');
266     }
267 
268     public static String dropFirstAndLastChar(String s) {
269         return s.substring(1, s.length() - 1);
270     }
271 
272     /**
273      * Parses a method descriptor string, and return a list of field descriptor
274      * strings, return type first, then parameter types
275      *
276      * @param descriptor the descriptor string
277      * @return the list of types
278      * @throws IllegalArgumentException if the descriptor string is not valid
279      */
280     public static List<ClassDesc> parseMethodDescriptor(String descriptor) {
281         int cur = 0, end = descriptor.length();
282         ArrayList<ClassDesc> ptypes = new ArrayList<>();
283         ptypes.add(null); // placeholder for return type
284 
285         if (cur >= end || descriptor.charAt(cur) != '(')
286             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
287 
288         ++cur;  // skip '('
289         while (cur < end && descriptor.charAt(cur) != ')') {
290             int len = skipOverFieldSignature(descriptor, cur, end, false);
291             if (len == 0)
292                 throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
293             ptypes.add(resolveClassDesc(descriptor, cur, len));
294             cur += len;
295         }
296         if (cur >= end)
297             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
298         ++cur;  // skip ')'
299 
300         int rLen = skipOverFieldSignature(descriptor, cur, end, true);
301         if (rLen == 0 || cur + rLen != end)
302             throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
303         ptypes.set(0, resolveClassDesc(descriptor, cur, rLen));
304         return ptypes;
305     }
306 
307     private static ClassDesc resolveClassDesc(String descriptor, int start, int len) {
308         if (len == 1) {
309             return Wrapper.forPrimitiveType(descriptor.charAt(start)).basicClassDescriptor();
310         }
311         // Pre-verified in parseMethodDescriptor; avoid redundant verification
312         return ReferenceClassDescImpl.ofValidated(descriptor.substring(start, start + len));
313     }
314 
315     private static final char JVM_SIGNATURE_ARRAY = '[';
316     private static final char JVM_SIGNATURE_BYTE = 'B';
317     private static final char JVM_SIGNATURE_CHAR = 'C';
318     private static final char JVM_SIGNATURE_CLASS = 'L';
319     private static final char JVM_SIGNATURE_ENDCLASS = ';';
320     private static final char JVM_SIGNATURE_ENUM = 'E';
321     private static final char JVM_SIGNATURE_FLOAT = 'F';
322     private static final char JVM_SIGNATURE_DOUBLE = 'D';
323     private static final char JVM_SIGNATURE_FUNC = '(';
324     private static final char JVM_SIGNATURE_ENDFUNC = ')';
325     private static final char JVM_SIGNATURE_INT = 'I';
326     private static final char JVM_SIGNATURE_LONG = 'J';
327     private static final char JVM_SIGNATURE_SHORT = 'S';
328     private static final char JVM_SIGNATURE_VOID = 'V';
329     private static final char JVM_SIGNATURE_BOOLEAN = 'Z';
330 
331     /**
332      * Validates that the characters at [start, end) within the provided string
333      * describe a valid field type descriptor.
334      * @param descriptor the descriptor string
335      * @param start the starting index into the string
336      * @param end the ending index within the string
337      * @param voidOK is void acceptable?
338      * @return the length of the descriptor, or 0 if it is not a descriptor
339      * @throws IllegalArgumentException if the descriptor string is not valid
340      */
341     @SuppressWarnings("fallthrough")
342     static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) {
343         int arrayDim = 0;
344         int index = start;
345         while (index < end) {
346             switch (descriptor.charAt(index)) {
347                 case JVM_SIGNATURE_VOID: if (!voidOK) { return 0; }
348                 case JVM_SIGNATURE_BOOLEAN:
349                 case JVM_SIGNATURE_BYTE:
350                 case JVM_SIGNATURE_CHAR:
351                 case JVM_SIGNATURE_SHORT:
352                 case JVM_SIGNATURE_INT:
353                 case JVM_SIGNATURE_FLOAT:
354                 case JVM_SIGNATURE_LONG:
355                 case JVM_SIGNATURE_DOUBLE:
356                     return index - start + 1;
357                 case JVM_SIGNATURE_CLASS:
358                     // state variable for detection of illegal states, such as:
359                     // empty unqualified name, '//', leading '/', or trailing '/'
360                     boolean legal = false;
361                     while (++index < end) {
362                         switch (descriptor.charAt(index)) {
363                             case ';' -> {
364                                 // illegal state on parser exit indicates empty unqualified name or trailing '/'
365                                 return legal ? index - start + 1 : 0;
366                             }
367                             case '.', '[' -> {
368                                 // do not permit '.' or '['
369                                 return 0;
370                             }
371                             case '/' -> {
372                                 // illegal state when received '/' indicates '//' or leading '/'
373                                 if (!legal) return 0;
374                                 legal = false;
375                             }
376                             default ->
377                                 legal = true;
378                         }
379                     }
380                     return 0;
381                 case JVM_SIGNATURE_ARRAY:
382                     arrayDim++;
383                     if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
384                         throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
385                                 ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
386                     }
387                     // The rest of what's there better be a legal descriptor
388                     index++;
389                     voidOK = false;
390                     break;
391                 default:
392                     return 0;
393             }
394         }
395         return 0;
396     }
397 }