org.netbeans.lib.profiler.classfile.ClassInfo Maven / Gradle / Ivy
/*
* 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 java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.netbeans.lib.profiler.classfile.ClassInfo.StackMapFrame.FrameType;
import org.netbeans.lib.profiler.global.CommonConstants;
import org.netbeans.lib.profiler.instrumentation.JavaClassConstants;
/**
* A representation of a binary Java class, that is relatively compact - it does not contain method bodies,
* and contains only a subset of information from the constant pool. Method bodies (or, more precisely, byte
* arrays representing either full MethodInfos as defined in JVM Specification, or just method bodies), can
* be obtained individually on demand.
*
* This class is abstract, since it contains a single abstract method that actually returns the class file bytes
* for the whole class. Concrete subclasses of this class may choose to simply store this byte array, or retrieve
* it e.g. from disk on demand.
*
* @author Misha Dmitirev
* @author Tomas Hurka
*/
public abstract class ClassInfo extends BaseClassInfo implements JavaClassConstants, CommonConstants {
//~ Inner Classes ------------------------------------------------------------------------------------------------------------
public static class LineNumberTables {
//~ Instance fields ------------------------------------------------------------------------------------------------------
private char[][] lineNumbers;
private char[][] startPCs;
private boolean hasTable;
//~ Constructors -------------------------------------------------------------------------------------------------------------
LineNumberTables(ClassInfo ci) {
byte[] classBuf = null;
try {
classBuf = ci.getClassFileBytes();
} catch (IOException ex1) { // Should not happen - class file already loaded once by this time
} catch (ClassNotFoundException ex2) {
} // Ditto
int nMethods = ci.getMethodNames().length;
startPCs = new char[nMethods][];
lineNumbers = new char[nMethods][];
for (int i = 0; i < nMethods; i++) {
int ofs = ci.methodInfoOffsets[i] + ci.lineNumberTablesOffsets[i];
if (ofs == -1) {
continue; // Abstract or native method, or no line number tables in this class
}
hasTable = true;
int tableLen = ci.lineNumberTablesLengths[i];
char[] startPC = startPCs[i] = new char[tableLen];
char[] lineNumber = lineNumbers[i] = new char[tableLen];
for (int j = 0; j < tableLen; j++) {
startPC[j] = (char) (((classBuf[ofs++] & 255) << 8) + (classBuf[ofs++] & 255));
lineNumber[j] = (char) (((classBuf[ofs++] & 255) << 8) + (classBuf[ofs++] & 255));
}
}
}
//~ Methods --------------------------------------------------------------------------------------------------------------
public char[][] getStartPCs() {
return startPCs;
}
int[] getMinAndMaxLinesForMethod(int methodIdx) {
int[] lines = new int[2];
if (startPCs[methodIdx] == null) { // No line number table for this method - return special value
lines[0] = lines[1] = -1;
return lines;
}
lines[0] = 10000000;
lines[1] = -10000000;
char[] lns = lineNumbers[methodIdx];
for (int i = 0; i < lns.length; i++) {
if (lns[i] < lines[0]) {
lines[0] = lns[i];
}
if (lns[i] > lines[1]) {
lines[1] = lns[i];
}
}
return lines;
}
int bciForLineNo(int methodIdx, int lineNo) {
char[] spcs = startPCs[methodIdx];
if (spcs == null) {
return -1;
}
int tableLen = spcs.length;
char[] lns = lineNumbers[methodIdx];
int minLine = 100000000;
int bestLine = 100000000;
int maxLine = 0;
int curLine = -1;
int bestBCI = 100000000;
for (int i = 0; i < tableLen; i++) {
curLine = lns[i];
if (curLine > maxLine) {
maxLine = curLine;
}
if (curLine < minLine) {
minLine = curLine;
}
if (curLine == lineNo) { // Perfect match
bestBCI = spcs[i];
break;
} else if ((curLine > lineNo) && (curLine <= bestLine)) { // Update bci/line
if (spcs[i] < bestBCI) { // ..but check first if it's the smallest bci for this line.
// The whole issue is due to 'while() { }' effectively compiled as 'do { } while()', where for the actual
// line of the 'while' statementwe get two different bci's in the line number table:
// 1. the one for the initial 'goto' that transfers us to the condition check block in the end of the loop body
// 2. the first bci of that condition check block.
// Whether we hit this line as the first or the last line of our code fragment, the smallest bci is a correct answer.
bestBCI = spcs[i];
bestLine = curLine;
}
}
}
// Found a valid matching line if there is a perfect match or at least the specified
// line is within this method's line number table.
if ((curLine == lineNo) || ((lineNo >= minLine) && (lineNo <= maxLine))) {
return bestBCI;
} else {
return -1;
}
}
int lineNoForBci(int methodIdx, int bci) {
char[] spcs = startPCs[methodIdx];
if (spcs == null) {
return -1;
}
int tableLen = spcs.length;
char[] lns = lineNumbers[methodIdx];
int bestLine = -1;
for (int i = 0; i < tableLen; i++) {
if (spcs[i] > bci) {
break; // reached in last cycle
}
bestLine = lns[i];
}
return bestLine;
}
private boolean hasTable() {
return hasTable;
}
}
public static class LocalVariableTables {
public static final int ATTR_SIZE = 10;
//~ Instance fields ------------------------------------------------------------------------------------------------------
private char[][] lengths;
private char[][] startPCs;
private boolean hasTable;
//~ Constructors -------------------------------------------------------------------------------------------------------------
LocalVariableTables(ClassInfo ci) {
this(ci, ci.localVariableTablesOffsets, ci.localVariableTablesLengths);
}
private LocalVariableTables(ClassInfo ci, int[] tablesOffsets, char[] tablesLengths) {
byte[] classBuf = null;
try {
classBuf = ci.getClassFileBytes();
} catch (IOException ex1) { // Should not happen - class file already loaded once by this time
} catch (ClassNotFoundException ex2) {
} // Ditto
int nMethods = ci.getMethodNames().length;
startPCs = new char[nMethods][];
lengths = new char[nMethods][];
for (int i = 0; i < nMethods; i++) {
int tableLen = tablesLengths[i];
if (tableLen == 0) {
continue;
}
int ofs = ci.methodInfoOffsets[i] + tablesOffsets[i];
char[] startPC = startPCs[i] = new char[tableLen];
char[] length = lengths[i] = new char[tableLen];
for (int j = 0; j < tableLen; j++, ofs+=ATTR_SIZE ) {
int offset = ofs;
startPC[j] = (char) (((classBuf[offset++] & 255) << 8) + (classBuf[offset++] & 255));
length[j] = (char) (((classBuf[offset++] & 255) << 8) + (classBuf[offset++] & 255));
}
hasTable = true;
}
}
//~ Methods --------------------------------------------------------------------------------------------------------------
char[][] getStartPCs() {
return startPCs;
}
char[][] getLengts() {
return lengths;
}
public boolean hasTable() {
return hasTable;
}
public void updateTable(int injectionPos, int injectedBytesCount, int methodIdx) {
if (hasTable()) {
char[] startPC = getStartPCs()[methodIdx];
char[] lengths = getLengts()[methodIdx];
if (startPC != null) {
for (int i = 0; i < startPC.length; i++) {
char currentBCI = startPC[i];
if (currentBCI >= injectionPos) {
startPC[i] = (char)(currentBCI + injectedBytesCount);
} else {
char currentLength = lengths[i];
if (currentBCI + currentLength > injectionPos) {
lengths[i] = (char)(currentLength + injectedBytesCount);
}
}
}
}
}
}
public void writeTable(final byte[] buffer, int locVarTablePtr, int methodIdx) {
char[] startPC = getStartPCs()[methodIdx];
char[] lengths = getLengts()[methodIdx];
if (startPC != null) {
for (int i = 0; i < startPC.length; i++, locVarTablePtr+=ATTR_SIZE) {
putU2(buffer, locVarTablePtr, startPC[i]);
putU2(buffer, locVarTablePtr + 2, lengths[i]);
}
}
}
}
public static class LocalVariableTypeTables extends LocalVariableTables {
LocalVariableTypeTables(ClassInfo ci) {
super(ci, ci.localVariableTypeTablesOffsets, ci.localVariableTypeTablesLengths);
}
}
// public static Logger LOG = Logger.getLogger(ClassInfo.class.getName());
public class StackMapTables {
private StackMapFrame[][] frames;
private byte[][] framesBytes;
private boolean hasTable;
StackMapTables() {
byte[] classBuf = null;
try {
classBuf = ClassInfo.this.getClassFileBytes();
} catch (IOException ex1) { // Should not happen - class file already loaded once by this time
} catch (ClassNotFoundException ex2) {
} // Ditto
int nMethods = ClassInfo.this.getMethodNames().length;
frames = new StackMapFrame[nMethods][];
framesBytes = new byte[nMethods][];
for (int i = 0; i < nMethods; i++) {
int tableLen = ClassInfo.this.stackMapTablesLengths[i];
if (tableLen == 0) {
continue;
}
int startOfs = ClassInfo.this.methodInfoOffsets[i] + ClassInfo.this.stackMapTablesOffsets[i];
int ofs = startOfs;
StackMapFrame[] frms = frames[i] = new StackMapFrame[tableLen];
// LOG.finer("Class "+ClassInfo.this.name+" method "+ClassInfo.this.getMethodName(i));
for (int j = 0; j < tableLen; j++) {
frms[j] = new StackMapFrame(classBuf,ofs);
ofs+=frms[j].getSize();
// LOG.finer(frms[j].toString());
}
int len = ofs - startOfs;
framesBytes[i] = new byte[len+tableLen+2*(StackMapFrame.FrameType.FULL_FRAME.size()+2*3)];
System.arraycopy(classBuf,startOfs,framesBytes[i],0,len);
hasTable = true;
}
}
public boolean hasTable() {
return hasTable;
}
public void updateTable(int injectionPos, int injectedBytesCount, int methodIdx, boolean changeTypeIsInjectNewInstr, boolean injectionBindsToFollowingInstruction) {
String method = getMethodName(methodIdx);
if (hasTable()) {
StackMapFrame[] frms = frames[methodIdx];
if (frms != null) {
int bciIter = -1;
boolean offsetAdjusted = false; // only need to adjust one offset
for (StackMapFrame frame : frms) {
int offsetDelta = frame.getOffsetDelta();
bciIter += offsetDelta;
if (!offsetAdjusted) {
if (adjustOffset(bciIter, injectionPos, changeTypeIsInjectNewInstr, injectionBindsToFollowingInstruction)) {
setOffsetDelta(methodIdx, frame, offsetDelta + injectedBytesCount);
offsetAdjusted = true;
}
}
frame.updateUnitilializedList(injectionPos, injectedBytesCount, changeTypeIsInjectNewInstr, injectionBindsToFollowingInstruction);
}
}
}
}
public int getNumberOfFrames(int methodIdx) {
StackMapFrame[] frms = frames[methodIdx];
if (frms != null) {
return frms.length;
}
return 0;
}
public byte[] getAttributeHeader(int methodIdx) {
byte[] header = new byte[8];
putU2(header,0,ClassInfo.this.stackMapTableCPindex);
return header;
}
public byte[] writeTable(int methodIdx) {
StackMapFrame[] frms = frames[methodIdx];
byte frameBytes[] = framesBytes[methodIdx];
if (frms != null) {
byte[] ret;
int offset = 0;
for (StackMapFrame frame : frms) {
frame.writeFrame(frameBytes, offset);
offset+=frame.getSize();
}
ret = new byte[offset];
System.arraycopy(frameBytes,0,ret,0,offset);
return ret;
}
return null;
}
void addFullStackMapFrameEntry(int methodIdx, int endPC, int[] locals, int[] stacks) {
StackMapFrame[] frms = frames[methodIdx];
StackMapFrame[] newFrms;
if (frms != null) {
int bciIter = -1;
for (StackMapFrame frame : frms) {
bciIter += frame.getOffsetDelta();
}
newFrms = frames[methodIdx] = new StackMapFrame[frms.length+1];
System.arraycopy(frms,0,newFrms,0,frms.length);
newFrms[frms.length] = new FullStackMapFrame(endPC - bciIter, locals, stacks);
} else {
newFrms = frames[methodIdx] = new StackMapFrame[1];
newFrms[0] = new FullStackMapFrame(endPC + 1, locals, stacks);
framesBytes[methodIdx] = new byte[newFrms[0].getSize()];
hasTable = true;
}
}
private void setOffsetDelta(int methodIdx, StackMapFrame frame, int newOffsetDelta) {
FrameType frameType = frame.frameType;
if (frameType == FrameType.SAME && newOffsetDelta > 63) {
extendFrame(methodIdx,frame,2);
frame.setFrameType(FrameType.SAME_FRAME_EXTENDED);
}
if (frameType == FrameType.SAME_LOCALS_1_STACK_ITEM && newOffsetDelta > 63) {
extendFrame(methodIdx,frame,2);
frame.setFrameType(FrameType.SAME_LOCALS_1_STACK_ITEM_EXTENDED);
}
frame.setOffsetDelta(newOffsetDelta);
}
private void extendFrame(int methodIdx, StackMapFrame frame, int addBytes) {
StackMapFrame[] frms = frames[methodIdx];
byte[] data = framesBytes[methodIdx];
int offset = 0;
for (StackMapFrame f : frms) {
if (f == frame) {
break;
}
offset += f.getSize();
}
System.arraycopy(data,offset,data,offset+addBytes,data.length-offset-addBytes);
}
}
static class StackMapFrame {
enum FrameType {
SAME(1),
SAME_LOCALS_1_STACK_ITEM(1),
SAME_LOCALS_1_STACK_ITEM_EXTENDED(3),
CHOP(3),
SAME_FRAME_EXTENDED(3),
APPEND(3),
FULL_FRAME(7);
private int frameSize;
int size() {return frameSize;}
FrameType(int size){
frameSize = size;
}
}
FrameType frameType;
int storedOffsetDelta;
int size;
boolean modified;
boolean frameModified;
boolean uninitializedListModified;
List uninitializedList;
StackMapFrame(FrameType type, int offset, int s) {
frameType = type;
storedOffsetDelta = offset - 1;
size = type.size()+s;
}
StackMapFrame(byte[] buffer, int offset) {
int type;
type = buffer[offset++] & 0xff;
if (type <= 63) {
frameType = FrameType.SAME;
storedOffsetDelta = type;
} else if (type <= 127) {
frameType = FrameType.SAME_LOCALS_1_STACK_ITEM;
storedOffsetDelta = type - 64;
size = getVerificationTypeInfoSize(buffer[offset]);
storeUninitializedVariableInfo(buffer,offset,0);
} else if (type <= 246) {
throw new IllegalArgumentException("Type: "+type);
} else if (type == 247) {
frameType = FrameType.SAME_LOCALS_1_STACK_ITEM_EXTENDED;
storedOffsetDelta = getU2(buffer,offset);
offset+=2;
size = getVerificationTypeInfoSize(buffer[offset]);
storeUninitializedVariableInfo(buffer,offset,0);
} else if (type <= 250) {
frameType = FrameType.CHOP;
storedOffsetDelta = getU2(buffer,offset);
} else if (type == 251) {
frameType = FrameType.SAME_FRAME_EXTENDED;
storedOffsetDelta = getU2(buffer,offset);
} else if (type <= 254) {
frameType = FrameType.APPEND;
storedOffsetDelta = getU2(buffer,offset);
offset+=2;
int locals = type - 251;
for (int i=0; i();
}
while (uninitializedList.size() < listIndex+1) {
uninitializedList.add(null);
}
uninitializedList.set(listIndex,Integer.valueOf(getU2(buffer,offset)));
// LOG.finer("ITEM_Unitialized "+Integer.valueOf(getU2(buffer,offset)));
}
}
void writeFrame(byte[] ret, int newFrameOffset) {
if (modified) {
// LOG.finer("Updating "+frameType+" new offset "+getOffsetDelta()+" old type "+Integer.valueOf(ret[newFrameOffset]&0xff));
if (frameModified) {
switch (frameType) {
case SAME_LOCALS_1_STACK_ITEM_EXTENDED:
ret[newFrameOffset] = (byte)247;
break;
case SAME_FRAME_EXTENDED:
ret[newFrameOffset] = (byte)251;
break;
}
}
switch (frameType) {
case SAME:
ret[newFrameOffset] = (byte)(storedOffsetDelta & 0x3F);
break;
case SAME_LOCALS_1_STACK_ITEM:
ret[newFrameOffset] = (byte)(64 + (storedOffsetDelta & 0x3F));
break;
case SAME_LOCALS_1_STACK_ITEM_EXTENDED:
case CHOP:
case SAME_FRAME_EXTENDED:
case APPEND:
case FULL_FRAME:
putU2(ret, newFrameOffset+1, storedOffsetDelta);
break;
}
}
if (uninitializedListModified) {
switch (frameType) {
case SAME_LOCALS_1_STACK_ITEM:
putU2(ret,newFrameOffset+2,uninitializedList.get(0).intValue());
break;
case SAME_LOCALS_1_STACK_ITEM_EXTENDED:
putU2(ret,newFrameOffset+3,uninitializedList.get(0).intValue());
break;
case APPEND: {
int offset = newFrameOffset+3;
for (Integer off : uninitializedList) {
byte type = ret[offset];
int typeInfoSize = getVerificationTypeInfoSize(type);
if (type == 8) { // ITEM_Unitialized
putU2(ret,offset+1,off.intValue());
}
offset += typeInfoSize;
}
break;
}
case FULL_FRAME: {
int offset = newFrameOffset+3;
int locals = getU2(ret,offset);
offset+=2;
// LOG.finer("Locals: "+locals);
for (int i=0; i0 && locals[0] == 0)?locals.length:3*locals.length));
localsCPIdx = locals;
stacksCPIdx = stacks;
}
void writeFrame(byte[] ret, int offset) {
ret[offset++] = (byte)255; // FULL_FRAME
putU2(ret,offset,storedOffsetDelta); // offset_delta
offset+=2;
putU2(ret,offset,localsCPIdx.length); // locals
offset+=2;
for (int i=0; i= opc_iload) && (opcode <= opc_aload)) || ((opcode >= opc_istore) && (opcode <= opc_astore))
|| (opcode == opc_ret)) {
offset += 4;
} else if (opcode == opc_iinc) {
offset += 6;
} else {
offset++;
}
} else {
switch (opcode) {
case opc_tableswitch: {
int tbl = (offset + 1 + 3) & (~3); // four byte boundry
long default_skip = intAt(codeBytes, tbl, 0);
long low = intAt(codeBytes, tbl, 1);
long high = intAt(codeBytes, tbl, 2);
tbl += (3 << 2); // three int header
offset = tbl + (int) ((high - low + 1) << 2);
break;
}
case opc_lookupswitch: {
int tbl = (offset + 1 + 3) & (~3); // four byte boundry
long default_skip = intAt(codeBytes, tbl, 0);
int npairs = (int) intAt(codeBytes, tbl, 1);
int nints = npairs * 2;
tbl += (2 << 2); // two int header
offset = tbl + (nints << 2);
break;
}
default:
offset += opc_length[opcode];
break;
}
}
}
return prev_offset;
}
public int lineNoForMethodAndBci(int methodIdx, int bci) { // TODO CHECK: unused method
initLineNumberTables();
return lineNumberTables.lineNoForBci(methodIdx, bci);
}
/**
* Returns a {method idx, best BCI} pair for the given source line number in this class. If no suitable method is
* found, returns {-1, -1}. If this class doesn't have any line number tables (because it's abstract or because
* it was compiled without tables), returns {-2, -2}.
*/
public int[] methodIdxAndBestBCIForLineNo(int lineNo) {
initLineNumberTables();
if (!lineNumberTables.hasTable()) {
return new int[] { -2, -2 };
}
int nMethods = methodNames.length;
// We need to take into account the fact that for a constructor/class initializer the line numbers may span
// a much larger range than the constructor body. That's due to instance/static initialization statements
// that can be scattered about the whole class. If we put the cursor into a method that is between two
// initializers, we may well get a constructor as the "best match" for the given line. Thus, we first
// search normal methods, and only if this fails - constructors and class initializer.
for (int i = 0; i < nMethods; i++) {
if ((methodNames[i] == "") || (methodNames[i] == "")) { // NOI18N
continue;
}
int bestBCI = lineNumberTables.bciForLineNo(i, lineNo);
if (bestBCI != -1) {
return new int[] { i, bestBCI };
}
}
// No success with ordinary methods - try constructors now
for (int i = 0; i < nMethods; i++) {
if ((methodNames[i] != "") && (methodNames[i] != "")) { // NOI18N
continue;
}
int bestBCI = lineNumberTables.bciForLineNo(i, lineNo);
if (bestBCI != -1) {
return new int[] { i, bestBCI };
}
}
return new int[] { -1, -1 };
}
// WARNING: this call doesn't check if the method in superClass is not private, final, static or constructor. This is done for
// speedup, since we call it only in the context when it is already known that the above is true.
public int overridesVirtualMethod(ClassInfo superClass, int superMethodIdx) { // TODO CHECK: unused method
int idx = getMethodIndex(superClass.methodNames[superMethodIdx], superClass.methodSignatures[superMethodIdx]);
if (idx == -1) {
return -1;
}
if (superClass.isMethodPublic(superMethodIdx) || superClass.isMethodProtected(superMethodIdx)) {
return idx;
} else if (superClass.packageName == this.packageName) {
return idx;
} else {
return -1;
}
}
//-------------------------------------- Protected methods -------------------------------------------
/** Returns the class file bytes for this class. */
protected abstract byte[] getClassFileBytes() throws IOException, ClassNotFoundException;
/**
* Returns package name for the given class. In case of no package, returns an
* empty, but non-null string. Returned string is interned.
*/
protected static String getPackageName(String clazzName) {
int ldi = clazzName.lastIndexOf('/'); // For convenience, we use system-internal slashes, not dots
if (ldi == -1) {
return ""; // NOI18N
} else {
return clazzName.substring(0, ldi).intern();
}
}
/** Given the table at the specified index, return the specified entry */
static long intAt(byte[] codeBytes, int tbl, int entry) { // TODO CHECK: unused method
int base = tbl + (entry << 2);
return (codeBytes[base] << 24) | ((codeBytes[base + 1] & 0xFF) << 16) | ((codeBytes[base + 2] & 0xFF) << 8)
| (codeBytes[base + 3] & 0xFF);
}
static void putU2(byte[] buf, int pos, int value) {
buf[pos] = (byte) ((value >> 8) & 0xFF);
buf[pos + 1] = (byte) (value & 0xFF);
}
static int getU2(byte[] buf, int pos) {
return ((buf[pos] & 0xFF) << 8) + (buf[pos + 1] & 0xFF);
}
private static boolean adjustOffset(int bciIter, int injectionPos, boolean changeTypeIsInjectNewInstr, boolean injectionBindsToFollowingInstruction) {
boolean adjustOffset = false;
if (bciIter > injectionPos) {
adjustOffset = true;
} else if (changeTypeIsInjectNewInstr) {
if (injectionPos == 0 && bciIter == 0) {
adjustOffset = true;
} else if (!injectionBindsToFollowingInstruction && bciIter >= injectionPos) {
adjustOffset = true;
}
}
return adjustOffset;
}
//----------------------------------------- Private implementation -----------------------------------
private synchronized void initLineNumberTables() {
if (lineNumberTables == null) {
lineNumberTables = new LineNumberTables(this);
}
}
private synchronized void initLocalVariableTables() {
if (localVariableTables == null) {
localVariableTables = new LocalVariableTables(this);
}
}
private synchronized void initLocalVariableTypeTables() {
if (localVariableTypeTables == null) {
localVariableTypeTables = new LocalVariableTypeTables(this);
}
}
private synchronized void initStackMapTables() {
if (stackMapTables == null) {
stackMapTables = new StackMapTables();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy