WALA/com.ibm.wala.shrike/src/com/ibm/wala/shrikeBT/Util.java

662 lines
19 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.shrikeBT;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import com.ibm.wala.shrikeBT.IInvokeInstruction.Dispatch;
import com.ibm.wala.util.collections.Pair;
/**
* This class contains miscellaneous useful functions.
*
* In the documentation below, we refer to a 'Java class name'. These are formatted according to the rules for Class.forName() and
* Class.getName(). A Java class name must use '$' to separate inner class names from their containing class. There is no way to for
* Shrike to disambiguate 'A.B' otherwise.
*/
public final class Util {
private Util() {
// prevent instantiation
}
/**
* @return the JVM "stack word size" for the given JVM type
* @throws IllegalArgumentException if s is null
*/
public static byte getWordSize(String s) {
if (s == null || s.length() == 0) {
throw new IllegalArgumentException("invalid s: " + s);
}
return getWordSize(s, 0);
}
/**
* @return the JVM "stack word size" for the given JVM type, looking at index 'index'
*/
static byte getWordSize(String s, int index) {
switch (s.charAt(index)) {
case 'V':
return 0;
case 'J':
case 'D':
return 2;
default:
return 1;
}
}
/**
* Computes the character length of the internal JVM type given by s.substring(i).
*/
private static int getTypeLength(String s, int i) {
switch (s.charAt(i)) {
case 'L':
return s.indexOf(';', i) - i + 1;
case '[':
return getTypeLength(s, i + 1) + 1;
default:
return 1;
}
}
/**
* Compute the total number of JVM "stack words" occupied by the method parameters for method signature "type". Any "this"
* parameter is not included.
*
* @throws IllegalArgumentException if type is null
*/
public static int getParamsWordSize(String type) throws IllegalArgumentException {
if (type == null) {
throw new IllegalArgumentException("type is null");
}
int index = 1;
int count = 0;
if (type.indexOf(')', 1) < 0) {
throw new IllegalArgumentException("Invalid method descriptor (missing ')'): " + type);
}
while (type.charAt(index) != ')') {
count += getWordSize(type, index);
index += getTypeLength(type, index);
}
return count;
}
/**
* Convert a fully-qualified Java class name ('.' separated) into an internal JVM type name ('/' separated, starting with 'L' and
* ending with ';').
*
* @throws IllegalArgumentException if c is null
*/
public static String makeType(String c) {
if (c == null) {
throw new IllegalArgumentException("c is null");
}
if (c.startsWith("[")) {
return c.replace('.', '/');
} else if (!c.endsWith(";")) {
return "L" + c.replace('.', '/') + ";";
} else {
return c;
}
}
/**
* Convert a fully-qualified Java type name (either primitive or class name, '.' separated) into an internal JVM type name (one
* letter for primitive and '/' separated, starting with 'L' and ending with ';' for class name).
*/
public static String makeTypeAll(String c) {
String alias = typeAliases.get(c);
if (alias != null) {
return alias;
} else {
return makeType(c);
}
}
/**
* Convert a JVM type name back into a Java class name.
*
* @throws IllegalArgumentException if t is null
*/
public static String makeClass(String t) throws IllegalArgumentException {
if (t == null) {
throw new IllegalArgumentException("t is null");
}
if (t.startsWith("[")) {
return t;
} else if (!t.startsWith("L")) {
throw new IllegalArgumentException(t + " is not a valid class type");
} else {
return t.substring(1, t.length() - 1).replace('/', '.');
}
}
/**
* Convert a JVM type name (either for a primitive or a class name) into a Java type name.
*/
static String makeClassAll(String t) {
String alias = classAliases.get(t);
if (alias != null) {
return alias;
} else {
return makeClass(t);
}
}
final private static HashMap<String, String> classAliases;
final private static HashMap<String, String> typeAliases;
private static void addAlias(String c, String t) {
typeAliases.put(c, t);
classAliases.put(t, c);
}
static {
typeAliases = new HashMap<String, String>();
classAliases = new HashMap<String, String>();
addAlias("void", "V");
addAlias("int", "I");
addAlias("long", "J");
addAlias("float", "F");
addAlias("double", "D");
addAlias("byte", "B");
addAlias("char", "C");
addAlias("short", "S");
addAlias("boolean", "Z");
}
/**
* Compute the JVM type name for an actual Java class. Names such as "int", "void", etc are also converted to their JVM type
* names.
*
* @throws IllegalArgumentException if c is null
*/
public static String makeType(Class<?> c) {
if (c == null) {
throw new IllegalArgumentException("c is null");
}
String name = c.getName();
String alias = typeAliases.get(name);
if (alias != null) {
return alias;
} else {
return makeType(name);
}
}
/**
* Compute the number of parameters given by method signature "type". Any "this" parameter is not included.
*
* @throws IllegalArgumentException if type == null
*/
static int getParamsCount(String type) throws IllegalArgumentException {
if (type == null || type.length() < 2) {
throw new IllegalArgumentException("invalid type: " + type);
}
int index = 1;
int count = 0;
try {
while (type.charAt(index) != ')') {
count++;
index += getTypeLength(type, index);
}
return count;
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException("invalid type: " + type);
}
}
/**
* Extract the types of the parameters given by method signature "type".
*
* @param thisClassType null if the method is static, otherwise the type of "this"
* @return an array of the parameter types in order, including "this" as the first parameter if thisClassType was non-null
* @throws IllegalArgumentException if type == null
*/
public static String[] getParamsTypes(String thisClassType, String type) throws IllegalArgumentException {
if (type == null) {
throw new IllegalArgumentException("type == null");
}
int count = thisClassType != null ? 1 : 0;
String[] r = new String[getParamsCount(type) + count];
int index = 1;
if (thisClassType != null) {
r[0] = thisClassType;
}
while (type.charAt(index) != ')') {
int len = getTypeLength(type, index);
r[count] = type.substring(index, index + len);
count++;
index += len;
}
return r;
}
/**
* Compute the types of the local variables on entry to a method. Similar to "getParamsTypes" except null array entries are
* inserted to account for unused local variables because of 2-word parameter values.
*
* @throws IllegalArgumentException if type == null
*/
public static String[] getParamsTypesInLocals(String thisClassType, String type) throws IllegalArgumentException {
if (type == null) {
throw new IllegalArgumentException("type == null");
}
int count = thisClassType != null ? 1 : 0;
String[] r = new String[getParamsWordSize(type) + count];
int index = 1;
if (thisClassType != null) {
r[0] = thisClassType;
}
while (type.charAt(index) != ')') {
int len = getTypeLength(type, index);
String t = getStackType(type.substring(index, index + len));
r[count] = t;
count += getWordSize(t);
index += len;
}
return r;
}
/**
* Compute the promoted type that the JVM uses to manipulate values of type "t" on its working stack.
*
* @throws IllegalArgumentException if t is null
*/
public static String getStackType(String t) {
if (t == null || t.length() < 1) {
throw new IllegalArgumentException("invalid t: " + t);
}
switch (t.charAt(0)) {
case 'Z':
case 'C':
case 'B':
case 'S':
return "I";
default:
return t;
}
}
/**
* Compute the type "array of t".
*/
public static String makeArray(String t) {
return ("[" + t).intern();
}
/**
* @return true iff t is an array type
*/
public static boolean isArrayType(String t) {
if (t == null || t.length() == 0) {
return false;
} else {
switch (t.charAt(0)) {
case '[':
return true;
default:
return false;
}
}
}
/**
* @return true iff t is a primitive type
*/
public static boolean isPrimitiveType(String t) {
if (t == null || t.length() == 0) {
return false;
} else {
switch (t.charAt(0)) {
case 'L':
case '[':
return false;
default:
return true;
}
}
}
/**
* Get the return type from a method signature.
*
* @throws IllegalArgumentException if s is null
*/
public static String getReturnType(String s) {
if (s == null) {
throw new IllegalArgumentException("s is null");
}
return s.substring(s.lastIndexOf(')') + 1);
}
/**
* General "print an error" routine.
*/
public static void error(String s) {
System.err.println(s);
(new Error("Stack Trace")).printStackTrace();
}
/**
* Given a Java Method, compute the VM-style type signature.
*
* @throws IllegalArgumentException if params == null
*/
public static String computeSignature(Class<?>[] params, Class<?> result) throws IllegalArgumentException {
if (params == null) {
throw new IllegalArgumentException("params == null");
}
StringBuffer buf = new StringBuffer();
buf.append("(");
for (int i = 0; i < params.length; i++) {
buf.append(makeType(params[i]));
}
buf.append(")");
buf.append(makeType(result));
return buf.toString();
}
/**
* Make an Instruction which loads the value of a field, given its name and Java Class. The field type is obtained using
* reflection.
*
* @throws IllegalArgumentException if c is null
*/
public static GetInstruction makeGet(Class<?> c, String name) {
if (c == null) {
throw new IllegalArgumentException("c is null");
}
try {
Field f = c.getField(name);
return GetInstruction.make(makeType(f.getType()), makeType(c), name, (f.getModifiers() & Constants.ACC_STATIC) != 0);
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
/**
* Make an Instruction which stores the value of a field, given its name and Java Class. The field type is obtained using
* reflection.
*
* @throws IllegalArgumentException if c is null
*/
public static PutInstruction makePut(Class<?> c, String name) {
if (c == null) {
throw new IllegalArgumentException("c is null");
}
try {
Field f = c.getField(name);
return PutInstruction.make(makeType(f.getType()), makeType(c), name, (f.getModifiers() & Constants.ACC_STATIC) != 0);
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
private static String makeName(String name, Class<?>[] paramTypes) {
if (paramTypes == null) {
return name;
} else {
StringBuffer buf = new StringBuffer(name);
buf.append("(");
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
buf.append(",");
}
buf.append(paramTypes[i].getName());
}
buf.append(")");
return buf.toString();
}
}
public static Method findMethod(Class<?> c, String name) {
return findMethod(c, name, null);
}
public static Method findMethod(Class<?> c, String name, Class<?>[] paramTypes) {
if (c == null) {
throw new IllegalArgumentException("c is null");
}
Method[] methods = c.getMethods();
Method result = null;
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName().equals(name) && (paramTypes == null || Arrays.equals(m.getParameterTypes(), paramTypes))) {
if (result != null) {
throw new IllegalArgumentException("Method " + makeName(name, paramTypes) + " is ambiguous in class " + c);
}
result = m;
}
}
return result;
}
/**
* Make an Instruction which calls a method, given its name, Java Class, and a list of parameter classes to use for overload
* resolution. Method information is obtained using reflection.
*
* @throws IllegalArgumentException if name is null
*/
public static InvokeInstruction makeInvoke(Class<?> c, String name, Class<?>[] paramTypes) {
if (name == null) {
throw new IllegalArgumentException("name is null");
}
InvokeInstruction result = null;
if (name.equals("<init>")) {
Constructor<?>[] cs = c.getConstructors();
for (int i = 0; i < cs.length; i++) {
Constructor<?> con = cs[i];
if (paramTypes == null || Arrays.equals(con.getParameterTypes(), paramTypes)) {
if (result != null) {
throw new IllegalArgumentException("Constructor " + makeName(name, paramTypes) + " is ambiguous in class " + c);
}
result = InvokeInstruction
.make(computeSignature(con.getParameterTypes(), Void.TYPE), makeType(c), name, Dispatch.SPECIAL);
}
}
} else {
Method m = findMethod(c, name, paramTypes);
if (m != null) {
Dispatch opcode = Dispatch.VIRTUAL;
if ((m.getModifiers() & Constants.ACC_STATIC) != 0) {
opcode = Dispatch.STATIC;
} else if (m.getDeclaringClass().isInterface()) {
opcode = Dispatch.INTERFACE;
}
result = InvokeInstruction.make(computeSignature(m.getParameterTypes(), m.getReturnType()), makeType(c), name, opcode);
}
}
if (result == null) {
throw new IllegalArgumentException("Method " + makeName(name, paramTypes) + " is not present in class " + c);
} else {
return result;
}
}
/**
* Make an Instruction which calls a method, given its name and Java Class. Method information is obtained using reflection. If
* there is more than one method with the given name, an error will be thrown.
*
* @throws IllegalArgumentException if name is null
*/
public static InvokeInstruction makeInvoke(Class<?> c, String name) {
return makeInvoke(c, name, null);
}
/**
* Compute the type index constant (Constants.TYPE_...) from the JVM type. Returns -1 if the type is not one of the predefined
* constants.
*/
static int getTypeIndex(String t) {
if (t == null) {
return -1;
} else {
int len = t.length();
if (len < 1) {
return -1;
} else {
char ch = t.charAt(0);
if (ch < typeIndices.length) {
int r = typeIndices[ch];
if (r != 4) {
if (len > 1) {
return -1;
} else {
return r;
}
} else {
return r;
}
} else {
return -1;
}
}
}
}
private static final byte[] typeIndices = makeTypeIndices();
private static byte[] makeTypeIndices() {
byte[] r = new byte[128];
for (int i = 0; i < r.length; i++) {
r[i] = -1;
}
r['I'] = 0;
r['J'] = 1;
r['F'] = 2;
r['D'] = 3;
r['L'] = 4;
r['['] = 4;
r['B'] = 5;
r['C'] = 6;
r['S'] = 7;
r['Z'] = 8;
return r;
}
public static void readFully(InputStream s, byte[] bytes) throws IllegalArgumentException, IllegalArgumentException, IOException {
if (s == null) {
throw new IllegalArgumentException("s == null");
}
if (bytes == null) {
throw new IllegalArgumentException("bytes == null");
}
int offset = 0;
while (offset < bytes.length) {
int r = s.read(bytes, offset, bytes.length - offset);
if (r < 0) {
throw new IOException("Class truncated");
}
offset += r;
}
}
public static byte[] readFully(InputStream s) throws IOException {
if (s == null) {
throw new IllegalArgumentException("s is null");
}
byte[] bytes = new byte[s.available()];
readFully(s, bytes);
int b = s.read();
if (b < 0) {
return bytes;
} else {
byte[] big = new byte[bytes.length * 2 + 1];
System.arraycopy(bytes, 0, big, 0, bytes.length);
big[bytes.length] = (byte) b;
int offset = bytes.length + 1;
do {
if (big.length == offset) {
// grow array by factor of 2
bytes = new byte[offset * 2];
System.arraycopy(big, 0, bytes, 0, offset);
big = bytes;
}
int r = s.read(big, offset, big.length - offset);
if (r < 0) {
bytes = new byte[offset];
System.arraycopy(big, 0, bytes, 0, offset);
return bytes;
}
offset += r;
} while (true);
}
}
public static Pair<boolean[], boolean[]> computeBasicBlocks(IInstruction[] instructions, ExceptionHandler[][] handlers) {
// Compute r so r[i] == true iff instruction i begins a basic block.
boolean[] r = new boolean[instructions.length];
boolean[] catchers = new boolean[instructions.length];
r[0] = true;
for (int i = 0; i < instructions.length; i++) {
int[] targets = instructions[i].getBranchTargets();
// if there are any targets, then break the basic block here.
// also break the basic block after a return
if (targets.length > 0 || !instructions[i].isFallThrough()) {
if (i + 1 < instructions.length && !r[i + 1]) {
r[i + 1] = true;
}
}
for (int j = 0; j < targets.length; j++) {
if (!r[targets[j]]) {
r[targets[j]] = true;
}
}
if (instructions[i].isPEI()) {
ExceptionHandler[] hs = handlers[i];
// break the basic block here.
if (i + 1 < instructions.length && !r[i + 1]) {
r[i + 1] = true;
}
if (hs != null && hs.length > 0) {
for (int j = 0; j < hs.length; j++) {
// exceptionHandlers.add(hs[j]);
if (!r[hs[j].getHandler()]) {
// we have not discovered the catch block yet.
// form a new basic block
r[hs[j].getHandler()] = true;
}
catchers[hs[j].getHandler()] = true;
}
}
}
}
return Pair.make(r, catchers);
}
}