520 lines
16 KiB
Java
520 lines
16 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
package com.ibm.wala.classLoader;
|
|
|
|
import java.io.IOException;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
|
|
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
|
import com.ibm.wala.shrikeBT.Decoder;
|
|
import com.ibm.wala.shrikeBT.IndirectionData;
|
|
import com.ibm.wala.shrikeBT.shrikeCT.CTDecoder;
|
|
import com.ibm.wala.shrikeCT.AnnotationsReader;
|
|
import com.ibm.wala.shrikeCT.AnnotationsReader.UnimplementedException;
|
|
import com.ibm.wala.shrikeCT.ClassReader;
|
|
import com.ibm.wala.shrikeCT.ClassReader.AttrIterator;
|
|
import com.ibm.wala.shrikeCT.CodeReader;
|
|
import com.ibm.wala.shrikeCT.ExceptionsReader;
|
|
import com.ibm.wala.shrikeCT.InvalidClassFileException;
|
|
import com.ibm.wala.shrikeCT.LineNumberTableReader;
|
|
import com.ibm.wala.shrikeCT.LocalVariableTableReader;
|
|
import com.ibm.wala.shrikeCT.RuntimeInvisibleAnnotationsReader;
|
|
import com.ibm.wala.shrikeCT.RuntimeVisibleAnnotationsReader;
|
|
import com.ibm.wala.shrikeCT.SignatureReader;
|
|
import com.ibm.wala.shrikeCT.SourcePositionTableReader;
|
|
import com.ibm.wala.shrikeCT.SourcePositionTableReader.Position;
|
|
import com.ibm.wala.types.TypeReference;
|
|
import com.ibm.wala.types.annotations.Annotation;
|
|
import com.ibm.wala.types.generics.MethodTypeSignature;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.debug.Assertions;
|
|
|
|
/**
|
|
* A wrapper around a Shrike object that represents a method
|
|
*/
|
|
public final class ShrikeCTMethod extends ShrikeBTMethod implements IBytecodeMethod {
|
|
|
|
/**
|
|
* The index of this method in the declaring class's method list according to Shrike CT.
|
|
*/
|
|
final private int shrikeMethodIndex;
|
|
|
|
/**
|
|
* JVM-level modifiers for this method a value of -1 means "uninitialized"
|
|
*/
|
|
private int modifiers = -1;
|
|
|
|
private final IClassHierarchy cha;
|
|
|
|
public ShrikeCTMethod(IClass klass, int index) {
|
|
|
|
super(klass);
|
|
if (klass == null) {
|
|
throw new IllegalArgumentException("klass is null");
|
|
}
|
|
this.shrikeMethodIndex = index;
|
|
this.cha = klass.getClassHierarchy();
|
|
}
|
|
|
|
@Override
|
|
public byte[] getBytecodes() {
|
|
CodeReader code = getCodeReader();
|
|
if (code == null) {
|
|
return null;
|
|
} else {
|
|
return code.getBytecode();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected String getMethodName() throws InvalidClassFileException {
|
|
ClassReader reader = getClassReader();
|
|
return reader.getMethodName(shrikeMethodIndex);
|
|
}
|
|
|
|
@Override
|
|
protected String getMethodSignature() throws InvalidClassFileException {
|
|
ClassReader reader = getClassReader();
|
|
return reader.getMethodType(shrikeMethodIndex);
|
|
}
|
|
|
|
@Override
|
|
protected int getModifiers() {
|
|
if (modifiers == -1) {
|
|
modifiers = getClassReader().getMethodAccessFlags(shrikeMethodIndex);
|
|
}
|
|
return modifiers;
|
|
}
|
|
|
|
@Override
|
|
protected Decoder makeDecoder() {
|
|
CodeReader reader = getCodeReader();
|
|
if (reader == null) {
|
|
return null;
|
|
}
|
|
final Decoder d = new CTDecoder(reader);
|
|
try {
|
|
d.decode();
|
|
} catch (Decoder.InvalidBytecodeException ex) {
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
return d;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxLocals() {
|
|
CodeReader reader = getCodeReader();
|
|
return reader.getMaxLocals();
|
|
}
|
|
|
|
@Override
|
|
public int getMaxStackHeight() {
|
|
CodeReader reader = getCodeReader();
|
|
// note that Shrike returns the maximum index in the zero-indexed stack
|
|
// array.
|
|
// Instead, we want the max number of entries on the stack.
|
|
// So we add 1.
|
|
// Additionally, ShrikeBT may add additional stack entries with
|
|
// Constant instructions. We add an additional 1 to account for this,
|
|
// which seems to handle all ShrikeBT code generation patterns.
|
|
// TODO: ShrikeBT should have a getMaxStack method on Decoder, I think.
|
|
return reader.getMaxStack() + 2;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasExceptionHandler() {
|
|
CodeReader reader = getCodeReader();
|
|
if (reader == null)
|
|
return false;
|
|
int[] handlers = reader.getRawHandlers();
|
|
return handlers != null && handlers.length > 0;
|
|
}
|
|
|
|
@Override
|
|
protected String[] getDeclaredExceptionTypeNames() throws InvalidClassFileException {
|
|
ExceptionsReader reader = getExceptionReader();
|
|
if (reader == null) {
|
|
return null;
|
|
} else {
|
|
return reader.getClasses();
|
|
}
|
|
}
|
|
/** BEGIN Custom change: precise positions */
|
|
|
|
private static final class SPos implements SourcePosition {
|
|
|
|
final int firstLine;
|
|
final int lastLine;
|
|
final int firstCol;
|
|
final int lastCol;
|
|
|
|
private SPos(int firstLine, int lastLine, int firstCol, int lastCol) {
|
|
this.firstLine = firstLine;
|
|
this.lastLine = lastLine;
|
|
this.firstCol = firstCol;
|
|
this.lastCol = lastCol;
|
|
}
|
|
|
|
|
|
public int getFirstCol() {
|
|
return firstCol;
|
|
}
|
|
|
|
public int getFirstLine() {
|
|
return firstLine;
|
|
}
|
|
|
|
public int getFirstOffset() {
|
|
return 0;
|
|
}
|
|
|
|
public int getLastCol() {
|
|
return lastCol;
|
|
}
|
|
|
|
public int getLastLine() {
|
|
return lastLine;
|
|
}
|
|
|
|
public int getLastOffset() {
|
|
return 0;
|
|
}
|
|
|
|
public int compareTo(Object o) {
|
|
if (o instanceof SourcePosition) {
|
|
SourcePosition p = (SourcePosition) o;
|
|
if (firstLine != p.getFirstLine()) {
|
|
return firstLine - p.getFirstLine();
|
|
} else if (firstCol != p.getFirstCol()) {
|
|
return firstCol - p.getFirstCol();
|
|
} else if (lastLine != p.getLastLine()) {
|
|
return lastLine - p.getLastLine();
|
|
} else if (lastCol != p.getLastCol()) {
|
|
return lastCol - p.getLastCol();
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "(" + firstLine + "," + firstCol + "-" + lastLine + "," + lastCol + ")";
|
|
}
|
|
|
|
}
|
|
/** END Custom change: precise positions */
|
|
|
|
@Override
|
|
protected void processDebugInfo(BytecodeInfo bcInfo) throws InvalidClassFileException {
|
|
CodeReader cr = getCodeReader();
|
|
bcInfo.lineNumberMap = LineNumberTableReader.makeBytecodeToSourceMap(cr);
|
|
bcInfo.localVariableMap = LocalVariableTableReader.makeVarMap(cr);
|
|
/** BEGIN Custom change: precise bytecode positions */
|
|
|
|
Position param = null;
|
|
try {
|
|
param = SourcePositionTableReader.findParameterPosition(shrikeMethodIndex, cr);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
bcInfo.paramPositionMap = new SPos[getNumberOfParameters()];
|
|
if (param != null) {
|
|
SPos paramPos = new SPos(param.firstLine, param.lastLine, param.firstCol, param.lastCol);
|
|
for (int i = 0; i < getNumberOfParameters(); i++) {
|
|
bcInfo.paramPositionMap[i] = paramPos;
|
|
}
|
|
}
|
|
|
|
Position pos[] = null;
|
|
try {
|
|
pos = SourcePositionTableReader.makeBytecodeToPositionMap(cr);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
if (pos == null && bcInfo.lineNumberMap != null) {
|
|
pos = SourcePositionTableReader.makeLineNumberToPositionMap(bcInfo.lineNumberMap);
|
|
}
|
|
|
|
if (pos != null) {
|
|
bcInfo.positionMap = new SPos[pos.length];
|
|
for (int i = 0; i < pos.length; i++) {
|
|
Position p = pos[i];
|
|
bcInfo.positionMap[i] = new SPos(p.firstLine, p.lastLine, p.firstCol, p.lastCol);
|
|
}
|
|
}
|
|
/** END Custom change: : precise bytecode positions */
|
|
}
|
|
|
|
@Override
|
|
public String getLocalVariableName(int bcIndex, int localNumber){
|
|
int[][] map = null;
|
|
try {
|
|
map = getBCInfo().localVariableMap;
|
|
} catch (InvalidClassFileException e1) {
|
|
return null;
|
|
}
|
|
|
|
if (localNumber > getMaxLocals()) {
|
|
throw new IllegalArgumentException("illegal local number: " + localNumber + ", method " + getName() + " uses at most "
|
|
+ getMaxLocals());
|
|
}
|
|
|
|
if (map == null) {
|
|
return null;
|
|
} else {
|
|
int[] localPairs = map[bcIndex];
|
|
int localIndex = localNumber * 2;
|
|
if (localPairs == null || localIndex >= localPairs.length) {
|
|
// no information about the specified local at this program point
|
|
return null;
|
|
}
|
|
int nameIndex = localPairs[localIndex];
|
|
if (nameIndex == 0) {
|
|
return null;
|
|
} else {
|
|
try {
|
|
return getClassReader().getCP().getCPUtf8(nameIndex);
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: cache for efficiency?
|
|
*
|
|
* @see com.ibm.wala.classLoader.IMethod#hasLocalVariableTable()
|
|
*/
|
|
@Override
|
|
public boolean hasLocalVariableTable() {
|
|
try {
|
|
ClassReader.AttrIterator iter = new ClassReader.AttrIterator();
|
|
getCodeReader().initAttributeIterator(iter);
|
|
for (; iter.isValid(); iter.advance()) {
|
|
if (iter.getName().equals("LocalVariableTable")) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private ClassReader getClassReader() {
|
|
return ((ShrikeClass) getDeclaringClass()).getReader();
|
|
}
|
|
|
|
private CodeReader getCodeReader() {
|
|
ClassReader.AttrIterator iter = new AttrIterator();
|
|
getClassReader().initMethodAttributeIterator(shrikeMethodIndex, iter);
|
|
|
|
// search for the code attribute
|
|
CodeReader code = null;
|
|
try {
|
|
for (; iter.isValid(); iter.advance()) {
|
|
if (iter.getName().equals("Code")) {
|
|
code = new CodeReader(iter);
|
|
break;
|
|
}
|
|
}
|
|
} catch (InvalidClassFileException e) {
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
return code;
|
|
}
|
|
|
|
private ExceptionsReader getExceptionReader() {
|
|
ClassReader.AttrIterator iter = new AttrIterator();
|
|
getClassReader().initMethodAttributeIterator(shrikeMethodIndex, iter);
|
|
|
|
// search for the desired attribute
|
|
ExceptionsReader result = null;
|
|
try {
|
|
for (; iter.isValid(); iter.advance()) {
|
|
if (iter.getName().equals("Exceptions")) {
|
|
result = new ExceptionsReader(iter);
|
|
break;
|
|
}
|
|
}
|
|
} catch (InvalidClassFileException e) {
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private SignatureReader getSignatureReader() {
|
|
ClassReader.AttrIterator iter = new AttrIterator();
|
|
getClassReader().initMethodAttributeIterator(shrikeMethodIndex, iter);
|
|
|
|
// search for the desired attribute
|
|
SignatureReader result = null;
|
|
try {
|
|
for (; iter.isValid(); iter.advance()) {
|
|
if (iter.getName().equals("Signature")) {
|
|
result = new SignatureReader(iter);
|
|
break;
|
|
}
|
|
}
|
|
} catch (InvalidClassFileException e) {
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private AnnotationsReader getAnnotationsReader(boolean runtimeInvisable) {
|
|
ClassReader.AttrIterator iter = new AttrIterator();
|
|
getClassReader().initMethodAttributeIterator(shrikeMethodIndex, iter);
|
|
|
|
// search for the desired attribute
|
|
AnnotationsReader result = null;
|
|
try {
|
|
for (; iter.isValid(); iter.advance()) {
|
|
if (runtimeInvisable) {
|
|
if (iter.getName().equals(RuntimeInvisibleAnnotationsReader.attrName)) {
|
|
result = new RuntimeInvisibleAnnotationsReader(iter);
|
|
break;
|
|
}
|
|
} else {
|
|
if (iter.getName().equals(RuntimeVisibleAnnotationsReader.attrName)) {
|
|
result = new RuntimeVisibleAnnotationsReader(iter);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (InvalidClassFileException e) {
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private String computeGenericsSignature() throws InvalidClassFileException {
|
|
SignatureReader reader = getSignatureReader();
|
|
if (reader == null) {
|
|
return null;
|
|
} else {
|
|
return reader.getSignature();
|
|
}
|
|
}
|
|
|
|
public TypeReference getReturnType() {
|
|
return getReference().getReturnType();
|
|
}
|
|
|
|
public IClassHierarchy getClassHierarchy() {
|
|
return cha;
|
|
}
|
|
|
|
/**
|
|
* TODO: cache?
|
|
*
|
|
* @return raw "Signature" attribute from the bytecode
|
|
* @throws InvalidClassFileException
|
|
*/
|
|
private String getGenericsSignature() throws InvalidClassFileException {
|
|
return computeGenericsSignature();
|
|
}
|
|
|
|
/**
|
|
* UNDER CONSTRUCTION
|
|
*
|
|
* @throws InvalidClassFileException
|
|
*/
|
|
public MethodTypeSignature getMethodTypeSignature() throws InvalidClassFileException {
|
|
String sig = getGenericsSignature();
|
|
return sig == null ? null : MethodTypeSignature.make(sig);
|
|
}
|
|
|
|
/**
|
|
* read the runtime-invisible annotations from the class file
|
|
*/
|
|
public Collection<Annotation> getRuntimeInvisibleAnnotations() throws InvalidClassFileException, UnimplementedException {
|
|
return getAnnotations(true);
|
|
}
|
|
|
|
/**
|
|
* read the runtime-visible annotations from the class file
|
|
*/
|
|
public Collection<Annotation> getRuntimeVisibleAnnotations() throws InvalidClassFileException, UnimplementedException {
|
|
return getAnnotations(false);
|
|
}
|
|
|
|
public Collection<Annotation> getAnnotations(boolean runtimeInvisable) throws InvalidClassFileException, UnimplementedException {
|
|
AnnotationsReader r = getAnnotationsReader(runtimeInvisable);
|
|
if (r != null) {
|
|
int[] offsets = r.getAnnotationOffsets();
|
|
Collection<Annotation> result = HashSetFactory.make();
|
|
for (int i : offsets) {
|
|
String type = r.getAnnotationType(i);
|
|
type = type.replaceAll(";", "");
|
|
TypeReference t = TypeReference.findOrCreate(getDeclaringClass().getClassLoader().getReference(), type);
|
|
result.add(Annotation.make(t));
|
|
}
|
|
return result;
|
|
} else {
|
|
return Collections.emptySet();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves all runtime-invisible annotations associated with a given index. Returns null if the index is not valid or the
|
|
* annotation contains arrays.
|
|
*/
|
|
public HashMap<String, String> getAnnotations(int index) {
|
|
AnnotationsReader r = getAnnotationsReader(true);
|
|
if (r == null)
|
|
return null;
|
|
int offsets[];
|
|
try {
|
|
offsets = r.getAnnotationOffsets();
|
|
if (offsets.length <= index)
|
|
return null;
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
|
|
int curOffset = offsets[index];
|
|
HashMap<String, String> res = r.getAnnotationValues(curOffset);
|
|
|
|
return res;
|
|
}
|
|
|
|
private static final IndirectionData NO_INDIRECTIONS = new IndirectionData() {
|
|
|
|
private final int[] NOTHING = new int[0];
|
|
|
|
public int[] indirectlyReadLocals(int instructionIndex) {
|
|
return NOTHING;
|
|
}
|
|
|
|
public int[] indirectlyWrittenLocals(int instructionIndex) {
|
|
return NOTHING;
|
|
}
|
|
|
|
};
|
|
|
|
public IndirectionData getIndirectionData() {
|
|
return NO_INDIRECTIONS;
|
|
}
|
|
}
|