org.iobserve.mobile.instrument.bytecode.visitors.StandardClassVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.iobserve.mobile.instrument.bytecode.visitors.StandardClassVisitor.java

Source

/***************************************************************************
 * Copyright (C) 2016 iObserve Project (https://www.iobserve-devops.net)
 *
 * 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 org.iobserve.mobile.instrument.bytecode.visitors;

import java.util.HashSet;
import java.util.Set;

import org.iobserve.mobile.instrument.bytecode.ActivitiyBytecodeInstrumenter;
import org.iobserve.mobile.instrument.bytecode.InstrumentationPoint;
import org.iobserve.mobile.instrument.bytecode.SensorBytecodeInstrumenter;
import org.iobserve.mobile.instrument.bytecode.SystemWebViewInstrumenter;
import org.iobserve.mobile.instrument.config.InstrumentationConfiguration;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import android.app.Activity;

/**
 * {@link ClassVisitor} for instrumenting whole classes.
 * 
 * @author Robert Heinrich
 * @author David Monschein
 *
 */
public class StandardClassVisitor extends ClassVisitor {

    // STATIC
    /** Activity creation method. */
    private static final InstrumentationPoint POINT_AGENT_INIT = new InstrumentationPoint("android/app/Activity",
            "onCreate", "(Landroid/os/Bundle;)V");

    /** Activity destruction method. */
    private static final InstrumentationPoint POINT_AGENT_DESTROY = new InstrumentationPoint("android/app/Activity",
            "onDestroy", "()V");

    /** Activity start method. */
    private static final InstrumentationPoint POINT_ACT_START = new InstrumentationPoint("android/app/Activity",
            "onStart", "()V");

    /** Activity stop method. */
    private static final InstrumentationPoint POINT_ACT_STOP = new InstrumentationPoint("android/app/Activity",
            "onStop", "()V");

    /** Set of all activity methods listed above. */
    private static final Set<InstrumentationPoint> POINTS_INIT = new HashSet<InstrumentationPoint>();

    /**
     * Create the static set.
     */
    static {
        POINTS_INIT.add(POINT_AGENT_INIT);
        POINTS_INIT.add(POINT_AGENT_DESTROY);
        POINTS_INIT.add(POINT_ACT_START);
        POINTS_INIT.add(POINT_ACT_STOP);
    }

    // INNER
    /** Bytecode instrumenter for {@link Activity} class. */
    private final ActivitiyBytecodeInstrumenter initInstrumenter;

    /** Bytecode instrumenter for apache cordova. */
    private final SystemWebViewInstrumenter systemWebViewInstrumenter;

    /** Configuration for the instrumentation. */
    private InstrumentationConfiguration config;

    /** Flag whether the bytecode of the class has been changed or not. */
    private boolean written;

    /** Name of the class. */
    private String className;

    /** Name of the super class. */
    private String superName;

    /** Flag whether it is an {@link Activity} class. */
    private boolean classWithInit;

    /** Flag whether the class is from the application or not. */
    private boolean traceClass;

    /** Flag whether the class is the cordova system web view client or not. */
    private boolean systemWebViewClient;

    /**
     * Creates a new standard class visitor from a given {@link ClassVisitor}.
     * 
     * @param api
     *            the api version
     * @param cv
     *            the class visitor
     * @param config
     *            the instrumentation config
     */
    public StandardClassVisitor(final int api, final ClassVisitor cv, final InstrumentationConfiguration config) {
        super(api, cv);
        this.config = config;
        this.initInstrumenter = new ActivitiyBytecodeInstrumenter(config);
        this.systemWebViewInstrumenter = new SystemWebViewInstrumenter();
        this.written = false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(final int version, final int access, final String name, final String signature,
            final String supername, final String[] interfaces) {
        super.visit(version, access & (~Opcodes.ACC_FINAL), name, signature, supername, interfaces);

        this.className = name;
        this.superName = supername;
        this.traceClass = config.isTraceRelevantClass(name); // monitor
        this.systemWebViewClient = name.equals("org/apache/cordova/engine/SystemWebViewClient");

        if (this.superName != null) {
            final Type superType = Type.getType(this.superName);
            this.classWithInit = matchesActivityClass(superType.getInternalName()) && !matchesActivityClass(name);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visitEnd() {
        super.visitEnd();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature,
            final String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        mv = new DefaultMethodCodeVisitor(config, this, api, mv, access, name, desc);

        if (classWithInit && matchesInstrumentationPoint(name, desc, POINTS_INIT) != null) {
            setWritten(true);
            return new DefaultInstrumentationAdapter(Opcodes.ASM5, className, access, name, desc, mv,
                    initInstrumenter);
        } else if (this.systemWebViewClient) {
            setWritten(true);
            return new DefaultInstrumentationAdapter(Opcodes.ASM5, className, access, name, desc, mv,
                    systemWebViewInstrumenter);
        } else if (this.traceClass) {
            setWritten(true);
            return new DefaultInstrumentationAdapter(Opcodes.ASM5, className, access, name, desc, mv,
                    new SensorBytecodeInstrumenter(config.getKiekerSensor()));
        }

        // DEFAULT
        return mv;
    }

    /**
     * @return the written
     */
    public boolean isWritten() {
        return written;
    }

    /**
     * @param written
     *            the written to set
     */
    public void setWritten(final boolean written) {
        this.written = written;
    }

    /**
     * @return the className
     */
    public String getClassName() {
        return className;
    }

    /**
     * @param className
     *            the className to set
     */
    public void setClassName(final String className) {
        this.className = className;
    }

    /**
     * Determines whether a class representation is an activity representing
     * class.
     * 
     * @param icn
     *            internal class representation
     * @return true if it is an activity class - false otherwise
     */
    private boolean matchesActivityClass(final String icn) {
        return "android/app/Activity".equals(icn) || "android/support/v7/app/AppCompatActivity".equals(icn);
    }

    /**
     * Determines whether there is a instrumentation point for a given method.
     * 
     * @param methodName
     *            the method name
     * @param desc
     *            the method description
     * @param set
     *            set of instrumentation points
     * @return the point which matches or null if there is none
     */
    private InstrumentationPoint matchesInstrumentationPoint(final String methodName, final String desc,
            final Set<InstrumentationPoint> set) {
        for (InstrumentationPoint point : set) {
            if (point.getMethodName().equalsIgnoreCase(methodName) && point.getDescription().equals(desc)) {
                return point;
            }
        }
        return null;
    }

}