/*******************************************************************************
* 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.shrikeBT;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
/**
* This class is a container for a bunch of information that we might know about a method. It's here for convenience so users can
* just pass one MethodData around instead of passing around an array of instructions, an array of exception handler lists, etc.
*
* It also provides a place to hang annotations. We provide a table mapping abstract "keys" to annotation objects. We also provide
* an invalidation protocol so that the annotation objects can be notified of code changes and react appropriately. This is useful
* for caching analysis results for a method.
*/
public final class MethodData {
final private HashMap<Object, Results> map = new HashMap<Object, Results>();
final private int access;
final private String classType;
final private String name;
final private String signature;
private IInstruction[] instructions;
private ExceptionHandler[][] handlers;
private int[] instructionsToBytecodes;
private boolean hasChanged = false;
/**
* Create information for a method, with no exception handlers and a dummy mapping of instructions to original bytecodes.
* @param access the access flags
* @param classType the class in which the method is defined, in JVM type format (e.g., Ljava/lang/Object;)
* @param name the method name
* @param signature the method signature, in JVM type format (e.g., (ILjava/lang/Object;)V)
* @param instructions the instructions making up the method
*/
public static MethodData makeWithDefaultHandlersAndInstToBytecodes(int access, String classType, String name, String signature, IInstruction[] instructions) {
ExceptionHandler[][] handlers = new ExceptionHandler[instructions.length][];
Arrays.fill(handlers, new ExceptionHandler[0]);
int[] i2b = new int[instructions.length];
for (int i = 0; i < i2b.length; i++) {
i2b[i] = i;
}
return new MethodData(access, classType, name, signature, instructions, handlers, i2b);
}
/**
* Gather the information for a method "from scratch".
*
* @param access the access flags
* @param classType the class in which the method is defined, in JVM type format (e.g., Ljava/lang/Object;)
* @param name the method name
* @param signature the method signature, in JVM type format (e.g., (ILjava/lang/Object;)V)
* @param instructions the instructions making up the method
* @param handlers a list of exception handlers for each instruction
* @param instructionsToBytecodes a map stating, for each instruction, the offset of the original bytecode instruction(s) giving
* rise to this instruction
*/
public MethodData(int access, String classType, String name, String signature, IInstruction[] instructions,
ExceptionHandler[][] handlers, int[] instructionsToBytecodes) {
this.classType = classType;
this.access = access;
this.name = name;
this.signature = signature;
this.instructions = instructions;
this.handlers = handlers;
this.instructionsToBytecodes = instructionsToBytecodes;
if (instructions == null) {
throw new IllegalArgumentException("Instruction array cannot be null");
}
if (handlers == null) {
throw new IllegalArgumentException("Handler array cannot be null");
}
if (instructionsToBytecodes == null) {
throw new IllegalArgumentException("InstructionToBytecodes array cannot be null");
}
if (instructions.length != handlers.length) {
throw new IllegalArgumentException("Handlers array must be the same length as the instructions");
}
if (instructions.length != instructionsToBytecodes.length) {
throw new IllegalArgumentException("Bytecode map array must be the same length as the instructions");
}
}
/**
* Gather the information for a method after it has been decoded.
*
* @param d the decoder which has decoded the method
* @param access the access flags
* @param classType the class in which the method is defined, in JVM type format (e.g., Ljava/lang/Object;)
* @param name the method name
* @param signature the method signature, in JVM type format (e.g., (ILjava/lang/Object;)V)
* @throws NullPointerException if d is null
*/
public MethodData(Decoder d, int access, String classType, String name, String signature) throws NullPointerException {
this(access, classType, name, signature, d.getInstructions(), d.getHandlers(), d.getInstructionsToBytecodes());
}
public void setHasChanged() {
hasChanged = true;
}
/**
* @return the method signature, in JVM format
*/
public String getSignature() {
return signature;
}
/**
* @return the method name
*/
public String getName() {
return name;
}
/**
* @return the method access flags
*/
public int getAccess() {
return access;
}
/**
* @return the JVM type for the class defining the method (e.g., Ljava/lang/Object;)
*/
public String getClassType() {
return classType;
}
/**
* @return whether or not the method is static
*/
public boolean getIsStatic() {
return (access & Constants.ACC_STATIC) != 0;
}
/**
* @return whether or not the method is synchronized
*/
public boolean getIsSynchronized() {
return (access & Constants.ACC_SYNCHRONIZED) != 0;
}
/**
* @return the exception handler lists
*/
public ExceptionHandler[][] getHandlers() {
return handlers;
}
/**
* @return the instruction array
*/
public IInstruction[] getInstructions() {
return instructions;
}
/**
* @return the map from instructions to bytecode offsets
*/
public int[] getInstructionsToBytecodes() {
return instructionsToBytecodes;
}
/**
* Annotation objects implement this Results interface. The Results interface is used to notify an annotation that the method code
* has been updated.
*/
public static interface Results {
/**
* This method is called just before the code for a method changes. The existing instructions, handlers, etc can be read from
* the current info.
*
* @param info the method data this annotation is attached to
* @param newInstructions the instructions the method will change to
* @param newHandlers the handler lists the method will change to
* @param newInstructionMap the instructions-to-bytecodes map the method will change to
* @return true to remove the object from the info set, for example because the annotation is now invalid
*/
public boolean notifyUpdate(MethodData info, IInstruction[] newInstructions, ExceptionHandler[][] newHandlers,
int[] newInstructionMap);
}
/**
* Get the annotation for the given key.
*
* @return the annotation or null if there isn't one
*/
public Results getInfo(Object key) {
return map.get(key);
}
/**
* Set the annotation for the given key.
*/
public void putInfo(Object key, Results value) {
map.put(key, value);
}
void update(IInstruction[] instructions, ExceptionHandler[][] handlers, int[] newInstructionMap, int[] instructionsToBytecodes) {
for (Iterator<Object> i = map.keySet().iterator(); i.hasNext();) {
Object key = i.next();
Results r = map.get(key);
if (r.notifyUpdate(this, instructions, handlers, newInstructionMap)) {
i.remove();
}
}
this.instructions = instructions;
this.handlers = handlers;
this.instructionsToBytecodes = instructionsToBytecodes;
hasChanged = true;
}
/**
* @return true iff the code has been updated at least once
*/
public boolean getHasChanged() {
return hasChanged;
}
@Override
public String toString() {
return getClassType() + "." + getName() + getSignature();
}
}