
com.ibm.wala.shrike.shrikeBT.Util Maven / Gradle / Ivy
Show all versions of com.ibm.wala.shrike Show documentation
/*
* 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.shrike.shrikeBT;
import com.ibm.wala.shrike.shrikeBT.IInvokeInstruction.Dispatch;
import com.ibm.wala.util.collections.Pair;
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;
/**
* 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.isEmpty()) {
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);
}
}
private static final HashMap classAliases;
private static final HashMap typeAliases;
private static void addAlias(String c, String t) {
typeAliases.put(c, t);
classAliases.put(t, c);
}
static {
typeAliases = new HashMap<>();
classAliases = new HashMap<>();
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
*/
public 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, e);
}
}
/**
* 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.isEmpty()) {
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.isEmpty()) {
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.isEmpty()) {
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");
}
StringBuilder buf = new StringBuilder();
buf.append('(');
for (Class> param : params) {
buf.append(makeType(param));
}
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 | 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 | NoSuchFieldException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
private static String makeName(String name, Class>[] paramTypes) {
if (paramTypes == null) {
return name;
} else {
StringBuilder buf = new StringBuilder(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 (Method m : methods) {
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("")) {
Constructor>[] cs = c.getConstructors();
for (Constructor> con : cs) {
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];
Arrays.fill(r, (byte) -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 = Arrays.copyOf(bytes, bytes.length * 2 + 1);
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 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 target : targets) {
if (!r[target]) {
r[target] = 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) {
for (ExceptionHandler h : hs) {
// exceptionHandlers.add(hs[j]);
if (!r[h.getHandler()]) {
// we have not discovered the catch block yet.
// form a new basic block
r[h.getHandler()] = true;
}
catchers[h.getHandler()] = true;
}
}
}
}
return Pair.make(r, catchers);
}
}