/*
* Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
* Systems and Internet Infrastructure Security Laboratory
*
* Author: Damien Octeau
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.psu.cse.siis.ic3;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import soot.Hierarchy;
import soot.MethodOrMethodContext;
import soot.Scene;
import soot.SceneTransformer;
import soot.SootClass;
import soot.SootMethod;
import soot.Type;
import soot.jimple.infoflow.entryPointCreators.AndroidEntryPointConstants;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import edu.psu.cse.siis.coal.AnalysisParameters;
import edu.psu.cse.siis.coal.PropagationTimers;
public class EntryPointMappingSceneTransformer extends SceneTransformer {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static SootClass activityClass = null;
private static SootClass serviceClass = null;
private static SootClass gcmBaseIntentServiceClass = null;
private static SootClass receiverClass = null;
private static SootClass providerClass = null;
private static SootClass applicationClass = null;
private final Set<String> entryPointClasses;
private final Map<String, Set<String>> callbackMethods;
private final Map<SootMethod, Set<String>> entryPointMap;
private final Set<SootMethod> visitedEntryPoints = new HashSet<>();
public EntryPointMappingSceneTransformer(Set<String> entryPointClasses,
Map<String, Set<String>> callbackMethods, Map<SootMethod, Set<String>> entryPointMap) {
this.entryPointClasses = entryPointClasses;
this.callbackMethods = callbackMethods;
this.entryPointMap = entryPointMap;
}
@Override
protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) {
PropagationTimers.v().totalTimer.start();
Timers.v().entryPointMapping.start();
// Set<String> signatures = new HashSet<>();
Map<SootMethod, Set<String>> entryPointMap = this.entryPointMap;
if (logger.isDebugEnabled()) {
Set<String> difference = new HashSet<>(this.callbackMethods.keySet());
difference.removeAll(entryPointClasses);
if (difference.size() == 0) {
logger.debug("Difference size is 0");
} else {
logger.debug("Difference is " + difference);
}
}
// Set<String> lifecycleMethods = new HashSet<>();
// lifecycleMethods.addAll(AndroidEntryPointConstants.getActivityLifecycleMethods());
// lifecycleMethods.addAll(AndroidEntryPointConstants.getApplicationLifecycleMethods());
// lifecycleMethods.addAll(AndroidEntryPointConstants.getBroadcastLifecycleMethods());
// lifecycleMethods.addAll(AndroidEntryPointConstants.getContentproviderLifecycleMethods());
// lifecycleMethods.addAll(AndroidEntryPointConstants.getServiceLifecycleMethods());
activityClass = Scene.v().getSootClass(AndroidEntryPointConstants.ACTIVITYCLASS);
serviceClass = Scene.v().getSootClass(AndroidEntryPointConstants.SERVICECLASS);
gcmBaseIntentServiceClass =
Scene.v().getSootClass(AndroidEntryPointConstants.GCMBASEINTENTSERVICECLASS);
receiverClass = Scene.v().getSootClass(AndroidEntryPointConstants.BROADCASTRECEIVERCLASS);
providerClass = Scene.v().getSootClass(AndroidEntryPointConstants.CONTENTPROVIDERCLASS);
applicationClass = Scene.v().getSootClass(AndroidEntryPointConstants.APPLICATIONCLASS);
if (logger.isDebugEnabled()) {
logger.debug(this.callbackMethods.toString());
}
for (String entryPoint : entryPointClasses) {
// if (!entryPointClasses.contains(entryPoint)) {
// System.err.println("Warning: " + entryPoint + " is not an entry point");
// continue;
// }
SootClass entryPointClass = Scene.v().getSootClass(entryPoint);
List<MethodOrMethodContext> callbacks = new ArrayList<>();
// Add methods for component.
boolean knownComponentType = addLifecycleMethods(entryPointClass, callbacks);
for (SootMethod method : entryPointClass.getMethods()) {
String methodName = method.getName();
if (methodName.equals(SootMethod.constructorName)
|| methodName.equals(SootMethod.staticInitializerName) || !knownComponentType) {
callbacks.add(method);
}
}
Set<String> callbackMethodStrings = this.callbackMethods.get(entryPoint);
if (callbackMethodStrings != null) {
for (String callbackMethodString : callbackMethodStrings) {
if (!Scene.v().containsMethod(callbackMethodString)) {
if (logger.isWarnEnabled()) {
logger.warn("Warning: " + callbackMethodString + " is not in scene");
}
continue;
}
SootMethod method = Scene.v().getMethod(callbackMethodString);
// Add constructors for callbacks.
for (SootMethod potentialInit : method.getDeclaringClass().getMethods()) {
if (potentialInit.isPrivate()) {
continue;
}
String name = potentialInit.getName();
if (name.equals(SootMethod.constructorName)) {
addConstructorStack(potentialInit, callbacks);
} else if (name.equals(SootMethod.staticInitializerName)) {
callbacks.add(potentialInit);
}
}
callbacks.add(method);
}
}
if (logger.isDebugEnabled()) {
logger.debug(callbacks.toString());
}
ReachableMethods reachableMethods =
new ReachableMethods(Scene.v().getCallGraph(), callbacks.iterator(), null);
reachableMethods.update();
for (Iterator<MethodOrMethodContext> iter = reachableMethods.listener(); iter.hasNext();) {
SootMethod method = iter.next().method();
if (!AnalysisParameters.v().isAnalysisClass(method.getDeclaringClass().getName())) {
continue;
}
if (logger.isDebugEnabled()) {
logger.debug(method.toString());
}
Set<String> entryPoints = entryPointMap.get(method);
if (entryPoints == null) {
entryPoints = new HashSet<>();
entryPointMap.put(method, entryPoints);
}
entryPoints.add(entryPoint);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Entry points");
logger.debug(entryPointMap.toString());
CallGraph cg = Scene.v().getCallGraph();
Iterator<Edge> it = cg.listener();
StringBuilder stringBuilder = new StringBuilder("Call graph:\n");
while (it.hasNext()) {
soot.jimple.toolkits.callgraph.Edge e = it.next();
stringBuilder.append("" + e.src() + e.srcStmt() + " =" + e.kind() + "=> " + e.tgt() + "\n");
}
logger.debug(stringBuilder.toString());
}
Timers.v().entryPointMapping.end();
PropagationTimers.v().totalTimer.end();
}
private boolean addLifecycleMethods(SootClass entryPointClass,
List<MethodOrMethodContext> callbacks) {
boolean result = true;
Hierarchy hierarchy = Scene.v().getActiveHierarchy();
if (hierarchy.isClassSubclassOf(entryPointClass, activityClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getActivityLifecycleMethods(), callbacks);
} else if (hierarchy.isClassSubclassOf(entryPointClass, gcmBaseIntentServiceClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getGCMIntentServiceMethods(), callbacks);
} else if (hierarchy.isClassSubclassOf(entryPointClass, serviceClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getServiceLifecycleMethods(), callbacks);
} else if (hierarchy.isClassSubclassOf(entryPointClass, receiverClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getBroadcastLifecycleMethods(), callbacks);
} else if (hierarchy.isClassSubclassOf(entryPointClass, providerClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getContentproviderLifecycleMethods(), callbacks);
} else if (hierarchy.isClassSubclassOf(entryPointClass, applicationClass)) {
addLifecycleMethodsHelper(entryPointClass,
AndroidEntryPointConstants.getApplicationLifecycleMethods(), callbacks);
} else {
System.err.println("Unknown entry point type: " + entryPointClass);
result = false;
}
return result;
}
private void addLifecycleMethodsHelper(SootClass entryPointClass, List<String> lifecycleMethods,
List<MethodOrMethodContext> callbacks) {
for (String lifecycleMethod : lifecycleMethods) {
SootMethod method = findMethod(entryPointClass, lifecycleMethod);
if (method != null) {
callbacks.add(method);
}
}
}
/**
* Finds a method with the given signature in the given class or one of its super classes
*
* @param currentClass The current class in which to start the search
* @param subsignature The subsignature of the method to find
* @return The method with the given signature if it has been found, otherwise null
*/
protected SootMethod findMethod(SootClass currentClass, String subsignature) {
if (currentClass.declaresMethod(subsignature)) {
return currentClass.getMethod(subsignature);
}
if (currentClass.hasSuperclass()) {
return findMethod(currentClass.getSuperclass(), subsignature);
}
return null;
}
private void addConstructorStack(SootMethod method, List<MethodOrMethodContext> callbacks) {
if (visitedEntryPoints.contains(method)) {
return;
}
callbacks.add(method);
visitedEntryPoints.add(method);
for (Type type : method.getParameterTypes()) {
String typeString = type.toString();
if (AnalysisParameters.v().isAnalysisClass(typeString)) {
if (Scene.v().containsClass(typeString)) {
SootClass sootClass = Scene.v().getSootClass(typeString);
for (SootMethod sootMethod : sootClass.getMethods()) {
if (sootMethod.getName().equals(SootMethod.constructorName)) {
addConstructorStack(sootMethod, callbacks);
}
}
} else if (logger.isWarnEnabled()) {
logger.warn("Warning: " + typeString + " is not in scene");
}
}
}
}
}