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 }