org.eclipse.objectteams.otredyn.bytecode.asm.AsmWritableBoundClass.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.objectteams.otredyn.bytecode.asm.AsmWritableBoundClass.java

Source

/**********************************************************************
 * This file is part of "Object Teams Dynamic Runtime Environment"
 * 
 * Copyright 2009, 2015 Oliver Frank and others.
 * 
 * All rights reserved. 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
 * 
 * Please visit http://www.eclipse.org/objectteams for updates and contact.
 * 
 * Contributors:
 *      Oliver Frank - Initial API and implementation
 *      Stephan Herrmann - Initial API and implementation
 **********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.objectteams.otredyn.bytecode.AbstractBoundClass;
import org.eclipse.objectteams.otredyn.bytecode.Field;
import org.eclipse.objectteams.otredyn.bytecode.IBytecodeProvider;
import org.eclipse.objectteams.otredyn.bytecode.Method;
import org.eclipse.objectteams.otredyn.bytecode.RedefineStrategyFactory;
import org.eclipse.objectteams.otredyn.bytecode.Types;
import org.eclipse.objectteams.otredyn.runtime.TeamManager;
import org.eclipse.objectteams.otredyn.transformer.jplis.ObjectTeamsTransformer;
import org.eclipse.objectteams.otredyn.transformer.names.ClassNames;
import org.eclipse.objectteams.otredyn.transformer.names.ConstantMembers;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * This class implements the bytecode manipulating part of {@link AbstractBoundClass}.
 * It uses ASM to manipulate the bytecode.
 * @author Oliver Frank
 */
class AsmWritableBoundClass extends AsmBoundClass {
    private static boolean dumping = false;

    static {
        if (System.getProperty("ot.dump") != null)
            dumping = true;
    }

    private ClassWriter writer;
    private MultiClassAdapter multiAdapter;
    private ClassReader reader;
    private boolean isTransformed;
    private boolean isTransformedForMemberAccess;
    private boolean isTransformedStatic;
    private List<AbstractTransformableClassNode> nodes;
    private boolean isFirstTransformation = true;

    private boolean isTransformationActive;

    protected AsmWritableBoundClass(String name, String id, IBytecodeProvider bytecodeProvider,
            ClassLoader loader) {
        super(name, id, bytecodeProvider, loader);
    }

    /**
     * Adds a field to the class
     * 
     * @param field defines name and type of the field
     * @param access access flags for the field
     * @see AddFieldAdapter
     */
    private void addField(Field field, int access) {
        assert (isTransformationActive) : "No transformation active";
        String desc = field.getSignature();
        multiAdapter.addVisitor(new AddFieldAdapter(writer, field.getName(), access, desc));
    }

    /**
     * Adds an empty method to the class
     * @param method
     * @param access
     * @param signature
     * @param exceptions
     * @param superToCall may be null, else the super class to which a super-call should be inserted
     * @see AddEmptyMethodAdapter
     */
    private void addEmptyMethod(Method method, int access, String signature, String[] exceptions,
            String superToCall) {
        assert (isTransformationActive) : "No transformation active";
        String desc = method.getSignature();
        Type[] args = Type.getArgumentTypes(desc);
        multiAdapter.addVisitor(new AddEmptyMethodAdapter(writer, method.getName(), access, desc, exceptions,
                signature, args.length + 1, superToCall));
    }

    /**
     * Adds an interface to the class
     * @see AddInterfaceAdapter
     * @param name
     */
    private void addInterface(String name) {
        assert (isTransformationActive) : "No transformation active";
        multiAdapter.setToplevelVisitor(new AddInterfaceAdapter(writer, name));
    }

    /**
     * This method must be called before any transformation
     * can be done. It makes it possible to collect all transformations
     * and transform the bytecode only one time at the end.
     */
    @Override
    protected void startTransformation() {

        reader = new ClassReader(allocateAndGetBytecode());

        writer = getClassWriter();
        multiAdapter = new MultiClassAdapter(writer);
        nodes = new ArrayList<AbstractTransformableClassNode>();
        isTransformationActive = true;
    }

