001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.classfile;
042:
043: import org.netbeans.lib.profiler.instrumentation.JavaClassConstants;
044: import org.netbeans.lib.profiler.utils.StringUtils;
045:
046: /**
047: * This class implements parsing a byte array representing a class file, generating a ClassInfo object.
048: *
049: * @author Misha Dmitirev
050: */
051: public class ClassFileParser implements JavaClassConstants {
052: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
053:
054: public static class ClassFileReadException extends Exception {
055: //~ Instance fields ------------------------------------------------------------------------------------------------------
056:
057: ClassFileReadRuntimeException e;
058:
059: //~ Constructors ---------------------------------------------------------------------------------------------------------
060:
061: private ClassFileReadException(ClassFileReadRuntimeException e) {
062: this .e = e;
063: }
064:
065: //~ Methods --------------------------------------------------------------------------------------------------------------
066:
067: public Throwable getCause() {
068: return e;
069: }
070:
071: public String getMessage() {
072: return e.getMessage();
073: }
074:
075: public String toString() {
076: return e.toString();
077: }
078: }
079:
080: private static class ClassFileReadRuntimeException extends
081: RuntimeException {
082: //~ Constructors ---------------------------------------------------------------------------------------------------------
083:
084: public ClassFileReadRuntimeException(String msg) {
085: super (msg);
086: }
087: }
088:
089: //~ Instance fields ----------------------------------------------------------------------------------------------------------
090:
091: private ClassInfo classInfo;
092: private byte[] classBuf;
093: private Object[] cpObjectCache;
094: private int[] cpOffsets;
095: private byte[] cpTags;
096: private int curBufPos;
097:
098: //~ Methods ------------------------------------------------------------------------------------------------------------------
099:
100: public void parseClassFile(byte[] classFile, ClassInfo classInfo)
101: throws ClassFileReadException {
102: classBuf = classFile;
103: this .classInfo = classInfo;
104: curBufPos = 0;
105:
106: try {
107: readPreamble();
108: readConstantPool();
109: readIntermediate();
110: skipFields();
111: readMethods();
112: readAttributes();
113: } catch (ClassFileReadRuntimeException e) {
114: throw new ClassFileReadException(e);
115: }
116: }
117:
118: private char getChar(int bufPos) {
119: return (char) (((classBuf[bufPos++] & 255) << 8) + (classBuf[bufPos++] & 255));
120: }
121:
122: private void badCPEntry(int entryNo) { // TODO CHECK: unused method
123: throw classFileReadException("Constant pool entry " + entryNo
124: + " : invalid type"); // NOI18N
125: }
126:
127: private void badCPReference(int ofs, int i) {
128: throw classFileReadException("Bad constant pool reference: "
129: + ofs + " from entry " + i); // NOI18N
130: }
131:
132: private ClassFileReadRuntimeException classFileReadException(
133: String msg) {
134: msg = "Error reading class " + classInfo.name + ":\n" + msg; // NOI18N
135:
136: return new ClassFileReadRuntimeException(msg);
137: }
138:
139: /**
140: * Read class name at the given CONSTANT_Utf8 constant pool index, and return it
141: * trimmed of the possible 'L' prefix and ';' suffix.
142: */
143: private String classNameAtCPIndex(int idx) {
144: if (cpTags[idx] != CONSTANT_Utf8) {
145: throw classFileReadException("Constant pool entry " + idx
146: + " should be UTF8 constant"); // NOI18N
147: }
148:
149: int arrayLevel = 0;
150:
151: if (cpObjectCache[idx] == null) {
152: int utf8Len = getChar(cpOffsets[idx]);
153: int stPos = cpOffsets[idx] + 2;
154: int initStPos = stPos;
155:
156: while (classBuf[stPos] == '[') { // NOI18N
157: stPos++;
158: arrayLevel++;
159: }
160:
161: if (stPos != initStPos) {
162: if (classBuf[stPos] == 'L') { // NOI18N // Non-primitive array type
163: stPos++;
164: utf8Len--; // To get rid of the terminating ';'
165: }
166: }
167:
168: utf8Len = utf8Len - (stPos - initStPos);
169:
170: String res = StringUtils.utf8ToString(classBuf, stPos,
171: utf8Len);
172:
173: for (int i = 0; i < arrayLevel; i++) {
174: res = "[" + res; // NOI18N
175: }
176:
177: cpObjectCache[idx] = res;
178: }
179:
180: return (String) cpObjectCache[idx];
181: }
182:
183: private ClassFileReadRuntimeException dataFormatError() { // TODO CHECK: unused method
184:
185: return classFileReadException("Data format error"); // NOI18N
186: }
187:
188: private char nextChar() {
189: return (char) (((classBuf[curBufPos++] & 255) << 8) + (classBuf[curBufPos++] & 255));
190: }
191:
192: private int nextInt() {
193: return ((classBuf[curBufPos++] & 255) << 24)
194: + ((classBuf[curBufPos++] & 255) << 16)
195: + ((classBuf[curBufPos++] & 255) << 8)
196: + (classBuf[curBufPos++] & 255);
197: }
198:
199: /**
200: * This method actually reads only the information related to the nested classes, and
201: * records only those of them which are first level nested classes of this class. The class
202: * may also reference other classes which are not package members through the same
203: * InnerClasses attribute - their names would be processed when their respective enclosing
204: * classes are read.
205: */
206: private void readAttributes() {
207: int i;
208: int j;
209:
210: classInfo.attrsStartOfs = curBufPos;
211:
212: char attrCount = nextChar();
213:
214: for (i = 0; i < attrCount; i++) {
215: int attrNameIdx = nextChar();
216: int attrLen = nextInt();
217:
218: if (utf8AtCPIndex(attrNameIdx).equals("InnerClasses")) { // NOI18N
219:
220: int nOfClasses = nextChar();
221: String[] nestedClasses = new String[nOfClasses];
222: int curIdx = 0;
223: int nonMemberClassCount = 0;
224:
225: for (j = 0; j < nOfClasses; j++) {
226: int innerClassInfoIdx = nextChar();
227: int outerClassInfoIdx = nextChar();
228: int innerClassNameIdx = nextChar();
229: char innerClassAccessFlags = nextChar();
230:
231: String nestedClassFullName = classNameAtCPIndex(getChar(cpOffsets[innerClassInfoIdx]));
232:
233: // We are not interested in references to nested classes whose enclosing class is not this one.
234: if (innerClassNameIdx != 0) {
235: String nestedClassSimpleName = utf8AtCPIndex(innerClassNameIdx);
236:
237: if (!nestedClassFullName.equals(classInfo.name
238: + "$" + nestedClassSimpleName)) { // NOI18N
239: // Let's check if it's a local class, with the name like "EncClass$1$Local"
240:
241: int count = nonMemberClassCount + 1;
242:
243: if (!nestedClassFullName
244: .equals(classInfo.name + "$"
245: + count + "$"
246: + nestedClassSimpleName)) {
247: continue; // NOI18N
248: } else {
249: nonMemberClassCount = count;
250: }
251: }
252: } else {
253: nonMemberClassCount++;
254:
255: if (!nestedClassFullName.equals(classInfo.name
256: + "$" + nonMemberClassCount)) {
257: continue; // NOI18N
258: }
259: }
260:
261: nestedClasses[curIdx++] = nestedClassFullName;
262: }
263:
264: if (curIdx == nOfClasses) {
265: classInfo.nestedClassNames = nestedClasses;
266: } else if (curIdx > 0) {
267: // We found fewer nested classes for this class than we originally expected, but still more than 0.
268: // Create a new array to fit their number exactly.
269: classInfo.nestedClassNames = new String[curIdx];
270: System.arraycopy(nestedClasses, 0,
271: classInfo.nestedClassNames, 0, curIdx);
272: }
273:
274: break;
275: } else {
276: curBufPos += attrLen;
277: }
278: }
279: }
280:
281: private void readConstantPool() {
282: int methodRefsNo = 0;
283: int classRefsNo = 0;
284:
285: classInfo.cpoolStartOfs = curBufPos;
286: classInfo.origCPoolCount = nextChar();
287: cpOffsets = new int[classInfo.origCPoolCount];
288: cpTags = new byte[classInfo.origCPoolCount];
289:
290: int cpStart = curBufPos;
291: int len;
292: int i = 1;
293:
294: while (i < cpOffsets.length) {
295: byte tag = classBuf[curBufPos++];
296: cpOffsets[i] = curBufPos;
297: cpTags[i] = tag;
298: i++;
299:
300: switch (tag) {
301: case CONSTANT_Utf8:
302: len = nextChar();
303: curBufPos += len;
304:
305: break;
306: case CONSTANT_Class:
307: classRefsNo++;
308: case CONSTANT_String:
309: curBufPos += 2;
310:
311: break;
312: case CONSTANT_Fieldref:
313: case CONSTANT_NameAndType:
314: case CONSTANT_Integer:
315: case CONSTANT_Float:
316: curBufPos += 4;
317:
318: break;
319: case CONSTANT_Methodref:
320: case CONSTANT_InterfaceMethodref:
321: methodRefsNo++;
322: curBufPos += 4;
323:
324: break;
325: case CONSTANT_Long:
326: case CONSTANT_Double:
327: curBufPos += 8;
328: i++;
329:
330: break;
331: default:
332: throw classFileReadException("Bad constant pool tag: "
333: + tag + " at "
334: + Integer.toString(curBufPos - 1)); // NOI18N
335: }
336: }
337:
338: classInfo.cpoolRefsToMethodIdx = new char[methodRefsNo];
339: classInfo.cpoolRefsToMethodClassNameAndSig = new String[methodRefsNo][3];
340: classInfo.cpoolRefsToClassIdx = new char[classRefsNo];
341: classInfo.cpoolRefsToClassName = new String[classRefsNo];
342:
343: int curMethodRef = 0;
344: int curClassRef = 0;
345: cpObjectCache = new Object[cpOffsets.length];
346:
347: for (i = 0; i < cpOffsets.length; i++) {
348: int ofs = cpOffsets[i];
349:
350: if ((cpTags[i] == CONSTANT_Methodref)
351: || (cpTags[i] == CONSTANT_InterfaceMethodref)) {
352: classInfo.cpoolRefsToMethodIdx[curMethodRef] = (char) i;
353: classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef] = new String[3];
354:
355: int classIdx = getChar(ofs);
356: int nameAndTypeIdx = getChar(ofs + 2);
357:
358: if ((cpTags[classIdx] != CONSTANT_Class)
359: || (cpTags[nameAndTypeIdx] != CONSTANT_NameAndType)) {
360: badCPReference(ofs, i);
361: }
362:
363: classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][0] = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
364: ofs = cpOffsets[nameAndTypeIdx];
365:
366: int nameIdx = getChar(ofs);
367: int sigIdx = getChar(ofs + 2);
368:
369: if ((cpTags[nameIdx] != CONSTANT_Utf8)
370: || (cpTags[sigIdx] != CONSTANT_Utf8)) {
371: badCPReference(ofs, i);
372: }
373:
374: classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][1] = utf8AtCPIndex(nameIdx);
375: classInfo.cpoolRefsToMethodClassNameAndSig[curMethodRef][2] = signatureAtCPIndex(sigIdx);
376: curMethodRef++;
377: } else if (cpTags[i] == CONSTANT_Class) {
378: classInfo.cpoolRefsToClassIdx[curClassRef] = (char) i;
379: classInfo.cpoolRefsToClassName[curClassRef] = classNameAtCPIndex(getChar(ofs));
380: curClassRef++;
381: }
382: }
383: }
384:
385: private void readIntermediate() {
386: int i;
387: int classIdx;
388: int super ClassIdx;
389:
390: classInfo.intermediateDataStartOfs = curBufPos;
391: classInfo.accessFlags = nextChar();
392: classIdx = nextChar();
393:
394: if (cpTags[classIdx] != CONSTANT_Class) {
395: throw classFileReadException("Bad reference to this class name"); // NOI18N
396: }
397:
398: classInfo.name = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
399: super ClassIdx = nextChar();
400:
401: if (cpTags[super ClassIdx] != CONSTANT_Class) {
402: if ((super ClassIdx == 0)
403: && classInfo.name.equals("java/lang/Object")) {
404: classInfo.super Name = "java/lang/Object"; // NOI18N
405: } else {
406: throw classFileReadException("Bad reference to super class name"); // NOI18N
407: }
408: } else {
409: classInfo.super Name = classNameAtCPIndex(getChar(cpOffsets[super ClassIdx]));
410: }
411:
412: char intfCount = nextChar();
413:
414: if (intfCount != 0) {
415: classInfo.interfaces = new String[intfCount];
416:
417: for (i = 0; i < intfCount; i++) {
418: classIdx = nextChar();
419:
420: if (cpTags[classIdx] != CONSTANT_Class) {
421: throw classFileReadException("Bad reference to an implemented interface"); // NOI18N
422: }
423:
424: classInfo.interfaces[i] = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
425: }
426: }
427: }
428:
429: private void readMethods() {
430: classInfo.methodsStartOfs = curBufPos;
431:
432: char methodCount = nextChar();
433:
434: if (methodCount == 0) {
435: classInfo.methodNames = new String[0];
436:
437: return;
438: }
439:
440: String[] names = new String[methodCount];
441: String[] signatures = new String[methodCount];
442: char[] accessFlags = new char[methodCount];
443: int[] methodInfoOffsets = new int[methodCount];
444: int[] methodInfoLengths = new int[methodCount];
445: int[] bytecodeOffsets = new int[methodCount];
446: char[] bytecodeLengths = new char[methodCount];
447: int[] exceptionTableStartOffsets = new int[methodCount];
448: int[] lineNumberTableOffsets = new int[methodCount];
449: char[] lineNumberTableLengths = new char[methodCount];
450:
451: for (int i = 0; i < methodCount; i++) {
452: methodInfoOffsets[i] = curBufPos;
453: accessFlags[i] = nextChar();
454: names[i] = utf8AtCPIndex(nextChar());
455: signatures[i] = signatureAtCPIndex(nextChar());
456: bytecodeOffsets[i] = 0;
457: lineNumberTableOffsets[i] = 0;
458:
459: int attrCount = nextChar();
460:
461: for (int j = 0; j < attrCount; j++) {
462: int attrNameIdx = nextChar();
463: int attrLen = nextInt();
464:
465: if (utf8AtCPIndex(attrNameIdx).equals("Code")) { // NOI18N
466: curBufPos += 4; // Skip max_stack and max_locals
467:
468: char codeLen = bytecodeLengths[i] = (char) nextInt();
469: bytecodeOffsets[i] = curBufPos
470: - methodInfoOffsets[i];
471: curBufPos += codeLen;
472: exceptionTableStartOffsets[i] = curBufPos
473: - methodInfoOffsets[i];
474:
475: int count = nextChar(); // Exception table length
476: curBufPos += (8 * count); // Skip exception table
477: count = nextChar(); // Attribute (or rather sub-attribute) count
478:
479: for (int k = 0; k < count; k++) {
480: attrNameIdx = nextChar();
481: attrLen = nextInt();
482:
483: if (utf8AtCPIndex(attrNameIdx).equals(
484: "LineNumberTable")) { // NOI18N
485:
486: char tableLen = lineNumberTableLengths[i] = nextChar();
487: lineNumberTableOffsets[i] = curBufPos
488: - methodInfoOffsets[i];
489: curBufPos += (4 * tableLen);
490: } else {
491: curBufPos += attrLen;
492: }
493: }
494: } else {
495: curBufPos += attrLen;
496: }
497: }
498:
499: methodInfoLengths[i] = curBufPos - methodInfoOffsets[i];
500: }
501:
502: classInfo.methodNames = names;
503: classInfo.methodSignatures = signatures;
504: classInfo.methodAccessFlags = accessFlags;
505: classInfo.methodInfoOffsets = methodInfoOffsets;
506: classInfo.methodInfoLengths = methodInfoLengths;
507: classInfo.methodBytecodesOffsets = bytecodeOffsets;
508: classInfo.methodBytecodesLengths = bytecodeLengths;
509: classInfo.exceptionTableStartOffsets = exceptionTableStartOffsets;
510: classInfo.lineNumberTablesOffsets = lineNumberTableOffsets;
511: classInfo.lineNumberTablesLengths = lineNumberTableLengths;
512: }
513:
514: private void readPreamble() {
515: int magic = nextInt();
516:
517: if (magic != JAVA_MAGIC) {
518: throw classFileReadException("Illegal start of class file"); // NOI18N
519: }
520:
521: int minorVersion = nextChar();
522: int majorVersion = nextChar();
523:
524: if ((majorVersion > JAVA_MAJOR_VERSION)
525: || (((majorVersion * 1000) + minorVersion) < ((JAVA_MIN_MAJOR_VERSION * 1000) + JAVA_MIN_MINOR_VERSION))) {
526: if ((majorVersion != 49) && (majorVersion != 50)) {
527: String versionCode = majorVersion + "." + minorVersion; // NOI18N
528: String message = "Unsupported class file version: "
529: + versionCode; // NOI18N
530: throw classFileReadException(message);
531: }
532: }
533: }
534:
535: private String signatureAtCPIndex(int idx) {
536: return utf8AtCPIndex(idx);
537: }
538:
539: private void skipFields() {
540: classInfo.fieldsStartOfs = curBufPos;
541:
542: char definedFieldCount = nextChar();
543:
544: for (int i = 0; i < definedFieldCount; i++) {
545: curBufPos += 6; // skip 3 chars: flags, name index and signature index
546:
547: int attrCount = nextChar();
548:
549: for (int j = 0; j < attrCount; j++) {
550: curBufPos += 2; // Skip char: attr name index
551:
552: int attrLen = nextInt();
553: curBufPos += attrLen;
554: }
555: }
556: }
557:
558: private String utf8AtCPIndex(int idx) {
559: if (cpTags[idx] != CONSTANT_Utf8) {
560: throw classFileReadException("Constant pool entry " + idx
561: + " should be UTF8 constant"); // NOI18N
562: }
563:
564: if (cpObjectCache[idx] == null) {
565: int utf8Len = getChar(cpOffsets[idx]);
566: cpObjectCache[idx] = StringUtils.utf8ToString(classBuf,
567: cpOffsets[idx] + 2, utf8Len);
568: }
569:
570: return (String) cpObjectCache[idx];
571: }
572: }
|