blob: 394f5f3c5a65cc08b58d0565cb33a4e7f345023d [file] [log] [blame]
/*
* Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
package com.sun.classanalyzer;
import com.sun.tools.classfile.*;
import com.sun.tools.classfile.Type.*;
import com.sun.tools.classfile.Descriptor.InvalidDescriptor;
import static com.sun.tools.classfile.AccessFlags.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
*
* @author Mandy Chung
*/
public class ClassFileParser {
final Klass this_klass;
final ClassFile classfile;
final ConstantPoolParser constantPoolParser;
final AnnotationParser annotationParser;
final CodeAttributeParser codeAttributeParser;
private final boolean buildDeps;
protected ClassFileParser(InputStream in, long size, boolean buildDeps) throws IOException {
try {
this.classfile = ClassFile.read(in);
this.this_klass = getKlass(this.classfile);
this.buildDeps = buildDeps;
this.constantPoolParser = new ConstantPoolParser(this);
this.annotationParser = new AnnotationParser(this);
this.codeAttributeParser = new CodeAttributeParser(this);
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
}
}
private Klass getKlass(ClassFile cf) throws ConstantPoolException {
Klass k = Klass.getKlass(cf.getName());
k.setAccessFlags(cf.access_flags.flags);
k.setFileSize(cf.byteLength());
return k;
}
public static ClassFileParser newParser(InputStream in, long size, boolean buildDeps) throws IOException {
return new ClassFileParser(in, size, buildDeps);
}
public static ClassFileParser newParser(String classPathname, boolean buildDeps) throws IOException {
return newParser(new File(classPathname), buildDeps);
}
public static ClassFileParser newParser(File f, boolean buildDeps) throws IOException {
BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
try {
return newParser(in, f.length(), buildDeps);
} finally {
in.close();
}
}
public void parseDependency(boolean publicAPIs) throws IOException {
if (publicAPIs && !classfile.access_flags.is(ACC_PUBLIC)) {
// process public APIs only
return;
}
parseClassInfo();
if (!publicAPIs) {
// parse all references in the classfile
constantPoolParser.parseDependency();
}
parseMethods(publicAPIs);
parseFields(publicAPIs);
}
void parseClassInfo() throws IOException {
ConstantPool cpool = classfile.constant_pool;
try {
Signature_attribute sigAttr = (Signature_attribute) classfile.attributes.get(Attribute.Signature);
if (sigAttr == null) {
// use info from class file header
if (classfile.isClass() && classfile.super_class != 0) {
String sn = classfile.getSuperclassName();
addExtends(sn);
}
for (int i = 0; i < classfile.interfaces.length; i++) {
String interf = classfile.getInterfaceName(i);
if (classfile.isClass()) {
addImplements(interf);
} else {
addExtends(interf);
}
}
} else {
Type t = sigAttr.getParsedSignature().getType(cpool);
// The signature parser cannot disambiguate between a
// FieldType and a ClassSignatureType that only contains a superclass type.
if (t instanceof Type.ClassSigType) {
Type.ClassSigType cst = Type.ClassSigType.class.cast(t);
if (cst.superclassType != null) {
for (Klass k : getKlass(cst.superclassType)) {
addExtends(k);
}
}
if (cst.superinterfaceTypes != null) {
for (Type t1 : cst.superinterfaceTypes) {
for (Klass k : getKlass(t1)) {
addImplements(k);
}
}
}
} else {
for (Klass k : getKlass(t)) {
addExtends(k);
}
}
}
// parse attributes
annotationParser.parseAttributes(classfile.attributes);
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
}
}
private void parseFields(boolean publicAPIs) throws IOException {
ConstantPool cpool = classfile.constant_pool;
for (Field f : classfile.fields) {
try {
AccessFlags flags = f.access_flags;
if (publicAPIs && !flags.is(ACC_PUBLIC) && !flags.is(ACC_PROTECTED)) {
continue;
}
String fieldname = f.getName(cpool);
Signature_attribute sigAttr = (Signature_attribute) f.attributes.get(Attribute.Signature);
if (sigAttr == null) {
Set<Klass> types = parseDescriptor(f.descriptor);
String info = getFlag(flags) + " " + f.descriptor.getFieldType(cpool) + " " + fieldname;
addFieldTypes(types, info, flags);
} else {
Type t = sigAttr.getParsedSignature().getType(cpool);
String info = getFlag(flags) + " " + t + " " + fieldname;
addFieldTypes(getKlass(t), info, flags);
}
// parse attributes
annotationParser.parseAttributes(f.attributes);
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
} catch (InvalidDescriptor ex) {
throw new RuntimeException(ex);
}
}
}
private void parseMethods(boolean publicAPIs) {
for (Method m : classfile.methods) {
if (publicAPIs && !m.access_flags.is(ACC_PUBLIC) && !m.access_flags.is(ACC_PROTECTED)) {
// only interest in the API level
return;
}
parseMethod(m);
}
}
String checkClassName(String classname) {
int i = 0;
while (i < classname.length()) {
switch (classname.charAt(i)) {
case 'Z':
case 'B':
case 'C':
case 'S':
case 'I':
case 'J':
case 'F':
case 'D':
return "";
case 'L':
if (!classname.endsWith(";")) {
throw new RuntimeException("Invalid classname " + classname);
}
return classname.substring(i + 1, classname.length() - 1);
case '[':
i++;
break;
default:
if (classname.endsWith(";")) {
throw new RuntimeException("Invalid classname " + classname);
}
return classname;
}
}
throw new RuntimeException("Invalid classname " + classname);
}
private void addExtends(String classname) throws IOException {
if (!buildDeps) {
return;
}
addExtends(Klass.getKlass(classname));
}
private void addExtends(Klass k) {
if (!buildDeps) {
return;
}
ResolutionInfo resInfo = ResolutionInfo.resolvedExtends(this_klass, k);
resInfo.setPublicAccess(classfile.access_flags.is(ACC_PUBLIC));
this_klass.addDep(k, resInfo);
k.addReferrer(this_klass, resInfo);
}
private void addImplements(String classname) throws IOException {
if (!buildDeps) {
return;
}
addImplements(Klass.getKlass(classname));
}
private void addImplements(Klass k) {
if (!buildDeps) {
return;
}
ResolutionInfo resInfo = ResolutionInfo.resolvedImplements(this_klass, k);
resInfo.setPublicAccess(classfile.access_flags.is(ACC_PUBLIC));
this_klass.addDep(k, resInfo);
k.addReferrer(this_klass, resInfo);
}
private Set<Klass> getKlass(Type type) throws IOException {
Set<Klass> refTypes = new TreeSet<Klass>();
if (!buildDeps) {
return refTypes;
}
type.accept(typevisitor, refTypes);
return refTypes;
}
private Type.Visitor<Void, Set<Klass>> typevisitor = new Type.Visitor<Void, Set<Klass>>() {
public Void visitSimpleType(SimpleType type, Set<Klass> klasses) {
// nop
return null;
}
public Void visitArrayType(ArrayType type, Set<Klass> klasses) {
try {
klasses.addAll(getKlass(type.elemType));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return null;
}
public Void visitMethodType(MethodType type, Set<Klass> klasses) {
throw new InternalError("Unexpected type " + type);
}
public Void visitClassSigType(ClassSigType type, Set<Klass> klasses) {
try {
if (type.superclassType != null) {
klasses.addAll(getKlass(type.superclassType));
}
if (type.superinterfaceTypes != null) {
for (Type t : type.superinterfaceTypes) {
klasses.addAll(getKlass(t));
}
}
if (type.typeParamTypes != null) {
for (Type t : type.typeParamTypes) {
klasses.addAll(getKlass(t));
}
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return null;
}
public Void visitClassType(ClassType type, Set<Klass> klasses) {
klasses.add(Klass.getKlass(type.getBinaryName()));
if (type.typeArgs != null) {
for (Type t : type.typeArgs) {
try {
klasses.addAll(getKlass(t));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return null;
}
public Void visitTypeParamType(TypeParamType type, Set<Klass> klasses) {
try {
if (type.classBound != null) {
klasses.addAll(getKlass(type.classBound));
}
if (type.interfaceBounds != null) {
for (Type t : type.interfaceBounds) {
klasses.addAll(getKlass(t));
}
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return null;
}
public Void visitWildcardType(WildcardType type, Set<Klass> klasses) {
if (type.boundType != null) {
try {
klasses.addAll(getKlass(type.boundType));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return null;
}
};
private void printMethod(Method m) {
try {
System.out.println("parsing " + m.getName(classfile.constant_pool) + "(" +
m.descriptor.getParameterTypes(classfile.constant_pool) + ") return type " +
m.descriptor.getReturnType(classfile.constant_pool));
} catch (ConstantPoolException ex) {
} catch (InvalidDescriptor ex) {
}
}
private static StringBuilder appendWord(StringBuilder sb, String word) {
if (sb.length() > 0) {
sb.append(" ");
}
sb.append(word);
return sb;
}
private static String getFlag(AccessFlags flags) {
StringBuilder modifier = new StringBuilder();
if (flags.is(ACC_PUBLIC)) {
modifier.append("public");
}
if (flags.is(ACC_PRIVATE)) {
modifier.append("private");
}
if (flags.is(ACC_PROTECTED)) {
modifier.append("protected");
}
if (flags.is(ACC_STATIC)) {
appendWord(modifier, "static");
}
if (flags.is(ACC_FINAL)) {
appendWord(modifier, "final");
}
if (flags.is(ACC_SYNCHRONIZED)) {
// return "synchronized";
}
if (flags.is(0x80)) {
// return (t == Type.Field ? "transient" : null);
// return "transient";
}
if (flags.is(ACC_VOLATILE)) {
// return "volatile";
}
if (flags.is(ACC_NATIVE)) {
// return "native";
}
if (flags.is(ACC_ABSTRACT)) {
appendWord(modifier, "abstract");
}
if (flags.is(ACC_STRICT)) {
// return "strictfp";
}
if (flags.is(ACC_MODULE)) {
appendWord(modifier, "module");
}
return modifier.toString();
}
private Klass.Method toKlassMethod(Method m, Descriptor d) {
try {
ConstantPool cpool = classfile.constant_pool;
String methodname = m.getName(cpool);
StringBuilder sb = new StringBuilder();
sb.append(getFlag(m.access_flags));
if (methodname.equals("<init>")) {
String s = this_klass.getBasename() + d.getParameterTypes(cpool);
appendWord(sb, s);
} else if (methodname.equals("<clinit>")) {
// <clinit>
appendWord(sb, methodname);
} else {
String s = d.getReturnType(cpool) + " " + methodname + d.getParameterTypes(cpool);
appendWord(sb, s);
}
String signature = sb.toString().replace('/', '.');
return this_klass.getMethod(methodname, signature);
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
} catch (InvalidDescriptor ex) {
throw new RuntimeException(ex);
}
}
Klass.Method parseMethod(Method m) {
AccessFlags flags = m.access_flags;
Descriptor d;
List<? extends Type> methodExceptions = null;
try {
ConstantPool cpool = classfile.constant_pool;
Klass.Method kmethod;
Signature_attribute sigAttr = (Signature_attribute) m.attributes.get(Attribute.Signature);
if (sigAttr == null) {
d = m.descriptor;
Set<Klass> types = parseDescriptor(d);
kmethod = toKlassMethod(m, d);
addMethodTypes(types, kmethod, flags);
} else {
Type.MethodType methodType;
Signature methodSig = sigAttr.getParsedSignature();
d = methodSig;
try {
kmethod = toKlassMethod(m, d);
methodType = (Type.MethodType) methodSig.getType(cpool);
addMethodTypes(getKlass(methodType.returnType), kmethod, flags);
if (methodType.paramTypes != null) {
for (Type t : methodType.paramTypes) {
addMethodTypes(getKlass(t), kmethod, flags);
}
}
if (methodType.typeParamTypes != null) {
for (Type t : methodType.typeParamTypes) {
addMethodTypes(getKlass(t), kmethod, flags);
}
}
methodExceptions = methodType.throwsTypes;
if (methodExceptions != null) {
if (methodExceptions.size() == 0) {
methodExceptions = null;
} else {
for (Type t : methodExceptions) {
addCheckedExceptionTypes(getKlass(t), kmethod, flags);
}
}
}
} catch (ConstantPoolException e) {
throw new RuntimeException(e);
}
}
Attribute e_attr = m.attributes.get(Attribute.Exceptions);
if (e_attr != null && methodExceptions == null) {
// if there are generic exceptions, there must be erased exceptions
if (e_attr instanceof Exceptions_attribute) {
Exceptions_attribute exceptions = (Exceptions_attribute) e_attr;
for (int i = 0; i < exceptions.number_of_exceptions; i++) {
String classname = checkClassName(exceptions.getException(i, classfile.constant_pool));
if (classname.length() > 0 && buildDeps) {
Klass to = Klass.getKlass(classname);
ResolutionInfo resInfo = ResolutionInfo.resolvedCheckedException(this_klass, to, kmethod);
resInfo.setPublicAccess(flags.is(ACC_PUBLIC));
this_klass.addDep(to, resInfo);
to.addReferrer(this_klass, resInfo);
}
}
} else {
throw new RuntimeException("Invalid attribute: " + e_attr);
}
}
Code_attribute c_attr = (Code_attribute) m.attributes.get(Attribute.Code);
if (c_attr != null) {
codeAttributeParser.parse(c_attr, kmethod);
}
kmethod.isAbstract = classfile.access_flags.is(ACC_ABSTRACT);
kmethod.setCodeLength(m.byteLength());
// parse annotation attributes
annotationParser.parseAttributes(m.attributes, kmethod);
return kmethod;
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void addFieldTypes(Set<Klass> types, String info, AccessFlags flags) {
if (types.isEmpty() || !buildDeps) {
return;
}
for (Klass to : types) {
ResolutionInfo resInfo = ResolutionInfo.resolvedField(this_klass, to, info);
resInfo.setPublicAccess(flags.is(ACC_PUBLIC));
this_klass.addDep(to, resInfo);
to.addReferrer(this_klass, resInfo);
}
}
private void addReferencedTypes(Method m, Descriptor d, AccessFlags flags) {
Set<Klass> types = parseDescriptor(d);
Klass.Method method = toKlassMethod(m, d);
addMethodTypes(types, method, flags);
}
private void addMethodTypes(Set<Klass> types, Klass.Method method, AccessFlags flags) {
if (types.isEmpty() || !buildDeps) {
return;
}
for (Klass to : types) {
ResolutionInfo resInfo = ResolutionInfo.resolvedMethodSignature(this_klass, to, method);
resInfo.setPublicAccess(flags.is(ACC_PUBLIC));
this_klass.addDep(to, resInfo);
to.addReferrer(this_klass, resInfo);
}
}
private void addCheckedExceptionTypes(Set<Klass> types, Klass.Method method, AccessFlags flags) {
if (types.isEmpty() || !buildDeps) {
return;
}
for (Klass to : types) {
ResolutionInfo resInfo = ResolutionInfo.resolvedCheckedException(this_klass, to, method);
resInfo.setPublicAccess(flags.is(ACC_PUBLIC));
this_klass.addDep(to, resInfo);
to.addReferrer(this_klass, resInfo);
}
}
private Set<Klass> parseDescriptor(Descriptor d) {
Set<Klass> types = new TreeSet<Klass>();
try {
String desc = d.getValue(classfile.constant_pool);
int p = 0;
while (p < desc.length()) {
String type;
char ch;
switch (ch = desc.charAt(p++)) {
case '(':
case ')':
case '[':
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
case 'V':
continue;
case 'L':
int sep = desc.indexOf(';', p);
if (sep == -1) {
throw new RuntimeException("Invalid descriptor: " + (p - 1) + " " + desc);
}
type = checkClassName(desc.substring(p, sep));
p = sep + 1;
break;
default:
throw new RuntimeException("Invalid descriptor: " + (p - 1) + " " + desc);
}
if (!type.isEmpty() && buildDeps) {
Klass to = Klass.getKlass(type);
types.add(to);
}
}
} catch (ConstantPoolException ex) {
throw new RuntimeException(ex);
}
return types;
}
}