com.ibm.wala.ipa.summaries.VolatileMethodSummary Maven / Gradle / Ivy
/*
* 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
* 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.core.util.strings.Atom;
import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.ssa.SSAGotoInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.MemberReference;
import com.ibm.wala.types.TypeReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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 #reserveProgramCounters(int)} * {@link #allowReserved(boolean)}
*
* @see com.ibm.wala.ssa.SSAInstructionFactory
* @see com.ibm.wala.ipa.callgraph.impl.FakeRootMethod
* @see com.ibm.wala.ipa.summaries.MethodSummary
* @author Tobias Blaschke <[email protected]>
* @since 2013-09-08
*/
public class VolatileMethodSummary {
private static final boolean DEBUG = false;
private boolean allowReservedPC = false;
private final MethodSummary summary;
private List instructions = new ArrayList<>();
private final Map localNames = new HashMap<>();
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);
}
@Override
public SSAInstruction copyForSSA(SSAInstructionFactory insts, int[] defs, int[] uses) {
throw new IllegalStateException();
}
@Override
public int hashCode() {
return 12384;
}
@Override
public boolean isFallThrough() {
return true;
}
@Override
public String toString(SymbolTable symbolTable) {
return "Reserved Slot";
}
@Override
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;
return false;
}
/**
* Not exactly dual to {@link #isFree(int)}.
*
*
Returns whether an instruction is located at ProgramCounter. Thus it is a shortcut to {@link
* #getStatementAt(int)} != null.
*
*
It is not the exact dual to {@link #isFree(int)} 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(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 %s to %s\n", 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) {
if (DEBUG) {
System.err.printf("No instruction at iindex %d\n", i);
}
this.summary.addStatement(null);
} else if (inst == RESERVED) {
// replace reserved slots by 'goto next' statements
this.summary.addStatement(new SSAGotoInstruction(i, i + 1));
} else {
if (DEBUG) {
System.err.printf("Adding @%s: ", 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
* #overwriteStatement(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 merge) {
localNames.putAll(merge);
}
/** A mapping from SSA-Values to Variable-names. */
public Map 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 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(int)}) 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() {
while (isUsed(this.currentProgramCounter) || isReserved(this.currentProgramCounter)) {
this.currentProgramCounter++;
}
while (this.instructions.size() < this.currentProgramCounter) this.instructions.add(null);
return this.currentProgramCounter;
}
/**
* 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 #isFactory()}
*
* @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 #isStatic()}
*
* @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. */
@Override
public java.lang.String toString() {
return "VolatileMethodSummary of " + this.summary.toString();
}
}