/* * 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. * * This file is a derivative of code released under the terms listed below. * */ /* * Copyright (c) 2013, * Tobias Blaschke <code@tobiasblaschke.de> * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The names of the contributors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.ibm.wala.dalvik.ipa.callgraph.androidModel.parameters; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.ssa.SSAInstruction; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.ssa.ParameterAccessor; import com.ibm.wala.util.ssa.SSAValue; /** * Manages SSA-Numbers for the arguments to Entrypoints. * * This class comes in handy if you want to use loops or mix a return value of a function into the * parameter of a later function. It supports multiple levels of cascading code blocks and delivers * information which SSA-Value is the latest to use or which aught to be combined using a Phi-Statement. * <p> * However it does no allocations or Phi-Statements on its own. It just juggles with the numbers. * * @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.structure.AbstractAndroidModel * @see com.ibm.wala.dalvik.ipa.callgraph.impl.DexFakeRootMethod * * @author Tobias Blaschke <code@toiasblaschke.de> * @since 2013-09-19 * * TODO: * @todo Track if a variable has been refered to to be able to prune unused Phi-Instructions later * @todo Trim Memory consumption? The whole class should not be in memory for long time so this * might be not neccessary. */ public class AndroidModelParameterManager { private static final Logger logger = LoggerFactory.getLogger(AndroidModelParameterManager.class); private enum ValueStatus { UNUSED, /** Value has never been mentioned before */ UNALLOCATED, /** Awaiting to be set using setAllocation */ ALLOCATED, /** Set and ready to use */ FREE, /** Has to be assigned using a Phi-Instruction */ INVALIDATED, /** Should only be used as argument to a Phi Instruction */ CLOSED, /** Should not be referenced any more */ FREE_INVALIDATED, /** Well FREE and INVALIDATED */ FREE_CLOSED /** Well FREE and CLOSED */ } // TODO: nextLocal may be 0 on getUnamanged! /** The next variable not under management yet */ private int nextLocal; /** for managing cascaded code blocks */ private int currentScope = 0; /** For checking if type is CREATE or REUSE (optional) */ private IInstantiationBehavior behaviour = null; /** Description only used for toString() */ private String description; // private MethodReference forMethod; /** * Representing a ssa-number - thus a version of an instance to a type. */ private static class ManagedParameter { public ValueStatus status = ValueStatus.UNUSED; public TypeReference type = null; public int ssa = -1; // public SSAInstruction setBy = null; public int setInScope = -1; } /** The main data-structure of the management */ private Map<TypeReference, List<ManagedParameter>> seenTypes = new HashMap<TypeReference, List<ManagedParameter>>(); /** * Setting the behaviour may be handy in the later model. * * However it brings no benefit to the AndroidModelParameterManager. */ public AndroidModelParameterManager(IInstantiationBehavior behaviour) { this.behaviour = behaviour; this.description = " based on behaviours of " + behaviour; // this.forMethod = null; // XXX } public AndroidModelParameterManager(MethodReference mRef, boolean isStatic) { this(new ParameterAccessor(mRef, isStatic)); this.description = " based on MethodReference " + mRef; // this.forMethod = mRef; } public AndroidModelParameterManager(ParameterAccessor acc) { this.behaviour = null; nextLocal = acc.getFirstAfter(); /* for (Parameter param: acc.all()) { setAllocation(param.getType(), param.getNumber()); }*/ this.description = " based on ParameterAccessor " + acc; // this.forMethod = acc.forMethod(); } //public AndroidModelParameterManager() { // this.behaviour = null; //} /* public void readDescriptior(Descriptor forMethod) { for (int i=0; i < forMethod.getParameters().length; ++i) { setAllocation(forMethod.getParameters()[i], i + 1); } }*/ /** * Register a variable _after_ allocation. * * The proper way to add an allocation is to get a Variable using {@link #getUnallocated}. Then * assign it a value. And at last call this function. * * You can however directly call the function if the type has not been seen before. * * @param type The type allocated * @param ssaValue an unallocated SSA-Variable to assign the allocation to * @param setBy The instruction that set the value * @throws IllegalStateException if you set more than one allocation for that type (TODO better check!) * @throws IllegalArgumentException if type is null or ssaValue is zero or negative */ public void setAllocation(TypeReference type, int ssaValue, SSAInstruction setBy) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (ssaValue <= 0) { throw new IllegalArgumentException("The SSA-Variable may not be zero or negative."); } if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { if (param.status == ValueStatus.UNALLOCATED) { // XXX: Allow more? assert (param.type.equals(type)) : "Inequal types"; if ((ssaValue + 1) > nextLocal) { nextLocal = ssaValue + 1; } logger.debug("reSetting SSA {} to allocated {}", ssaValue, type.getName()); param.status = ValueStatus.ALLOCATED; param.ssa = ssaValue; param.setInScope = currentScope; // param.setBy = setBy; return; } else { continue; } } throw new IllegalStateException("The parameter " + type.getName() + " has already been allocated!"); } else { ManagedParameter param = new ManagedParameter(); param.status = ValueStatus.ALLOCATED; param.type = type; param.ssa = ssaValue; if ((ssaValue + 1) > nextLocal) { nextLocal = ssaValue + 1; } param.setInScope = currentScope; List<ManagedParameter> aParam = new ArrayList<ManagedParameter>(); aParam.add(param); logger.debug("Setting SSA{} to allocated {} ", ssaValue, type.getName()); seenTypes.put(type, aParam); return; } } public void setAllocation(TypeReference type, int ssaValue) { setAllocation(type, ssaValue, null); } public void setAllocation(SSAValue val) { setAllocation(val.getType(), val.getNumber(), null); } /** * Register a Phi-Instruction _after_ added to the model. * * @param type the type the Phi-Instruction sets * @param ssaValue the number the SSA-Instruction assignes to * @param setBy the Phi-Instruction itself - may be null * @throws IllegalArgumentException if you assign to a number requested using * {@link #getFree(TypeReference)} but types mismach. * @throws IllegalStateException if you forgot to close some Phis */ public void setPhi(TypeReference type, int ssaValue, SSAInstruction setBy) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (ssaValue <= 0) { throw new IllegalArgumentException("The SSA-Variable may not be zero or negative."); } boolean didPhi = false; if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { if ((param.status == ValueStatus.FREE) || (param.status == ValueStatus.FREE_INVALIDATED) || (param.status == ValueStatus.FREE_CLOSED)) { // XXX: Allow more? assert (param.type.equals(type)) : "Inequal types"; if (param.ssa != ssaValue) { if ((param.status == ValueStatus.FREE) && (param.setInScope == currentScope)) { param.status = ValueStatus.FREE_CLOSED; } continue; } if (param.status == ValueStatus.FREE) { param.status = ValueStatus.ALLOCATED; } else if (param.status == ValueStatus.FREE_INVALIDATED) { param.status = ValueStatus.INVALIDATED; } else if (param.status == ValueStatus.FREE_CLOSED) { param.status = ValueStatus.CLOSED; } param.setInScope = currentScope; // param.setBy = setBy; logger.info("Setting SSA {} to phi {} now {}", ssaValue, type.getName(), param.status); didPhi = true; } else if (param.setInScope == currentScope) { if (param.status == ValueStatus.INVALIDATED) { logger.info("Closing SSA Value {} in scope {} for {}", param.ssa, param.setInScope, param.type.getName()); param.status = ValueStatus.CLOSED; } else if (param.status == ValueStatus.FREE_INVALIDATED) { // TODO: FREE CLOSED logger.info("Closing free SSA Value {} in scope {} for {}", param.ssa, param.setInScope, param.type.getName()); param.status = ValueStatus.FREE_CLOSED; } } else if (param.setInScope < currentScope) { //param.status = ValueStatus.INVALIDATED; } else { // TODO: NO! I JUST WANTED TO ADD THEM! *grrr* //logger.error("MISSING PHI for " //throw new IllegalStateException("You forgot Phis in subordinate blocks"); } } assert (didPhi); return; } else { ManagedParameter param = new ManagedParameter(); param.status = ValueStatus.ALLOCATED; param.type = type; param.setInScope = currentScope; param.ssa = ssaValue; if ((ssaValue + 1) > nextLocal) { nextLocal = ssaValue + 1; } logger.debug("Setting SSA {} to phi {}", ssaValue, type.getName()); List<ManagedParameter> aParam = new ArrayList<ManagedParameter>(); aParam.add(param); seenTypes.put(type, aParam); return; } } /** * Returns and registers a free SSA-Number to a Type. * * You have to set the type using a Phi-Instruction. Also you don't have to add * that instruction immediatly it is required that it is added before the Model * gets finished. * * You can request the List of unmet Phi-Instructions by using XXX * * @return an unused SSA-Number * @throws IllegalArgumentException if type is null */ public int getFree(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } ManagedParameter param = new ManagedParameter(); param.status = ValueStatus.FREE; param.type = type; param.ssa = nextLocal++; param.setInScope = currentScope; if (seenTypes.containsKey(type)) { seenTypes.get(type).add(param); } else { List<ManagedParameter> aParam = new ArrayList<ManagedParameter>(); aParam.add(param); seenTypes.put(type, aParam); } logger.debug("Returning as Free SSA: {} for {}.", param.ssa, type.getName()); return param.ssa; } /** * Get an unused number to assign to. * * There may only be one unallocated value for each type at a time. XXX: Really? * * @return SSA-Variable * @throws IllegalStateException if there is already an unallocated variable of that type * @throws IllegalArgumentException if type is null */ public int getUnallocated(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (seenTypes.containsKey(type)) { for (ManagedParameter p : seenTypes.get(type)) { if (p.status == ValueStatus.UNALLOCATED) { throw new IllegalStateException("There may be only one unallocated instance to a type (" + type + ") at a time" ); } } } ManagedParameter param = new ManagedParameter(); param.status = ValueStatus.UNALLOCATED; param.type = type; param.ssa = nextLocal++; param.setInScope = currentScope; if (seenTypes.containsKey(type)) { seenTypes.get(type).add(param); } else { List<ManagedParameter> aParam = new ArrayList<ManagedParameter>(); aParam.add(param); seenTypes.put(type, aParam); } logger.debug("Returning as Unallocated SSA: {} for {}.", param.ssa, type.getName()); return param.ssa; } /** * Retreive a SSA-Value that is not under management. * * Use instead of 'nextLocal++', else SSA-Values will clash! * * @return SSA-Variable */ public int getUnmanaged() { int ret = nextLocal++; return ret; } /** * Retreive the SSA-Number that is valid for a type in the current scope. * * Either that number origins from an allocation or a PhiInstruction (to be). * * @return a ssa number * @throws IllegalStateException if no number is assignable * @throws IllegalArgumentException if type was not seen before or is null */ public int getCurrent(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } int candidateSSA = -1; int candidateScope = -1; if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { if ((param.status == ValueStatus.FREE) || (param.status == ValueStatus.ALLOCATED)) { assert (param.type.equals(type)) : "Inequal types"; if (param.setInScope > currentScope) { logger.debug("SSA Value {} of {} is out of scope {}", param.ssa, param.type, currentScope); continue; } else if (param.setInScope == currentScope) { logger.debug("Returning SSA Value {} of {} is {}", param.ssa, param.type, param.status); return param.ssa; } else { if (param.setInScope > candidateScope) { candidateScope = param.setInScope; candidateSSA = param.ssa; } } } else { logger.debug("SSA Value {} of {} is {} ", param.ssa, param.type, param.status); } } } else { throw new IllegalArgumentException("Type " + type + " has never been seen before!"); } if (candidateSSA < 0 ) { logger.debug("Returning inherited (from {} SSA Value {} for {}", candidateScope, candidateSSA, type.getName()); return candidateSSA; } else { throw new IllegalStateException("No suitable candidate has been found for " + type.getName()); } } /** * Retreive the SSA-Number that is valid for a type in the super-ordinate scope. * * Either that number origins from an allocation or a PhiInstruction (to be). * * @return a ssa number * @throws IllegalStateException if no number is assignable * @throws IllegalArgumentException if type was not seen before or is null */ public int getSuper(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } int ssa; currentScope--; assert(currentScope >= 0 ); ssa = getCurrent(type); currentScope++; return ssa; } /** * * @throws IllegalArgumentException if type was not seen before or is null */ public List<Integer> getAllForPhi(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } List<Integer> ret = new ArrayList<Integer>(); if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { if ((param.status == ValueStatus.FREE) || (param.status == ValueStatus.ALLOCATED)) { assert (param.type.equals(type)) : "Inequal types"; ret.add(param.ssa); } else if ((param.status == ValueStatus.INVALIDATED) && param.setInScope > currentScope) { ret.add(param.ssa); } } } else { throw new IllegalArgumentException("Type " + type + " has never been seen before!"); } return ret; } /** * Return if the type is managed by this class. * * @param withSuper when true return true if a managed key may be cast to type, * when false type has to match exactly * @param type the type in question * @throws IllegalArgumentException if type is null */ public boolean isSeen(TypeReference type, boolean withSuper) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (withSuper) { return seenTypes.containsKey(type); } else { if (seenTypes.containsKey(type)) { if (seenTypes.get(type).get(0).type.equals(type)) { return true; } } return false; } } public boolean isSeen(TypeReference type) { return isSeen(type, true); } /** * Returns if an instance for that type needs to be allocated. * * However this function does not respect weather a PhiInstruction is * needed. * * @throws IllegalArgumentException if type is null */ public boolean needsAllocation(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (seenTypes.containsKey(type)) { if (seenTypes.get(type).size() > 1) { // TODO INCORRECT may all be UNALLOCATED return false; } else { return (seenTypes.get(type).get(0).status == ValueStatus.UNALLOCATED); } } else { return true; } } /** * Returns if a PhiInstruction (still) has to be added. * * This is true if the Value has changed in a deeper scope, has been invalidated * or requested using getFree * * @throws IllegalArgumentException if type is null or has not been seen before */ public boolean needsPhi(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } boolean seenLive = false; if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { // TODO: Check all these if ((param.status == ValueStatus.FREE)) { // TODO: What about scopes return true; } if (param.status == ValueStatus.ALLOCATED) { if (seenLive) { return true; } else { seenLive = true; } } } } else { throw new IllegalArgumentException("Type " + type + " has never been seen before!"); } throw new IllegalStateException("No suitable candidate has been found"); // TODO WRONG text } /** * @throws IllegalArgumentException if type was not seen before or is null */ public void invalidate(TypeReference type) { if (type == null) { throw new IllegalArgumentException("The argument type may not be null"); } if (seenTypes.containsKey(type)) { for (ManagedParameter param : seenTypes.get(type)) { if ((param.status != ValueStatus.CLOSED) && (param.status != ValueStatus.FREE_CLOSED) && (param.status != ValueStatus.FREE_INVALIDATED) && (param.status != ValueStatus.INVALIDATED) && (param.setInScope==currentScope) ) { assert(param.type.equals(type)); if (param.status == ValueStatus.FREE) { param.status = ValueStatus.FREE_INVALIDATED; } else { param.status = ValueStatus.INVALIDATED; } logger.info("Invalidated SSA {} for type {}", param.ssa, type.getName()); } } } } /** * Enter a subordinate scope. * * Call this whenever a new code block starts i.e. when ever you would have to put a * left curly-bracket in yout java code. * <p> * This function influences the placement of Phi-Functions. Thus if you don't change * values you don't have to call it. * * @param doesLoop set to true if the scope is introduced for a loop * @return The depth */ public int scopeDown(boolean doesLoop) { // TODO: Rename scopeInto // TODO: Delete Parameters if therw already was scopeNo currentScope++; return currentScope; } /** * Leave a subordinate scope. * * All changes are marked invalid thus to be expected to be collected by a PhiInstruction. * @throws IllegalStateException if already at top level */ public int scopeUp() { // TODO: Rename scopeOut // First: Invalidate changed values for (List<ManagedParameter> plist : seenTypes.values()) { for (ManagedParameter param : plist) { if (param.setInScope == currentScope) { invalidate(param.type); } else if ((param.setInScope > currentScope) && ((param.status != ValueStatus.INVALIDATED) || (param.status != ValueStatus.CLOSED))) { throw new IllegalStateException("Something went wrong in leaving a sub-subordinate scope"); } } } currentScope--; return currentScope; } /** * Handed through to an IInstantiationBehavior if set in the constructor. * * @return true if Type is a REUSE type * @throws IllegalStateExcpetion if AndroidModelParameterManager was constructed without an IInstanciationBehavior */ public boolean isReuse(TypeReference type) { if (this.behaviour == null) { throw new IllegalStateException("AndroidModelParameterManager was constructed without an IInstanciationBehavior"); } if (type.isPrimitiveType()) return false; final IInstantiationBehavior.InstanceBehavior beh = this.behaviour.getBehavior(type.getName(), null, null, null); // TODO: More info here! return (beh == IInstantiationBehavior.InstanceBehavior.REUSE); } /** * Shorthand for not({@link #isReuse(TypeReference)}. * * @return true if type is a CREATE-Type * @throws IllegalStateExcpetion if AndroidModelParameterManager was constructed without an IInstanciationBehavior */ public boolean isCreate(TypeReference type) { return (! isReuse(type)); } @Override public String toString() { return "<AndroidModelParameterManager " + this.description + ">"; } }