    LoaderAwareClassWriter getClassWriter() {
        int flags = ClassWriter.COMPUTE_FRAMES;
        // DEBUG: when frame computation throws an exception, enable dumping of class file without frames computed:
        //      if (getName().contains("JUnitLaunchConfigurationDelegate"))
        //         flags = 0;
        return new LoaderAwareClassWriter(reader, flags, this.loader);
    }

    /**
     * Is the class manipulated right now.
     */
    @Override
    public boolean isTransformationActive() {
        return isTransformationActive;
    }

    /**
     * Executes all pending transformations.
     */
    @Override
    protected void endTransformation() {
        assert (isTransformationActive) : "No transformation active";

        if (multiAdapter.hasVisitors() || !nodes.isEmpty()) {
            // //TODO (ofra): Do everything in one transformation
            // Do all transformation with the Core API of ASM
            reader.accept(multiAdapter, ClassReader.SKIP_FRAMES);
            setBytecode(writer.toByteArray());
            //Do all transformations with the Tree API of ASM
            for (AbstractTransformableClassNode node : nodes) {
                reader = new ClassReader(allocateAndGetBytecode());
                reader.accept(node, ClassReader.SKIP_FRAMES);
                if (node.transform()) {
                    writer = getClassWriter();
                    node.accept(writer);
                    setBytecode(writer.toByteArray());
                }
            }

            dump();
            reader = null;
            writer = null;
            multiAdapter = null;
            nodes = null;
            //Check, if this is the first transformation for this class
            if (!this.isFirstTransformation) {
                // It is not the first transformation, so redefine the class
                try {
                    redefine();
                } catch (Throwable t) {
                    //            t.printStackTrace(System.out);
                    // if redefinition failed (ClassCircularity?) install a runnable for deferred redefinition:
                    final Runnable previousTask = TeamManager.pendingTasks.get();
                    TeamManager.pendingTasks.set(new Runnable() {
                        public void run() {
                            if (previousTask != null)
                                previousTask.run();
                            redefine();
                        }

                        @Override
                        public String toString() {
                            return "Retry " + AsmWritableBoundClass.this.toString();
                        }
                    });
                }
            }
        } else {
            reader = null;
            writer = null;
            multiAdapter = null;
            nodes = null;
        }
        isTransformationActive = false;
        isFirstTransformation = false;
        releaseBytecode();
    }

    /**
     * Creates the dispatch code in the original method.
     * @see CreateDispatchCodeInOrgMethodAdapter
     */
    @Override
    protected void createDispatchCodeInOrgMethod(Method boundMethod, int joinPointId, int boundMethodId) {
        assert (isTransformationActive) : "No transformation active";
        nodes.add(new CreateDispatchCodeInOrgMethodAdapter(boundMethod, joinPointId, boundMethodId));
    }

    /**
     * Creates the dispatch code in the method callAllBindings.
     * @see CreateDispatchCodeInCallAllBindingsAdapter
     */
    @Override
    protected void createDispatchCodeInCallAllBindings(int joinpointId, int boundMethodId) {
        assert (isTransformationActive) : "No transformation active";
        nodes.add(new CreateDispatchCodeInCallAllBindingsAdapter(joinpointId, boundMethodId));
    }

    /**
     * Moves the code of the original method to callOrig or callOrigStatic.
     * @see MoveCodeToCallOrigAdapter
     */
    @Override
    protected void moveCodeToCallOrig(Method boundMethod, int boundMethodId) {
        if (boundMethod.getName().equals("<init>"))
            return; // don't move constructor code
        assert (isTransformationActive) : "No transformation active";
        nodes.add(new MoveCodeToCallOrigAdapter(this, boundMethod, boundMethodId, this.weavingContext));
    }

