WALA/com.ibm.wala.core/src/com/ibm/wala/util/ssa/SSAValueManager.java

735 lines
28 KiB
Java

/******************************************************************************
* Copyright (c) 2002 - 2014 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
*****************************************************************************/
/*
* 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.util.ssa;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.ssa.SSAValue.NamedKey;
import com.ibm.wala.util.ssa.SSAValue.VariableKey;
import com.ibm.wala.util.strings.Atom;
/**
* Manage SSA-Variables in synthetic methods.
*
* @author Tobias Blaschke <code@tobiasblaschke.de>
* @since 2013-09-19
*/
public class SSAValueManager {
private final static boolean DEBUG = false;
private final boolean AUTOMAKE_NAMES = true;
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;
/** Description only used for toString() */
private String description;
private MethodReference forMethod;
/** User-Defined debugging info */
public String breadCrumb = "";
private static class Managed<T extends SSAValue> {
public ValueStatus status = ValueStatus.UNUSED;
public SSAInstruction setBy = null;
public int setInScope = -1;
public final VariableKey key;
public final T value;
public Managed(final T value, final VariableKey key) {
this.value = value;
this.key = key;
}
@Override
public String toString() {
return "<Managed " + this.value + " key=\"" + this.key + "\" status=\"" + this.status + " setIn=\"" +
this.setInScope + "\" setBy=\"" + this.setBy + "\" />";
}
}
/** The main data-structure of the management */
private Map<VariableKey, List<Managed<? extends SSAValue>>> seenTypes = HashMapFactory.make();
private List<SSAValue> unmanaged = new ArrayList<SSAValue>();
public SSAValueManager(ParameterAccessor acc) {
this.nextLocal = acc.getFirstAfter();
this.description = " based on ParameterAccessor " + acc;
this.forMethod = acc.forMethod();
for (SSAValue val : acc.all()) {
setAllocation(val, null);
}
}
/*
public SSAValueManager(final MethodReference forMethod) {
this (new
//this.nextLocal = nextLocal;
this.description = " stand alone";
this.forMethod = forMethod;
}*/
/**
* 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 value an unallocated SSA-Variable to assign the allocation to
* @param setBy The instruction that set the value (optional)
* @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(SSAValue value, SSAInstruction setBy) {
if (value == null) {
throw new IllegalArgumentException("The SSA-Variable may not be null");
}
if (seenTypes.containsKey(value.key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(value.key)) {
if (param.status == ValueStatus.UNALLOCATED) {
// XXX: Allow more?
assert (param.value.getType().equals(value.getType())) : "Inequal types";
if ((param.value.getNumber() + 1) > nextLocal) {
nextLocal = param.value.getNumber() + 1;
}
debug("reSetting SSA {} to allocated", value);
param.status = ValueStatus.ALLOCATED;
param.setInScope = currentScope;
param.setBy = setBy;
return;
} else {
continue;
}
}
{ // DEBUG
System.out.println("Keys for " + value + ":");
for (Managed<? extends SSAValue> param : seenTypes.get(value.key)) {
System.out.println("\tKey " + param.key + "\t=>" + param.status);
}
} // */
throw new IllegalStateException("The parameter " + value + " using Key " + value.key + " has already been allocated");
} else {
info("New variable in management: {}", value);
final Managed<SSAValue> param = new Managed<>(value, value.key);
param.status = ValueStatus.ALLOCATED;
param.setInScope = currentScope;
param.setBy = setBy;
final List<Managed<? extends SSAValue>> aParam = new ArrayList<Managed<? extends SSAValue>>();
aParam.add(param);
seenTypes.put(value.key, aParam);
}
}
/**
* Register a Phi-Instruction _after_ added to the model.
*
* @param value the number the SSA-Instruction assigns to
* @param setBy the Phi-Instruction itself - may be null
* @throws IllegalArgumentException if you assign to a number requested using
* {@link #getFree} but types mismatch.
* @throws IllegalStateException if you forgot to close some Phis
*/
public void setPhi(final SSAValue value, SSAInstruction setBy) {
if (value == null) {
throw new IllegalArgumentException("The SSA-Variable may not be null.");
}
boolean didPhi = false;
if (seenTypes.containsKey(value.key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(value.key)) {
if ((param.status == ValueStatus.FREE) ||
(param.status == ValueStatus.FREE_INVALIDATED) ||
(param.status == ValueStatus.FREE_CLOSED)) {
// XXX: Allow more?
assert (param.value.getType().equals(value.getType())) : "Unequal types";
if (param.value.getNumber() != value.getNumber()) {
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;
info("Setting SSA {} to phi! now {}", value, param.status);
didPhi = true;
} else if (param.setInScope == currentScope) {
if (param.status == ValueStatus.INVALIDATED) {
info("Closing SSA Value {} in scope {}", param.value, param.setInScope);
param.status = ValueStatus.CLOSED;
} else if (param.status == ValueStatus.FREE_INVALIDATED) { // TODO: FREE CLOSED
info("Closing free SSA Value {} in scope {}", param.value, param.setInScope);
param.status = ValueStatus.FREE_CLOSED;
}
} else if (param.setInScope < currentScope) {
//param.status = ValueStatus.INVALIDATED;
} else {
// TODO: NO! I JUST WANTED TO ADD THEM! *grrr*
//error("MISSING PHI for "
//throw new IllegalStateException("You forgot Phis in subordinate blocks");
}
}
assert (didPhi);
return;
} else {
throw new IllegalStateException("This should not be reached!");
}
}
/**
* 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 immediately 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 SSAValue getFree(TypeReference type, VariableKey key) {
if (type == null) {
throw new IllegalArgumentException("The argument type may not be null");
}
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
final SSAValue var = new SSAValue(nextLocal++, type, this.forMethod, key);
final Managed<SSAValue> param = new Managed<>(var, key);
param.status = ValueStatus.FREE;
param.setInScope = currentScope;
if (seenTypes.containsKey(key)) {
seenTypes.get(key).add(param);
} else {
List<Managed<? extends SSAValue>> aParam = new ArrayList<Managed<? extends SSAValue>>();
aParam.add(param);
seenTypes.put(key, aParam);
}
debug("Returning as Free SSA: {}", param);
return var;
}
/**
* 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 SSAValue getUnallocated(TypeReference type, VariableKey key) {
if (type == null) {
throw new IllegalArgumentException("The argument type may not be null");
}
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
if (seenTypes.containsKey(key)) {
for (Managed<? extends SSAValue> p : seenTypes.get(key)) {
if (p.status == ValueStatus.UNALLOCATED) {
throw new IllegalStateException("There may be only one unallocated instance to a kay (" + key +
") at a time" );
}
}
}
final SSAValue var = new SSAValue(nextLocal++, type, this.forMethod, key);
final Managed<SSAValue> param = new Managed<>(var, key);
param.status = ValueStatus.UNALLOCATED;
param.setInScope = currentScope;
if (seenTypes.containsKey(key)) {
seenTypes.get(key).add(param);
} else {
List<Managed<? extends SSAValue>> aParam = new ArrayList<Managed<? extends SSAValue>>();
aParam.add(param);
seenTypes.put(key, aParam);
}
debug("Returning as Unallocated SSA: {}", param);
return var;
}
/**
* Retrieve a SSA-Value that is not under management.
*
* Use instead of 'nextLocal++', else SSA-Values will clash!
*
* @return SSA-Variable
*/
public SSAValue getUnmanaged(TypeReference type, VariableKey key) {
final SSAValue var = new SSAValue(nextLocal++, type, this.forMethod, key);
this.unmanaged.add(var);
return var;
}
public SSAValue getUnmanaged(TypeReference type, String name) {
return getUnmanaged(type, new NamedKey(type.getName(), name));
}
/**
* Retrieve 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 SSAValue getCurrent(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
Managed<? extends SSAValue> candidate = null;
if (seenTypes.containsKey(key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(key)) {
if ((param.status == ValueStatus.FREE) ||
(param.status == ValueStatus.ALLOCATED)) {
//assert (param.value.getType().equals(type)) : "Unequal types";
if (param.setInScope > currentScope) {
debug("SSA Value {} is out of scope {}", param, currentScope);
continue;
} else if (param.setInScope == currentScope) {
debug("Returning SSA Value {} is {}", param.value, param.status);
return param.value;
} else {
if ((candidate == null) || (param.setInScope > candidate.setInScope)) {
candidate = param;
}
}
} else {
debug("SSA Value {} is {}", param, param.status);
}
}
} else {
throw new IllegalArgumentException("Key " + key + " has never been seen before! Known keys are " + seenTypes.keySet() );
}
if (candidate != null ) {
debug("Returning inherited (from {}) SSA Value {}", candidate.setInScope, candidate);
return candidate.value;
} else {
throw new IllegalStateException("No suitable candidate has been found for Key " + key);
}
}
/**
* Retrieve 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 SSAValue getSuper(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
final SSAValue cand;
currentScope--;
assert(currentScope >= 0 );
cand = getCurrent(key);
currentScope++;
return cand;
}
/**
* Returns all "free" and "allocated" variables and the invalid ones in a sub-scope.
*
* This is a suggestion which variables to considder as parameter to a Phi-Function.
*
* @throws IllegalArgumentException if type was not seen before or is null
*/
public List<SSAValue> getAllForPhi(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
List<SSAValue> ret = new ArrayList<SSAValue>();
if (seenTypes.containsKey(key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(key)) {
if ((param.status == ValueStatus.FREE) ||
(param.status == ValueStatus.ALLOCATED)) {
//assert (param.type.equals(type)) : "Unequal types";
ret.add(param.value);
} else if ((param.status == ValueStatus.INVALIDATED) &&
param.setInScope > currentScope) {
ret.add(param.value);
}
}
} else {
throw new IllegalArgumentException("Key " + key + " 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 key the type in question
* @throws IllegalArgumentException if key is null
*/
public boolean isSeen(VariableKey key, boolean withSuper) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
if (withSuper) {
return seenTypes.containsKey(key);
} else {
if (seenTypes.containsKey(key)) {
/* if (seenTypes.get(key).get(0).type.equals(type)) { // TODO: Rethink
return true;
}*/
}
return false;
}
}
/**
* Return if the type is managed by this class.
*
* This variant respects super-types. Use isSeen(VariableKey, boolean) with a setting
* for withSuper of false to enforce exact matches.
*
* @return if the type is managed by this class.
*/
public boolean isSeen(VariableKey key) {
return isSeen(key, 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(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
if (seenTypes.containsKey(key)) {
if (seenTypes.get(key).size() > 1) { // TODO INCORRECT may all be UNALLOCATED
return false;
} else {
return (seenTypes.get(key).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(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
boolean seenLive = false;
if (seenTypes.containsKey(key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(key)) { // 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("Key " + key + " has never been seen before!");
}
throw new IllegalStateException("No suitable candidate has been found"); // TODO WRONG text
}
/**
* Marks all known instances of VariableKey invalid.
*
* A call to this method is useful before a call to setAllocation. This methods sets all
* known instances to invalid, setAllocation will assign the new "current" instance to
* use.
*
* @param key Which variables to invalidate.
* @throws IllegalArgumentException if type was not seen before or is null
*/
public void invalidate(VariableKey key) {
if (key == null) {
throw new IllegalArgumentException("The argument key may not be null");
}
if (seenTypes.containsKey(key)) {
for (Managed<? extends SSAValue> param : seenTypes.get(key)) {
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;
}
info("Invalidated SSA {} for key {}", param, key);
}
}
}
}
/**
* 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 the 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 there 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<Managed<? extends SSAValue>> plist : seenTypes.values()) {
for (Managed<? extends SSAValue> param : plist) {
if (param.setInScope == currentScope) {
invalidate(param.value.key);
} else if ((param.setInScope > currentScope) &&
((param.status != ValueStatus.INVALIDATED) &&
(param.status != ValueStatus.CLOSED))) {
throw new IllegalStateException("A parameter was in wrong status when leaving a sub-subordinate scope: "
+ param + " should have been invalidated or closed by an other scope.");
}
}
}
currentScope--;
return currentScope;
}
@Override
public String toString() {
return "<AndroidModelParameterManager " + this.description + ">";
}
/**
* Create new SSAValue with UniqueKey and Exception-Type.
*
* The generated SSAValue will be unmanaged. It is mainly useful for SSAInvokeInstructions.
*
* @return new unmanaged SSAValue with Exception-Type
*/
public SSAValue getException() {
SSAValue exc = new SSAValue(nextLocal++, TypeReference.JavaLangException, this.forMethod, "exception_" + nextLocal); // UniqueKey
this.unmanaged.add(exc);
return exc;
}
/**
* Collect the variable-names of all known variables.
*/
public Map<Integer, Atom> makeLocalNames() {
final Map<Integer, Atom> names = new HashMap<Integer, Atom>();
final Map<VariableKey, Integer> suffix = new HashMap<VariableKey, Integer>();
int currentSuffix = 0;
for (final List<Managed<? extends SSAValue>> manageds : seenTypes.values()) {
for (final Managed<? extends SSAValue> managed : manageds) {
final SSAValue val = managed.value;
final String name = val.getVariableName();
if (name != null) {
final Atom nameAtom = Atom.findOrCreateAsciiAtom(name);
names.put(val.getNumber(), nameAtom);
} else if (AUTOMAKE_NAMES) {
String autoName = val.getType().getName().toString();
if (autoName.contains("/")) {
autoName = autoName.substring(autoName.lastIndexOf("/") + 1);
}
if (autoName.contains("$")) {
autoName = autoName.substring(autoName.lastIndexOf("$") + 1);
}
autoName = autoName.replace("[", "Ar");
final int mySuffix;
if (suffix.containsKey(val.key)) {
mySuffix = suffix.get(val.key);
} else {
mySuffix = currentSuffix++;
suffix.put(val.key, mySuffix);
}
autoName = "m" + autoName + "_" + mySuffix;
final Atom nameAtom = Atom.findOrCreateAsciiAtom(autoName);
names.put(val.getNumber(), nameAtom);
}
}
}
for (final SSAValue val : this.unmanaged) {
final String name = val.getVariableName();
if (name != null) {
final Atom nameAtom = Atom.findOrCreateAsciiAtom(name);
names.put(val.getNumber(), nameAtom);
} else if (AUTOMAKE_NAMES) {
String autoName = val.getType().getName().toString();
if (autoName.contains("/")) {
autoName = autoName.substring(autoName.lastIndexOf("/") + 1);
}
if (autoName.contains("$")) {
autoName = autoName.substring(autoName.lastIndexOf("$") + 1);
}
autoName = autoName.replace("[", "Ar");
final int mySuffix;
if (suffix.containsKey(val.key)) {
mySuffix = suffix.get(val.key);
} else {
mySuffix = currentSuffix++;
suffix.put(val.key, mySuffix);
}
autoName = "m" + autoName + "_" + mySuffix;
final Atom nameAtom = Atom.findOrCreateAsciiAtom(autoName);
names.put(val.getNumber(), nameAtom);
}
}
return names;
}
private static void debug(String s, Object ... args) {
if (DEBUG) { System.err.printf(s, args); }
}
private static void info(String s, Object ... args) {
if (DEBUG) { System.err.printf(s, args); }
}
}