438 lines
19 KiB
Java
438 lines
19 KiB
Java
/*
|
|
* 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.structure;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint.ExecutionOrder;
|
|
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint.IExecutionOrder;
|
|
import com.ibm.wala.ipa.callgraph.Entrypoint;
|
|
import com.ibm.wala.ipa.summaries.VolatileMethodSummary;
|
|
import com.ibm.wala.types.TypeReference;
|
|
import com.ibm.wala.util.ssa.SSAValueManager;
|
|
import com.ibm.wala.util.ssa.TypeSafeInstructionFactory;
|
|
|
|
/**
|
|
* Aids in handling code to be inserted at given points into the model.
|
|
*
|
|
* Overload this class to change the structure of the model. When the model is being built the
|
|
* enterLABEL-functions are called when ever a label gets stepped over.
|
|
* <p>
|
|
* You can then add instructions to the body using the insts-Instruction factory. Instructions
|
|
* don't have to be in ascending order. Instead they will be sorted by their IIndex once the model
|
|
* gets finished.
|
|
*
|
|
* If you want to add loops to the model you might want to have a look at AndroidModelParameterManager
|
|
* which aids in keeping track of SSA-Variables and adding Phi-Functions.
|
|
*
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint.ExecutionOrder
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.impl.DexFakeRootMethod
|
|
* @see com.ibm.wala.dalvik.ipa.callgraph.androidModel.parameters.AndroidModelParameterManager
|
|
*
|
|
* @author Tobias Blaschke <code@tobiasblaschke.de>
|
|
* @since 2013-09-07
|
|
*/
|
|
public abstract class AbstractAndroidModel {
|
|
private static final Logger logger = LoggerFactory.getLogger(AbstractAndroidModel.class);
|
|
|
|
private ExecutionOrder currentSection = null;
|
|
protected VolatileMethodSummary body = null;
|
|
protected TypeSafeInstructionFactory insts = null;
|
|
protected SSAValueManager paramManager = null;
|
|
protected Iterable<? extends Entrypoint> entryPoints = null;
|
|
private IExecutionOrder lastQueriedMethod = null; // Used for sanity checks only
|
|
|
|
//
|
|
// Helper functions
|
|
//
|
|
|
|
/**
|
|
* Return a List of all Types returned by functions between start (inclusive) and end (exclusive).
|
|
*
|
|
* @return That list
|
|
* @throws IllegalArgumentException if an EntryPoint was not an AndroidEntryPoint
|
|
*/
|
|
protected List<TypeReference> returnTypesBetween(IExecutionOrder start, IExecutionOrder end) {
|
|
assert (start != null) : "The argument start was null";
|
|
assert (end != null) : "The argument end was null";
|
|
|
|
List<TypeReference> returnTypes = new ArrayList<TypeReference>();
|
|
for (Entrypoint ep : this.entryPoints) {
|
|
if (ep instanceof AndroidEntryPoint) {
|
|
AndroidEntryPoint aep = (AndroidEntryPoint)ep;
|
|
if ((aep.compareTo(start) >= 0) &&
|
|
(aep.compareTo(end) <= 0)) {
|
|
if (! (aep.getMethod().getReturnType().equals(TypeReference.Void) ||
|
|
aep.getMethod().getReturnType().isPrimitiveType ())) {
|
|
if (! returnTypes.contains(aep.getMethod().getReturnType())) { // TODO: Use a set?
|
|
returnTypes.add(aep.getMethod().getReturnType());
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Entrypoint (given to Constructor) is not an AndroidEntryPoint!");
|
|
}
|
|
}
|
|
|
|
return returnTypes;
|
|
}
|
|
//
|
|
// The rest :)
|
|
//
|
|
|
|
/**
|
|
* If you don't intend to use the paramManager, you can pass null. However all other parameters are required.
|
|
*
|
|
* @param body The MethodSummary to add instructions to
|
|
* @param insts Will be used to generate the instructions
|
|
* @param paramManager aids in handling SSA-Values
|
|
* @param entryPoints This iterable has to contain only instances of AnroidEntryPoint.
|
|
*/
|
|
public AbstractAndroidModel(VolatileMethodSummary body, TypeSafeInstructionFactory insts,
|
|
SSAValueManager paramManager, Iterable<? extends Entrypoint> entryPoints) {
|
|
|
|
if (body == null) {
|
|
throw new IllegalArgumentException("The argument body may not be null.");
|
|
}
|
|
if (insts == null) {
|
|
throw new IllegalArgumentException("The argument insts may not be null.");
|
|
}
|
|
if (entryPoints == null) {
|
|
throw new IllegalArgumentException("The argument entryPoints may not be null.");
|
|
}
|
|
//if (!(entryPoints.hasNext())) {
|
|
// throw new IllegalArgumentException("The iterable entryPoints may not be empty.");
|
|
//}
|
|
this.body = body;
|
|
this.insts = insts;
|
|
this.paramManager = paramManager;
|
|
this.entryPoints = entryPoints;
|
|
}
|
|
|
|
/**
|
|
* Determines for an AndroidEntryPoint if a label got skipped over.
|
|
*
|
|
* If a label got skipped over special handling code has to be inserted before
|
|
* the entrypoints invocation.
|
|
*
|
|
* This function is expected to be called on entrypoints in ascending order.
|
|
*
|
|
* You are expected to call {@link #enter(ExecutionOrder, int)} iff a
|
|
* Label got skipped over.
|
|
*
|
|
* @param order The entrypoint in question
|
|
* @return true if a label got stepped over
|
|
* @throws IllegalArgumentException If the entrypoints weren't in ascending order
|
|
* @throws IllegalStateException if you didn't call enter()
|
|
*/
|
|
public final boolean hadSectionSwitch(IExecutionOrder order) {
|
|
if (order == null) {
|
|
throw new IllegalArgumentException("the argument order may not be null.");
|
|
}
|
|
if (this.currentSection == null) {
|
|
if (this.lastQueriedMethod != null) {
|
|
throw new IllegalStateException("You didn't call AbstractAndroidModel.enter(AT_FIRST) after a section-switch");
|
|
}
|
|
// The first method is added to the model
|
|
// don't set this.currentSection here or enter() will not actually enter;)
|
|
this.lastQueriedMethod = order;
|
|
return true;
|
|
}
|
|
if (order.compareTo(lastQueriedMethod) < 0) {
|
|
throw new IllegalArgumentException("This method is meant to be called on AndoidEntrypoints in ascending order");
|
|
}
|
|
if ((currentSection.compareTo(lastQueriedMethod.getSection()) != 0) &&
|
|
(order.getSection().compareTo(lastQueriedMethod.getSection()) == 0)) {
|
|
throw new IllegalStateException("You didn't call AbstractAndroidModel.enter(" + order.getSection() + ") after a section-switch");
|
|
}
|
|
|
|
this.lastQueriedMethod = order;
|
|
return (this.currentSection.compareTo(order.getSection()) < 0);
|
|
}
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.AT_FIRST got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.AT_FIRST, int)} instead.
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterAT_FIRST(int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.BEFORE_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.BEFORE_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterBEFORE_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.START_OF_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.START_OF_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterSTART_OF_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.MIDDLE_OF_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.MIDDLE_OF_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterMIDDLE_OF_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.MULTIPLE_TIMES_IN_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterMULTIPLE_TIMES_IN_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.END_OF_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.END_OF_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterEND_OF_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.AFTER_LOOP got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.AFTER_LOOP, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterAFTER_LOOP (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when Label ExecutionOrder.AT_LAST got stepped over.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #enter(ExecutionOrder.AT_LAST, int)} instead
|
|
*
|
|
* Sideeffects: currentSection is updated, instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int enterAT_LAST (int PC) { return PC; };
|
|
|
|
/**
|
|
* Gets called when the model gets finished.
|
|
*
|
|
* In most cases you don't want to invoke this function directly but to use
|
|
* {@link #finish(int)} instead
|
|
*
|
|
* Sideeffects: instructions are inserted into the body
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*/
|
|
protected int leaveAT_LAST (int PC) { return PC; };
|
|
|
|
/**
|
|
* Dispatches to the enterLABEL-functions. Does also call functions to any labels that
|
|
* got stepped over.
|
|
*
|
|
* @param section The Section to enter
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*
|
|
* @throws IllegalArgumentException if you didn't use sections in ascending order, pc is negative
|
|
*/
|
|
public int enter (ExecutionOrder section, int PC) {
|
|
section = section.getSection(); // Just to be shure
|
|
|
|
if ((this.currentSection != null) && (this.currentSection.compareTo(section) >= 0)) {
|
|
if (this.currentSection.compareTo(section) == 0) {
|
|
logger.error("You entered {} twice! Ignoring second atempt.", section);
|
|
} else {
|
|
throw new IllegalArgumentException("Sections must be in ascending order! When trying to " +
|
|
"enter " + this.currentSection.toString() + " from " + section.toString());
|
|
}
|
|
}
|
|
|
|
if (PC < 0) {
|
|
throw new IllegalArgumentException("The PC can't be negative!");
|
|
}
|
|
|
|
|
|
if (section.compareTo(AndroidEntryPoint.ExecutionOrder.AT_FIRST) == 0) {
|
|
if (this.currentSection != null) {
|
|
throw new IllegalArgumentException("Sections must be in ascending order!");
|
|
}
|
|
}
|
|
if ((this.currentSection == null) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.AT_FIRST) >= 0)) {
|
|
logger.info("ENTER: AT_FIRST");
|
|
PC = enterAT_FIRST(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.AT_FIRST;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.AT_FIRST) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.BEFORE_LOOP) >= 0)) {
|
|
logger.info("ENTER: BEFORE_LOOP");
|
|
PC = enterBEFORE_LOOP(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.BEFORE_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.BEFORE_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.START_OF_LOOP) >= 0)) {
|
|
logger.info("ENTER: START_OF_LOOP");
|
|
PC = enterSTART_OF_LOOP(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.START_OF_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.START_OF_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.MIDDLE_OF_LOOP) >= 0)) {
|
|
logger.info("ENTER: MIDDLE_OF_LOOP");
|
|
PC = enterMIDDLE_OF_LOOP(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.MIDDLE_OF_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.MIDDLE_OF_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) >= 0)) {
|
|
PC = enterMULTIPLE_TIMES_IN_LOOP(PC);
|
|
logger.info("ENTER: MULTIPLE_TIMES_IN_LOOP");
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.MULTIPLE_TIMES_IN_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.END_OF_LOOP) >= 0)) {
|
|
logger.info("ENTER: END_OF_LOOP");
|
|
PC = enterEND_OF_LOOP(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.END_OF_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.END_OF_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.AFTER_LOOP) >= 0)) {
|
|
logger.info("ENTER: AFTER_LOOP");
|
|
PC = enterAFTER_LOOP(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.AFTER_LOOP;
|
|
}
|
|
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.AFTER_LOOP) <= 0) &&
|
|
(section.compareTo(AndroidEntryPoint.ExecutionOrder.AT_LAST) >= 0)) {
|
|
logger.info("ENTER: AT_LAST");
|
|
PC = enterAT_LAST(PC);
|
|
this.currentSection = AndroidEntryPoint.ExecutionOrder.AT_LAST;
|
|
}
|
|
|
|
return PC;
|
|
}
|
|
|
|
/**
|
|
* Calls all remaining enterLABEL-functions, finally calls leaveAT_LAST.
|
|
*
|
|
* Then Locks the model and frees some memory.
|
|
*
|
|
* @param PC Program Counter instructions shall be placed at. In most cases
|
|
* you'll simply pass body.getNextProgramCounter()
|
|
* @return Program Counter after insertion of the code
|
|
*
|
|
* @throws IllegalStateException if called on an empty model
|
|
*/
|
|
public int finish (int PC) { /* package private */
|
|
if (this.currentSection == null) {
|
|
throw new IllegalStateException("Called finish() on a model that doesn't " +
|
|
"contain any sections - an empty model of" + this.body.getMethod().toString());
|
|
}
|
|
if ((this.currentSection.compareTo(AndroidEntryPoint.ExecutionOrder.AT_LAST) < 0)) {
|
|
PC = enter(AndroidEntryPoint.ExecutionOrder.AT_LAST, PC);
|
|
}
|
|
PC = leaveAT_LAST(PC);
|
|
|
|
// Lock everything:
|
|
currentSection = new ExecutionOrder(Integer.MAX_VALUE);
|
|
// Free memory:
|
|
body = null;
|
|
insts = null;
|
|
paramManager = null;
|
|
entryPoints = null;
|
|
lastQueriedMethod = null;
|
|
|
|
return PC;
|
|
}
|
|
}
|