/* * Copyright (C) 2015 The Android Open Source Project * * 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 dodola.anole.lib; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.tree.LabelNode; import java.util.List; /** * A specialized redirection that handles redirecting the part that redirects the * argument construction for the super()/this() call in a constructor. * <p/> * Note that the generated bytecode does not have a direct translation to code, but as an * example, for a constructor of the form: * <code> * <init>(int x) { * super(x = 1, expr2() ? 3 : 7) * doSomething(x) * } * </code> * <p/> * it becomes: * <code> * <init>(int x) { * Change change = $change; // Move to a variable to avoid multithreading issues. * int a, b; // These variables are not needed in bytecode but are needed for the example. * if (change != null) { * Object[] locals = new Object[2]; * locals[0] = locals; // So the unboxed receiver can update this array * locals[1] = x; * Object[] constructorArguments = change.access$dispatch("init$args", locals); * x = locals[1]; * this(constructorArguments, null); * } else { * a = x = 1; * b = expr2() ? 3 : 7; * super(a, b); * } * if (change != null) { * Object[] locals = new Object[2]; * locals[0] = this; * locals[1] = x; * change.access$dispatch("init$body", locals); * return; * } * doSomething(x); * } * </code> * * @see ConstructorDelegationDetector for the generation of init$args and init$body. */ public class ConstructorArgsRedirection extends Redirection { private final String thisClassName; private final Type[] types; private final LabelNode end; private int locals; // The signature of the dynamically dispatching 'this' constructor. The final parameters is // to disambiguate from other constructors that might preexist on the class. static final String DISPATCHING_THIS_SIGNATURE = "([Ljava/lang/Object;L" + IncrementalVisitor.INSTANT_RELOAD_EXCEPTION + ";)V"; /** * @param thisClassName name of the class that this constructor is in. * @param name the name to redirect to. * @param end the label where the redirection should end (before the super()/this() call). * @param types the types of the arguments on the super()/this() call. */ ConstructorArgsRedirection(LabelNode label, String thisClassName, String name, LabelNode end, Type[] types) { super(label, name); this.thisClassName = thisClassName; this.types = types; this.end = end; locals = -1; } @Override protected void createLocals(GeneratorAdapter mv, List<Type> args) { super.createLocals(mv, args); // Override the locals creation to keep a reference to it. We keep a reference to this // array because we use it to receive the values of the local variables after the // redirection is done. locals = mv.newLocal(Type.getType("[Ljava/lang/Object;")); mv.dup(); mv.storeLocal(locals); } @Override protected void redirectLocal(GeneratorAdapter mv, int stackIndex, Type arg) { // If the stack index is 0, we do not send the local variable 0 (this) as it // cannot escape the constructor. Instead, we use this argument position to send // a reference to the locals array where the redirected method will return their // values. if (stackIndex == 0) { mv.loadLocal(locals); } else { super.redirectLocal(mv, stackIndex, arg); } } @Override protected void restore(GeneratorAdapter mv, List<Type> args) { // At this point, init$args has been called and the result Object is on the stack. // The value of that Object is Object[] with exactly n + 1 elements. // The first element is a string with the qualified name of the constructor to call. // The remaining elements are the constructtor arguments. // Create a new local that holds the result of init$args call. mv.visitTypeInsn(Opcodes.CHECKCAST, "[Ljava/lang/Object;"); int constructorArgs = mv.newLocal(Type.getType("[Ljava/lang/Object;")); mv.storeLocal(constructorArgs); // Reinstate local values mv.loadLocal(locals); int stackIndex = 0; for (int arrayIndex = 0; arrayIndex < args.size(); arrayIndex++) { Type arg = args.get(arrayIndex); // Do not restore "this" if (arrayIndex > 0) { // duplicates the array mv.dup(); // index in the array of objects to restore the boxed parameter. mv.push(arrayIndex); // get it from the array mv.arrayLoad(Type.getType(Object.class)); // unbox the argument ByteCodeUtils.unbox(mv, arg); // restore the argument mv.visitVarInsn(arg.getOpcode(Opcodes.ISTORE), stackIndex); } // stack index must progress according to the parameter type we just processed. stackIndex += arg.getSize(); } // pops the array mv.pop(); // Push a null for the marker parameter. mv.loadLocal(constructorArgs); mv.visitInsn(Opcodes.ACONST_NULL); // Invoke the constructor mv.visitMethodInsn(Opcodes.INVOKESPECIAL, thisClassName, "<init>", DISPATCHING_THIS_SIGNATURE, false); mv.goTo(end.getLabel()); } }