    /**
     * Creates a super call in callOrig.
     * @see CreateSuperCallInCallOrigAdapter
     */
    @Override
    protected void createSuperCallInCallOrig(int joinpointId) {
        assert (isTransformationActive) : "No transformation active";
        nodes.add(new CreateSuperCallInCallOrigAdapter(getInternalSuperClassName(), joinpointId));

    }

    /**
     * Creates a call of callAllBindings in the original method.
     * @see CreateCallAllBindingsCallInOrgMethod
     */
    @Override
    protected void createCallAllBindingsCallInOrgMethod(Method boundMethod, int boundMethodId,
            boolean needToAddMethod) {
        assert (isTransformationActive) : "No transformation active";
        if (needToAddMethod) {
            String desc = boundMethod.getSignature();
            Type[] args = Type.getArgumentTypes(desc);
            multiAdapter.addVisitor(
                    new AddEmptyMethodAdapter(writer, boundMethod.getName(), boundMethod.getAccessFlags(), desc,
                            null, boundMethod.getSignature(), args.length + 1/*maxLocals*/, null));
        }
        nodes.add(new CreateCallAllBindingsCallInOrgMethod(boundMethod, boundMethodId));

    }

    /**
     * Prepares a the class for a decapsulation of one of its methods
     */
    @Override
    protected void weaveMethodAccess(Method method, int accessId) {
        nodes.add(new CreateMethodAccessAdapter(method, accessId));

    }

    /**
     * Prepares a the class for a decapsulation of one of its fields
     */
    @Override
    protected void weaveFieldAccess(Field field, int accessId) {
        nodes.add(new CreateFieldAccessAdapter(field, accessId));

    }

    /**
     * Write the bytecode in the directory ./otdyn to the hard disk, 
     * if the system property "ot.dump" is set.
     */
    private void dump() {
        if (!dumping)
            return;

        FileOutputStream fos = null;
        try {
            String name = getName();
            int index = name.indexOf('/');
            if (index == -1)
                index = name.indexOf('.');
            File dir = new File("otdyn");
            if (!dir.exists())
                dir.mkdir();
            String filename = "otdyn/" + name.substring(index + 1) + ".class";
            fos = new FileOutputStream(filename);
            fos.write(allocateAndGetBytecode());
            fos.close();
        } catch (Exception e) {
            // TODO (ofra): Log error while dumping
            e.printStackTrace();
        }
    }

    private int n = 0; // counts dump files for this class

    public void dump(byte[] bytecode, String postfix) {
        if (!dumping)
            return;

        FileOutputStream fos = null;
        try {
            String name = getName();
            int index = name.indexOf('/');
            if (index == -1)
                index = name.indexOf('.');
            File dir = new File("otdyn");
            if (!dir.exists())
                dir.mkdir();
            String filename = "otdyn/" + name.substring(index + 1) + postfix + ".#" + (n++);
            fos = new FileOutputStream(filename);
            fos.write(bytecode);
            fos.close();
        } catch (Exception e) {
            // TODO (ofra): Log error while dumping
            e.printStackTrace();
        }
    }

    /**
     * Redefines the class
     */
    private void redefine() {
        try {
            Class<?> clazz = this.loader.loadClass(this.getName()); // boot classes may have null classloader, can't be redefined anyway?
            byte[] bytecode = allocateAndGetBytecode();
            dump(bytecode, "redef");
            RedefineStrategyFactory.getRedefineStrategy().redefine(clazz, bytecode);
        } catch (Throwable t) {
            throw new RuntimeException(
                    "Error occured while dynamically redefining class " + getName() + "\n" + t.getMessage(), t);
        }
    }

