• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // ASM: a very small and fast Java bytecode manipulation framework
2 // Copyright (c) 2000-2011 INRIA, France Telecom
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 // 3. Neither the name of the copyright holders nor the names of its
14 //    contributors may be used to endorse or promote products derived from
15 //    this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 // THE POSSIBILITY OF SUCH DAMAGE.
28 package org.objectweb.asm.signature;
29 
30 /**
31  * A parser for signature literals, as defined in the Java Virtual Machine Specification (JVMS), to
32  * visit them with a SignatureVisitor.
33  *
34  * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1">JVMS
35  *     4.7.9.1</a>
36  * @author Thomas Hallgren
37  * @author Eric Bruneton
38  */
39 public class SignatureReader {
40 
41   /** The JVMS signature to be read. */
42   private final String signatureValue;
43 
44   /**
45    * Constructs a {@link SignatureReader} for the given signature.
46    *
47    * @param signature A <i>JavaTypeSignature</i>, <i>ClassSignature</i> or <i>MethodSignature</i>.
48    */
SignatureReader(final String signature)49   public SignatureReader(final String signature) {
50     this.signatureValue = signature;
51   }
52 
53   /**
54    * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is
55    * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to
56    * be called on a {@link SignatureReader} that was created using a <i>ClassSignature</i> (such as
57    * the <code>signature</code> parameter of the {@link org.objectweb.asm.ClassVisitor#visit}
58    * method) or a <i>MethodSignature</i> (such as the <code>signature</code> parameter of the {@link
59    * org.objectweb.asm.ClassVisitor#visitMethod} method).
60    *
61    * @param signatureVistor the visitor that must visit this signature.
62    */
accept(final SignatureVisitor signatureVistor)63   public void accept(final SignatureVisitor signatureVistor) {
64     String signature = this.signatureValue;
65     int length = signature.length();
66     int offset; // Current offset in the parsed signature (parsed from left to right).
67     char currentChar; // The signature character at 'offset', or just before.
68 
69     // If the signature starts with '<', it starts with TypeParameters, i.e. a formal type parameter
70     // identifier, followed by one or more pair ':',ReferenceTypeSignature (for its class bound and
71     // interface bounds).
72     if (signature.charAt(0) == '<') {
73       // Invariant: offset points to the second character of a formal type parameter name at the
74       // beginning of each iteration of the loop below.
75       offset = 2;
76       do {
77         // The formal type parameter name is everything between offset - 1 and the first ':'.
78         int classBoundStartOffset = signature.indexOf(':', offset);
79         signatureVistor.visitFormalTypeParameter(
80             signature.substring(offset - 1, classBoundStartOffset));
81 
82         // If the character after the ':' class bound marker is not the start of a
83         // ReferenceTypeSignature, it means the class bound is empty (which is a valid case).
84         offset = classBoundStartOffset + 1;
85         currentChar = signature.charAt(offset);
86         if (currentChar == 'L' || currentChar == '[' || currentChar == 'T') {
87           offset = parseType(signature, offset, signatureVistor.visitClassBound());
88         }
89 
90         // While the character after the class bound or after the last parsed interface bound
91         // is ':', we need to parse another interface bound.
92         while ((currentChar = signature.charAt(offset++)) == ':') {
93           offset = parseType(signature, offset, signatureVistor.visitInterfaceBound());
94         }
95 
96         // At this point a TypeParameter has been fully parsed, and we need to parse the next one
97         // (note that currentChar is now the first character of the next TypeParameter, and that
98         // offset points to the second character), unless the character just after this
99         // TypeParameter signals the end of the TypeParameters.
100       } while (currentChar != '>');
101     } else {
102       offset = 0;
103     }
104 
105     // If the (optional) TypeParameters is followed by '(' this means we are parsing a
106     // MethodSignature, which has JavaTypeSignature type inside parentheses, followed by a Result
107     // type and optional ThrowsSignature types.
108     if (signature.charAt(offset) == '(') {
109       offset++;
110       while (signature.charAt(offset) != ')') {
111         offset = parseType(signature, offset, signatureVistor.visitParameterType());
112       }
113       // Use offset + 1 to skip ')'.
114       offset = parseType(signature, offset + 1, signatureVistor.visitReturnType());
115       while (offset < length) {
116         // Use offset + 1 to skip the first character of a ThrowsSignature, i.e. '^'.
117         offset = parseType(signature, offset + 1, signatureVistor.visitExceptionType());
118       }
119     } else {
120       // Otherwise we are parsing a ClassSignature (by hypothesis on the method input), which has
121       // one or more ClassTypeSignature for the super class and the implemented interfaces.
122       offset = parseType(signature, offset, signatureVistor.visitSuperclass());
123       while (offset < length) {
124         offset = parseType(signature, offset, signatureVistor.visitInterface());
125       }
126     }
127   }
128 
129   /**
130    * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is
131    * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to
132    * be called on a {@link SignatureReader} that was created using a <i>JavaTypeSignature</i>, such
133    * as the <code>signature</code> parameter of the {@link
134    * org.objectweb.asm.ClassVisitor#visitField} or {@link
135    * org.objectweb.asm.MethodVisitor#visitLocalVariable} methods.
136    *
137    * @param signatureVisitor the visitor that must visit this signature.
138    */
acceptType(final SignatureVisitor signatureVisitor)139   public void acceptType(final SignatureVisitor signatureVisitor) {
140     parseType(signatureValue, 0, signatureVisitor);
141   }
142 
143   /**
144    * Parses a JavaTypeSignature and makes the given visitor visit it.
145    *
146    * @param signature a string containing the signature that must be parsed.
147    * @param startOffset index of the first character of the signature to parsed.
148    * @param signatureVisitor the visitor that must visit this signature.
149    * @return the index of the first character after the parsed signature.
150    */
parseType( final String signature, final int startOffset, final SignatureVisitor signatureVisitor)151   private static int parseType(
152       final String signature, final int startOffset, final SignatureVisitor signatureVisitor) {
153     int offset = startOffset; // Current offset in the parsed signature.
154     char currentChar = signature.charAt(offset++); // The signature character at 'offset'.
155 
156     // Switch based on the first character of the JavaTypeSignature, which indicates its kind.
157     switch (currentChar) {
158       case 'Z':
159       case 'C':
160       case 'B':
161       case 'S':
162       case 'I':
163       case 'F':
164       case 'J':
165       case 'D':
166       case 'V':
167         // Case of a BaseType or a VoidDescriptor.
168         signatureVisitor.visitBaseType(currentChar);
169         return offset;
170 
171       case '[':
172         // Case of an ArrayTypeSignature, a '[' followed by a JavaTypeSignature.
173         return parseType(signature, offset, signatureVisitor.visitArrayType());
174 
175       case 'T':
176         // Case of TypeVariableSignature, an identifier between 'T' and ';'.
177         int endOffset = signature.indexOf(';', offset);
178         signatureVisitor.visitTypeVariable(signature.substring(offset, endOffset));
179         return endOffset + 1;
180 
181       case 'L':
182         // Case of a ClassTypeSignature, which ends with ';'.
183         // These signatures have a main class type followed by zero or more inner class types
184         // (separated by '.'). Each can have type arguments, inside '<' and '>'.
185         int start = offset; // The start offset of the currently parsed main or inner class name.
186         boolean visited = false; // Whether the currently parsed class name has been visited.
187         boolean inner = false; // Whether we are currently parsing an inner class type.
188         // Parses the signature, one character at a time.
189         while (true) {
190           currentChar = signature.charAt(offset++);
191           if (currentChar == '.' || currentChar == ';') {
192             // If a '.' or ';' is encountered, this means we have fully parsed the main class name
193             // or an inner class name. This name may already have been visited it is was followed by
194             // type arguments between '<' and '>'. If not, we need to visit it here.
195             if (!visited) {
196               String name = signature.substring(start, offset - 1);
197               if (inner) {
198                 signatureVisitor.visitInnerClassType(name);
199               } else {
200                 signatureVisitor.visitClassType(name);
201               }
202             }
203             // If we reached the end of the ClassTypeSignature return, otherwise start the parsing
204             // of a new class name, which is necessarily an inner class name.
205             if (currentChar == ';') {
206               signatureVisitor.visitEnd();
207               break;
208             }
209             start = offset;
210             visited = false;
211             inner = true;
212           } else if (currentChar == '<') {
213             // If a '<' is encountered, this means we have fully parsed the main class name or an
214             // inner class name, and that we now need to parse TypeArguments. First, we need to
215             // visit the parsed class name.
216             String name = signature.substring(start, offset - 1);
217             if (inner) {
218               signatureVisitor.visitInnerClassType(name);
219             } else {
220               signatureVisitor.visitClassType(name);
221             }
222             visited = true;
223             // Now, parse the TypeArgument(s), one at a time.
224             while ((currentChar = signature.charAt(offset)) != '>') {
225               switch (currentChar) {
226                 case '*':
227                   // Unbounded TypeArgument.
228                   ++offset;
229                   signatureVisitor.visitTypeArgument();
230                   break;
231                 case '+':
232                 case '-':
233                   // Extends or Super TypeArgument. Use offset + 1 to skip the '+' or '-'.
234                   offset =
235                       parseType(
236                           signature, offset + 1, signatureVisitor.visitTypeArgument(currentChar));
237                   break;
238                 default:
239                   // Instanceof TypeArgument. The '=' is implicit.
240                   offset = parseType(signature, offset, signatureVisitor.visitTypeArgument('='));
241                   break;
242               }
243             }
244           }
245         }
246         return offset;
247 
248       default:
249         throw new IllegalArgumentException();
250     }
251   }
252 }
253