WALA/com.ibm.wala.core/src/com/ibm/wala/ipa/summaries/VolatileMethodSummary.java

611 lines
22 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.ipa.summaries;
import com.ibm.wala.ipa.summaries.MethodSummary;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.types.MemberReference;
import com.ibm.wala.types.TypeReference;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.ibm.wala.util.strings.Atom;
/**
* Instructions can be added in a non-ascending manner.
*
* The later position of the instruction is determined by it's iindex.
* Additionally this Summary may be instructed to prune unnecessary
* instructions.
*
* However don't go berserk with the iindex as this will consume loads of
* memory.
*
* You can get an ordinary MethodSummary using the {@link #getMethodSummary()}-Method.
*
* It extends the MethodSummarys capabilities by the functions:
* * {@link #getStatementAt(int)}
* * {@link #getStatementsAfter(int)}
* * {@link #reserveProgramCounters(int)}
* * {@link #allowReserved(boolean)}
*
* @see com.ibm.wala.dalvik.ssa.AndroidModelInstructionFectory
* @see com.ibm.wala.dalvik.ipa.callgraph.impl.DexFakeRootMethod
* @see com.ibm.wala.ipa.summaries.MethodSummary
*
* @author Tobias Blaschke <code@tobiasblaschke.de>
* @since 2013-09-08
*/
public class VolatileMethodSummary {
private static final boolean DEBUG = false;
private boolean allowReservedPC = false;
private MethodSummary summary;
private List<SSAInstruction> instructions = new ArrayList<SSAInstruction>();
private Map<Integer, Atom> localNames = new HashMap<Integer, Atom>();
private int currentProgramCounter = 0;
private boolean locked = false;
/**
* Placeholder for Reserved slots.
*/
private static final class Reserved extends SSAInstruction {
public Reserved () { super(SSAInstruction.NO_INDEX); }
public SSAInstruction copyForSSA (SSAInstructionFactory insts, int[] defs, int[] uses) {
throw new IllegalStateException();
}
public int hashCode () { return 12384; }
public boolean isFallThrough() { return true; }
public String toString (SymbolTable symbolTable) { return "Reserved Slot"; }
public void visit (IVisitor v) { throw new IllegalStateException(); }
}
private static final Reserved RESERVED = new Reserved();
/**
* @param summary a "real" summary methods get added to.
* @throws IllegalArgumentException if this summary is null or not empty
*/
public VolatileMethodSummary(MethodSummary summary) {
if (summary == null) {
throw new IllegalArgumentException("The given summary is null");
}
if (summary.getNumberOfStatements() > 0) {
throw new IllegalArgumentException("The given summary is not empty");
}
this.summary = summary;
}
/**
* @param programCounter the ProgramCounter to retrieve the Instruction from
* @return The instruction or null if there is none
* @throws IllegalArgumentException if the ProgramCounter is negative
*/
public SSAInstruction getStatementAt(int programCounter) {
if (programCounter < 0) {
throw new IllegalArgumentException("Program-Counter may not be negative!");
}
if (this.instructions.size() <= programCounter) {
return null;
}
if (this.instructions.get(programCounter).equals(RESERVED)) {
return null;
}
return this.instructions.get(programCounter);
}
/**
* Reserves an amount of ProgramCounters for later use.
*
* This method reserves a count of ProgramCounters and thus affects the value
* returned by getNextProgramCounter. It also marks these ProgramCounters as
* reserved so you can't use them unless you explicitly allow it by
* {@link #allowReserved(boolean)}.
*
* @param count The amount of ProgramCounters to reserve ongoing from the
* current ProgramCounter
* @throws IllegalArgumentException if the count is negative (a count of zero
* is however ok)
*/
public void reserveProgramCounters(int count) {
if (count < 0) {
throw new IllegalArgumentException("The count of ProgramCounters to reserve may not be negative");
}
for (int i=0; i<count; ++i) {
instructions.add(RESERVED);
}
currentProgramCounter += count;
}
/**
* (Dis-)allows the usage of reserved ProgramCounters.
*
* The setting of this function defaults to disallow upon class creation
*
* @param enable A value of true allows the usage of all reserved ProgramCounters
* @return the previous value of allowReserved
*/
public boolean allowReserved(boolean enable) {
boolean prev = this.allowReservedPC;
this.allowReservedPC = enable;
return prev;
}
/**
* Returns if the ProgramCounter is reserved.
*
* @param programCounter the ProgramCounter in question
* @return true if the position is reserved
* @throws IllegalArgumentException if the ProgramCounter is negative
*/
public boolean isReserved(int programCounter) {
if (programCounter < 0) {
throw new IllegalArgumentException("The Program-Counter may not be negative");
}
if (instructions.size() - 1 < programCounter) return false;
if (instructions.get(programCounter) == null) return false;
return (instructions.get(programCounter).equals(RESERVED));
}
/**
* Returns if the ProgramCounter is writable.
*
* The ProgramCounter is not writable if there is already an instruction located at that
* ProgramCounter or if the reserved ProgramCounters forbid usage. Thus the answer may
* depend on the setting of {@link #allowReserved(boolean)}.
*
* @param programCounter the ProgramCounter in question
* @return true if you may write to the location
* @throws IllegalArgumentException if the ProgramCounter is negative
*/
public boolean isFree(int programCounter) {
if (programCounter < 0) {
throw new IllegalArgumentException("The Program-Counter may not be negative");
}
if (instructions.size() - 1 < programCounter) return true;
if (instructions.get(programCounter) == null) return true;
if (instructions.get(programCounter).equals(RESERVED)) return false;
return false;
}
/**
* Not exactly dual to {@link #isFree(boolean)}.
*
* Returns whether an instruction is located at ProgramCounter. Thus it is a shortcut
* to {@link #getStatementAt(int)} != null.
* <p>
* It is not the exact dual to {@link #isFree(boolean)} as it does not consider reserved
* ProgramCounters.
*
* @param programCounter the ProgramCounter in question
* @return true if there's an instruction at that program counter
* @throws IllegalArgumentException if the ProgramCounter is negative
*/
public boolean isUsed(int programCounter) {
if (programCounter < 0) {
throw new IllegalArgumentException("The Program-Counter may not be negative");
}
if (instructions.size() - 1 < programCounter) return false;
if (instructions.get(programCounter) == null) return false;
if (instructions.get(programCounter).equals(RESERVED)) return false;
return true;
}
/**
* Like {@link addStatement(SSAInstructionWithPC <? extends SSAInstruction>) but may replace an existing one.
*
* @param statement The statement to add without care of overwriting
* @return true if a statement has actually been overwritten
* @throws IllegalStateException if you may not write to the ProgramCounter due to
* the setting of {@link #allowReserved(boolean)} or {@link #getMethodSummary()} has
* been called and thus this summary got locked.
* @throws NullPointerException if statement is null
* @throws IllegalArgumentException if the statement has set an invalid ProgramCounter
*/
public boolean overwriteStatement(SSAInstruction statement) {
if (this.locked) {
throw new IllegalStateException("Summary locked due to call to getMethodSummary().");
}
if (statement == null) {
throw new NullPointerException("Statement is null!");
}
if (statement.iindex < 0) {
throw new IllegalArgumentException("Statement has a negative iindex");
}
if ((!this.allowReservedPC) && isReserved(statement.iindex)) {
throw new IllegalStateException("ProgramCounter " + statement.iindex + " is reserved! Use allowReserved(true).");
}
if (statement.iindex > this.currentProgramCounter) {
throw new IllegalArgumentException("IIndex " + statement.iindex + " is greater than currentProgramCounter. Use getNextProgramCounter.");
}
boolean didOverwrite = isUsed(statement.iindex);
while (this.instructions.size() - 1 < statement.iindex) this.instructions.add(null);
if (DEBUG) { System.err.printf("Setting {} to {}", statement.iindex, statement); }
this.instructions.set(statement.iindex, statement);
return didOverwrite;
}
/**
* Generates the MethodSummary and locks class.
*
* @throws IllegalStateException if you altered the referenced (by constructor) summary
* @return the finished MethodSummary
*/
public MethodSummary getMethodSummary() {
if (locked) {
// Already generated
return this.summary;
}
if (summary.getNumberOfStatements() > 0) {
throw new IllegalStateException("Meanwhile Statements have been added to the summary given " +
"to the constructor. This behavior is not supported!");
}
this.locked = true;
for (int i = 0; i < this.instructions.size(); ++i) {
final SSAInstruction inst = this.instructions.get(i);
if ((inst == null) || (inst == RESERVED)) {
if (DEBUG) { System.err.printf("No instruction at iindex {}", i); }
} else {
if (DEBUG) { System.err.printf("Adding @{}: ", inst); }
this.summary.addStatement(inst);
}
}
// Let the GC free instructions..
this.instructions = null;
return this.summary;
}
// /**
// * Re-enable write access to VolatileMethodSummary (CAUTION...).
// *
// * On a call to {@link #getMethodSummary()} the AndroidModelMethodSummary gets locked
// * to prevent unintended behaviour.
// *
// * Through the call of this function you gain back write access. However you should
// * know what you are doing as the "exported" MethodSummary will not get updated. A
// * AndroidModelMethodSummary of course starts in unlocked state.
// */
//public void unlock() {
// this.locked = false;
//}
//
// Now for the stuff you should be familiar with from MethodSummary, but with a view
// more checks
//
/**
* Adds a statement to the MethodSummary.
*
* @param statement The statement to be added
* @throws IllegalStateException if you may not write to the ProgramCounter due to
* the setting of {@link #allowReserved(boolean)} or {@link #getMethodSummary()} has
* been called and thus this summary got locked.
* @throws NullPointerException if statement is null
* @throws IllegalArgumentException if the statement has set an invalid ProgramCounter or
* if there is already a statement at the statements iindex. In this case you can use
* {@link #overwritetatement(SSAInstruction)}.
*/
public void addStatement(SSAInstruction statement) {
if (isUsed(statement.iindex)) {
throw new IllegalArgumentException("ProgramCounter " + statement.iindex + " is in use! By " +
getStatementAt(statement.iindex) + " Use overwriteStatement().");
}
overwriteStatement(statement);
}
/**
* Optionally add a name for a local variable.
*/
public void setLocalName(final int number, final String name) {
localNames.put(number, Atom.findOrCreateAsciiAtom(name));
}
/**
* Set localNames merges with existing names.
*
* If a key in merge exists the value is overwritten if not the value is
* kept (it's a putAll on the internal map).
*/
public void setLocalNames(Map<Integer, Atom> merge) {
localNames.putAll(merge);
}
/**
* A mapping from SSA-Values to Variable-names.
*/
public Map<Integer, Atom> getLocalNames() {
return localNames;
}
/**
* Assigns a new Constant to a SSA-Value.
*
* @throws IllegalStateException if you redefine a constant or use the number of an existent
* SSA-Variable
* @throws IllegalArgumentException if value is null or negative
*/
public void addConstant(java.lang.Integer vn, ConstantValue value) {
if ((summary.getConstants() != null) && (summary.getConstants().containsKey(vn))) {
throw new IllegalStateException("You redefined a constant at number " + vn);
}
if (vn <= 0) {
throw new IllegalArgumentException("SSA-Value may not be zero or negative.");
}
this.summary.addConstant(vn, value);
}
/**
* Adds posion to the function.
*
* This call gets passed directly to the internal MethodSummary.
*/
public void addPoison(java.lang.String reason) {
this.summary.addPoison(reason);
}
/**
* Retrieves a mapping from SSA-Number to a constant.
*
* You can add Constants using the function {@link addConstant(java.lang.Integer, ConstantValue)}.
* A call to this function gets passed directly to the internal MethodSummary.
*
* @return a mapping from SSA-Number to assigned ConstantValue
*/
public java.util.Map<java.lang.Integer,ConstantValue> getConstants() {
return this.summary.getConstants();
}
/**
* Retrieve the Method this Summary implements.
*
* You'll get a MemberReference which contains the declaring class (which should be the
* FakeRootClass in most cases) and the signature of the method.
*
* This call gets passed directly to the internal MethodSummary.
*
* @return the implemented method as stated above
*/
public MemberReference getMethod() {
return this.summary.getMethod();
}
/**
* Gets you a non-reserved ProgramCounter you can write to.
*
* This function returns the next ProgramCounter for which not({@link #isUsed(ProgramCounter)})
* holds. Thus it will _not_ give you a ProgramCounter which is reserved even if you enabled
* writing to reserved ProgramCounters using {@link #allowReserved(boolean)}! You'll have to
* keep track of them on your own.
*
* @return A non-reserved writable ProgramCounter
*/
public int getNextProgramCounter() {
this.currentProgramCounter++;
while (this.instructions.size() < this.currentProgramCounter) this.instructions.add(null);
int pc = this.currentProgramCounter;
return pc;
}
/**
* Get the count of parameters of the Method this Summary implements.
*
* This call gets passed directly to the internal MethodSummary.
*
* @return Number of parameters
*/
public int getNumberOfParameters() {
return this.summary.getNumberOfParameters();
}
/**
* Gets you the TypeReference of a parameter.
*
* This call gets passed directly to the internal MethodSummary after some checks.
*
* @return the TypeReference of the i-th parameter.
* @throws IllegalArgumentException if the parameter is zero or negative
* @throws ArrayIndexOutOfBoundsException if the parameter is to large
*/
public TypeReference getParameterType(int i) {
if (i <= 0) {
throw new IllegalArgumentException("The parater number may not be zero or negative! " + i + " given");
}
if (i >= this.summary.getNumberOfParameters() ) {
throw new ArrayIndexOutOfBoundsException ("No such parameter index: " + i);
}
return this.summary.getParameterType(i);
}
/**
* Retrieves the poison set using {@link #addPoison(java.lang.String)}
*
* @return The poison-String
*/
public java.lang.String getPoison() {
return this.summary.getPoison();
}
/**
* Retrieves the value of Poison-Level.
*
* This call gets passed directly to the internal MethodSummary.
*
* @return the poison level
*/
public byte getPoisonLevel() {
return this.summary.getPoisonLevel();
}
/**
* Retrieves the return-type of the Function whose body this Summary implements.
*
* This call gets passed directly to the internal MethodSummary.
*/
public TypeReference getReturnType() {
return this.summary.getReturnType();
}
/**
* Get all statements added to the Summary.
*
* This builds a copy of the internal list and may contain 'null'-values if no
* instruction has been placed at a particular pc.
*
* @return The statements of the summary
*/
public SSAInstruction[] getStatements() {
SSAInstruction[] ret = new SSAInstruction[this.instructions.size()];
ret = this.instructions.toArray(ret);
// Remove Reserved
for (int i=0; i<ret.length; ++i) {
if (ret[i].equals(RESERVED)) {
ret[i] = null;
}
}
return ret;
}
/**
* Returns if Poison has been added using {@link #addPoison(java.lang.String)}.
*
* This call gets passed directly to the internal MethodSummary.
*
* @return true if poison has been added
*/
public boolean hasPoison() {
return this.summary.hasPoison();
}
/**
* Returns if the implemented method is a factory.
*
* This call gets passed directly to the internal MethodSummary.
*
* @return true if it's a factory
*/
public boolean isFactory() {
return this.summary.isFactory();
}
/**
* Return if the implemented method is a native one (which it shouldn't be).
*
* This call gets passed directly to the internal MethodSummary.
*
* @return almost always false
*/
public boolean isNative() {
return this.summary.isNative();
}
/**
* Return if the implemented method is static.
*
* A static method may not access non-static (and thus instance-specific) content.
*
* @return true if the method is static.
*/
public boolean isStatic() {
return this.summary.isStatic();
}
/**
* Set the value returned by {@link #getFactory()}
*
* @throws IllegalStateException if summary was locked
*/
public void setFactory(boolean b) {
if (this.locked) {
throw new IllegalStateException("Summary is locked. Unlock using unlock()");
}
this.summary.setFactory(b);
}
/**
* Set the value returned by {@link #getPoisonLevel()}
*
* @throws IllegalStateException if summary was locked
*/
public void setPoisonLevel(byte b) {
if (this.locked) {
throw new IllegalStateException("Summary is locked. Unlock using unlock()");
}
this.summary.setPoisonLevel(b);
}
/**
* Set the value returned by {@link #getStatic()}
*
* @throws IllegalStateException if summary was locked
*/
public void setStatic(boolean b) {
if (this.locked) {
throw new IllegalStateException("Summary is locked. Unlock using unlock()");
}
this.summary.setStatic(b);
}
/**
* Generates a String-Representation of an instance of the class.
*/
public java.lang.String toString() {
return "VolatileMethodSummary of " + this.summary.toString();
}
}