edu.umich.oasis.sandbox.ResolvedSoda.java Source code

Java tutorial

Introduction

Here is the source code for edu.umich.oasis.sandbox.ResolvedSoda.java

Source

/*
 * Copyright (C) 2017 The Regents of the University of Michigan
 *
 * 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.umich.oasis.sandbox;

import android.os.BadParcelableException;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;

import org.apache.commons.lang3.ClassUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import edu.umich.oasis.annotations.InOut;
import edu.umich.oasis.annotations.MayContain;
import edu.umich.oasis.annotations.PreserveThis;
import edu.umich.oasis.annotations.TaintedWith;
import edu.umich.oasis.common.CallFlags;
import edu.umich.oasis.common.CallParam;
import edu.umich.oasis.common.CallResult;
import edu.umich.oasis.common.Direction;
import edu.umich.oasis.common.ParamInfo;
import edu.umich.oasis.common.ParceledPayload;
import edu.umich.oasis.common.SodaDescriptor;
import edu.umich.oasis.common.SodaDetails;
import edu.umich.oasis.common.TaintSet;
import edu.umich.oasis.internal.IResolvedSoda;
import edu.umich.oasis.internal.ISodaCallback;

/*package*/ final class ResolvedSoda extends IResolvedSoda.Stub {
    private static final String TAG = "OASIS.SODA";
    private static final boolean localLOGV = Log.isLoggable(TAG, Log.VERBOSE);
    private static final boolean localLOGD = Log.isLoggable(TAG, Log.DEBUG);

    private interface Caller {
        Object call(Object... args) throws Exception;
    }

    private final SandboxService mSandbox;
    private final SodaDescriptor mOriginalDescriptor;
    private final SandboxContext mContext;
    private final Class<?> mDefiningClass;
    private final MemberData<?> mMemberData;

    private Class<?>[] classNamesToClasses(List<String> classNames, ClassLoader loader) throws Exception {
        Class<?>[] classes = new Class<?>[classNames.size()];
        for (int i = 0; i < classNames.size(); i++) {
            classes[i] = ClassUtils.getClass(loader, classNames.get(i));
        }
        return classes;
    }

    protected static <TAnnot extends Annotation, TMember extends AnnotatedElement & Member> TAnnot getMatchingAnnotation(
            Class<TAnnot> annotClass, TMember member) {
        TAnnot annot = member.getAnnotation(annotClass);
        if (annot != null) {
            return annot;
        }

        Class<?> declaringClass = member.getDeclaringClass();
        annot = declaringClass.getAnnotation(annotClass);
        if (annot != null) {
            return annot;
        }

        Package declaringPackage = declaringClass.getPackage();
        annot = declaringPackage.getAnnotation(annotClass);
        if (annot != null) {
            return annot;
        }

        return null;
    }

    private abstract class MemberData<TElem extends AnnotatedElement & Member> {
        protected final TElem element;
        private SodaDetails details = null;

        protected MemberData(TElem element) {
            this.element = element;
        }

        public abstract Object call(Object... args) throws Exception;

        public synchronized final SodaDetails getDetails() {
            if (details == null) {
                details = new SodaDetails();
                fillInDetails();
            }
            return details;
        }

        private void fillInDetails() {
            details.descriptor = getDescriptor();
            details.resultType = getReturnType().getName();

            details.paramInfo = new ArrayList<>(countParameters());
            fillInParamInfo(details.paramInfo);

            TaintedWith taintedWith = getMatchingAnnotation(TaintedWith.class, element);
            if (taintedWith != null) {
                details.requiredTaints = TaintSet.fromStrings(Arrays.asList(taintedWith.value()));
            } else {
                details.requiredTaints = TaintSet.EMPTY;
            }

            MayContain mayContain = getMatchingAnnotation(MayContain.class, element);
            if (mayContain != null) {
                details.optionalTaints = TaintSet.fromStrings(Arrays.asList(mayContain.value()));
            } else {
                details.optionalTaints = TaintSet.EMPTY;
            }
        }

        public int countParameters() {
            return getDeclaredParameterTypes().length;
        }

        public abstract Class<?> getReturnType();

        protected abstract Class<?>[] getDeclaredParameterTypes();

        protected abstract Annotation[][] getDeclaredAnnotations();

        public SodaDescriptor getDescriptor() {
            return mOriginalDescriptor;
        }

        protected void fillInParamInfo(List<ParamInfo> paramInfo) {
            Class<?>[] paramTypes = getDeclaredParameterTypes();
            Annotation[][] annotations = getDeclaredAnnotations();

            final int offset = paramInfo.size();

            for (int i = 0; i < paramTypes.length; i++) {
                Direction d = Direction.IN;
                for (int j = 0; j < annotations[i].length; j++) {
                    if (annotations[i][j] instanceof InOut) {
                        d = Direction.INOUT;
                    }
                }
                paramInfo.add(new ParamInfo(paramTypes[i].getName(), i + offset, d));
            }
        }
    }

    private class MethodData extends MemberData<Method> {
        public MethodData(Method element) {
            super(element);
        }

        @Override
        public Object call(Object... args) throws Exception {
            return element.invoke(null, args);
        }

        @Override
        protected Class<?>[] getDeclaredParameterTypes() {
            return element.getParameterTypes();
        }

        @Override
        protected Annotation[][] getDeclaredAnnotations() {
            return element.getParameterAnnotations();
        }

        @Override
        public Class<?> getReturnType() {
            return element.getReturnType();
        }

        @Override
        public SodaDescriptor getDescriptor() {
            return SodaDescriptor.forMethod(mContext, element);
        }
    }

    private class InstanceMethodData extends MethodData {
        public InstanceMethodData(Method element) {
            super(element);
        }

        @Override
        public Object call(Object... args) throws Exception {
            if (args.length == 0) {
                throw new IllegalArgumentException("Not enough arguments");
            }
            Object[] methodArgs = Arrays.copyOfRange(args, 1, args.length);
            return element.invoke(args[0], methodArgs);
        }

        @Override
        public int countParameters() {
            return super.countParameters() + 1;
        }

        @Override
        protected void fillInParamInfo(List<ParamInfo> paramInfo) {
            Direction dir = Direction.INOUT;
            PreserveThis preserveThis = element.getAnnotation(PreserveThis.class);
            if (preserveThis != null && preserveThis.value()) {
                dir = Direction.IN;
            }
            paramInfo.add(new ParamInfo(element.getDeclaringClass().getName(), 0, dir));

            super.fillInParamInfo(paramInfo);
        }
    }

    private class ConstructorData<T> extends MemberData<Constructor<T>> {
        public ConstructorData(Constructor<T> element) {
            super(element);
        }

        @Override
        public Object call(Object... args) throws Exception {
            return element.newInstance(args);
        }

        @Override
        protected Class<?>[] getDeclaredParameterTypes() {
            return element.getParameterTypes();
        }

        @Override
        protected Annotation[][] getDeclaredAnnotations() {
            return element.getParameterAnnotations();
        }

        @Override
        public Class<?> getReturnType() {
            return element.getDeclaringClass();
        }

        @Override
        public SodaDescriptor getDescriptor() {
            return SodaDescriptor.forConstructor(mContext, element);
        }
    }

    private InstanceMethodData resolveInstance(Class<?> definingClass, String methodName, Class<?>[] paramClasses,
            boolean bestMatch) throws Exception {
        if (localLOGD) {
            Log.d(TAG, "Resolving as instance");
        }
        final Method method = definingClass.getMethod(methodName, paramClasses);
        if (Modifier.isStatic(method.getModifiers())) {
            throw new NoSuchMethodException("Method is static, but was resolved as instance");
        }

        return new InstanceMethodData(method);
    }

    private MethodData resolveStatic(Class<?> definingClass, String methodName, Class<?>[] paramClasses,
            boolean bestMatch) throws Exception {
        if (localLOGD) {
            Log.d(TAG, "Resolving as static");
        }
        final Method method = definingClass.getMethod(methodName, paramClasses);
        if (!Modifier.isStatic(method.getModifiers())) {
            throw new NoSuchMethodException("Method is instance, but was resolved as static");
        }

        return new MethodData(method);
    }

    private <T> ConstructorData<T> resolveConstructor(Class<T> definingClass, Class<?>[] paramClasses,
            boolean bestMatch) throws Exception {

        if (localLOGD) {
            Log.d(TAG, "Resolving as ctor");
        }
        final Constructor<T> ctor = definingClass.getConstructor(paramClasses);

        return new ConstructorData<>(ctor);
    }

    public ResolvedSoda(SandboxService sandbox, SodaDescriptor descriptor, boolean bestMatch) throws Exception {
        //if (localLOGD) {
        Log.d(TAG, "Resolving " + descriptor.toString());
        //}
        mSandbox = sandbox;
        mContext = mSandbox.getContextForPackage(descriptor.definingClass.getPackageName());
        //if (localLOGD) {
        Log.d(TAG, "Got package context");
        //}
        final ClassLoader loader = mContext.getClassLoader();

        mDefiningClass = ClassUtils.getClass(loader, descriptor.definingClass.getClassName(), true);

        Class<?>[] paramClasses = classNamesToClasses(descriptor.paramTypes, loader);

        switch (descriptor.kind) {
        case SodaDescriptor.KIND_INSTANCE:
            mMemberData = resolveInstance(mDefiningClass, descriptor.methodName, paramClasses, bestMatch);
            break;
        case SodaDescriptor.KIND_STATIC:
            mMemberData = resolveStatic(mDefiningClass, descriptor.methodName, paramClasses, bestMatch);
            break;
        case SodaDescriptor.KIND_CTOR:
            mMemberData = resolveConstructor(mDefiningClass, paramClasses, bestMatch);
            break;
        default:
            throw new IllegalArgumentException("Unknown SodaDescriptor type");
        }

        // Check that return type can be marshalled.
        if (!ParceledPayload.canParcelType(mMemberData.getReturnType(), true)) {
            throw new BadParcelableException("Cannot parcel type " + mMemberData.getReturnType().getName());
        }

        mOriginalDescriptor = descriptor;
    }

    public String getResultType() {
        return mMemberData.getReturnType().getName();
    }

    @Override
    public final void getDetails(SodaDetails details) {
        if (details != null) {
            details.copyFrom(mMemberData.getDetails());
        }
    }

    private Object unpack(CallParam param) throws RemoteException {
        if (param == null) {
            return null;
        }

        switch (param.getType()) {
        case CallParam.TYPE_NULL: {
            return null;
        }

        case CallParam.TYPE_DATA: {
            return param.decodePayload(mContext.getClassLoader());
        }

        case CallParam.TYPE_HANDLE: {
            IBinder handle = (IBinder) param.getPayload();
            boolean shouldRelease = (param.getHeader() & CallParam.HANDLE_RELEASE) != 0;
            return SandboxObject.objectForBinder(handle, mContext.getClassLoader(), shouldRelease);
        }

        default: {
            throw new IllegalArgumentException("Bad CallParam type");
        }
        }
    }

    @Override
    public void call(int flags, ISodaCallback callback, List<CallParam> params) throws RemoteException {
        try {
            if (localLOGD) {
                Log.d(TAG, String.format("Incoming sandbox call for %s, %d parameters:", mOriginalDescriptor,
                        params.size()));
                for (CallParam param : params) {
                    Log.d(TAG, param.toString(mContext.getClassLoader()));
                }
            }
            if (localLOGV) {
                Log.v(TAG, String.format("Callback %s, flags %d", callback, flags));
            }
            // Sanity check.
            final int numParams = params.size();
            if (numParams != mMemberData.countParameters()) {
                throw new IllegalArgumentException("Wrong number of arguments supplied");
            }

            boolean hasReturn = (flags & CallFlags.NO_RETURN_VALUE) == 0;

            final ArrayList<Object> args = new ArrayList<>();
            final SparseArray<IBinder> outs = new SparseArray<>();

            mContext.beginSoda();
            try {
                if (hasReturn) {
                    outs.append(CallResult.RETURN_VALUE, null);
                }

                for (int i = 0; i < numParams; i++) {
                    CallParam param = params.get(i);
                    int paramHeader = param.getHeader();
                    if (param.getType() == CallParam.TYPE_HANDLE
                            && (paramHeader & CallParam.HANDLE_SYNC_ONLY) != 0) {
                        Log.w(TAG, "HANDLE_SYNC_ONLY in sandbox for " + mOriginalDescriptor);
                        continue;
                    }
                    // Deserialize argument, marshaling as necessary.
                    Object arg = unpack(param);
                    // TODO: FLAG_BY_REF
                    args.add(arg);
                    // Put together the out parameter for inout params.
                    if ((paramHeader & CallParam.FLAG_RETURN) != 0) {
                        if (localLOGV) {
                            Log.v(TAG, String.format("Adding out param %d", i));
                        }
                        outs.append(i, SandboxObject.binderForObject(this, arg));
                    }
                }

                // Actually do the call.
                Object[] argArray = args.toArray();
                if (localLOGD) {
                    Log.d(TAG, "Preparing to call " + mOriginalDescriptor.printCall(argArray));
                }

                Object retval = mMemberData.call(argArray);

                if (localLOGD) {
                    Log.d(TAG, "Call returned: " + Objects.toString(retval));
                }

                // Bundle up handle for return value.
                if (hasReturn) {
                    IBinder retvalObj = SandboxObject.binderForObject(this, retval);
                    outs.put(CallResult.RETURN_VALUE, retvalObj);
                }

                // DEBUG: print out params
                if (localLOGV) {
                    for (int i = 0; i < outs.size(); i++) {
                        Log.v(TAG, String.format("out[%d] = %s", outs.keyAt(i), outs.valueAt(i)));
                    }
                }
                // Post results to caller.
                if (localLOGD) {
                    Log.d(TAG, "Posting results to caller");
                }
                callback.onResult(new CallResult(outs));
            } catch (InvocationTargetException ioe) {
                Throwable t = ioe.getTargetException();
                if (t instanceof Exception) {
                    throw ((Exception) t);
                }
                throw ioe;
            } finally {
                // Clear our ambient context.
                if (localLOGD) {
                    Log.d(TAG, "Clearing call token");
                }
                mContext.endSoda();
            }
        } catch (Exception e) {
            //Log.e(TAG, String.format("Error invoking %s", mOriginalDescriptor), e);
            callback.onResult(new CallResult(e));
        }
    }

    @Override
    public String toString() {
        return String.format("ResolvedSoda[%s]", mOriginalDescriptor);
    }
}