WALA/com.ibm.wala.dalvik/src/com/ibm/wala/dalvik/ipa/callgraph/androidModel/stubs/AndroidStartComponentTool.java

535 lines
24 KiB
Java

/*
* 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.androidModel.stubs;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.dalvik.ipa.callgraph.androidModel.AndroidModelClass;
import com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentStarters.StartInfo;
import com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentStarters.StarterFlags;
import com.ibm.wala.dalvik.util.AndroidTypes;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ipa.summaries.VolatileMethodSummary;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.types.Descriptor;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.Selector;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.ssa.ParameterAccessor;
import com.ibm.wala.util.ssa.ParameterAccessor.Parameter;
import com.ibm.wala.util.ssa.SSAValue;
import com.ibm.wala.util.ssa.SSAValueManager;
import com.ibm.wala.util.ssa.TypeSafeInstructionFactory;
import com.ibm.wala.util.strings.Atom;
/**
* Grab and set data of AndroidClasses.
*
* This class is only used by AndroidModel.getMethodAs() as it got a bit lengthy.
*
* @author Tobias Blaschke <code@tobiasblaschke.de>
* @since 2013-10-22
*/
public class AndroidStartComponentTool {
private static final Logger logger = LoggerFactory.getLogger(AndroidStartComponentTool.class);
private final IClassHierarchy cha;
// private final MethodReference asMethod;
private final Set<StarterFlags> flags;
private final TypeReference caller;
private final TypeSafeInstructionFactory instructionFactory;
private final ParameterAccessor acc;
private final SSAValueManager pm;
private final VolatileMethodSummary redirect;
private final Parameter self;
// private final StartInfo info;
// private final CGNode callerNd;
private AndroidTypes.AndroidContextType callerContext;
public AndroidStartComponentTool(final IClassHierarchy cha, final MethodReference asMethod, final Set<StarterFlags> flags,
final TypeReference caller, final TypeSafeInstructionFactory instructionFactory, final ParameterAccessor acc,
final SSAValueManager pm, final VolatileMethodSummary redirect, final Parameter self,
final StartInfo info) {
if (cha == null) {
throw new IllegalArgumentException("cha may not be null");
}
if (asMethod == null) {
throw new IllegalArgumentException("asMethod may not be null");
}
if (flags == null) {
throw new IllegalArgumentException("Flags may not be null");
}
if ((caller == null) && (! flags.contains(StarterFlags.CONTEXT_FREE))) {
throw new IllegalArgumentException("Caller may not be null if StarterFlags.CONTEXT_FREE is not set. Flags: " + flags);
}
if (instructionFactory == null) {
throw new IllegalArgumentException("The instructionFactory may not be null");
}
if (acc == null) {
throw new IllegalArgumentException("acc may not be null");
}
if (pm == null) {
throw new IllegalArgumentException("pm may not be null");
}
if (redirect == null) {
throw new IllegalArgumentException("self may not be null");
}
if (info == null) {
throw new IllegalArgumentException("info may not be null");
}
//if ((callerNd == null) && (! flags.contains(StarterFlags.CONTEXT_FREE))) {
// throw new IllegalArgumentException("CallerNd may not be null if StarterFlags.CONTEXT_FREE is not set. Flags: " + flags);
//}
this.cha = cha;
// this.asMethod = asMethod;
this.flags = flags;
this.caller = caller;
this.instructionFactory = instructionFactory;
this.acc = acc;
this.pm = pm;
this.redirect = redirect;
this.self = self;
// this.info = info;
// this.callerNd = callerNd;
}
public void attachActivities(Set<? extends SSAValue> activities, SSAValue application, SSAValue thread, SSAValue context,
SSAValue iBinderToken, SSAValue intent) {
// call: final void Activity.attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
// Application application, Intent intent, ActivityInfo info, CharSequence title,
// Activity parent, String id, Object lastNonConfigurationInstance,
// Configuration config)
final SSAValue nullInstrumentation;
{
nullInstrumentation = pm.getUnmanaged(AndroidTypes.Instrumentation, "nullInstrumentation");
this.redirect.addConstant(nullInstrumentation.getNumber(), new ConstantValue(null));
nullInstrumentation.setAssigned();
}
final SSAValue nullInfo;
{
nullInfo = pm.getUnmanaged(AndroidTypes.ActivityInfo, "nullInfo");
this.redirect.addConstant(nullInfo.getNumber(), new ConstantValue(null));
nullInfo.setAssigned();
}
final SSAValue title;
{
title = pm.getUnmanaged(TypeReference.JavaLangString, "title"); // XXX CharSequence
this.redirect.addConstant(title.getNumber(), new ConstantValue("title"));
title.setAssigned();
}
final SSAValue nullParent;
{
nullParent = pm.getUnmanaged(AndroidTypes.Activity, "nullParent");
this.redirect.addConstant(nullParent.getNumber(), new ConstantValue(null));
nullParent.setAssigned();
}
final SSAValue nullConfigInstance;
{
final TypeName cName = TypeName.string2TypeName("Landroid/app/Activity$NonConfigurationInstances");
final TypeReference type = TypeReference.findOrCreate(com.ibm.wala.types.ClassLoaderReference.Primordial, cName);
nullConfigInstance = pm.getUnmanaged(type, "noState");
this.redirect.addConstant(nullConfigInstance.getNumber(), new ConstantValue(null));
nullConfigInstance.setAssigned();
}
final SSAValue nullConfiguration;
{
nullConfiguration = pm.getUnmanaged(AndroidTypes.Configuration, "nullConfig");
this.redirect.addConstant(nullConfiguration.getNumber(), new ConstantValue(null));
nullConfiguration.setAssigned();
}
final Descriptor desc = Descriptor.findOrCreate(new TypeName[] {
AndroidTypes.ContextName,
AndroidTypes.ActivityThreadName,
AndroidTypes.InstrumentationName,
AndroidTypes.IBinderName,
AndroidTypes.ApplicationName,
AndroidTypes.IntentName,
AndroidTypes.ActivityInfoName,
TypeName.string2TypeName("Ljava/lang/CharSequence"),
AndroidTypes.ActivityName,
TypeReference.JavaLangString.getName(),
TypeName.string2TypeName("Landroid/app/Activity$NonConfigurationInstances"),
AndroidTypes.ConfigurationName }, TypeReference.VoidName);
final Selector mSel = new Selector(Atom.findOrCreateAsciiAtom("attach"), desc);
final MethodReference mRef = MethodReference.findOrCreate(AndroidTypes.Activity, mSel);
final List<SSAValue> params = new ArrayList<>(13);
params.add(null); // activity
params.add(context);
params.add(thread);
params.add(nullInstrumentation);
params.add(iBinderToken);
params.add(application);
params.add(intent);
params.add(nullInfo);
params.add(title);
params.add(nullParent);
params.add(title);
params.add(nullConfigInstance);
params.add(nullConfiguration);
for (final SSAValue activity: activities) {
final int callPC = redirect.getNextProgramCounter();
final CallSiteReference site = CallSiteReference.make(callPC, mRef, IInvokeInstruction.Dispatch.VIRTUAL);
final SSAValue exception = pm.getException();
params.set(0, activity);
final SSAInstruction invokation = instructionFactory.InvokeInstruction(callPC, params, exception, site);
redirect.addStatement(invokation);
}
}
public AndroidTypes.AndroidContextType typeOfCallerContext() {
return this.callerContext;
}
/**
* Fetches the context of the caller.
*
* @return A new SSAValue representing the androidContext (may be null!). // XXX
*/
public SSAValue fetchCallerContext() {
/*if (flags.contains(StarterFlags.CONTEXT_FREE)) {
return null; // XXX: Return a synthetic null?
}*/
if (caller == null) {
return null;
}
final IClass iCaller = cha.lookupClass(caller);
final IClass iActivity = cha.lookupClass(AndroidTypes.Activity);
final IClass iApp = cha.lookupClass(AndroidTypes.Application);
final IClass iService = cha.lookupClass(AndroidTypes.Service);
final SSAValue androidContext;
if (caller.getName().equals(AndroidTypes.ContextWrapperName)) {
this.callerContext = AndroidTypes.AndroidContextType.USELESS;
return null;
/*{ // Fetch ContextWrapperName.mBase => androidContext
androidContext = pm.getUnmanaged(AndroidTypes.Context, "callerContext");
final FieldReference mBaseRef = FieldReference.findOrCreate(AndroidTypes.ContextWrapper, Atom.findOrCreateAsciiAtom("mBase"),
AndroidTypes.Context);
final int instPC = redirect.getNextProgramCounter();
final SSAInstruction getInst = instructionFactory.GetInstruction(instPC, androidContext, self, mBaseRef);
redirect.addStatement(getInst);
// TODO: somehow dispatch on type of mBase?
this.callerContext = AndroidTypes.AndroidContextType.CONTEXT_IMPL;
logger.info("Caller has android-context type: ContextWrapper(ContextImpl)");
return androidContext;
} */
} else if (caller.getName().equals(AndroidTypes.ContextImplName)) {
{ // self is already the right context
androidContext = self;
this.callerContext = AndroidTypes.AndroidContextType.CONTEXT_IMPL;
return androidContext;
}
} else if (cha.isAssignableFrom(iActivity, iCaller)) {
// We don't need it for now - TODO grab anyway
androidContext = null;
this.callerContext = AndroidTypes.AndroidContextType.ACTIVITY;
return androidContext;
} else if (caller.equals(AndroidModelClass.ANDROID_MODEL_CLASS)) {
// TODO: Return something useful
this.callerContext = AndroidTypes.AndroidContextType.USELESS;
return null;
} else if (caller.getName().equals(AndroidTypes.BridgeContextName)) {
// XXX ???
androidContext = self;
this.callerContext = AndroidTypes.AndroidContextType.CONTEXT_BRIDGE;
return androidContext;
} else if (cha.isAssignableFrom(iApp, iCaller)) {
androidContext = self;
this.callerContext = AndroidTypes.AndroidContextType.APPLICATION;
return androidContext;
} else if (cha.isAssignableFrom(iService, iCaller)) {
androidContext = self;
this.callerContext = AndroidTypes.AndroidContextType.SERVICE;
return androidContext;
} else {
logger.debug("Can not handle the callers android-context of " + caller);
return null;
}
}
/**
* Fetch the permissions to start the component with.
*
* Fetching depends on StarterFlags.QUENCH_PERMISSIONS, XXX
*
* @return an iBinder
* @throws UnsupportedOperationException when fetching is not supported with the current settings
*/
public SSAValue fetchIBinder(SSAValue androidContext) {
final SSAValue iBinder = pm.getUnmanaged(AndroidTypes.IBinder, "foreignIBinder");
if (flags.contains(StarterFlags.CONTEXT_FREE)) {
// TODO: Can we do somethig?
return null;
} else if (flags.contains(StarterFlags.QUENCH_PERMISSIONS)) {
// If this flag is set the given asMethod has a IntentSender-Parameter
final Parameter intentSender = acc.firstOf(AndroidTypes.IntentSenderName);
assert (intentSender != null) : "Unable to look up the IntentSender-Object";
assert (intentSender.getNumber() == 2) : "The IntentSender-Object was not located at SSA-Number 2. This may be entirely " +
"ok! I left this assertion to ashure the ParameterAccessor does its job right.";
// retreive the IBinder: IIntentSender.asBinder()
final SSAValue iIntentSender = pm.getUnmanaged(AndroidTypes.IIntentSender, "iIntentSender");
{ // call IIntentSender IntentSender.getTarget()
final int callPC = redirect.getNextProgramCounter();
final Selector mSel = Selector.make("getTarget()Landroid/content/IIntentSender;");
final MethodReference mRef = MethodReference.findOrCreate(AndroidTypes.IntentSender, mSel);
final CallSiteReference site = CallSiteReference.make(callPC, mRef, IInvokeInstruction.Dispatch.VIRTUAL);
final SSAValue exception = pm.getException();
final List<SSAValue> params = new ArrayList<>(1);
params.add(intentSender);
final SSAInstruction invokation = instructionFactory.InvokeInstruction(callPC, iIntentSender, params, exception, site);
redirect.addStatement(invokation);
}
{ // call IBinder IIntentSender.asBinder()
final int callPC = redirect.getNextProgramCounter();
final Selector mSel = Selector.make("asBinder()Landroid/os/IBinder;");
final MethodReference mRef = MethodReference.findOrCreate(AndroidTypes.IntentSender, mSel);
final CallSiteReference site = CallSiteReference.make(callPC, mRef, IInvokeInstruction.Dispatch.VIRTUAL);
final SSAValue exception = pm.getException();
final List<SSAValue> params = new ArrayList<>(1);
params.add(iIntentSender);
final SSAInstruction invokation = instructionFactory.InvokeInstruction(callPC, iBinder, params, exception, site);
redirect.addStatement(invokation);
}
return iBinder;
//} else if (caller.getName().equals(AndroidTypes.ActivityName)) {
} else if (this.callerContext == AndroidTypes.AndroidContextType.ACTIVITY) {
// The IBinder is Activity.mMainThread.getApplicationThread() // TODO: Verify
final SSAValue mMainThread = pm.getUnmanaged(AndroidTypes.ActivityThread, "callersMainThred");
{ // Fetch mMainthred
final int instPC = redirect.getNextProgramCounter();
final FieldReference mMainThreadRef = FieldReference.findOrCreate(AndroidTypes.Activity, Atom.findOrCreateAsciiAtom("mMainThread"),
AndroidTypes.ActivityThread);
final SSAInstruction getInst = instructionFactory.GetInstruction(instPC, mMainThread, self, mMainThreadRef);
redirect.addStatement(getInst);
}
/*{ // DEBUG
final com.ibm.wala.classLoader.IClass activityThread = cha.lookupClass(AndroidTypes.ActivityThread);
assert (activityThread != null);
for (com.ibm.wala.classLoader.IMethod m : activityThread.getDeclaredMethods()) {
System.out.println(m);
}
} // */
{ // Call getApplicationThread() on it
final int callPC = redirect.getNextProgramCounter();
final Selector mSel = Selector.make("getApplicationThread()Landroid/app/ActivityThread$ApplicationThread;");
final MethodReference mRef = MethodReference.findOrCreate(AndroidTypes.ActivityThread, mSel);
final CallSiteReference site = CallSiteReference.make(callPC, mRef, IInvokeInstruction.Dispatch.VIRTUAL);
final SSAValue exception = pm.getException();
final List<SSAValue> params = new ArrayList<>(1);
params.add(mMainThread);
final SSAInstruction invokation = instructionFactory.InvokeInstruction(callPC, iBinder, params, exception, site);
redirect.addStatement(invokation);
} // */
return iBinder;
} else if (this.callerContext == AndroidTypes.AndroidContextType.CONTEXT_IMPL) {
// For bindService its mActivityToken - TODO: For the rest?
// startActivity uses mMainThread.getApplicationThread()
{ // read mActivityToken -> iBinder
final FieldReference mActivityTokenRef = FieldReference.findOrCreate(AndroidTypes.ContextImpl,
Atom.findOrCreateAsciiAtom("mActivityToken"), AndroidTypes.IBinder);
final int instPC = redirect.getNextProgramCounter();
final SSAInstruction getInst = instructionFactory.GetInstruction(instPC, iBinder, androidContext, mActivityTokenRef);
redirect.addStatement(getInst);
}
return iBinder;
} else if (this.callerContext == AndroidTypes.AndroidContextType.CONTEXT_BRIDGE) {
// TODO: Return something useful
return null;
} else if (caller.equals(AndroidModelClass.ANDROID_MODEL_CLASS)) {
// TODO: Return something useful
return null;
} else {
throw new UnsupportedOperationException("No implementation on how to extract an iBinder from a " + caller);
}
}
/**
* Set the iBinder in the callee.
*/
public void assignIBinder(SSAValue iBinder, List<? extends SSAValue> allActivities) {
if (iBinder == null) {
// TODO: Some day we may throe here...
return;
}
// TODO: Use Phi?
for (SSAValue activity : allActivities) {
//final int callPC = redirect.getNextProgramCounter();
final FieldReference mTokenRef = FieldReference.findOrCreate(AndroidTypes.Activity, Atom.findOrCreateAsciiAtom("mToken"),
AndroidTypes.IBinder);
final int instPC = redirect.getNextProgramCounter();
final SSAInstruction putInst = instructionFactory.PutInstruction(instPC, activity, iBinder, mTokenRef);
redirect.addStatement(putInst);
}
}
/**
* Call Activity.setIntent.
*/
public void setIntent(SSAValue intent, List<? extends SSAValue> allActivities) {
if (intent == null) {
throw new IllegalArgumentException("Null-Intent");
}
// TODO: Use Phi?
for (SSAValue activity : allActivities) {
final int callPC = redirect.getNextProgramCounter();
final Selector mSel = Selector.make("setIntent(Landroid/content/Intent;)V");
final MethodReference mRef = MethodReference.findOrCreate(AndroidTypes.Activity, mSel);
final CallSiteReference site = CallSiteReference.make(callPC, mRef, IInvokeInstruction.Dispatch.VIRTUAL);
final SSAValue exception = pm.getException();
final List<SSAValue> params = new ArrayList<>(1);
params.add(activity);
params.add(intent);
final SSAInstruction invokation = instructionFactory.InvokeInstruction(callPC, params, exception, site);
redirect.addStatement(invokation);
}
}
/**
* Grab mResultCode and mResultData.
*
* This data is used to call onActivityResult of the caller.
*/
public void fetchResults(List<? super SSAValue> resultCodes, List<? super SSAValue> resultData,
List<? extends SSAValue> allActivities) {
for (SSAValue activity : allActivities) {
final SSAValue tmpResultCode = pm.getUnmanaged(TypeReference.Int, "mResultCode");
{ // Fetch mResultCode
//redirect.setLocalName(tmpResultCode, "gotResultCode");
final FieldReference mResultCodeRef = FieldReference.findOrCreate(AndroidTypes.Activity, Atom.findOrCreateAsciiAtom("mResultCode"),
TypeReference.Int);
final int instPC = redirect.getNextProgramCounter();
final SSAInstruction getInst = instructionFactory.GetInstruction(instPC, tmpResultCode, activity, mResultCodeRef);
redirect.addStatement(getInst);
}
final SSAValue tmpResultData = pm.getUnmanaged(AndroidTypes.Intent, "mResultData");
{ // Fetch mResultData
//redirect.setLocalName(tmpResultData, "gotResultData");
final FieldReference mResultDataRef = FieldReference.findOrCreate(AndroidTypes.Activity, Atom.findOrCreateAsciiAtom("mResultData"),
AndroidTypes.Intent);
final int instPC = redirect.getNextProgramCounter();
final SSAInstruction getInst = instructionFactory.GetInstruction(instPC, tmpResultData, activity, mResultDataRef);
redirect.addStatement(getInst);
} // */
resultCodes.add(tmpResultCode);
resultData.add(tmpResultData);
} // End: for all activities */
assert (resultCodes.size() == resultData.size());
}
/**
* Add Phi (if necessary) - not if only one from.
*/
public SSAValue addPhi(List<? extends SSAValue> from) {
if (from.size() == 1) {
return from.get(0);
} else {
final SSAValue retVal = this.pm.getUnmanaged(from.get(0).getType(), "forPhi");
final int phiPC = redirect.getNextProgramCounter();
final SSAInstruction phi = instructionFactory.PhiInstruction(phiPC, retVal, from);
this.redirect.addStatement(phi);
return retVal;
}
}
}