    /**
     * Do all transformations needed at load time
     */
    @Override
    protected void prepareAsPossibleBaseClass() {
        if (!isFirstTransformation)
            return;

        addInterface(ClassNames.I_BOUND_BASE_SLASH);

        int methodModifiers = Types.ACCESS_PUBLIC;
        if (isInterface())
            methodModifiers |= Opcodes.ACC_ABSTRACT;

        if (!isInterface())
            addField(ConstantMembers.roleSet, Types.ACCESS_PUBLIC);

        addEmptyMethod(ConstantMembers.callOrig, methodModifiers, null, null, null);
        addEmptyMethod(ConstantMembers.callAllBindingsClient, methodModifiers, null, null, null);

        // the methods callOrigStatic and accessStatic have to already exist to call it in a concrete team
        if (!isInterface()) {
            addEmptyMethod(getCallOrigStatic(), Types.ACCESS_PUBLIC + Types.ACCESS_STATIC, null, null, null);
            addEmptyMethod(ConstantMembers.accessStatic, Types.ACCESS_PUBLIC + Types.ACCESS_STATIC, null, null,
                    null);
        }

        String superClassName = getSuperClassName().replace('.', '/');
        if (!ObjectTeamsTransformer.isWeavable(superClassName))
            superClassName = null;
        addEmptyMethod(ConstantMembers.access, methodModifiers, null, null, superClassName);
        addEmptyMethod(ConstantMembers.addOrRemoveRole, methodModifiers, null, null, null);

        if (!isInterface())
            multiAdapter.addVisitor(new AddAfterClassLoadingHook(this.writer, this));

        if (AddThreadNotificationAdapter.shouldNotify(this))
            multiAdapter.addVisitor(new AddThreadNotificationAdapter(this.writer, this));
    }

    /** Get the suitable variant of _OT$callOrigStatic, respecting synth args for static role methods. */
    Method getCallOrigStatic() {
        if (isRole())
            return ConstantMembers.callOrigStaticRoleVersion(getEnclosingClassName());
        else
            return ConstantMembers.callOrigStatic;
    }

    /**
     * Prepares the class for implicit team activation by adding appropriate calls to
     * _OT$implicitlyActivate(), _OT$implicitlyDeactivate() into all relevant methods.
     */
    @Override
    protected void prepareTeamActivation() {
        if (!isFirstTransformation || isInterface())
            return;
        if (isTeam() || isRole())
            multiAdapter.addVisitor(new AddImplicitActivationAdapter(this.writer, this));
        AddGlobalTeamActivationAdapter.checkAddVisitor(this.multiAdapter, this.writer);
    }

    @Override
    protected void prepareLiftingParticipant() {
        if (isTeam() && LiftingParticipantAdapter.isLiftingParticipantConfigured(this.loader)) {
            multiAdapter.addVisitor(new LiftingParticipantAdapter(this.writer));
        }
    }

    /**
     * Prepares the methods callAllBindings and callOrig with an empty
     * switch statement
     */
    @Override
    protected void prepareForFirstTransformation() {
        if (!isTransformed && !isInterface()) {
            nodes.add(new CreateSwitchAdapter(ConstantMembers.callOrig));
            nodes.add(new CreateSwitchForCallAllBindingsNode());
            nodes.add(new CreateAddRemoveRoleMethod());
            isTransformed = true;
        }
    }

    /**
     * Prepares the method callOrigStatic with an empty
     * switch statement
     */
    @Override
    protected void prepareForFirstStaticTransformation() {
        if (!isTransformedStatic && !isInterface()) {
            nodes.add(new CreateSwitchAdapter(getCallOrigStatic(), isRole()));
            isTransformedStatic = true;
        }
    }

    /**
     * Prepares the methods access and accessStatic with an empty
     * switch statement
     */
    @Override
    protected void prepareForFirstMemberAccess() {
        if (!isTransformedForMemberAccess && !isInterface()) {
            nodes.add(new CreateSwitchForAccessAdapter(ConstantMembers.access, getInternalSuperClassName(), this));
            nodes.add(new CreateSwitchForAccessAdapter(ConstantMembers.accessStatic, getInternalSuperClassName(),
                    this));
            isTransformedForMemberAccess = true;
        }

    }

    /**
     * Was the class already transformed?
     */
    @Override
    public boolean isFirstTransformation() {
        return isFirstTransformation;
    }
}