662 lines
19 KiB
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);
|
|
}
|
|
} |