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