org.netbeans.lib.profiler.classfile.ClassFileParser Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.lib.profiler.classfile;
import org.netbeans.lib.profiler.classfile.ClassInfo.LocalVariableTables;
import org.netbeans.lib.profiler.classfile.ClassInfo.LocalVariableTypeTables;
import org.netbeans.lib.profiler.global.CommonConstants;
import org.netbeans.lib.profiler.instrumentation.JavaClassConstants;
import org.netbeans.lib.profiler.utils.StringUtils;
/**
* This class implements parsing a byte array representing a class file, generating a ClassInfo object.
*
* @author Misha Dmitirev
* @author Tomas Hurka
*/
public class ClassFileParser implements JavaClassConstants {
//~ Inner Classes ------------------------------------------------------------------------------------------------------------
public static class ClassFileReadException extends Exception {
//~ Instance fields ------------------------------------------------------------------------------------------------------
ClassFileReadRuntimeException e;
//~ Constructors ---------------------------------------------------------------------------------------------------------
private ClassFileReadException(ClassFileReadRuntimeException e) {
this.e = e;
}
//~ Methods --------------------------------------------------------------------------------------------------------------
public Throwable getCause() {
return e;
}
public String getMessage() {
return e.getMessage();
}
public String toString() {
return e.toString();
}
}
private static class ClassFileReadRuntimeException extends RuntimeException {
//~ Constructors ---------------------------------------------------------------------------------------------------------
public ClassFileReadRuntimeException(String msg) {
super(msg);
}
}
//~ Instance fields ----------------------------------------------------------------------------------------------------------
private ClassInfo classInfo;
private byte[] classBuf;
private Object[] cpObjectCache;
private int[] cpOffsets;
private byte[] cpTags;
private int curBufPos;
//~ Methods ------------------------------------------------------------------------------------------------------------------
public void parseClassFile(byte[] classFile, ClassInfo classInfo)
throws ClassFileReadException {
classBuf = classFile;
this.classInfo = classInfo;
curBufPos = 0;
try {
readPreamble();
readConstantPool();
readIntermediate();
skipFields();
readMethods();
readAttributes();
} catch (ClassFileReadRuntimeException e) {
throw new ClassFileReadException(e);
}
}
private char getChar(int bufPos) {
return (char) (((classBuf[bufPos++] & 255) << 8) + (classBuf[bufPos++] & 255));
}
private void badCPEntry(int entryNo) { // TODO CHECK: unused method
throw classFileReadException("Constant pool entry " + entryNo + " : invalid type"); // NOI18N
}
private void badCPReference(int ofs, int i) {
throw classFileReadException("Bad constant pool reference: " + ofs + " from entry " + i); // NOI18N
}
private ClassFileReadRuntimeException classFileReadException(String msg) {
msg = "Error reading class " + classInfo.name + ":\n" + msg; // NOI18N
return new ClassFileReadRuntimeException(msg);
}
/**
* Read class name at the given CONSTANT_Utf8 constant pool index, and return it
* trimmed of the possible 'L' prefix and ';' suffix.
*/
private String classNameAtCPIndex(int idx) {
if (cpTags[idx] != CONSTANT_Utf8) {
throw classFileReadException("Constant pool entry " + idx + " should be UTF8 constant"); // NOI18N
}
int arrayLevel = 0;
if (cpObjectCache[idx] == null) {
int utf8Len = getChar(cpOffsets[idx]);
int stPos = cpOffsets[idx] + 2;
int initStPos = stPos;
while (classBuf[stPos] == '[') { // NOI18N
stPos++;
arrayLevel++;
}
if (stPos != initStPos) {
if (classBuf[stPos] == 'L') { // NOI18N // Non-primitive array type
stPos++;
utf8Len--; // To get rid of the terminating ';'
}
}
utf8Len = utf8Len - (stPos - initStPos);
String res = StringUtils.utf8ToString(classBuf, stPos, utf8Len);
for (int i = 0; i < arrayLevel; i++) {
res = "[" + res; // NOI18N
}
cpObjectCache[idx] = res;
}
return (String) cpObjectCache[idx];
}
private ClassFileReadRuntimeException dataFormatError() { // TODO CHECK: unused method
return classFileReadException("Data format error"); // NOI18N
}
private char nextChar() {
return (char) (((classBuf[curBufPos++] & 255) << 8) + (classBuf[curBufPos++] & 255));
}
private int nextInt() {
return ((classBuf[curBufPos++] & 255) << 24) + ((classBuf[curBufPos++] & 255) << 16)
+ ((classBuf[curBufPos++] & 255) << 8) + (classBuf[curBufPos++] & 255);
}
/**
* This method actually reads only the information related to the nested classes, and
* records only those of them which are first level nested classes of this class. The class
* may also reference other classes which are not package members through the same
* InnerClasses attribute - their names would be processed when their respective enclosing
* classes are read.
*/
private void readAttributes() {
int i;
int j;
classInfo.attrsStartOfs = curBufPos;
char attrCount = nextChar();
for (i = 0; i < attrCount; i++) {
int attrNameIdx = nextChar();
int attrLen = nextInt();
if (utf8AtCPIndex(attrNameIdx).equals("InnerClasses")) { // NOI18N
int nOfClasses = nextChar();
String[] nestedClasses = new String[nOfClasses];
int curIdx = 0;
int nonMemberClassCount = 0;
for (j = 0; j < nOfClasses; j++) {
int innerClassInfoIdx = nextChar();
int outerClassInfoIdx = nextChar();
int innerClassNameIdx = nextChar();
char innerClassAccessFlags = nextChar();
String nestedClassFullName = classNameAtCPIndex(getChar(cpOffsets[innerClassInfoIdx]));
// We are not interested in references to nested classes whose enclosing class is not this one.
if (innerClassNameIdx != 0) {
String nestedClassSimpleName = utf8AtCPIndex(innerClassNameIdx);
if (!nestedClassFullName.equals(classInfo.name + "$" + nestedClassSimpleName)) { // NOI18N
// Let's check if it's a local class, with the name like "EncClass$1$Local"
int count = nonMemberClassCount + 1;
if (!nestedClassFullName.equals(classInfo.name + "$" + count + "$" + nestedClassSimpleName)) { // NOI18N
continue;
} else {
nonMemberClassCount = count;
}
}
} else {
nonMemberClassCount++;
if (!nestedClassFullName.equals(classInfo.name + "$" + nonMemberClassCount)) { // NOI18N
continue;
}
}
nestedClasses[curIdx++] = nestedClassFullName;
}
if (curIdx == nOfClasses) {
classInfo.nestedClassNames = nestedClasses;
} else if (curIdx > 0) {
// We found fewer nested classes for this class than we originally expected, but still more than 0.
// Create a new array to fit their number exactly.
classInfo.nestedClassNames = new String[curIdx];
System.arraycopy(nestedClasses, 0, classInfo.nestedClassNames, 0, curIdx);
}
break;
} else {
curBufPos += attrLen;
}
}
}
private void readConstantPool() {
int methodRefsNo = 0;
int classRefsNo = 0;
classInfo.cpoolStartOfs = curBufPos;
classInfo.origCPoolCount = nextChar();
cpOffsets = new int[classInfo.origCPoolCount];
cpTags = new byte[classInfo.origCPoolCount];
int cpStart = curBufPos;
int len;
int i = 1;
while (i < cpOffsets.length) {
byte tag = classBuf[curBufPos++];
cpOffsets[i] = curBufPos;
cpTags[i] = tag;
i++;
switch (tag) {
case CONSTANT_Utf8:
len = nextChar();
curBufPos += len;
break;
case CONSTANT_Class:
classRefsNo++;
case CONSTANT_String:
case CONSTANT_MethodType:
case CONSTANT_Module:
case CONSTANT_Package:
curBufPos += 2;
break;
case CONSTANT_MethodHandle:
curBufPos += 3;
break;
case CONSTANT_Fieldref:
case CONSTANT_NameAndType:
case CONSTANT_Integer:
case CONSTANT_Float:
case CONSTANT_InvokeDynamic:
case CONSTANT_ConstantDynamic:
curBufPos += 4;
break;
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
methodRefsNo++;
curBufPos += 4;
break;
case CONSTANT_Long:
case CONSTANT_Double:
curBufPos += 8;
i++;
break;
default:
throw classFileReadException("Bad constant pool tag: " + tag + " at " + Integer.toString(curBufPos - 1)); // NOI18N
}
}
classInfo.cpoolRefsToMethodIdx = new char[methodRefsNo];
classInfo.cpoolRefsToMethodClassNameAndSig = new String[methodRefsNo][3];
classInfo.cpoolRefsToClassIdx = new char[classRefsNo];
classInfo.cpoolRefsToClassName = new String[classRefsNo];
int curMethodRef = 0;
int curClassRef = 0;
cpObjectCache = new Object[cpOffsets.length];
for (i = 0; i < cpOffsets.length; i++) {
int ofs = cpOffsets[i];
if ((cpTags[i] == CONSTANT_Methodref) || (cpTags[i] == CONSTANT_InterfaceMethodref)) {
classInfo.cpoolRefsToMethodIdx[curMethodRef] = (char) i;
classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef] = new String[3];
int classIdx = getChar(ofs);
int nameAndTypeIdx = getChar(ofs + 2);
if ((cpTags[classIdx] != CONSTANT_Class) || (cpTags[nameAndTypeIdx] != CONSTANT_NameAndType)) {
badCPReference(ofs, i);
}
classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][0] = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
ofs = cpOffsets[nameAndTypeIdx];
int nameIdx = getChar(ofs);
int sigIdx = getChar(ofs + 2);
if ((cpTags[nameIdx] != CONSTANT_Utf8) || (cpTags[sigIdx] != CONSTANT_Utf8)) {
badCPReference(ofs, i);
}
classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][1] = utf8AtCPIndex(nameIdx);
classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][2] = signatureAtCPIndex(sigIdx);
curMethodRef++;
} else if (cpTags[i] == CONSTANT_Class) {
classInfo.cpoolRefsToClassIdx[curClassRef] = (char) i;
classInfo.cpoolRefsToClassName[curClassRef] = classNameAtCPIndex(getChar(ofs));
curClassRef++;
}
}
}
private void readIntermediate() {
int i;
int classIdx;
int superClassIdx;
classInfo.intermediateDataStartOfs = curBufPos;
classInfo.accessFlags = nextChar();
classInfo.classIndex = classIdx = nextChar();
if (cpTags[classIdx] != CONSTANT_Class) {
throw classFileReadException("Bad reference to this class name"); // NOI18N
}
classInfo.name = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
superClassIdx = nextChar();
if (cpTags[superClassIdx] != CONSTANT_Class) {
if ((superClassIdx == 0) && classInfo.name.equals(CommonConstants.OBJECT_SLASHED_CLASS_NAME)) {
classInfo.superName = CommonConstants.OBJECT_SLASHED_CLASS_NAME;
} else {
throw classFileReadException("Bad reference to super class name"); // NOI18N
}
} else {
classInfo.superName = classNameAtCPIndex(getChar(cpOffsets[superClassIdx]));
}
char intfCount = nextChar();
if (intfCount != 0) {
classInfo.interfaces = new String[intfCount];
for (i = 0; i < intfCount; i++) {
classIdx = nextChar();
if (cpTags[classIdx] != CONSTANT_Class) {
throw classFileReadException("Bad reference to an implemented interface"); // NOI18N
}
classInfo.interfaces[i] = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
}
}
}
private void readMethods() {
classInfo.methodsStartOfs = curBufPos;
char methodCount = nextChar();
if (methodCount == 0) {
classInfo.methodNames = new String[0];
return;
}
String[] names = new String[methodCount];
String[] signatures = new String[methodCount];
char[] accessFlags = new char[methodCount];
int[] methodInfoOffsets = new int[methodCount];
int[] methodInfoLengths = new int[methodCount];
int[] bytecodeOffsets = new int[methodCount];
char[] bytecodeLengths = new char[methodCount];
int[] exceptionTableStartOffsets = new int[methodCount];
int[] lineNumberTableOffsets = new int[methodCount];
char[] lineNumberTableLengths = new char[methodCount];
int[] localVariableTableOffsets = new int[methodCount];
char[] localVariableTableLengths = new char[methodCount];
int localVaribaleTableCPindex = 0;
int[] localVariableTypeTableOffsets = new int[methodCount];
char[] localVariableTypeTableLengths = new char[methodCount];
int localVaribaleTypeTableCPindex = 0;
int[] stackMapTableOffsets = new int[methodCount];
char[] stackMapTableLengths = new char[methodCount];
int stackMapTableCPindex = 0;
for (int i = 0; i < methodCount; i++) {
methodInfoOffsets[i] = curBufPos;
accessFlags[i] = nextChar();
names[i] = utf8AtCPIndex(nextChar());
signatures[i] = signatureAtCPIndex(nextChar());
bytecodeOffsets[i] = 0;
lineNumberTableOffsets[i] = 0;
localVariableTableOffsets[i] = 0;
localVariableTypeTableOffsets[i] = 0;
int attrCount = nextChar();
for (int j = 0; j < attrCount; j++) {
int attrNameIdx = nextChar();
int attrLen = nextInt();
if (utf8AtCPIndex(attrNameIdx).equals("Code")) { // NOI18N
curBufPos += 4; // Skip max_stack and max_locals
char codeLen = bytecodeLengths[i] = (char) nextInt();
bytecodeOffsets[i] = curBufPos - methodInfoOffsets[i];
curBufPos += codeLen;
exceptionTableStartOffsets[i] = curBufPos - methodInfoOffsets[i];
int count = nextChar(); // Exception table length
curBufPos += (8 * count); // Skip exception table
count = nextChar(); // Attribute (or rather sub-attribute) count
for (int k = 0; k < count; k++) {
attrNameIdx = nextChar();
attrLen = nextInt();
if (utf8AtCPIndex(attrNameIdx).equals("LineNumberTable")) { // NOI18N
char tableLen = lineNumberTableLengths[i] = nextChar();
lineNumberTableOffsets[i] = curBufPos - methodInfoOffsets[i];
curBufPos += (4 * tableLen);
} else if (utf8AtCPIndex(attrNameIdx).equals("LocalVariableTable")){ // NOI18N
char tableLen = localVariableTableLengths[i] = nextChar();
localVariableTableOffsets[i] = curBufPos - methodInfoOffsets[i];
curBufPos += LocalVariableTables.ATTR_SIZE * tableLen;
if (localVaribaleTableCPindex == 0) {
localVaribaleTableCPindex = attrNameIdx;
} else {
assert localVaribaleTableCPindex == attrNameIdx;
}
} else if (utf8AtCPIndex(attrNameIdx).equals("LocalVariableTypeTable")){ // NOI18N
char tableLen = localVariableTypeTableLengths[i] = nextChar();
localVariableTypeTableOffsets[i] = curBufPos - methodInfoOffsets[i];
curBufPos += LocalVariableTypeTables.ATTR_SIZE * tableLen;
if (localVaribaleTypeTableCPindex == 0) {
localVaribaleTypeTableCPindex = attrNameIdx;
} else {
assert localVaribaleTypeTableCPindex == attrNameIdx;
}
} else if (utf8AtCPIndex(attrNameIdx).equals("StackMapTable")){ // NOI18N
char tableLen = stackMapTableLengths[i] = nextChar();
stackMapTableOffsets[i] = curBufPos - methodInfoOffsets[i];
curBufPos += attrLen - 2;
if (stackMapTableCPindex == 0) {
stackMapTableCPindex = attrNameIdx;
} else {
assert stackMapTableCPindex == attrNameIdx;
}
// LOG.info("StackMapTable size:"+(tableLen+0)+" for class:"+classInfo.name+" method:"+ names[i]);
} else {
curBufPos += attrLen;
}
}
} else {
curBufPos += attrLen;
}
}
methodInfoLengths[i] = curBufPos - methodInfoOffsets[i];
}
classInfo.methodNames = names;
classInfo.methodSignatures = signatures;
classInfo.methodAccessFlags = accessFlags;
classInfo.methodInfoOffsets = methodInfoOffsets;
classInfo.methodInfoLengths = methodInfoLengths;
classInfo.methodBytecodesOffsets = bytecodeOffsets;
classInfo.methodBytecodesLengths = bytecodeLengths;
classInfo.exceptionTableStartOffsets = exceptionTableStartOffsets;
classInfo.lineNumberTablesOffsets = lineNumberTableOffsets;
classInfo.lineNumberTablesLengths = lineNumberTableLengths;
classInfo.localVariableTablesOffsets = localVariableTableOffsets;
classInfo.localVariableTablesLengths = localVariableTableLengths;
classInfo.localVaribaleTableCPindex = localVaribaleTableCPindex;
classInfo.localVariableTypeTablesOffsets = localVariableTypeTableOffsets;
classInfo.localVariableTypeTablesLengths = localVariableTypeTableLengths;
classInfo.localVaribaleTypeTableCPindex = localVaribaleTypeTableCPindex;
classInfo.stackMapTablesOffsets = stackMapTableOffsets;
classInfo.stackMapTablesLengths = stackMapTableLengths;
classInfo.stackMapTableCPindex = stackMapTableCPindex;
}
private void readPreamble() {
int magic = nextInt();
if (magic != JAVA_MAGIC) {
throw classFileReadException("Illegal start of class file"); // NOI18N
}
int minorVersion = nextChar();
int majorVersion = nextChar();
if ((majorVersion > JAVA_MAJOR_VERSION)
|| (((majorVersion * 1000) + minorVersion) < ((JAVA_MIN_MAJOR_VERSION * 1000) + JAVA_MIN_MINOR_VERSION))) {
String versionCode = majorVersion + "." + minorVersion; // NOI18N
String message = "Unsupported class file version: " + versionCode; // NOI18N
throw classFileReadException(message);
}
classInfo.majorVersion = majorVersion;
}
private String signatureAtCPIndex(int idx) {
return utf8AtCPIndex(idx);
}
private void skipFields() {
classInfo.fieldsStartOfs = curBufPos;
char definedFieldCount = nextChar();
for (int i = 0; i < definedFieldCount; i++) {
curBufPos += 6; // skip 3 chars: flags, name index and signature index
int attrCount = nextChar();
for (int j = 0; j < attrCount; j++) {
curBufPos += 2; // Skip char: attr name index
int attrLen = nextInt();
curBufPos += attrLen;
}
}
}
private String utf8AtCPIndex(int idx) {
if (cpTags[idx] != CONSTANT_Utf8) {
throw classFileReadException("Constant pool entry " + idx + " should be UTF8 constant"); // NOI18N
}
if (cpObjectCache[idx] == null) {
int utf8Len = getChar(cpOffsets[idx]);
cpObjectCache[idx] = StringUtils.utf8ToString(classBuf, cpOffsets[idx] + 2, utf8Len);
}
return (String) cpObjectCache[idx];
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy