/* Soot - a J*va Optimization Framework
* Copyright (C) 1997-2000 Etienne Gagnon. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the Sable Research Group and others 1997-1999.
* See the 'credits' file distributed with Soot for the complete list of
* contributors. (Soot is distributed at http://www.sable.mcgill.ca/soot)
*/
package soot.jimple.toolkits.typing;
import soot.*;
import soot.jimple.*;
import soot.options.Options;
import java.util.*;
import soot.toolkits.graph.*;
import soot.toolkits.scalar.*;
import java.io.*;
/**
* This class resolves the type of local variables.
*
* <b>NOTE:</b> This class has been superseded by {@link soot.jimple.toolkits.typing.fast.TypeResolver}.
**/
public class TypeResolver
{
/** Reference to the class hierarchy **/
private final ClassHierarchy hierarchy;
/** All type variable instances **/
private final List<TypeVariable> typeVariableList = new ArrayList<TypeVariable>();
/** Hashtable: [TypeNode or Local] -> TypeVariable **/
private final Map<Object, TypeVariable> typeVariableMap = new HashMap<Object, TypeVariable>();
private final JimpleBody stmtBody;
final TypeNode NULL;
private final TypeNode OBJECT;
private static final boolean DEBUG = false;
// categories for type variables (solved = hard, unsolved = soft)
private List<TypeVariable> unsolved;
private List<TypeVariable> solved;
// parent categories for unsolved type variables
private List<TypeVariable> single_soft_parent;
private List<TypeVariable> single_hard_parent;
private List<TypeVariable> multiple_parents;
// child categories for unsolved type variables
private List<TypeVariable> single_child_not_null;
private List<TypeVariable> single_null_child;
private List<TypeVariable> multiple_children;
public ClassHierarchy hierarchy()
{
return hierarchy;
}
public TypeNode typeNode(Type type)
{
return hierarchy.typeNode(type);
}
/** Get type variable for the given local. **/
TypeVariable typeVariable(Local local)
{
TypeVariable result = typeVariableMap.get(local);
if(result == null)
{
int id = typeVariableList.size();
typeVariableList.add(null);
result = new TypeVariable(id, this);
typeVariableList.set(id, result);
typeVariableMap.put(local, result);
if(DEBUG)
{
G.v().out.println("[LOCAL VARIABLE \"" + local + "\" -> " + id + "]");
}
}
return result;
}
/** Get type variable for the given type node. **/
public TypeVariable typeVariable(TypeNode typeNode)
{
TypeVariable result = typeVariableMap.get(typeNode);
if(result == null)
{
int id = typeVariableList.size();
typeVariableList.add(null);
result = new TypeVariable(id, this, typeNode);
typeVariableList.set(id, result);
typeVariableMap.put(typeNode, result);
}
return result;
}
/** Get type variable for the given soot class. **/
public TypeVariable typeVariable(SootClass sootClass)
{
return typeVariable(hierarchy.typeNode(sootClass.getType()));
}
/** Get type variable for the given type. **/
public TypeVariable typeVariable(Type type)
{
return typeVariable(hierarchy.typeNode(type));
}
/** Get new type variable **/
public TypeVariable typeVariable()
{
int id = typeVariableList.size();
typeVariableList.add(null);
TypeVariable result = new TypeVariable(id, this);
typeVariableList.set(id, result);
return result;
}
private TypeResolver(JimpleBody stmtBody, Scene scene)
{
this.stmtBody = stmtBody;
hierarchy = ClassHierarchy.classHierarchy(scene);
OBJECT = hierarchy.OBJECT;
NULL = hierarchy.NULL;
typeVariable(OBJECT);
typeVariable(NULL);
// hack for J2ME library, reported by Stephen Cheng
if (!Options.v().j2me()) {
typeVariable(hierarchy.CLONEABLE);
typeVariable(hierarchy.SERIALIZABLE);
}
}
public static void resolve(JimpleBody stmtBody, Scene scene) {
if (DEBUG) {
G.v().out.println(stmtBody.getMethod());
}
try {
TypeResolver resolver = new TypeResolver(stmtBody, scene);
resolver.resolve_step_1();
} catch (TypeException e1) {
if (DEBUG) {
e1.printStackTrace();
G.v().out.println("Step 1 Exception-->" + e1.getMessage());
}
try {
TypeResolver resolver = new TypeResolver(stmtBody, scene);
resolver.resolve_step_2();
} catch (TypeException e2) {
if (DEBUG) {
e2.printStackTrace();
G.v().out.println("Step 2 Exception-->" + e2.getMessage());
}
try {
TypeResolver resolver = new TypeResolver(stmtBody, scene);
resolver.resolve_step_3();
} catch (TypeException e3) {
StringWriter st = new StringWriter();
PrintWriter pw = new PrintWriter(st);
e3.printStackTrace(pw);
pw.close();
throw new RuntimeException(st.toString());
}
}
}
soot.jimple.toolkits.typing.integer.TypeResolver.resolve(stmtBody);
}
private void debug_vars(String message)
{
if(DEBUG)
{
int count = 0;
G.v().out.println("**** START:" + message);
for (TypeVariable var : typeVariableList) {
G.v().out.println(count++ + " " + var);
}
G.v().out.println("**** END:" + message);
}
}
private void debug_body()
{
if(DEBUG)
{
G.v().out.println("-- Body Start --");
for( Iterator stmtIt = stmtBody.getUnits().iterator(); stmtIt.hasNext(); ) {
final Stmt stmt = (Stmt) stmtIt.next();
G.v().out.println(stmt);
}
G.v().out.println("-- Body End --");
}
}
private void resolve_step_1() throws TypeException
{
// remove_spurious_locals();
collect_constraints_1_2();
debug_vars("constraints");
compute_array_depth();
propagate_array_constraints();
debug_vars("arrays");
merge_primitive_types();
debug_vars("primitive");
merge_connected_components();
debug_vars("components");
remove_transitive_constraints();
debug_vars("transitive");
merge_single_constraints();
debug_vars("single");
assign_types_1_2();
debug_vars("assign");
check_constraints();
}
private void resolve_step_2() throws TypeException
{
debug_body();
split_new();
debug_body();
collect_constraints_1_2();
debug_vars("constraints");
compute_array_depth();
propagate_array_constraints();
debug_vars("arrays");
merge_primitive_types();
debug_vars("primitive");
merge_connected_components();
debug_vars("components");
remove_transitive_constraints();
debug_vars("transitive");
merge_single_constraints();
debug_vars("single");
assign_types_1_2();
debug_vars("assign");
check_constraints();
}
private void resolve_step_3() throws TypeException
{
collect_constraints_3();
compute_approximate_types();
assign_types_3();
check_and_fix_constraints();
}
private void collect_constraints_1_2()
{
ConstraintCollector collector = new ConstraintCollector(this, true);
for( Iterator stmtIt = stmtBody.getUnits().iterator(); stmtIt.hasNext(); ) {
final Stmt stmt = (Stmt) stmtIt.next();
if(DEBUG)
{
G.v().out.print("stmt: ");
}
collector.collect(stmt, stmtBody);
if(DEBUG)
{
G.v().out.println(stmt);
}
}
}
private void collect_constraints_3()
{
ConstraintCollector collector = new ConstraintCollector(this, false);
for( Iterator stmtIt = stmtBody.getUnits().iterator(); stmtIt.hasNext(); ) {
final Stmt stmt = (Stmt) stmtIt.next();
if(DEBUG)
{
G.v().out.print("stmt: ");
}
collector.collect(stmt, stmtBody);
if(DEBUG)
{
G.v().out.println(stmt);
}
}
}
private void compute_array_depth() throws TypeException
{
compute_approximate_types();
TypeVariable[] vars = new TypeVariable[typeVariableList.size()];
vars = typeVariableList.toArray(vars);
for (TypeVariable element : vars) {
element.fixDepth();
}
}
private void propagate_array_constraints()
{
// find max depth
int max = 0;
for (TypeVariable var : typeVariableList) {
int depth = var.depth();
if(depth > max)
{
max = depth;
}
}
if(max > 1) {
// hack for J2ME library, reported by Stephen Cheng
if (!Options.v().j2me()) {
typeVariable(ArrayType.v(RefType.v("java.lang.Cloneable"), max - 1));
typeVariable(ArrayType.v(RefType.v("java.io.Serializable"), max - 1));
}
}
// create lists for each array depth
LinkedList[] lists = new LinkedList[max + 1];
for(int i = 0; i <= max; i++)
{
lists[i] = new LinkedList();
}
for (TypeVariable var : typeVariableList) {
int depth = var.depth();
lists[depth].add(var);
}
// propagate constraints, starting with highest depth
for(int i = max; i >= 0; i--)
{
for (TypeVariable var : typeVariableList) {
var.propagate();
}
}
}
private void merge_primitive_types() throws TypeException
{
// merge primitive types with all parents/children
compute_solved();
Iterator<TypeVariable> varIt = solved.iterator();
while( varIt.hasNext() ) {
TypeVariable var = varIt.next();
if(var.type().type() instanceof IntType ||
var.type().type() instanceof LongType ||
var.type().type() instanceof FloatType ||
var.type().type() instanceof DoubleType)
{
List<TypeVariable> parents;
List<TypeVariable> children;
boolean finished;
do
{
finished = true;
parents = var.parents();
if(parents.size() != 0)
{
finished = false;
for (TypeVariable parent : parents) {
if(DEBUG)
{
G.v().out.print(".");
}
var = var.union(parent);
}
}
children = var.children();
if(children.size() != 0)
{
finished = false;
for (TypeVariable child : children) {
if(DEBUG)
{
G.v().out.print(".");
}
var = var.union(child);
}
}
}
while(!finished);
}
}
}
private void merge_connected_components() throws TypeException
{
refresh_solved();
List<TypeVariable> list = new LinkedList<TypeVariable>();
list.addAll(solved);
list.addAll(unsolved);
StronglyConnectedComponents.merge(list);
}
private void remove_transitive_constraints() throws TypeException
{
refresh_solved();
List<TypeVariable> list = new LinkedList<TypeVariable>();
list.addAll(solved);
list.addAll(unsolved);
for (TypeVariable var : list) {
var.removeIndirectRelations();
}
}
private void merge_single_constraints() throws TypeException
{
boolean finished = false;
boolean modified = false;
while(true)
{
categorize();
if(single_child_not_null.size() != 0)
{
finished = false;
modified = true;
Iterator<TypeVariable> i = single_child_not_null.iterator();
while( i.hasNext() ) {
TypeVariable var = i.next();
if(single_child_not_null.contains(var))
{
TypeVariable child = var.children().get(0);
var = var.union(child);
}
}
}
if(finished)
{
if(single_soft_parent.size() != 0)
{
finished = false;
modified = true;
Iterator<TypeVariable> i = single_soft_parent.iterator();
while( i.hasNext() ) {
TypeVariable var = i.next();
if(single_soft_parent.contains(var))
{
TypeVariable parent = var.parents().get(0);
var = var.union(parent);
}
}
}
if(single_hard_parent.size() != 0)
{
finished = false;
modified = true;
Iterator<TypeVariable> i = single_hard_parent.iterator();
while( i.hasNext() ) {
TypeVariable var = i.next();
if(single_hard_parent.contains(var))
{
TypeVariable parent = var.parents().get(0);
debug_vars("union single parent\n " + var + "\n " + parent);
var = var.union(parent);
}
}
}
if(single_null_child.size() != 0)
{
finished = false;
modified = true;
Iterator<TypeVariable> i = single_null_child.iterator();
while( i.hasNext() ) {
TypeVariable var = i.next();
if(single_null_child.contains(var))
{
TypeVariable child = var.children().get(0);
var = var.union(child);
}
}
}
if(finished)
{
break;
}
continue;
}
if(modified)
{
modified = false;
continue;
}
finished = true;
multiple_children:
for (TypeVariable var : multiple_children) {
TypeNode lca = null;
List<TypeVariable> children_to_remove = new LinkedList<TypeVariable>();
var.fixChildren();
for (TypeVariable child : var.children()) {
TypeNode type = child.type();
if(type != null && type.isNull())
{
var.removeChild(child);
}
else if(type != null && type.isClass())
{
children_to_remove.add(child);
if(lca == null)
{
lca = type;
}
else
{
lca = lca.lcaIfUnique(type);
if(lca == null)
{
if(DEBUG)
{
G.v().out.println
("==++==" +
stmtBody.getMethod().getDeclaringClass().getName() + "." +
stmtBody.getMethod().getName());
}
continue multiple_children;
}
}
}
}
if(lca != null)
{
for (TypeVariable child : children_to_remove) {
var.removeChild(child);
}
var.addChild(typeVariable(lca));
}
}
for (TypeVariable var : multiple_parents) {
LinkedList<TypeVariable> hp = new LinkedList<TypeVariable>(); // hard parents
var.fixParents();
for (TypeVariable parent : var.parents()) {
TypeNode type = parent.type();
if(type != null)
{
Iterator<TypeVariable> k = hp.iterator();
while( k.hasNext() ) {
TypeVariable otherparent = k.next();
TypeNode othertype = otherparent.type();
if(type.hasDescendant(othertype))
{
var.removeParent(parent);
type = null;
break;
}
if(type.hasAncestor(othertype))
{
var.removeParent(otherparent);
k.remove();
}
}
if(type != null)
{
hp.add(parent);
}
}
}
}
}
}
private void assign_types_1_2() throws TypeException
{
for( Iterator localIt = stmtBody.getLocals().iterator(); localIt.hasNext(); ) {
final Local local = (Local) localIt.next();
TypeVariable var = typeVariable(local);
if(var == null)
{
local.setType(RefType.v("java.lang.Object"));
}
else if (var.depth() == 0)
{
if(var.type() == null)
{
TypeVariable.error("Type Error(5): Variable without type");
}
else
{
local.setType(var.type().type());
}
}
else
{
TypeVariable element = var.element();
for(int j = 1; j < var.depth(); j++)
{
element = element.element();
}
if(element.type() == null)
{
TypeVariable.error("Type Error(6): Array variable without base type");
}
else if(element.type().type() instanceof NullType)
{
local.setType(NullType.v());
}
else
{
Type t = element.type().type();
if(t instanceof IntType)
{
local.setType(var.approx().type());
}
else
{
local.setType(ArrayType.v(t, var.depth()));
}
}
}
if(DEBUG)
{
if((var != null) &&
(var.approx() != null) &&
(var.approx().type() != null) &&
(local != null) &&
(local.getType() != null) &&
!local.getType().equals(var.approx().type()))
{
G.v().out.println("local: " + local + ", type: " + local.getType() + ", approx: " + var.approx().type());
}
}
}
}
private void assign_types_3() throws TypeException
{
for( Iterator localIt = stmtBody.getLocals().iterator(); localIt.hasNext(); ) {
final Local local = (Local) localIt.next();
TypeVariable var = typeVariable(local);
if(var == null ||
var.approx() == null ||
var.approx().type() == null)
{
local.setType(RefType.v("java.lang.Object"));
}
else
{
local.setType(var.approx().type());
}
}
}
private void check_constraints() throws TypeException
{
ConstraintChecker checker = new ConstraintChecker(this, false);
StringBuffer s = null;
if(DEBUG)
{
s = new StringBuffer("Checking:\n");
}
for( Iterator stmtIt = stmtBody.getUnits().iterator(); stmtIt.hasNext(); ) {
final Stmt stmt = (Stmt) stmtIt.next();
if(DEBUG)
{
s.append(" " + stmt + "\n");
}
try
{
checker.check(stmt, stmtBody);
}
catch(TypeException e)
{
if(DEBUG)
{
G.v().out.println(s);
}
throw e;
}
}
}
private void check_and_fix_constraints() throws TypeException
{
ConstraintChecker checker = new ConstraintChecker(this, true);
StringBuffer s = null;
PatchingChain units = stmtBody.getUnits();
Stmt[] stmts = new Stmt[units.size()];
units.toArray(stmts);
if(DEBUG)
{
s = new StringBuffer("Checking:\n");
}
for (Stmt stmt : stmts) {
if(DEBUG)
{
s.append(" " + stmt + "\n");
}
try
{
checker.check(stmt, stmtBody);
}
catch(TypeException e)
{
if(DEBUG)
{
G.v().out.println(s);
}
throw e;
}
}
}
private void compute_approximate_types() throws TypeException
{
TreeSet<TypeVariable> workList = new TreeSet<TypeVariable>();
for (TypeVariable var : typeVariableList) {
if(var.type() != null)
{
workList.add(var);
}
}
TypeVariable.computeApprox(workList);
for (TypeVariable var : typeVariableList) {
if(var.approx() == NULL)
{
var.union(typeVariable(NULL));
}
else if (var.approx() == null)
{
var.union(typeVariable(NULL));
}
}
}
private void compute_solved()
{
Set<TypeVariable> unsolved_set = new TreeSet<TypeVariable>();
Set<TypeVariable> solved_set = new TreeSet<TypeVariable>();
for (TypeVariable var : typeVariableList) {
if(var.depth() == 0)
{
if(var.type() == null)
{
unsolved_set.add(var);
}
else
{
solved_set.add(var);
}
}
}
solved = new LinkedList<TypeVariable>(solved_set);
unsolved = new LinkedList<TypeVariable>(unsolved_set);
}
private void refresh_solved() throws TypeException
{
Set<TypeVariable> unsolved_set = new TreeSet<TypeVariable>();
Set<TypeVariable> solved_set = new TreeSet<TypeVariable>(solved);
for (TypeVariable var : unsolved) {
if(var.depth() == 0)
{
if(var.type() == null)
{
unsolved_set.add(var);
}
else
{
solved_set.add(var);
}
}
}
solved = new LinkedList<TypeVariable>(solved_set);
unsolved = new LinkedList<TypeVariable>(unsolved_set);
// validate();
}
private void categorize() throws TypeException
{
refresh_solved();
single_soft_parent = new LinkedList<TypeVariable>();
single_hard_parent = new LinkedList<TypeVariable>();
multiple_parents = new LinkedList<TypeVariable>();
single_child_not_null = new LinkedList<TypeVariable>();
single_null_child = new LinkedList<TypeVariable>();
multiple_children = new LinkedList<TypeVariable>();
for (TypeVariable var : unsolved) {
// parent category
{
List<TypeVariable> parents = var.parents();
int size = parents.size();
if(size == 0)
{
var.addParent(typeVariable(OBJECT));
single_soft_parent.add(var);
}
else if(size == 1)
{
TypeVariable parent = parents.get(0);
if(parent.type() == null)
{
single_soft_parent.add(var);
}
else
{
single_hard_parent.add(var);
}
}
else
{
multiple_parents.add(var);
}
}
// child category
{
List<TypeVariable> children = var.children();
int size = children.size();
if(size == 0)
{
var.addChild(typeVariable(NULL));
single_null_child.add(var);
}
else if(size == 1)
{
TypeVariable child = children.get(0);
if(child.type() == NULL)
{
single_null_child.add(var);
}
else
{
single_child_not_null.add(var);
}
}
else
{
multiple_children.add(var);
}
}
}
}
private void split_new()
{
ExceptionalUnitGraph graph = new ExceptionalUnitGraph(stmtBody);
SimpleLocalDefs defs = new SimpleLocalDefs(graph);
// SimpleLocalUses uses = new SimpleLocalUses(graph, defs);
PatchingChain units = stmtBody.getUnits();
Stmt[] stmts = new Stmt[units.size()];
units.toArray(stmts);
for (Stmt stmt : stmts) {
if(stmt instanceof InvokeStmt)
{
InvokeStmt invoke = (InvokeStmt) stmt;
if(invoke.getInvokeExpr() instanceof SpecialInvokeExpr)
{
SpecialInvokeExpr special = (SpecialInvokeExpr) invoke.getInvokeExpr();
if(special.getMethodRef().name().equals("<init>"))
{
List<Unit> deflist = defs.getDefsOfAt((Local) special.getBase(), invoke);
while(deflist.size() == 1)
{
Stmt stmt2 = (Stmt) deflist.get(0);
if(stmt2 instanceof AssignStmt)
{
AssignStmt assign = (AssignStmt) stmt2;
if(assign.getRightOp() instanceof Local)
{
deflist = defs.getDefsOfAt((Local) assign.getRightOp(), assign);
continue;
}
else if(assign.getRightOp() instanceof NewExpr)
{
// We split the local.
//G.v().out.println("split: [" + assign + "] and [" + stmt + "]");
Local newlocal = Jimple.v().newLocal("tmp", null);
stmtBody.getLocals().add(newlocal);
special.setBase(newlocal);
units.insertAfter(Jimple.v().newAssignStmt(assign.getLeftOp(), newlocal), assign);
assign.setLeftOp(newlocal);
}
}
break;
}
}
}
}
}
}
}