|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package jdk.internal.org.objectweb.asm.util; |
|
|
|
import java.io.FileInputStream; |
|
import java.io.PrintWriter; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.Map; |
|
|
|
import jdk.internal.org.objectweb.asm.AnnotationVisitor; |
|
import jdk.internal.org.objectweb.asm.Attribute; |
|
import jdk.internal.org.objectweb.asm.ClassReader; |
|
import jdk.internal.org.objectweb.asm.ClassVisitor; |
|
import jdk.internal.org.objectweb.asm.FieldVisitor; |
|
import jdk.internal.org.objectweb.asm.Label; |
|
import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
import jdk.internal.org.objectweb.asm.Opcodes; |
|
import jdk.internal.org.objectweb.asm.Type; |
|
import jdk.internal.org.objectweb.asm.TypePath; |
|
import jdk.internal.org.objectweb.asm.TypeReference; |
|
import jdk.internal.org.objectweb.asm.tree.ClassNode; |
|
import jdk.internal.org.objectweb.asm.tree.MethodNode; |
|
import jdk.internal.org.objectweb.asm.tree.analysis.Analyzer; |
|
import jdk.internal.org.objectweb.asm.tree.analysis.BasicValue; |
|
import jdk.internal.org.objectweb.asm.tree.analysis.Frame; |
|
import jdk.internal.org.objectweb.asm.tree.analysis.SimpleVerifier; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class CheckClassAdapter extends ClassVisitor { |
|
|
|
|
|
|
|
*/ |
|
private int version; |
|
|
|
|
|
|
|
*/ |
|
private boolean start; |
|
|
|
|
|
|
|
*/ |
|
private boolean source; |
|
|
|
|
|
|
|
*/ |
|
private boolean outer; |
|
|
|
|
|
|
|
*/ |
|
private boolean end; |
|
|
|
|
|
|
|
|
|
*/ |
|
private Map<Label, Integer> labels; |
|
|
|
|
|
|
|
*/ |
|
private boolean checkDataFlow; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void main(final String[] args) throws Exception { |
|
if (args.length != 1) { |
|
System.err.println("Verifies the given class."); |
|
System.err.println("Usage: CheckClassAdapter " |
|
+ "<fully qualified class name or class file name>"); |
|
return; |
|
} |
|
ClassReader cr; |
|
if (args[0].endsWith(".class")) { |
|
cr = new ClassReader(new FileInputStream(args[0])); |
|
} else { |
|
cr = new ClassReader(args[0]); |
|
} |
|
|
|
verify(cr, false, new PrintWriter(System.err)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void verify(final ClassReader cr, final ClassLoader loader, |
|
final boolean dump, final PrintWriter pw) { |
|
ClassNode cn = new ClassNode(); |
|
cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG); |
|
|
|
Type syperType = cn.superName == null ? null : Type |
|
.getObjectType(cn.superName); |
|
List<MethodNode> methods = cn.methods; |
|
|
|
List<Type> interfaces = new ArrayList<Type>(); |
|
for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) { |
|
interfaces.add(Type.getObjectType(i.next())); |
|
} |
|
|
|
for (int i = 0; i < methods.size(); ++i) { |
|
MethodNode method = methods.get(i); |
|
SimpleVerifier verifier = new SimpleVerifier( |
|
Type.getObjectType(cn.name), syperType, interfaces, |
|
(cn.access & Opcodes.ACC_INTERFACE) != 0); |
|
Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier); |
|
if (loader != null) { |
|
verifier.setClassLoader(loader); |
|
} |
|
try { |
|
a.analyze(cn.name, method); |
|
if (!dump) { |
|
continue; |
|
} |
|
} catch (Exception e) { |
|
e.printStackTrace(pw); |
|
} |
|
printAnalyzerResult(method, a, pw); |
|
} |
|
pw.flush(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void verify(final ClassReader cr, final boolean dump, |
|
final PrintWriter pw) { |
|
verify(cr, null, dump, pw); |
|
} |
|
|
|
static void printAnalyzerResult(MethodNode method, Analyzer<BasicValue> a, |
|
final PrintWriter pw) { |
|
Frame<BasicValue>[] frames = a.getFrames(); |
|
Textifier t = new Textifier(); |
|
TraceMethodVisitor mv = new TraceMethodVisitor(t); |
|
|
|
pw.println(method.name + method.desc); |
|
for (int j = 0; j < method.instructions.size(); ++j) { |
|
method.instructions.get(j).accept(mv); |
|
|
|
StringBuilder sb = new StringBuilder(); |
|
Frame<BasicValue> f = frames[j]; |
|
if (f == null) { |
|
sb.append('?'); |
|
} else { |
|
for (int k = 0; k < f.getLocals(); ++k) { |
|
sb.append(getShortName(f.getLocal(k).toString())) |
|
.append(' '); |
|
} |
|
sb.append(" : "); |
|
for (int k = 0; k < f.getStackSize(); ++k) { |
|
sb.append(getShortName(f.getStack(k).toString())) |
|
.append(' '); |
|
} |
|
} |
|
while (sb.length() < method.maxStack + method.maxLocals + 1) { |
|
sb.append(' '); |
|
} |
|
pw.print(Integer.toString(j + 100000).substring(1)); |
|
pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1)); |
|
} |
|
for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { |
|
method.tryCatchBlocks.get(j).accept(mv); |
|
pw.print(" " + t.text.get(t.text.size() - 1)); |
|
} |
|
pw.println(); |
|
} |
|
|
|
private static String getShortName(final String name) { |
|
int n = name.lastIndexOf('/'); |
|
int k = name.length(); |
|
if (name.charAt(k - 1) == ';') { |
|
k--; |
|
} |
|
return n == -1 ? name : name.substring(n + 1, k); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public CheckClassAdapter(final ClassVisitor cv) { |
|
this(cv, true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) { |
|
this(Opcodes.ASM5, cv, checkDataFlow); |
|
if (getClass() != CheckClassAdapter.class) { |
|
throw new IllegalStateException(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected CheckClassAdapter(final int api, final ClassVisitor cv, |
|
final boolean checkDataFlow) { |
|
super(api, cv); |
|
this.labels = new HashMap<Label, Integer>(); |
|
this.checkDataFlow = checkDataFlow; |
|
} |
|
|
|
// ------------------------------------------------------------------------ |
|
// Implementation of the ClassVisitor interface |
|
// ------------------------------------------------------------------------ |
|
|
|
@Override |
|
public void visit(final int version, final int access, final String name, |
|
final String signature, final String superName, |
|
final String[] interfaces) { |
|
if (start) { |
|
throw new IllegalStateException("visit must be called only once"); |
|
} |
|
start = true; |
|
checkState(); |
|
checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL |
|
+ Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE |
|
+ Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC |
|
+ Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM |
|
+ Opcodes.ACC_DEPRECATED + 0x40000); |
|
if (name == null || !name.endsWith("package-info")) { |
|
CheckMethodAdapter.checkInternalName(name, "class name"); |
|
} |
|
if ("java/lang/Object".equals(name)) { |
|
if (superName != null) { |
|
throw new IllegalArgumentException( |
|
"The super class name of the Object class must be 'null'"); |
|
} |
|
} else { |
|
CheckMethodAdapter.checkInternalName(superName, "super class name"); |
|
} |
|
if (signature != null) { |
|
checkClassSignature(signature); |
|
} |
|
if ((access & Opcodes.ACC_INTERFACE) != 0) { |
|
if (!"java/lang/Object".equals(superName)) { |
|
throw new IllegalArgumentException( |
|
"The super class name of interfaces must be 'java/lang/Object'"); |
|
} |
|
} |
|
if (interfaces != null) { |
|
for (int i = 0; i < interfaces.length; ++i) { |
|
CheckMethodAdapter.checkInternalName(interfaces[i], |
|
"interface name at index " + i); |
|
} |
|
} |
|
this.version = version; |
|
super.visit(version, access, name, signature, superName, interfaces); |
|
} |
|
|
|
@Override |
|
public void visitSource(final String file, final String debug) { |
|
checkState(); |
|
if (source) { |
|
throw new IllegalStateException( |
|
"visitSource can be called only once."); |
|
} |
|
source = true; |
|
super.visitSource(file, debug); |
|
} |
|
|
|
@Override |
|
public void visitOuterClass(final String owner, final String name, |
|
final String desc) { |
|
checkState(); |
|
if (outer) { |
|
throw new IllegalStateException( |
|
"visitOuterClass can be called only once."); |
|
} |
|
outer = true; |
|
if (owner == null) { |
|
throw new IllegalArgumentException("Illegal outer class owner"); |
|
} |
|
if (desc != null) { |
|
CheckMethodAdapter.checkMethodDesc(desc); |
|
} |
|
super.visitOuterClass(owner, name, desc); |
|
} |
|
|
|
@Override |
|
public void visitInnerClass(final String name, final String outerName, |
|
final String innerName, final int access) { |
|
checkState(); |
|
CheckMethodAdapter.checkInternalName(name, "class name"); |
|
if (outerName != null) { |
|
CheckMethodAdapter.checkInternalName(outerName, "outer class name"); |
|
} |
|
if (innerName != null) { |
|
int start = 0; |
|
while (start < innerName.length() |
|
&& Character.isDigit(innerName.charAt(start))) { |
|
start++; |
|
} |
|
if (start == 0 || start < innerName.length()) { |
|
CheckMethodAdapter.checkIdentifier(innerName, start, -1, |
|
"inner class name"); |
|
} |
|
} |
|
checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE |
|
+ Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC |
|
+ Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE |
|
+ Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC |
|
+ Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM); |
|
super.visitInnerClass(name, outerName, innerName, access); |
|
} |
|
|
|
@Override |
|
public FieldVisitor visitField(final int access, final String name, |
|
final String desc, final String signature, final Object value) { |
|
checkState(); |
|
checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE |
|
+ Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC |
|
+ Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE |
|
+ Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC |
|
+ Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED + 0x40000); |
|
CheckMethodAdapter.checkUnqualifiedName(version, name, "field name"); |
|
CheckMethodAdapter.checkDesc(desc, false); |
|
if (signature != null) { |
|
checkFieldSignature(signature); |
|
} |
|
if (value != null) { |
|
CheckMethodAdapter.checkConstant(value); |
|
} |
|
FieldVisitor av = super |
|
.visitField(access, name, desc, signature, value); |
|
return new CheckFieldAdapter(av); |
|
} |
|
|
|
@Override |
|
public MethodVisitor visitMethod(final int access, final String name, |
|
final String desc, final String signature, final String[] exceptions) { |
|
checkState(); |
|
checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE |
|
+ Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC |
|
+ Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED |
|
+ Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE |
|
+ Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT |
|
+ Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED + 0x40000); |
|
if (!"<init>".equals(name) && !"<clinit>".equals(name)) { |
|
CheckMethodAdapter.checkMethodIdentifier(version, name, |
|
"method name"); |
|
} |
|
CheckMethodAdapter.checkMethodDesc(desc); |
|
if (signature != null) { |
|
checkMethodSignature(signature); |
|
} |
|
if (exceptions != null) { |
|
for (int i = 0; i < exceptions.length; ++i) { |
|
CheckMethodAdapter.checkInternalName(exceptions[i], |
|
"exception name at index " + i); |
|
} |
|
} |
|
CheckMethodAdapter cma; |
|
if (checkDataFlow) { |
|
cma = new CheckMethodAdapter(access, name, desc, super.visitMethod( |
|
access, name, desc, signature, exceptions), labels); |
|
} else { |
|
cma = new CheckMethodAdapter(super.visitMethod(access, name, desc, |
|
signature, exceptions), labels); |
|
} |
|
cma.version = version; |
|
return cma; |
|
} |
|
|
|
@Override |
|
public AnnotationVisitor visitAnnotation(final String desc, |
|
final boolean visible) { |
|
checkState(); |
|
CheckMethodAdapter.checkDesc(desc, false); |
|
return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); |
|
} |
|
|
|
@Override |
|
public AnnotationVisitor visitTypeAnnotation(final int typeRef, |
|
final TypePath typePath, final String desc, final boolean visible) { |
|
checkState(); |
|
int sort = typeRef >>> 24; |
|
if (sort != TypeReference.CLASS_TYPE_PARAMETER |
|
&& sort != TypeReference.CLASS_TYPE_PARAMETER_BOUND |
|
&& sort != TypeReference.CLASS_EXTENDS) { |
|
throw new IllegalArgumentException("Invalid type reference sort 0x" |
|
+ Integer.toHexString(sort)); |
|
} |
|
checkTypeRefAndPath(typeRef, typePath); |
|
CheckMethodAdapter.checkDesc(desc, false); |
|
return new CheckAnnotationAdapter(super.visitTypeAnnotation(typeRef, |
|
typePath, desc, visible)); |
|
} |
|
|
|
@Override |
|
public void visitAttribute(final Attribute attr) { |
|
checkState(); |
|
if (attr == null) { |
|
throw new IllegalArgumentException( |
|
"Invalid attribute (must not be null)"); |
|
} |
|
super.visitAttribute(attr); |
|
} |
|
|
|
@Override |
|
public void visitEnd() { |
|
checkState(); |
|
end = true; |
|
super.visitEnd(); |
|
} |
|
|
|
// ------------------------------------------------------------------------ |
|
// Utility methods |
|
// ------------------------------------------------------------------------ |
|
|
|
|
|
|
|
|
|
*/ |
|
private void checkState() { |
|
if (!start) { |
|
throw new IllegalStateException( |
|
"Cannot visit member before visit has been called."); |
|
} |
|
if (end) { |
|
throw new IllegalStateException( |
|
"Cannot visit member after visitEnd has been called."); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static void checkAccess(final int access, final int possibleAccess) { |
|
if ((access & ~possibleAccess) != 0) { |
|
throw new IllegalArgumentException("Invalid access flags: " |
|
+ access); |
|
} |
|
int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1; |
|
int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1; |
|
int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1; |
|
if (pub + pri + pro > 1) { |
|
throw new IllegalArgumentException( |
|
"public private and protected are mutually exclusive: " |
|
+ access); |
|
} |
|
int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1; |
|
int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1; |
|
if (fin + abs > 1) { |
|
throw new IllegalArgumentException( |
|
"final and abstract are mutually exclusive: " + access); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void checkClassSignature(final String signature) { |
|
// ClassSignature: |
|
// FormalTypeParameters? ClassTypeSignature ClassTypeSignature* |
|
|
|
int pos = 0; |
|
if (getChar(signature, 0) == '<') { |
|
pos = checkFormalTypeParameters(signature, pos); |
|
} |
|
pos = checkClassTypeSignature(signature, pos); |
|
while (getChar(signature, pos) == 'L') { |
|
pos = checkClassTypeSignature(signature, pos); |
|
} |
|
if (pos != signature.length()) { |
|
throw new IllegalArgumentException(signature + ": error at index " |
|
+ pos); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void checkMethodSignature(final String signature) { |
|
// MethodTypeSignature: |
|
// FormalTypeParameters? ( TypeSignature* ) ( TypeSignature | V ) ( |
|
// ^ClassTypeSignature | ^TypeVariableSignature )* |
|
|
|
int pos = 0; |
|
if (getChar(signature, 0) == '<') { |
|
pos = checkFormalTypeParameters(signature, pos); |
|
} |
|
pos = checkChar('(', signature, pos); |
|
while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) { |
|
pos = checkTypeSignature(signature, pos); |
|
} |
|
pos = checkChar(')', signature, pos); |
|
if (getChar(signature, pos) == 'V') { |
|
++pos; |
|
} else { |
|
pos = checkTypeSignature(signature, pos); |
|
} |
|
while (getChar(signature, pos) == '^') { |
|
++pos; |
|
if (getChar(signature, pos) == 'L') { |
|
pos = checkClassTypeSignature(signature, pos); |
|
} else { |
|
pos = checkTypeVariableSignature(signature, pos); |
|
} |
|
} |
|
if (pos != signature.length()) { |
|
throw new IllegalArgumentException(signature + ": error at index " |
|
+ pos); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void checkFieldSignature(final String signature) { |
|
int pos = checkFieldTypeSignature(signature, 0); |
|
if (pos != signature.length()) { |
|
throw new IllegalArgumentException(signature + ": error at index " |
|
+ pos); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static void checkTypeRefAndPath(int typeRef, TypePath typePath) { |
|
int mask = 0; |
|
switch (typeRef >>> 24) { |
|
case TypeReference.CLASS_TYPE_PARAMETER: |
|
case TypeReference.METHOD_TYPE_PARAMETER: |
|
case TypeReference.METHOD_FORMAL_PARAMETER: |
|
mask = 0xFFFF0000; |
|
break; |
|
case TypeReference.FIELD: |
|
case TypeReference.METHOD_RETURN: |
|
case TypeReference.METHOD_RECEIVER: |
|
case TypeReference.LOCAL_VARIABLE: |
|
case TypeReference.RESOURCE_VARIABLE: |
|
case TypeReference.INSTANCEOF: |
|
case TypeReference.NEW: |
|
case TypeReference.CONSTRUCTOR_REFERENCE: |
|
case TypeReference.METHOD_REFERENCE: |
|
mask = 0xFF000000; |
|
break; |
|
case TypeReference.CLASS_EXTENDS: |
|
case TypeReference.CLASS_TYPE_PARAMETER_BOUND: |
|
case TypeReference.METHOD_TYPE_PARAMETER_BOUND: |
|
case TypeReference.THROWS: |
|
case TypeReference.EXCEPTION_PARAMETER: |
|
mask = 0xFFFFFF00; |
|
break; |
|
case TypeReference.CAST: |
|
case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: |
|
case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: |
|
case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: |
|
case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: |
|
mask = 0xFF0000FF; |
|
break; |
|
default: |
|
throw new IllegalArgumentException("Invalid type reference sort 0x" |
|
+ Integer.toHexString(typeRef >>> 24)); |
|
} |
|
if ((typeRef & ~mask) != 0) { |
|
throw new IllegalArgumentException("Invalid type reference 0x" |
|
+ Integer.toHexString(typeRef)); |
|
} |
|
if (typePath != null) { |
|
for (int i = 0; i < typePath.getLength(); ++i) { |
|
int step = typePath.getStep(i); |
|
if (step != TypePath.ARRAY_ELEMENT |
|
&& step != TypePath.INNER_TYPE |
|
&& step != TypePath.TYPE_ARGUMENT |
|
&& step != TypePath.WILDCARD_BOUND) { |
|
throw new IllegalArgumentException( |
|
"Invalid type path step " + i + " in " + typePath); |
|
} |
|
if (step != TypePath.TYPE_ARGUMENT |
|
&& typePath.getStepArgument(i) != 0) { |
|
throw new IllegalArgumentException( |
|
"Invalid type path step argument for step " + i |
|
+ " in " + typePath); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkFormalTypeParameters(final String signature, int pos) { |
|
// FormalTypeParameters: |
|
// < FormalTypeParameter+ > |
|
|
|
pos = checkChar('<', signature, pos); |
|
pos = checkFormalTypeParameter(signature, pos); |
|
while (getChar(signature, pos) != '>') { |
|
pos = checkFormalTypeParameter(signature, pos); |
|
} |
|
return pos + 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkFormalTypeParameter(final String signature, int pos) { |
|
// FormalTypeParameter: |
|
// Identifier : FieldTypeSignature? (: FieldTypeSignature)* |
|
|
|
pos = checkIdentifier(signature, pos); |
|
pos = checkChar(':', signature, pos); |
|
if ("L[T".indexOf(getChar(signature, pos)) != -1) { |
|
pos = checkFieldTypeSignature(signature, pos); |
|
} |
|
while (getChar(signature, pos) == ':') { |
|
pos = checkFieldTypeSignature(signature, pos + 1); |
|
} |
|
return pos; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkFieldTypeSignature(final String signature, int pos) { |
|
// FieldTypeSignature: |
|
// ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature |
|
// |
|
// ArrayTypeSignature: |
|
// [ TypeSignature |
|
|
|
switch (getChar(signature, pos)) { |
|
case 'L': |
|
return checkClassTypeSignature(signature, pos); |
|
case '[': |
|
return checkTypeSignature(signature, pos + 1); |
|
default: |
|
return checkTypeVariableSignature(signature, pos); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkClassTypeSignature(final String signature, int pos) { |
|
// ClassTypeSignature: |
|
// L Identifier ( / Identifier )* TypeArguments? ( . Identifier |
|
// TypeArguments? )* ; |
|
|
|
pos = checkChar('L', signature, pos); |
|
pos = checkIdentifier(signature, pos); |
|
while (getChar(signature, pos) == '/') { |
|
pos = checkIdentifier(signature, pos + 1); |
|
} |
|
if (getChar(signature, pos) == '<') { |
|
pos = checkTypeArguments(signature, pos); |
|
} |
|
while (getChar(signature, pos) == '.') { |
|
pos = checkIdentifier(signature, pos + 1); |
|
if (getChar(signature, pos) == '<') { |
|
pos = checkTypeArguments(signature, pos); |
|
} |
|
} |
|
return checkChar(';', signature, pos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkTypeArguments(final String signature, int pos) { |
|
// TypeArguments: |
|
// < TypeArgument+ > |
|
|
|
pos = checkChar('<', signature, pos); |
|
pos = checkTypeArgument(signature, pos); |
|
while (getChar(signature, pos) != '>') { |
|
pos = checkTypeArgument(signature, pos); |
|
} |
|
return pos + 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkTypeArgument(final String signature, int pos) { |
|
// TypeArgument: |
|
// * | ( ( + | - )? FieldTypeSignature ) |
|
|
|
char c = getChar(signature, pos); |
|
if (c == '*') { |
|
return pos + 1; |
|
} else if (c == '+' || c == '-') { |
|
pos++; |
|
} |
|
return checkFieldTypeSignature(signature, pos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkTypeVariableSignature(final String signature, |
|
int pos) { |
|
// TypeVariableSignature: |
|
// T Identifier ; |
|
|
|
pos = checkChar('T', signature, pos); |
|
pos = checkIdentifier(signature, pos); |
|
return checkChar(';', signature, pos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkTypeSignature(final String signature, int pos) { |
|
// TypeSignature: |
|
// Z | C | B | S | I | F | J | D | FieldTypeSignature |
|
|
|
switch (getChar(signature, pos)) { |
|
case 'Z': |
|
case 'C': |
|
case 'B': |
|
case 'S': |
|
case 'I': |
|
case 'F': |
|
case 'J': |
|
case 'D': |
|
return pos + 1; |
|
default: |
|
return checkFieldTypeSignature(signature, pos); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkIdentifier(final String signature, int pos) { |
|
if (!Character.isJavaIdentifierStart(getChar(signature, pos))) { |
|
throw new IllegalArgumentException(signature |
|
+ ": identifier expected at index " + pos); |
|
} |
|
++pos; |
|
while (Character.isJavaIdentifierPart(getChar(signature, pos))) { |
|
++pos; |
|
} |
|
return pos; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int checkChar(final char c, final String signature, int pos) { |
|
if (getChar(signature, pos) == c) { |
|
return pos + 1; |
|
} |
|
throw new IllegalArgumentException(signature + ": '" + c |
|
+ "' expected at index " + pos); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static char getChar(final String signature, int pos) { |
|
return pos < signature.length() ? signature.charAt(pos) : (char) 0; |
|
} |
|
} |