com.ibm.wala.classLoader.ShrikeBTMethod Maven / Gradle / Ivy
/*
* 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 com.ibm.wala.core.util.bytecode.BytecodeStream;
import com.ibm.wala.core.util.shrike.ShrikeUtil;
import com.ibm.wala.core.util.strings.Atom;
import com.ibm.wala.core.util.strings.ImmutableByteArray;
import com.ibm.wala.shrike.shrikeBT.BytecodeConstants;
import com.ibm.wala.shrike.shrikeBT.Constants;
import com.ibm.wala.shrike.shrikeBT.Decoder;
import com.ibm.wala.shrike.shrikeBT.ExceptionHandler;
import com.ibm.wala.shrike.shrikeBT.IArrayLoadInstruction;
import com.ibm.wala.shrike.shrikeBT.IArrayStoreInstruction;
import com.ibm.wala.shrike.shrikeBT.IGetInstruction;
import com.ibm.wala.shrike.shrikeBT.IInstruction;
import com.ibm.wala.shrike.shrikeBT.IInvokeInstruction;
import com.ibm.wala.shrike.shrikeBT.IPutInstruction;
import com.ibm.wala.shrike.shrikeBT.ITypeTestInstruction;
import com.ibm.wala.shrike.shrikeBT.MonitorInstruction;
import com.ibm.wala.shrike.shrikeBT.NewInstruction;
import com.ibm.wala.shrike.shrikeCT.InvalidClassFileException;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.Descriptor;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.Selector;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/** A wrapper around a Shrike object that represents a method */
public abstract class ShrikeBTMethod implements IMethod, BytecodeConstants {
/** Some verbose progress output? */
private static final boolean verbose = false;
private static int methodsParsed = 0;
/** A wrapper around the declaring class. */
protected final IClass declaringClass;
/** Canonical reference for this method */
private MethodReference methodReference;
// break these out to save some space; they're computed lazily.
protected static class BytecodeInfo {
Decoder decoder;
CallSiteReference[] callSites;
FieldReference[] fieldsWritten;
FieldReference[] fieldsRead;
NewSiteReference[] newSites;
TypeReference[] arraysRead;
TypeReference[] arraysWritten;
TypeReference[] implicitExceptions;
TypeReference[] castTypes;
boolean hasMonitorOp;
/** Mapping from instruction index to program counter. */
private int[] pcMap;
/* BEGIN Custom change: precise positions */
/** Cached map representing position information for bytecode instruction at given index */
protected SourcePosition[] positionMap;
/** Sourcecode positions for method parameters */
protected SourcePosition[] paramPositionMap;
/* END Custom change: precise positions */
/**
* Cached map representing line number information in ShrikeCT format TODO: do more careful
* caching than just soft references
*/
protected int[] lineNumberMap;
/**
* an array mapping bytecode offsets to arrays representing the local variable maps for each
* offset; a local variable map is represented as an array of localVars*2 elements, containing a
* pair (nameIndex, typeIndex) for each local variable; a pair (0,0) indicates there is no
* information for that local variable at that offset
*/
protected int[][] localVariableMap;
/** Exception types this method might throw. Computed on demand. */
private TypeReference[] exceptionTypes;
}
/** Cache the information about the method statements. */
private SoftReference bcInfo;
public ShrikeBTMethod(IClass klass) {
this.declaringClass = klass;
}
protected synchronized BytecodeInfo getBCInfo() throws InvalidClassFileException {
BytecodeInfo result = null;
if (bcInfo != null) {
result = bcInfo.get();
}
if (result == null) {
result = computeBCInfo();
bcInfo = new SoftReference<>(result);
}
return result;
}
/** Return the program counter (bytecode index) for a particular Shrike instruction index. */
public int getBytecodeIndex(int instructionIndex) throws InvalidClassFileException {
return getBCInfo().pcMap[instructionIndex];
}
/**
* Return the Shrike instruction index for a particular valid program counter (bytecode index), or
* -1 if the Shrike instriction index could not be determined.
*
* This ShrikeBTMethod must not be native.
*/
public int getInstructionIndex(int bcIndex) throws InvalidClassFileException {
if (isNative()) {
throw new UnsupportedOperationException(
"getInstructionIndex(int bcIndex) is only supported for non-native bytecode");
}
final BytecodeInfo info = getBCInfo();
if (info.decoder.containsSubroutines()) return -1;
final int[] pcMap = info.pcMap;
assert isSorted(pcMap);
int iindex = Arrays.binarySearch(pcMap, bcIndex);
if (iindex < 0) return -1;
// Unfortunately, pcMap is not always *strictly* sorted: given bcIndex, there may be multiple
// adjacent indices
// i,j such that pcMap[i] == bcIndex == pcMap[j]. We pick the least such index.
while (iindex > 0 && pcMap[iindex - 1] == bcIndex) iindex--;
return iindex;
}
private static boolean isSorted(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
if (a[i + 1] < a[i]) return false;
}
return true;
}
/** Return the number of Shrike instructions for this method. */
public int getNumShrikeInstructions() throws InvalidClassFileException {
return getBCInfo().pcMap.length;
}
public Collection getCallSites() throws InvalidClassFileException {
return isNative() || getBCInfo().callSites == null
? Collections.emptySet()
: Collections.unmodifiableCollection(Arrays.asList(getBCInfo().callSites));
}
public Collection getNewSites() throws InvalidClassFileException {
return (isNative() || getBCInfo().newSites == null)
? Collections.emptySet()
: Collections.unmodifiableCollection(Arrays.asList(getBCInfo().newSites));
}
/**
* @return {@link Set}<{@link TypeReference}>, the exceptions that statements in this method
* may throw,
*/
public Collection getImplicitExceptionTypes() throws InvalidClassFileException {
if (isNative()) {
return Collections.emptySet();
}
return (getBCInfo().implicitExceptions == null)
? Collections.emptyList()
: Arrays.asList(getBCInfo().implicitExceptions);
}
/**
* Do a cheap pass over the bytecodes to collect some mapping information. Some methods require
* this as a pre-req to accessing ShrikeCT information.
*/
private BytecodeInfo computeBCInfo() throws InvalidClassFileException {
BytecodeInfo result = new BytecodeInfo();
result.exceptionTypes = computeDeclaredExceptions();
if (isNative()) {
return result;
}
if (verbose) {
methodsParsed += 1;
if (methodsParsed % 100 == 0) {
System.out.println(methodsParsed + " methods processed...");
}
}
processBytecodesWithShrikeBT(result);
return result;
}
/**
* @return true iff this method has a monitorenter or monitorexit
*/
public boolean hasMonitorOp() throws InvalidClassFileException {
if (isNative()) {
return false;
}
return getBCInfo().hasMonitorOp;
}
/**
* @return Set of FieldReference
*/
public Iterator getFieldsWritten() throws InvalidClassFileException {
if (isNative()) {
return EmptyIterator.instance();
}
if (getBCInfo().fieldsWritten == null) {
return EmptyIterator.instance();
} else {
List l = Arrays.asList(getBCInfo().fieldsWritten);
return l.iterator();
}
}
/**
* @return Iterator of FieldReference
*/
public Iterator getFieldsRead() throws InvalidClassFileException {
if (isNative()) {
return EmptyIterator.instance();
}
if (getBCInfo().fieldsRead == null) {
return EmptyIterator.instance();
} else {
List l = Arrays.asList(getBCInfo().fieldsRead);
return l.iterator();
}
}
/**
* @return Iterator of TypeReference
*/
public Iterator getArraysRead() throws InvalidClassFileException {
if (isNative()) {
return EmptyIterator.instance();
}
return (getBCInfo().arraysRead == null)
? EmptyIterator.instance()
: Arrays.asList(getBCInfo().arraysRead).iterator();
}
/**
* @return Iterator of TypeReference
*/
public Iterator getArraysWritten() throws InvalidClassFileException {
if (isNative()) {
return EmptyIterator.instance();
}
if (getBCInfo().fieldsRead == null) {
return EmptyIterator.instance();
} else {
List list = Arrays.asList(getBCInfo().arraysWritten);
return list.iterator();
}
}
/**
* @return Iterator of TypeReference
*/
public Iterator getCastTypes() throws InvalidClassFileException {
if (isNative()) {
return EmptyIterator.instance();
}
return (getBCInfo().castTypes == null)
? EmptyIterator.instance()
: Arrays.asList(getBCInfo().castTypes).iterator();
}
protected abstract byte[] getBytecodes();
/**
* Method getBytecodeStream.
*
* @return the bytecode stream for this method, or null if no bytecodes.
*/
public BytecodeStream getBytecodeStream() {
byte[] bytecodes = getBytecodes();
if (bytecodes == null) {
return null;
} else {
return new BytecodeStream(this, bytecodes);
}
}
protected abstract String getMethodName() throws InvalidClassFileException;
protected abstract String getMethodSignature() throws InvalidClassFileException;
private MethodReference computeMethodReference() {
try {
Atom name = Atom.findOrCreateUnicodeAtom(getMethodName());
ImmutableByteArray desc = ImmutableByteArray.make(getMethodSignature());
Descriptor D = Descriptor.findOrCreate(declaringClass.getClassLoader().getLanguage(), desc);
return MethodReference.findOrCreate(declaringClass.getReference(), name, D);
} catch (InvalidClassFileException e) {
Assertions.UNREACHABLE();
return null;
}
}
@Override
public MethodReference getReference() {
if (methodReference == null) {
methodReference = computeMethodReference();
}
return methodReference;
}
@Override
public boolean isClinit() {
return getReference().getSelector().equals(MethodReference.clinitSelector);
}
@Override
public boolean isInit() {
return getReference().getName().equals(MethodReference.initAtom);
}
protected abstract int getModifiers();
@Override
public boolean isNative() {
return ((getModifiers() & Constants.ACC_NATIVE) != 0);
}
@Override
public boolean isAbstract() {
return ((getModifiers() & Constants.ACC_ABSTRACT) != 0);
}
@Override
public boolean isPrivate() {
return ((getModifiers() & Constants.ACC_PRIVATE) != 0);
}
@Override
public boolean isProtected() {
return ((getModifiers() & Constants.ACC_PROTECTED) != 0);
}
@Override
public boolean isPublic() {
return ((getModifiers() & Constants.ACC_PUBLIC) != 0);
}
@Override
public boolean isFinal() {
return ((getModifiers() & Constants.ACC_FINAL) != 0);
}
@Override
public boolean isBridge() {
return ((getModifiers() & Constants.ACC_VOLATILE) != 0);
}
@Override
public boolean isSynchronized() {
return ((getModifiers() & Constants.ACC_SYNCHRONIZED) != 0);
}
@Override
public boolean isStatic() {
return ((getModifiers() & Constants.ACC_STATIC) != 0);
}
@Override
public boolean isSynthetic() {
return ((getModifiers() & Constants.ACC_SYNTHETIC) != 0);
}
@Override
public boolean isAnnotation() {
return ((getModifiers() & Constants.ACC_ANNOTATION) != 0);
}
@Override
public boolean isEnum() {
return ((getModifiers() & Constants.ACC_ENUM) != 0);
}
@Override
public boolean isModule() {
return ((getModifiers() & Constants.ACC_MODULE) != 0);
}
@Override
public boolean isWalaSynthetic() {
return false;
}
@Override
public IClass getDeclaringClass() {
return declaringClass;
}
/**
* Find the decoder object for this method, or create one if necessary.
*
* @return null if the method has no code.
*/
protected abstract Decoder makeDecoder();
/** Walk through the bytecodes and collect trivial information. */
protected abstract void processDebugInfo(BytecodeInfo bcInfo) throws InvalidClassFileException;
private void processBytecodesWithShrikeBT(BytecodeInfo info) throws InvalidClassFileException {
info.decoder = makeDecoder();
if (!isAbstract() && info.decoder == null) {
throw new InvalidClassFileException(
-1, "non-abstract method " + getReference() + " has no bytecodes");
}
if (info.decoder == null) {
return;
}
info.pcMap = info.decoder.getInstructionsToBytecodes();
processDebugInfo(info);
SimpleVisitor simpleVisitor = new SimpleVisitor(info);
BytecodeLanguage lang = (BytecodeLanguage) getDeclaringClass().getClassLoader().getLanguage();
IInstruction[] instructions = info.decoder.getInstructions();
for (int i = 0; i < instructions.length; i++) {
simpleVisitor.setInstructionIndex(i);
instructions[i].visit(simpleVisitor);
if (instructions[i].isPEI()) {
Collection t = lang.getImplicitExceptionTypes(instructions[i]);
if (t != null) {
simpleVisitor.implicitExceptions.addAll(t);
}
}
}
// copy the Set results into arrays; will use less
// storage
copyVisitorSetsToArrays(simpleVisitor, info);
}
private static void copyVisitorSetsToArrays(SimpleVisitor simpleVisitor, BytecodeInfo info) {
info.newSites = new NewSiteReference[simpleVisitor.newSites.size()];
int i = 0;
for (NewSiteReference newSiteReference : simpleVisitor.newSites) {
info.newSites[i++] = newSiteReference;
}
info.fieldsRead = new FieldReference[simpleVisitor.fieldsRead.size()];
i = 0;
for (FieldReference fieldReference : simpleVisitor.fieldsRead) {
info.fieldsRead[i++] = fieldReference;
}
info.fieldsRead = new FieldReference[simpleVisitor.fieldsRead.size()];
i = 0;
for (FieldReference fieldReference : simpleVisitor.fieldsRead) {
info.fieldsRead[i++] = fieldReference;
}
info.fieldsWritten = new FieldReference[simpleVisitor.fieldsWritten.size()];
i = 0;
for (FieldReference fieldReference : simpleVisitor.fieldsWritten) {
info.fieldsWritten[i++] = fieldReference;
}
info.callSites = new CallSiteReference[simpleVisitor.callSites.size()];
i = 0;
for (CallSiteReference callSiteReference : simpleVisitor.callSites) {
info.callSites[i++] = callSiteReference;
}
info.arraysRead = new TypeReference[simpleVisitor.arraysRead.size()];
i = 0;
for (TypeReference typeReference : simpleVisitor.arraysRead) {
info.arraysRead[i++] = typeReference;
}
info.arraysWritten = new TypeReference[simpleVisitor.arraysWritten.size()];
i = 0;
for (TypeReference typeReference : simpleVisitor.arraysWritten) {
info.arraysWritten[i++] = typeReference;
}
info.implicitExceptions = new TypeReference[simpleVisitor.implicitExceptions.size()];
i = 0;
for (TypeReference typeReference : simpleVisitor.implicitExceptions) {
info.implicitExceptions[i++] = typeReference;
}
info.castTypes = new TypeReference[simpleVisitor.castTypes.size()];
i = 0;
for (TypeReference typeReference : simpleVisitor.castTypes) {
info.castTypes[i++] = typeReference;
}
info.hasMonitorOp = simpleVisitor.hasMonitorOp;
}
@Override
public String toString() {
return getReference().toString();
}
@Override
public boolean equals(Object obj) {
// instanceof is OK because this class is final.
// if (this.getClass().equals(obj.getClass())) {
if (obj instanceof ShrikeBTMethod) {
ShrikeBTMethod that = (ShrikeBTMethod) obj;
return (getDeclaringClass().equals(that.getDeclaringClass())
&& getReference().equals(that.getReference()));
} else {
return false;
}
}
@Override
public int hashCode() {
return 9661 * getReference().hashCode();
}
/**
* @see com.ibm.wala.classLoader.SyntheticMethod#getMaxLocals()
*/
public abstract int getMaxLocals();
// TODO: ShrikeBT should have a getMaxStack method on Decoder, I think.
public abstract int getMaxStackHeight();
@Override
public Atom getName() {
return getReference().getName();
}
@Override
public Descriptor getDescriptor() {
return getReference().getDescriptor();
}
/** A visitor used to process bytecodes */
private class SimpleVisitor extends IInstruction.Visitor {
private final BytecodeInfo info;
public SimpleVisitor(BytecodeInfo info) {
this.info = info;
}
// TODO: make a better Set implementation for these.
final Set callSites = HashSetFactory.make(5);
final Set fieldsWritten = HashSetFactory.make(5);
final Set fieldsRead = HashSetFactory.make(5);
final Set newSites = HashSetFactory.make(5);
final Set arraysRead = HashSetFactory.make(5);
final Set arraysWritten = HashSetFactory.make(5);
final Set implicitExceptions = HashSetFactory.make(5);
final Set castTypes = HashSetFactory.make(5);
boolean hasMonitorOp;
private int instructionIndex;
public void setInstructionIndex(int i) {
instructionIndex = i;
}
public int getProgramCounter() {
return info.pcMap[instructionIndex];
}
@Override
public void visitMonitor(MonitorInstruction instruction) {
hasMonitorOp = true;
}
@Override
public void visitNew(NewInstruction instruction) {
ClassLoaderReference loader = getReference().getDeclaringClass().getClassLoader();
TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
newSites.add(NewSiteReference.make(getProgramCounter(), t));
}
@Override
public void visitGet(IGetInstruction instruction) {
ClassLoaderReference loader = getReference().getDeclaringClass().getClassLoader();
FieldReference f =
FieldReference.findOrCreate(
loader,
instruction.getClassType(),
instruction.getFieldName(),
instruction.getFieldType());
fieldsRead.add(f);
}
@Override
public void visitPut(IPutInstruction instruction) {
ClassLoaderReference loader = getReference().getDeclaringClass().getClassLoader();
FieldReference f =
FieldReference.findOrCreate(
loader,
instruction.getClassType(),
instruction.getFieldName(),
instruction.getFieldType());
fieldsWritten.add(f);
}
@Override
public void visitInvoke(IInvokeInstruction instruction) {
IClassLoader loader = getDeclaringClass().getClassLoader();
MethodReference m =
MethodReference.findOrCreate(
loader.getLanguage(),
loader.getReference(),
instruction.getClassType(),
instruction.getMethodName(),
instruction.getMethodSignature());
int programCounter = getProgramCounter();
CallSiteReference site =
CallSiteReference.make(programCounter, m, instruction.getInvocationCode());
callSites.add(site);
}
@Override
public void visitArrayLoad(IArrayLoadInstruction instruction) {
arraysRead.add(
ShrikeUtil.makeTypeReference(
getDeclaringClass().getClassLoader().getReference(), instruction.getType()));
}
@Override
public void visitArrayStore(IArrayStoreInstruction instruction) {
arraysWritten.add(
ShrikeUtil.makeTypeReference(
getDeclaringClass().getClassLoader().getReference(), instruction.getType()));
}
@Override
public void visitCheckCast(ITypeTestInstruction instruction) {
for (String t : instruction.getTypes()) {
castTypes.add(
ShrikeUtil.makeTypeReference(getDeclaringClass().getClassLoader().getReference(), t));
}
}
}
/** */
public IInstruction[] getInstructions() throws InvalidClassFileException {
if (getBCInfo().decoder == null) {
return null;
} else {
return getBCInfo().decoder.getInstructions();
}
}
public ExceptionHandler[][] getHandlers() throws InvalidClassFileException {
if (getBCInfo().decoder == null) {
return null;
} else {
return getBCInfo().decoder.getHandlers();
}
}
/** By convention, for a non-static method, getParameterType(0) is the this pointer */
@Override
public TypeReference getParameterType(int i) {
if (!isStatic()) {
if (i == 0) {
return declaringClass.getReference();
} else {
return getReference().getParameterType(i - 1);
}
} else {
return getReference().getParameterType(i);
}
}
/**
* Method getNumberOfParameters. This result includes the "this" pointer if applicable
*
* @return int
*/
@Override
public int getNumberOfParameters() {
if (isStatic() || isClinit()) {
return getReference().getNumberOfParameters();
} else {
return getReference().getNumberOfParameters() + 1;
}
}
@Override
public abstract boolean hasExceptionHandler();
/** Clients should not modify the returned array. TODO: clone to avoid the problem? */
@Override
public TypeReference[] getDeclaredExceptions() throws InvalidClassFileException {
return (getBCInfo().exceptionTypes == null) ? new TypeReference[0] : getBCInfo().exceptionTypes;
}
protected abstract String[] getDeclaredExceptionTypeNames() throws InvalidClassFileException;
/**
* @see com.ibm.wala.classLoader.IMethod#getDeclaredExceptions()
*/
private TypeReference[] computeDeclaredExceptions() {
try {
String[] strings = getDeclaredExceptionTypeNames();
if (strings == null) return null;
ClassLoaderReference loader = getDeclaringClass().getClassLoader().getReference();
TypeReference[] result = new TypeReference[strings.length];
Arrays.setAll(
result,
i ->
TypeReference.findOrCreate(
loader, TypeName.findOrCreate(ImmutableByteArray.make('L' + strings[i]))));
return result;
} catch (InvalidClassFileException e) {
Assertions.UNREACHABLE();
return null;
}
}
/* BEGIN Custom change: precise bytecode positions */
@Override
public SourcePosition getSourcePosition(int bcIndex) throws InvalidClassFileException {
return (getBCInfo().positionMap == null) ? null : getBCInfo().positionMap[bcIndex];
}
@Override
public SourcePosition getParameterSourcePosition(int paramNum) throws InvalidClassFileException {
return (getBCInfo().paramPositionMap == null) ? null : getBCInfo().paramPositionMap[paramNum];
}
/* END Custom change: precise bytecode positions */
@Override
public int getLineNumber(int bcIndex) {
try {
return (getBCInfo().lineNumberMap == null) ? -1 : getBCInfo().lineNumberMap[bcIndex];
} catch (InvalidClassFileException e) {
return -1;
}
}
/**
* @return {@link Set}<{@link TypeReference}>
*/
public Set getCaughtExceptionTypes() throws InvalidClassFileException {
ExceptionHandler[][] handlers = getHandlers();
if (handlers == null) {
return Collections.emptySet();
}
HashSet result = HashSetFactory.make(10);
ClassLoaderReference loader = getReference().getDeclaringClass().getClassLoader();
for (ExceptionHandler[] handler : handlers) {
for (ExceptionHandler element : handler) {
TypeReference t = ShrikeUtil.makeTypeReference(loader, element.getCatchClass());
if (t == null) {
t = TypeReference.JavaLangThrowable;
}
result.add(t);
}
}
return result;
}
@Override
public String getSignature() {
return getReference().getSignature();
}
@Override
public Selector getSelector() {
return getReference().getSelector();
}
@Override
public abstract String getLocalVariableName(int bcIndex, int localNumber);
/*
* TODO: cache for efficiency?
*
* @see com.ibm.wala.classLoader.IMethod#hasLocalVariableTable()
*/
@Override
public abstract boolean hasLocalVariableTable();
/** Clear all optional cached data associated with this class. */
public void clearCaches() {
bcInfo = null;
}
}