/* Soot - a J*va Optimization Framework * Copyright (C) 2008 Ben Bellamy * * 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. */ package soot.jimple.toolkits.typing.fast; import java.util.*; import soot.*; import soot.jimple.*; import soot.toolkits.graph.*; import soot.toolkits.scalar.*; /** * New Type Resolver by Ben Bellamy (see 'Efficient Local Type Inference' * at OOPSLA 08). * * Ben has tested this code, and verified that it provides a typing * that is at least as tight as the original algorithm (tighter in * 2914 methods out of 295598) on a number of benchmarks. These are: * abc-complete.jar, BlueJ, CSO (Scala code), Gant, Groovy, havoc.jar, * Java 3D, jEdit, Java Grande Forum, Jigsaw, Jython, Kawa, rt.jar, * Kawa, Scala and tools.jar. * The mean execution time improvement is around 10 times, * but for the longest methods (abc parser methods and havoc with * >9000 statements) the improvement is between 200 and 500 times. * * @author Ben Bellamy */ public class TypeResolver { private JimpleBody jb; private List<DefinitionStmt> assignments; private HashMap<Local, List<DefinitionStmt>> depends; public TypeResolver(JimpleBody jb) { this.jb = jb; this.assignments = new LinkedList<DefinitionStmt>(); this.depends = new HashMap<Local, List<DefinitionStmt>>(); for ( Local v : this.jb.getLocals() ) this.addLocal(v); this.initAssignments(); } private void initAssignments() { for ( Unit stmt : this.jb.getUnits() ) if ( stmt instanceof DefinitionStmt ) this.initAssignment((DefinitionStmt)stmt); } private void initAssignment(DefinitionStmt ds) { Value lhs = ds.getLeftOp(), rhs = ds.getRightOp(); if ( lhs instanceof Local || lhs instanceof ArrayRef) { this.assignments.add(ds); if ( rhs instanceof Local ) this.addDepend((Local)rhs, ds); else if ( rhs instanceof BinopExpr ) { BinopExpr be = (BinopExpr)rhs; Value lop = be.getOp1(), rop = be.getOp2(); if ( lop instanceof Local ) this.addDepend((Local)lop, ds); if ( rop instanceof Local ) this.addDepend((Local)rop, ds); } else if ( rhs instanceof NegExpr ) { Value op = ((NegExpr)rhs).getOp(); if ( op instanceof Local ) this.addDepend((Local)op, ds); } else if ( rhs instanceof ArrayRef ) this.addDepend((Local)((ArrayRef)rhs).getBase(), ds); } } private void addLocal(Local v) { this.depends.put(v, new LinkedList<DefinitionStmt>()); } private void addDepend(Local v, DefinitionStmt stmt) { this.depends.get(v).add(stmt); } public void inferTypes() { AugEvalFunction ef = new AugEvalFunction(this.jb); AugHierarchy ah = new AugHierarchy(); BytecodeHierarchy bh = new BytecodeHierarchy(); Collection<Typing> sigma = this.applyAssignmentConstraints( new Typing(this.jb.getLocals()), ef, bh); int[] castCount = new int[1]; Typing tg = this.minCasts(sigma, bh, castCount); if ( castCount[0] != 0 ) { this.split_new(); sigma = this.applyAssignmentConstraints( new Typing(this.jb.getLocals()), ef, bh); tg = this.minCasts(sigma, bh, castCount); } // RoboVM note: Don't insert casts. The output will not behave the same as the input. // Dalvik's 069-field-type test throws a ClassCastException instead of the expected // IncompatibleClassChangeError if we call insertCasts(). //this.insertCasts(tg, bh, false); for ( Local v : this.jb.getLocals() ) { Type t = tg.get(v); if ( t instanceof IntegerType ) { t = IntType.v(); tg.set(v, BottomType.v()); } v.setType(t); } tg = this.typePromotion(tg); if ( tg == null ) // Use original soot algorithm for inserting casts soot.jimple.toolkits.typing.integer.TypeResolver.resolve(this.jb); else for ( Local v : this.jb.getLocals() ) v.setType(tg.get(v)); } private class CastInsertionUseVisitor implements IUseVisitor { private JimpleBody jb; private Typing tg; private IHierarchy h; private boolean countOnly; private int count; public CastInsertionUseVisitor(boolean countOnly, JimpleBody jb, Typing tg, IHierarchy h) { this.jb = jb; this.tg = tg; this.h = h; this.countOnly = countOnly; this.count = 0; } public Value visit(Value op, Type useType, Stmt stmt) { Type t = AugEvalFunction.eval_(this.tg, op, stmt, this.jb); if ( this.h.ancestor(useType, t) ) return op; this.count++; if ( countOnly ) return op; else { Local vold; if ( !(op instanceof Local) ) { /* By the time we have countOnly == false, all variables must by typed with concrete Jimple types, and never [0..1], [0..127] or [0..32767]. */ vold = Jimple.v().newLocal("tmp", t); this.tg.set(vold, t); this.jb.getLocals().add(vold); this.jb.getUnits().insertBefore( Jimple.v().newAssignStmt(vold, op), stmt); } else vold = (Local)op; Local vnew = Jimple.v().newLocal("tmp", useType); this.tg.set(vnew, useType); this.jb.getLocals().add(vnew); this.jb.getUnits().insertBefore( Jimple.v().newAssignStmt(vnew, Jimple.v().newCastExpr(vold, useType)), stmt); return vnew; } } public int getCount() { return this.count; } public boolean finish() { return false; } } private class TypePromotionUseVisitor implements IUseVisitor { private JimpleBody jb; private Typing tg; public boolean fail; public boolean typingChanged; public TypePromotionUseVisitor(JimpleBody jb, Typing tg) { this.jb = jb; this.tg = tg; this.fail = false; this.typingChanged = false; } private Type promote(Type tlow, Type thigh) { if ( tlow instanceof Integer1Type ) { if ( thigh instanceof IntType ) return Integer127Type.v(); else if ( thigh instanceof ShortType ) return ByteType.v(); else if ( thigh instanceof BooleanType || thigh instanceof ByteType || thigh instanceof CharType || thigh instanceof Integer127Type || thigh instanceof Integer32767Type ) return thigh; else throw new RuntimeException(); } else if ( tlow instanceof Integer127Type ) { if ( thigh instanceof ShortType ) return ByteType.v(); else if ( thigh instanceof IntType ) return Integer127Type.v(); else if ( thigh instanceof ByteType || thigh instanceof CharType || thigh instanceof Integer32767Type ) return thigh; else throw new RuntimeException(); } else if ( tlow instanceof Integer32767Type ) { if ( thigh instanceof IntType ) return Integer32767Type.v(); else if ( thigh instanceof ShortType || thigh instanceof CharType ) return thigh; else throw new RuntimeException(); } else throw new RuntimeException(); } public Value visit(Value op, Type useType, Stmt stmt) { if ( this.finish() ) return op; Type t = AugEvalFunction.eval_(this.tg, op, stmt, this.jb); if ( !AugHierarchy.ancestor_(useType, t) ) this.fail = true; else if ( op instanceof Local && (t instanceof Integer1Type || t instanceof Integer127Type || t instanceof Integer32767Type) ) { Local v = (Local)op; if ( !typesEqual(t, useType) ) { Type t_ = this.promote(t, useType); if ( !typesEqual(t, t_) ) { this.tg.set(v, t_); this.typingChanged = true; } } } return op; } public boolean finish() { return this.typingChanged || this.fail; } } private Typing typePromotion(Typing tg) { AugEvalFunction ef = new AugEvalFunction(this.jb); AugHierarchy h = new AugHierarchy(); UseChecker uc = new UseChecker(this.jb); TypePromotionUseVisitor uv = new TypePromotionUseVisitor(jb, tg); do { Collection<Typing> sigma = this.applyAssignmentConstraints(tg, ef, h); if ( sigma.isEmpty() ) return null; tg = sigma.iterator().next(); uv.typingChanged = false; uc.check(tg, uv); if ( uv.fail ) return null; } while ( uv.typingChanged ); for ( Local v : this.jb.getLocals() ) { Type t = tg.get(v); if ( t instanceof Integer1Type ) { tg.set(v, BooleanType.v()); return this.typePromotion(tg); } else if ( t instanceof Integer127Type ) { tg.set(v, ByteType.v()); return this.typePromotion(tg); } else if ( t instanceof Integer32767Type ) { tg.set(v, ShortType.v()); return this.typePromotion(tg); } } return tg; } private int insertCasts(Typing tg, IHierarchy h, boolean countOnly) { UseChecker uc = new UseChecker(this.jb); CastInsertionUseVisitor uv = new CastInsertionUseVisitor(countOnly, this.jb, tg, h); uc.check(tg, uv); return uv.getCount(); } private Typing minCasts(Collection<Typing> sigma, IHierarchy h, int[] count) { Typing r = null; count[0] = -1; boolean setR = false; for ( Typing tg : sigma ) { int n = this.insertCasts(tg, h, true); if ( count[0] == -1 || n < count[0] ) { count[0] = n; r = tg; setR = true; } } if (setR) return r; else return null; } private Collection<Typing> applyAssignmentConstraints(Typing tg, IEvalFunction ef, IHierarchy h) { LinkedList<Typing> sigma = new LinkedList<Typing>(), r = new LinkedList<Typing>(); HashMap<Typing, QueuedSet<DefinitionStmt>> worklists = new HashMap<Typing, QueuedSet<DefinitionStmt>>(); sigma.add(tg); QueuedSet<DefinitionStmt> wl = new QueuedSet<DefinitionStmt>( this.assignments); worklists.put(tg, wl); while ( !sigma.isEmpty() ) { tg = sigma.element(); wl = worklists.get(tg); if ( wl.isEmpty() ) { r.add(tg); sigma.remove(); worklists.remove(tg); } else { DefinitionStmt stmt = wl.removeFirst(); Value lhs = stmt.getLeftOp(), rhs = stmt.getRightOp(); Local v; if ( lhs instanceof Local ) v = (Local)lhs; else v = (Local)((ArrayRef)lhs).getBase(); Type told = tg.get(v); boolean keep = false; Collection<Type> eval = ef.eval(tg, rhs, stmt); for ( Type t_ : eval ) { if ( lhs instanceof ArrayRef ) { /* We only need to consider array references on the LHS of assignments where there is supertyping between array types, which is only for arrays of reference types and multidimensional arrays. */ if ( !(t_ instanceof RefType || t_ instanceof ArrayType) ) { keep = true; continue; } t_ = t_.makeArrayType(); } Collection<Type> lcas = h.lcas(told, t_); for ( Type t : lcas ) if ( typesEqual(t, told) ) keep = true; else { Typing tg_; QueuedSet<DefinitionStmt> wl_; if ( eval.size() == 1 && lcas.size() == 1 ) { tg_ = tg; wl_ = wl; keep = true; } else { tg_ = new Typing(tg); wl_ = new QueuedSet<DefinitionStmt>(wl); sigma.add(tg_); worklists.put(tg_, wl_); } tg_.set(v, t); wl_.addLast(this.depends.get(v)); } }//end for if ( !keep ) { sigma.remove(); worklists.remove(tg); } } } Typing.minimize(r, h); return r; } // The ArrayType.equals method seems odd in Soot 2.2.5 public static boolean typesEqual(Type a, Type b) { if ( a instanceof ArrayType && b instanceof ArrayType ) { ArrayType a_ = (ArrayType)a, b_ = (ArrayType)b; return a_.numDimensions == b_.numDimensions && a_.baseType.equals(b_.baseType); } return a.equals(b); } /* Taken from the soot.jimple.toolkits.typing.TypeResolver class of Soot version 2.2.5. */ private void split_new() { ExceptionalUnitGraph graph = new ExceptionalUnitGraph(this.jb); SimpleLocalDefs defs = new SimpleLocalDefs(graph); // SimpleLocalUses uses = new SimpleLocalUses(graph, defs); PatchingChain<Unit> units = this.jb.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 ) { Local newlocal = Jimple.v().newLocal( "tmp", null); newlocal.setName("tmp$" + System.identityHashCode(newlocal)); this.jb.getLocals().add(newlocal); special.setBase(newlocal); DefinitionStmt assignStmt = Jimple.v().newAssignStmt( assign.getLeftOp(), newlocal); units.insertAfter(assignStmt, assign); assign.setLeftOp(newlocal); this.addLocal(newlocal); this.initAssignment(assignStmt); } } break; } } } } } } }