/* * 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.impl; import java.util.Comparator; import com.ibm.wala.dalvik.util.AndroidEntryPointLocator.AndroidPossibleEntryPoint; import com.ibm.wala.dalvik.util.AndroidComponent; import com.ibm.wala.dalvik.util.AndroidTypes; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeName; import com.ibm.wala.types.TypeReference; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.util.strings.StringStuff; import com.ibm.wala.util.strings.Atom; /** * An AdnroidEntryPoint is basically the same as a DexEntryPoint. The Difference is, that further * actions are taken to give a list of EntryPoints a specific order an to add information on how * the entrypoints are to be called in a model which represents the usage of an android application. * <p> * You might want to use AndroidEntryPointLocator to generate a list of all EntryPoints in an * android application * * @see com.ibm.wala.dalvik.util.AndroidEntryPointLocator * * @author Tobias Blaschke <code@toiasblaschke.de> * @since 2013-09-01 */ public class AndroidEntryPoint extends DexEntryPoint { //implements AndroidEntryPoint.IExecutionOrder { public ExecutionOrder order; // XXX protect? protected AndroidComponent superType; public AndroidEntryPoint(AndroidPossibleEntryPoint p, IMethod method, IClassHierarchy cha, AndroidComponent inComponent) { super(method, cha); this.order = p.order; this.superType = inComponent; } public AndroidEntryPoint(AndroidPossibleEntryPoint p, IMethod method, IClassHierarchy cha) { super(method, cha); this.order = p.order; this.superType = AndroidComponent.from(method, cha); } public AndroidEntryPoint(ExecutionOrder o, IMethod method, IClassHierarchy cha, AndroidComponent inComponent) { this(o, method, cha); this.superType = inComponent; } public AndroidEntryPoint(ExecutionOrder o, IMethod method, IClassHierarchy cha) { super(method, cha); if (o == null) { throw new IllegalArgumentException("The execution order may not be null."); } this.order = o; this.superType = AndroidComponent.from(method, cha); } public AndroidComponent getComponent() { return this.superType; } /** * If the function is defined in a class that extends an Activity. */ public boolean isActivity() { IClassHierarchy cha = getClassHierarchy(); final TypeReference activity = AndroidTypes.Activity; return cha.isSubclassOf(method.getDeclaringClass(), cha.lookupClass(activity)); } public boolean belongsTo(AndroidComponent compo) { if ((compo == AndroidComponent.SERVICE) && (this.superType.equals(AndroidComponent.INTENT_SERVICE))) { return true; } return (this.superType.equals(compo)); } public boolean isMemberOf(Atom klass) { return method.getDeclaringClass().getName().toString().startsWith(klass.toString()); //IClassHierarchy cha = getClassHierarchy(); //final TypeReference type = TypeReference.find(ClassLoaderReference.Primordial, klass.toString()); //if (type == null) { // throw new IllegalArgumentException("Unable to look up " + klass.toString()); //} //return cha.isSubclassOf(method.getDeclaringClass(), cha.lookupClass(type)); } /** * Implement this interface to put entitys into the AndroidModel. * * Currently only AndroidEntryPoints are supportet directly. If you want to add other stuff you might want * to subclass AbstractAndroidModel. */ public interface IExecutionOrder extends Comparable<IExecutionOrder>{ /** * Returns an integer-representation of the ExecutionOrder. */ public int getOrderValue(); /** * AbstractAndroidModel inserts code at section switches. * * There are eight hardcoded sections. Sections are derived by rounding the integer-representation. * * @return the section of this entity */ public ExecutionOrder getSection(); } /** * AndroidEntryPoints have to be sorted before building the model. */ public static class ExecutionOrderComperator implements Comparator<AndroidEntryPoint> { @Override public int compare(AndroidEntryPoint a, AndroidEntryPoint b) { return a.order.compareTo(b.order); } } /** * The section is used to build classes of EntryPoints on how they are to be called. * * The section is represented by the last label passed before the entity is reached. * * @return the section of this entrypoint */ public ExecutionOrder getSection() { if (this.order.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return ExecutionOrder.AT_FIRST; if (this.order.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return ExecutionOrder.BEFORE_LOOP; if (this.order.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return ExecutionOrder.START_OF_LOOP; if (this.order.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return ExecutionOrder.MIDDLE_OF_LOOP; if (this.order.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return ExecutionOrder.MULTIPLE_TIMES_IN_LOOP; if (this.order.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return ExecutionOrder.END_OF_LOOP; if (this.order.compareTo(ExecutionOrder.AT_LAST) == -1) return ExecutionOrder.AFTER_LOOP; return ExecutionOrder.AT_LAST; } /** {@inheritDoc} */ public int getOrderValue() { return order.getOrderValue(); } public int compareTo(AndroidEntryPoint.IExecutionOrder o) { return this.order.compareTo(o); } /** * The ExecutionOrder is used to partially order EntryPoints. * * The order has to be understood inclusive! E.g. "after(END_OF_LOOP)" means that the position is __BEFORE__ the * loop is actually closed! * * Before building the model a list of AdroidEntryPoints is to be sorted by that criterion. * You can use AndroidEntryPoint.ExecutionOrderComperator for that task. */ public static class ExecutionOrder implements Comparable<IExecutionOrder>, IExecutionOrder { // This is an Enum-Style class /** Visit the EntryPoint once at the beginning of the model use that for initialization stuff */ public final static ExecutionOrder AT_FIRST = new ExecutionOrder(0); /** Basicly the same as AT_FIRST but visited after AT_FIRST */ public final static ExecutionOrder BEFORE_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8); /** Visit multiple times (endless) in the loop */ public final static ExecutionOrder START_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 2); /** Basicly the same as START_OF_LOOP */ public final static ExecutionOrder MIDDLE_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 3); /** Do multiple calls in the loop. Visited after MIDDLE_OF_LOOP, before EEN_OF_LOOP */ public final static ExecutionOrder MULTIPLE_TIMES_IN_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 4); /** Things in END_OF_LOOP are acutually part of the loop. Use AFTER_LOOP if you want them executed only once */ public final static ExecutionOrder END_OF_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 5); /** Basicly the same as AT_LAST but visited before */ public final static ExecutionOrder AFTER_LOOP = new ExecutionOrder (Integer.MAX_VALUE / 8 * 6); /** Last calls in the model */ public final static ExecutionOrder AT_LAST = new ExecutionOrder (Integer.MAX_VALUE / 8 * 7); /** This value getts used by the detection heuristic - It is not recommended for manual use. */ public final static ExecutionOrder DEFAULT = MIDDLE_OF_LOOP; private int value; /** * Unrecommended way to generate the Order based on an Integer. * * This method is handy when reading back files. In your code you should prefer the methods * {@link #after(IExecutionOrder)} and {@link #between(IExecutionOrder, IExecutionOrder)}. */ public ExecutionOrder(int val) { this.value=val; } /** * Unrecommended way to generate the Order based on a Label-String. * * This method is handy when reading back files. If you want to refer to a label you * should prefer the static members. */ public ExecutionOrder(String label) { if (label.equals("AT_FIRST")) { this.value = ExecutionOrder.AT_FIRST.getOrderValue(); return; } if (label.equals("BEFORE_LOOP")) { this.value = ExecutionOrder.BEFORE_LOOP.getOrderValue(); return; } if (label.equals("START_OF_LOOP")) { this.value = ExecutionOrder.START_OF_LOOP.getOrderValue(); return; } if (label.equals("MIDDLE_OF_LOOP")) { this.value = ExecutionOrder.MIDDLE_OF_LOOP.getOrderValue(); return; } if (label.equals("MULTIPLE_TIMES_IN_LOOP")) { this.value = ExecutionOrder.MULTIPLE_TIMES_IN_LOOP.getOrderValue(); return; } if (label.equals("END_OF_LOOP")) { this.value = ExecutionOrder.END_OF_LOOP.getOrderValue(); return; } if (label.equals("AFTER_LOOP")) { this.value = ExecutionOrder.AFTER_LOOP.getOrderValue(); return; } if (label.equals("AT_LAST")) { this.value = ExecutionOrder.AT_LAST.getOrderValue(); return; } throw new IllegalArgumentException("ExecutionOrder was constructed from an illegal label: " + label); } /** {@inheritDoc} */ public int getOrderValue() { return this.value; } /** * Use {@link #between(IExecutionOrder, IExecutionOrder)} instead. * * Does the internal calculations for the placement. */ private static ExecutionOrder between(int after, int before) { if ((before - after) == 1) { // It could be ok to warn here instead of throwing: Functions would be located at the same // 'slot' in this case. // An alternative solution could be to choose other values for before or after. throw new ArithmeticException("Precision to low when cascading the ExecutionOrder. You can prevent this error by " + "assigning the ExecutionOrders (e.g. using before() and after()) in a different order."); } if (after > before) { throw new IllegalArgumentException("The requested ordering could not be established due to the after-parameter " + "being greater than the before parameter! after=" + after + " before=" + before); } return new ExecutionOrder(after + (before - after) / 2); } /** * Use this to place a call to an EntryPoint between two other EntryPoint calls or ExecutionOrder "labels". * between() does not care about section-boundaries by itself! * * Use {@link between(IExecutionOrder[], IExecutionOrder[])} and use labels as additional placement-information * to prevent unexpected misplacement. * * @param after the call or "label" to be executed before this one * @param before the call or "label" to be executed after this one (inclusive) * @return A sortable object to represent the execution order * @throws ArithmeticException when the precision is no more suitable for further cascading * @throws IllegalArgumentException if parameter after is larger than before. * @throws NullPointerException if either parameter is null */ public static ExecutionOrder between(IExecutionOrder after, IExecutionOrder before) { if (after == null) { throw new NullPointerException("after may not be null"); } if (before == null) { throw new NullPointerException("after may not be null"); } return between(after.getOrderValue(), before.getOrderValue()); } /** * Helper for internal use. */ private static ExecutionOrder between(IExecutionOrder after, int before) { return between(after.getOrderValue(), before); } /** * Use this variant to refer to multiple locations. * * The minimum / maximum is computed before the placement of the ExecutionOrder. * * This method is intended to be more robust when changing the position-information of referred-to * ExecutionOrders. * * In any other means it behaves exactly like {@link #between(IExecutionOrder, IExecutionOrder)}. * * @param after the calls or "labels" to be executed before this one * @param before the calls or "labels" to be executed after this one (inclusive) * @return A sortable object to represent the execution order * @throws ArithmeticException when the precision is no more suitable for further cascading * @throws IllegalArgumentException if parameter after is larger than before. * @throws NullPointerException if either parameter is null */ public static ExecutionOrder between(IExecutionOrder[] after, IExecutionOrder[] before) { if ((after == null) || (after.length == 0)) { throw new NullPointerException("after may not be null or empty array"); } if ((before == null) || (before.length == 0)) { throw new NullPointerException("before may not be null or empty array"); } int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue(); for (IExecutionOrder i : before) if (min > i.getOrderValue()) min=i.getOrderValue(); return between(max, min); } public static ExecutionOrder between(IExecutionOrder after, IExecutionOrder[] before) { if ((before == null) || (before.length == 0)) { throw new NullPointerException("after may not be null or empty array"); } int min = Integer.MAX_VALUE; for (IExecutionOrder i : before) if (min > i.getOrderValue()) min=i.getOrderValue(); return between(after, min); } public static ExecutionOrder between(IExecutionOrder[] after, IExecutionOrder before) { if ((after == null) || (after.length == 0)) { throw new NullPointerException("after may not be null or empty array"); } if (before == null) { throw new NullPointerException("before may not be null"); } int max = Integer.MIN_VALUE; for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue(); return between(max, before.getOrderValue()); } /** * Place the call in the same section after the given call or "label". * * @param after the call to be executed before this one or label the call belongs to * @return A sortable object to represent the execution order * @throws ArithmeticException when the precision is no more suitable for further cascading * @throws NullPointerException if the parameter is null */ public static ExecutionOrder after (IExecutionOrder after) { if (after == null) { throw new NullPointerException("after may not be null"); } return after(after.getOrderValue()); } /** * Prefer {@link #after(IExecutionOrder)} whenever possible. */ public static ExecutionOrder after (int after) { return between(after, ((after / (Integer.MAX_VALUE / 8)) + 1) * (Integer.MAX_VALUE / 8)); } /** * Use this variant to refer to multiple locations. * * The maximum is computed before the placement of the ExecutionOrder. * * @param after the call to be executed before this one or label the call belongs to * @return A sortable object to represent the execution order * @throws ArithmeticException when the precision is no more suitable for further cascading * @throws NullPointerException if the parameter is null */ public static ExecutionOrder after (IExecutionOrder[] after) { if (after == null) { throw new NullPointerException("after may not be null"); } int max = Integer.MIN_VALUE; for (IExecutionOrder i : after) if (max < i.getOrderValue()) max=i.getOrderValue(); return after(max); } public static ExecutionOrder directlyBefore(IExecutionOrder before) { return new ExecutionOrder(before.getOrderValue() - 1); } public static ExecutionOrder directlyAfter(IExecutionOrder before) { return new ExecutionOrder(before.getOrderValue() + 1); } /** * {@inheritDoc} */ public ExecutionOrder getSection() { if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return ExecutionOrder.AT_FIRST; if (this.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return ExecutionOrder.BEFORE_LOOP; if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return ExecutionOrder.START_OF_LOOP; if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return ExecutionOrder.MIDDLE_OF_LOOP; if (this.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return ExecutionOrder.MULTIPLE_TIMES_IN_LOOP; if (this.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return ExecutionOrder.END_OF_LOOP; if (this.compareTo(ExecutionOrder.AT_LAST) == -1) return ExecutionOrder.AFTER_LOOP; return ExecutionOrder.AT_LAST; } @Override public int compareTo(IExecutionOrder o) { if (this.value == o.getOrderValue()) return 0; if (this.value > o.getOrderValue()) return 1; return -1; } @Override public String toString() { if (this.compareTo(ExecutionOrder.AT_FIRST) == 0) return "ExecutionOrder.AT_FIRST"; if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == 0) return "ExecutionOrder.BEFORE_LOOP"; if (this.compareTo(ExecutionOrder.START_OF_LOOP) == 0) return "ExecutionOrder.START_OF_LOOP"; if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == 0) return "ExecutionOrder.MIDDLE_OF_LOOP"; if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == 0) return "ExecutionOrder.MULTIPLE_TIMES_IN_LOOP"; if (this.compareTo(ExecutionOrder.END_OF_LOOP) == 0) return "ExecutionOrder.END_OF_LOOP"; if (this.compareTo(ExecutionOrder.AFTER_LOOP) == 0) return "ExecutionOrder.AFTER_LOOP"; if (this.compareTo(ExecutionOrder.AT_LAST) == 0) return "ExecutionOrder.AT_LAST"; if (this.compareTo(ExecutionOrder.BEFORE_LOOP) == -1) return "in section ExecutionOrder.AT_FIRST"; if (this.compareTo(ExecutionOrder.START_OF_LOOP) == -1) return "in section ExecutionOrder.BEFORE_LOOP"; if (this.compareTo(ExecutionOrder.MIDDLE_OF_LOOP) == -1) return "in section ExecutionOrder.START_OF_LOOP"; if (this.compareTo(ExecutionOrder.MULTIPLE_TIMES_IN_LOOP) == -1) return "in section ExecutionOrder.MIDDLE_OF_LOOP"; if (this.compareTo(ExecutionOrder.END_OF_LOOP) == -1) return "in section ExecutionOrder.MULTIPLE_TIMES_IN_LOOP"; if (this.compareTo(ExecutionOrder.AFTER_LOOP) == -1) return "in section ExecutionOrder.END_OF_LOOP"; if (this.compareTo(ExecutionOrder.AT_LAST) == -1) return "in section ExecutionOrder.AFTER_LOOP"; return "in section ExecutionOrder.AT_LAST"; } } @Override public boolean equals(Object o) { if (o instanceof AndroidEntryPoint) { AndroidEntryPoint other = (AndroidEntryPoint) o; return this.getMethod().equals(other.getMethod()); } else { return false; } } @Override public int hashCode() { return 3 * this.getMethod().hashCode(); } }