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.global.CommonConstants;
044: import org.netbeans.lib.profiler.instrumentation.JavaClassConstants;
045: import java.io.IOException;
046: import java.lang.reflect.Modifier;
047:
048: /**
049: * A representation of a binary Java class, that is relatively compact - it does not contain method bodies,
050: * and contains only a subset of information from the constant pool. Method bodies (or, more precisely, byte
051: * arrays representing either full MethodInfos as defined in JVM Specification, or just method bodies), can
052: * be obtained individually on demand.
053: *
054: * This class is abstract, since it contains a single abstract method that actually returns the class file bytes
055: * for the whole class. Concrete subclasses of this class may choose to simply store this byte array, or retrieve
056: * it e.g. from disk on demand.
057: *
058: * @author Misha Dmitirev
059: */
060: public abstract class ClassInfo extends BaseClassInfo implements
061: JavaClassConstants, CommonConstants {
062: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
063:
064: public static class LineNumberTables {
065: //~ Instance fields ------------------------------------------------------------------------------------------------------
066:
067: char[][] lineNumbers;
068: char[][] startPCs;
069:
070: //~ Methods --------------------------------------------------------------------------------------------------------------
071:
072: public char[][] getStartPCs() {
073: return startPCs;
074: }
075:
076: int[] getMinAndMaxLinesForMethod(int methodIdx) {
077: int[] lines = new int[2];
078:
079: if (startPCs[methodIdx] == null) { // No line number table for this method - return special value
080: lines[0] = lines[1] = -1;
081:
082: return lines;
083: }
084:
085: lines[0] = 10000000;
086: lines[1] = -10000000;
087:
088: char[] lns = lineNumbers[methodIdx];
089:
090: for (int i = 0; i < lns.length; i++) {
091: if (lns[i] < lines[0]) {
092: lines[0] = lns[i];
093: }
094:
095: if (lns[i] > lines[1]) {
096: lines[1] = lns[i];
097: }
098: }
099:
100: return lines;
101: }
102:
103: int bciForLineNo(int methodIdx, int lineNo) {
104: char[] spcs = startPCs[methodIdx];
105:
106: if (spcs == null) {
107: return -1;
108: }
109:
110: int tableLen = spcs.length;
111: char[] lns = lineNumbers[methodIdx];
112:
113: int minLine = 100000000;
114: int bestLine = 100000000;
115: int maxLine = 0;
116:
117: int curLine = -1;
118: int bestBCI = 100000000;
119:
120: for (int i = 0; i < tableLen; i++) {
121: curLine = lns[i];
122:
123: if (curLine > maxLine) {
124: maxLine = curLine;
125: }
126:
127: if (curLine < minLine) {
128: minLine = curLine;
129: }
130:
131: if (curLine == lineNo) { // Perfect match
132: bestBCI = spcs[i];
133:
134: break;
135: } else if ((curLine > lineNo) && (curLine <= bestLine)) { // Update bci/line
136:
137: if (spcs[i] < bestBCI) { // ..but check first if it's the smallest bci for this line.
138: // The whole issue is due to 'while() { }' effectively compiled as 'do { } while()', where for the actual
139: // line of the 'while' statementwe get two different bci's in the line number table:
140: // 1. the one for the initial 'goto' that transfers us to the condition check block in the end of the loop body
141: // 2. the first bci of that condition check block.
142: // Whether we hit this line as the first or the last line of our code fragment, the smallest bci is a correct answer.
143: bestBCI = spcs[i];
144: bestLine = curLine;
145: }
146: }
147: }
148:
149: // Found a valid matching line if there is a perfect match or at least the specified
150: // line is within this method's line number table.
151: if ((curLine == lineNo)
152: || ((lineNo >= minLine) && (lineNo <= maxLine))) {
153: return bestBCI;
154: } else {
155: return -1;
156: }
157: }
158:
159: int lineNoForBci(int methodIdx, int bci) {
160: char[] spcs = startPCs[methodIdx];
161:
162: if (spcs == null) {
163: return -1;
164: }
165:
166: int tableLen = spcs.length;
167: char[] lns = lineNumbers[methodIdx];
168:
169: int bestLine = -1;
170:
171: for (int i = 0; i < tableLen; i++) {
172: if (spcs[i] > bci) {
173: break; // reached in last cycle
174: }
175:
176: bestLine = lns[i];
177: }
178:
179: return bestLine;
180: }
181: }
182:
183: //~ Instance fields ----------------------------------------------------------------------------------------------------------
184:
185: String packageName;
186: String super Name;
187: char[] cpoolRefsToClassIdx; // Cpool indices of ClassEntry entires for referenced classes
188: String[] cpoolRefsToClassName; // Names of classes referenced from cpool, including array classes.
189: String[][] cpoolRefsToMethodClassNameAndSig;
190:
191: // In all signatures we replace the 'L' and ';' symbols that enclose non-primitive type names with '@' and '#' respectively,
192: // so that class names inside signatures can be located fast and unambiguously.
193: char[] cpoolRefsToMethodIdx; // Cpool indices of MethodEntry entries for referenced methods
194: // The following array consists of "referenced method's class name, name, signature" triplets.
195: // Defining classes names are trimmed of enclosing 'L' and ';' symbols
196: int[] exceptionTableStartOffsets; // Relative offsets within a MethodInfo
197: String[] interfaces;
198: char[] lineNumberTablesLengths;
199: int[] lineNumberTablesOffsets; // Relative offsets within a MethodInfo
200: char[] methodAccessFlags;
201: char[] methodBytecodesLengths;
202: int[] methodBytecodesOffsets; // Relative offsets within a MethodInfo
203: int[] methodInfoLengths;
204: int[] methodInfoOffsets;
205: String[] methodNames;
206: String[] methodSignatures;
207: String[] nestedClassNames;
208: char accessFlags; // isInterface flag included
209: int attrsStartOfs; // Ditto for class attributes
210: int cpoolStartOfs; // Starting offset, in bytes, of the original cpool (cpool length char included)
211: int fieldsStartOfs; // Ditto for fields
212: int intermediateDataStartOfs; // Ditto for intermediate data (class flags, name, super, etc.)
213: int methodsStartOfs; // Ditto for methods
214: int origCPoolCount; // The number of entries in the original cpool of this class
215: short lineNumberTablesInitStatus; // 0 : not yet initialized, 1 : OK, -1 : no tables exist
216: private LineNumberTables lineNumberTables;
217:
218: //~ Constructors -------------------------------------------------------------------------------------------------------------
219:
220: protected ClassInfo(String className, int loaderId) {
221: super (className, loaderId);
222: packageName = getPackageName(name);
223: }
224:
225: /**
226: * This constructor is used for creation of temporary instances of ClassInfo, typically to just find out something about
227: * class contained in a given .class file.
228: */
229: ClassInfo(byte[] buf) throws ClassFormatError {
230: super ("", 0); // NOI18N
231:
232: try {
233: (new ClassFileParser()).parseClassFile(buf, this );
234: } catch (ClassFileParser.ClassFileReadException ex) {
235: throw new ClassFormatError(ex.getMessage());
236: }
237:
238: packageName = getPackageName(name);
239: }
240:
241: //~ Methods ------------------------------------------------------------------------------------------------------------------
242:
243: public boolean isAbstract() {
244: return Modifier.isAbstract(accessFlags);
245: }
246:
247: public int getExceptionTableStartOffsetInMethodInfo(int i) {
248: return exceptionTableStartOffsets[i];
249: }
250:
251: public boolean isInterface() {
252: return Modifier.isInterface(accessFlags);
253: }
254:
255: public String[] getInterfaceNames() {
256: return interfaces;
257: }
258:
259: public LineNumberTables getLineNumberTables() {
260: if (lineNumberTables == null) {
261: initLineNumberTables();
262: }
263:
264: return lineNumberTables;
265: }
266:
267: public boolean isMethodAbstract(int i) {
268: return Modifier.isAbstract(methodAccessFlags[i]);
269: }
270:
271: public byte[] getMethodBytecode(int i) {
272: try {
273: byte[] classFile = getClassFileBytes();
274: byte[] res = new byte[methodBytecodesLengths[i]];
275: System.arraycopy(classFile, methodInfoOffsets[i]
276: + methodBytecodesOffsets[i], res, 0,
277: methodBytecodesLengths[i]);
278:
279: return res;
280: } catch (IOException ex1) {
281: return null; // Should not happen - class file already loaded once by this time
282: } catch (ClassNotFoundException ex2) {
283: return null;
284: } // Ditto
285: }
286:
287: public int getMethodBytecodeOffsetInMethodInfo(int i) {
288: return methodBytecodesOffsets[i];
289: }
290:
291: public int getMethodBytecodesLength(int i) {
292: return methodBytecodesLengths[i];
293: }
294:
295: public boolean isMethodFinal(int i) {
296: return Modifier.isFinal(methodAccessFlags[i]);
297: }
298:
299: public int getMethodIndex(String name, String sig) {
300: for (int i = 0; i < methodNames.length; i++) {
301: if ((methodNames[i] == name)
302: && (methodSignatures[i] == sig)) {
303: return i;
304: }
305: }
306:
307: return -1;
308: }
309:
310: public byte[] getMethodInfo(int i) {
311: try {
312: byte[] classFile = getClassFileBytes();
313: byte[] res = new byte[methodInfoLengths[i]];
314: System.arraycopy(classFile, methodInfoOffsets[i], res, 0,
315: methodInfoLengths[i]);
316:
317: return res;
318: } catch (IOException ex1) {
319: return null; // Should not happen - class file already loaded once by this time
320: } catch (ClassNotFoundException ex2) {
321: return null;
322: } // Ditto
323: }
324:
325: public int getMethodInfoLength(int i) {
326: return methodInfoLengths[i];
327: }
328:
329: public String getMethodName(int i) {
330: return methodNames[i];
331: }
332:
333: public String[] getMethodNames() {
334: return methodNames;
335: }
336:
337: public boolean isMethodNative(int i) {
338: return Modifier.isNative(methodAccessFlags[i]);
339: }
340:
341: public boolean isMethodPrivate(int i) {
342: return Modifier.isPrivate(methodAccessFlags[i]);
343: }
344:
345: public boolean isMethodProtected(int i) {
346: return Modifier.isProtected(methodAccessFlags[i]);
347: }
348:
349: public boolean isMethodPublic(int i) {
350: return Modifier.isPublic(methodAccessFlags[i]);
351: }
352:
353: public String getMethodSignature(int i) {
354: return methodSignatures[i];
355: }
356:
357: public String[] getMethodSignatures() {
358: return methodSignatures;
359: }
360:
361: public boolean isMethodStatic(int i) {
362: return Modifier.isStatic(methodAccessFlags[i]);
363: }
364:
365: public int[] getMinAndMaxLinesForMethod(int methodIdx) {
366: if (lineNumberTables == null) {
367: initLineNumberTables();
368: }
369:
370: return lineNumberTables.getMinAndMaxLinesForMethod(methodIdx);
371: }
372:
373: public String[] getNestedClassNames() {
374: return nestedClassNames;
375: }
376:
377: public int getOrigAttrsStartOfs() {
378: return attrsStartOfs;
379: }
380:
381: public int getOrigCPoolCount() {
382: return origCPoolCount;
383: }
384:
385: public int getOrigCPoolStartOfs() {
386: return cpoolStartOfs;
387: }
388:
389: public int getOrigFieldsStartOfs() {
390: return fieldsStartOfs;
391: }
392:
393: public int getOrigIntermediateDataStartOfs() {
394: return intermediateDataStartOfs;
395: }
396:
397: public int getOrigMethodsStartOfs() {
398: return methodsStartOfs;
399: }
400:
401: public String getRefClassName(int refClassIdx) {
402: for (int i = 0; i < cpoolRefsToClassIdx.length; i++) {
403: if (cpoolRefsToClassIdx[i] == refClassIdx) {
404: return cpoolRefsToClassName[i];
405: }
406: }
407:
408: return null;
409: }
410:
411: public String[] getRefMethodsClassNameAndSig(int refMethodIdx) {
412: for (int i = 0; i < cpoolRefsToMethodIdx.length; i++) {
413: if (cpoolRefsToMethodIdx[i] == refMethodIdx) {
414: return cpoolRefsToMethodClassNameAndSig[i];
415: }
416: }
417:
418: return null;
419: }
420:
421: public String getSuperclassName() {
422: return super Name;
423: }
424:
425: public int bciForMethodAndLineNo(int methodIdx, int lineNo) {
426: if (lineNumberTables == null) {
427: initLineNumberTables();
428: }
429:
430: return lineNumberTables.bciForLineNo(methodIdx, lineNo);
431: }
432:
433: /** Check if the given method's opcode at bci is goto. or goto_w. If so, find and return the bci of the previous opcode */
434: public int checkIfAtGoTo(int methodIdx, int bci) { // TODO CHECK: unused method
435:
436: byte[] codeBytes = getMethodBytecode(methodIdx);
437: int codeAtBCI = codeBytes[bci] & 0xFF;
438:
439: if ((codeAtBCI != opc_goto) && (codeAtBCI != opc_goto_w)) {
440: return bci;
441: }
442:
443: return findPreviousBCI(codeBytes, bci);
444: }
445:
446: public boolean containsMethod(String name, String sig) { // TODO CHECK: unused method
447:
448: return (getMethodIndex(name, sig) != -1);
449: }
450:
451: /** For given bytecode offset bci, return the offset of the bytecode before the one at bci */
452: public static int findPreviousBCI(byte[] codeBytes, int bci) {
453: int prev_offset = 0;
454:
455: for (int offset = 0; offset < bci;) {
456: prev_offset = offset;
457:
458: int opcode = codeBytes[offset] & 0xFF;
459:
460: if (opcode == opc_wide) {
461: opcode = codeBytes[offset + 1] & 0xFF;
462:
463: if (((opcode >= opc_iload) && (opcode <= opc_aload))
464: || ((opcode >= opc_istore) && (opcode <= opc_astore))
465: || (opcode == opc_ret)) {
466: offset += 4;
467: } else if (opcode == opc_iinc) {
468: offset += 6;
469: } else {
470: offset++;
471: }
472: } else {
473: switch (opcode) {
474: case opc_tableswitch: {
475: int tbl = (offset + 1 + 3) & (~3); // four byte boundry
476: long default_skip = intAt(codeBytes, tbl, 0);
477: long low = intAt(codeBytes, tbl, 1);
478: long high = intAt(codeBytes, tbl, 2);
479: tbl += (3 << 2); // three int header
480: offset = tbl + (int) ((high - low + 1) << 2);
481:
482: break;
483: }
484: case opc_lookupswitch: {
485: int tbl = (offset + 1 + 3) & (~3); // four byte boundry
486: long default_skip = intAt(codeBytes, tbl, 0);
487: int npairs = (int) intAt(codeBytes, tbl, 1);
488: int nints = npairs * 2;
489: tbl += (2 << 2); // two int header
490: offset = tbl + (nints << 2);
491:
492: break;
493: }
494: default:
495: offset += opc_length[opcode];
496:
497: break;
498: }
499: }
500: }
501:
502: return prev_offset;
503: }
504:
505: public int lineNoForMethodAndBci(int methodIdx, int bci) { // TODO CHECK: unused method
506:
507: if (lineNumberTables == null) {
508: initLineNumberTables();
509: }
510:
511: return lineNumberTables.lineNoForBci(methodIdx, bci);
512: }
513:
514: /**
515: * Returns a {method idx, best BCI} pair for the given source line number in this class. If no suitable method is
516: * found, returns {-1, -1}. If this class doesn't have any line number tables (because it's abstract or because
517: * it was compiled without tables), returns {-2, -2}.
518: */
519: public int[] methodIdxAndBestBCIForLineNo(int lineNo) {
520: if (lineNumberTablesInitStatus == 0) {
521: initLineNumberTables();
522: }
523:
524: if (lineNumberTablesInitStatus == -1) {
525: return new int[] { -2, -2 };
526: }
527:
528: int nMethods = methodNames.length;
529:
530: // We need to take into account the fact that for a constructor/class initializer the line numbers may span
531: // a much larger range than the constructor body. That's due to instance/static initialization statements
532: // that can be scattered about the whole class. If we put the cursor into a method that is between two
533: // initializers, we may well get a constructor as the "best match" for the given line. Thus, we first
534: // search normal methods, and only if this fails - constructors and class initializer.
535: for (int i = 0; i < nMethods; i++) {
536: if ((methodNames[i] == "<init>")
537: || (methodNames[i] == "<clinit>")) {
538: continue; // NOI18N
539: }
540:
541: int bestBCI = lineNumberTables.bciForLineNo(i, lineNo);
542:
543: if (bestBCI != -1) {
544: return new int[] { i, bestBCI };
545: }
546: }
547:
548: // No success with ordinary methods - try constructors now
549: for (int i = 0; i < nMethods; i++) {
550: if ((methodNames[i] != "<init>")
551: && (methodNames[i] != "<clinit>")) {
552: continue; // NOI18N
553: }
554:
555: int bestBCI = lineNumberTables.bciForLineNo(i, lineNo);
556:
557: if (bestBCI != -1) {
558: return new int[] { i, bestBCI };
559: }
560: }
561:
562: return new int[] { -1, -1 };
563: }
564:
565: // WARNING: this call doesn't check if the method in superClass is not private, final, static or constructor. This is done for
566: // speedup, since we call it only in the context when it is already known that the above is true.
567: public int overridesVirtualMethod(ClassInfo super Class,
568: int super MethodIdx) { // TODO CHECK: unused method
569:
570: int idx = getMethodIndex(
571: super Class.methodNames[super MethodIdx],
572: super Class.methodSignatures[super MethodIdx]);
573:
574: if (idx == -1) {
575: return -1;
576: }
577:
578: if (super Class.isMethodPublic(super MethodIdx)
579: || super Class.isMethodProtected(super MethodIdx)) {
580: return idx;
581: } else if (super Class.packageName == this .packageName) {
582: return idx;
583: } else {
584: return -1;
585: }
586: }
587:
588: //-------------------------------------- Protected methods -------------------------------------------
589:
590: /** Returns the class file bytes for this class. */
591: protected abstract byte[] getClassFileBytes() throws IOException,
592: ClassNotFoundException;
593:
594: /**
595: * Returns package name for the given class. In case of no package, returns an
596: * empty, but non-null string. Returned string is interned.
597: */
598: protected static String getPackageName(String clazzName) {
599: int ldi = clazzName.lastIndexOf('/'); // For convenience, we use system-internal slashes, not dots
600:
601: if (ldi == -1) {
602: return ""; // NOI18N
603: } else {
604: return clazzName.substring(0, ldi).intern();
605: }
606: }
607:
608: //----------------------------------------- Private implementation -----------------------------------
609:
610: /** Given the table at the specified index, return the specified entry */
611: private static final long intAt(byte[] codeBytes, int tbl, int entry) { // TODO CHECK: unused method
612:
613: int base = tbl + (entry << 2);
614:
615: return (codeBytes[base] << 24)
616: | ((codeBytes[base + 1] & 0xFF) << 16)
617: | ((codeBytes[base + 2] & 0xFF) << 8)
618: | (codeBytes[base + 3] & 0xFF);
619: }
620:
621: private void initLineNumberTables() {
622: byte[] classBuf = null;
623:
624: try {
625: classBuf = getClassFileBytes();
626: } catch (IOException ex1) { // Should not happen - class file already loaded once by this time
627: } catch (ClassNotFoundException ex2) {
628: } // Ditto
629:
630: int nMethods = methodNames.length;
631: char[][] startPCs = new char[nMethods][];
632: char[][] lineNumbers = new char[nMethods][];
633: boolean lineNumberTablesExist = false;
634:
635: for (int i = 0; i < nMethods; i++) {
636: int ofs = methodInfoOffsets[i] + lineNumberTablesOffsets[i];
637:
638: if (ofs == -1) {
639: continue; // Abstract or native method, or no line number tables in this class
640: }
641:
642: lineNumberTablesExist = true;
643:
644: int tableLen = lineNumberTablesLengths[i];
645: char[] startPC = startPCs[i] = new char[tableLen];
646: char[] lineNumber = lineNumbers[i] = new char[tableLen];
647:
648: for (int j = 0; j < tableLen; j++) {
649: startPC[j] = (char) (((classBuf[ofs++] & 255) << 8) + (classBuf[ofs++] & 255));
650: lineNumber[j] = (char) (((classBuf[ofs++] & 255) << 8) + (classBuf[ofs++] & 255));
651: }
652: }
653:
654: // Let's init this data anyway, to avoid NullPointerExceptions etc.
655: lineNumberTables = new LineNumberTables();
656: lineNumberTables.startPCs = startPCs;
657: lineNumberTables.lineNumbers = lineNumbers;
658:
659: if (lineNumberTablesExist) {
660: lineNumberTablesInitStatus = 1;
661: } else {
662: lineNumberTablesInitStatus = -1;
663: }
664: }
665: }
|