Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a30efb5b authored by Deepanshu Gupta's avatar Deepanshu Gupta Committed by Android Git Automerger
Browse files

am 198537c2: am 7dc35060: am df076962: am 1cf5df38: Layoutlib Create: Remove...

am 198537c2: am 7dc35060: am df076962: am 1cf5df38: Layoutlib Create: Remove references to non-std Java classes.

* commit '198537c2':
  Layoutlib Create: Remove references to non-std Java classes.
parents e5f3f658 198537c2
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="src" path="src"/>
	<classpathentry excluding="mock_android/" kind="src" path="tests"/>
	<classpathentry excluding="mock_data/" kind="src" path="tests"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/>
	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
	<classpathentry kind="output" path="bin"/>
</classpath>
+43 −12
Original line number Diff line number Diff line
@@ -71,6 +71,9 @@ class names, for example "android.*.R**" ("*" does not matches dots whilst "**"
and "." and "$" are interpreted as-is).
In practice we almost but not quite request the inclusion of full packages.

The analyzer is also given a list of classes to exclude. A fake implementation of these
classes is injected by the Generator.

With this information, the analyzer parses the input zip to find all the classes.
All classes deriving from the requested bases classes are kept.
All classes which name matched the glob pattern are kept.
@@ -93,6 +96,7 @@ and lists:
- specific methods for which to delegate calls.
- specific methods to remove based on their return type.
- specific classes to rename.
- specific classes to refactor.

Each of these are specific strategies we use to be able to modify the Android code
to fit within the Eclipse renderer. These strategies are explained beow.
@@ -100,10 +104,7 @@ to fit within the Eclipse renderer. These strategies are explained beow.
The core method of the generator is transform(): it takes an input ASM ClassReader
and modifies it to produce a byte array suitable for the final JAR file.

The first step of the transformation is changing the name of the class in case
we requested the class to be renamed. This uses the RenameClassAdapter to also rename
all inner classes and references in methods and types. Note that other classes are
not transformed and keep referencing the original name.
The first step of the transformation is to implement the method delegates.

The TransformClassAdapter is then used to process the potentially renamed class.
All protected or private classes are market as public.
@@ -115,11 +116,25 @@ Methods are also changed from protected/private to public.
The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub.

The transformed class is then fed through the DelegateClassAdapter to implement
method delegates. 

Finally fields are also visited and changed from protected/private to public.

The next step of the transformation is changing the name of the class in case
we requested the class to be renamed. This uses the RenameClassAdapter to also rename
all inner classes and references in methods and types. Note that other classes are
not transformed and keep referencing the original name.

The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but
updates the references in all classes. This is used to update the references of classes
in the java package that were added in the Dalvik VM but are not a part of the standard
JVM. The existing classes are modified to update all references to these non-standard
classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is
injected.

The ClassAdapters are chained together to achieve the desired output. (Look at section
2.2.7 Transformation chains in the asm user guide, link in the References.) The order of
execution of these is:
ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
RefactorClassAdapter -> ClassWriter

- Method stubs
--------------
@@ -141,19 +156,27 @@ This strategy is now obsolete and replaced by the method delegates.
- Strategies
------------

We currently have 4 strategies to deal with overriding the rendering code
We currently have 6 strategies to deal with overriding the rendering code
and make it run in Eclipse. Most of these strategies are implemented hand-in-hand
by the bridge (which runs in Eclipse) and the generator.


1- Class Injection

This is the easiest: we currently inject 4 classes, namely:
This is the easiest: we currently inject the following classes:
- OverrideMethod and its associated MethodListener and MethodAdapter are used
  to intercept calls to some specific methods that are stubbed out and change
  their return value.
- CreateInfo class, which configured the generator. Not used yet, but could
  in theory help us track what the generator changed.
- AutoCloseable is part of Java 7. To enable us to still run on Java 6, a new class is
  injected. The implementation for the class has been taken from Android's libcore
  (platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java).
- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM.
  They are added to the Dalvik VM for performance reasons. An implementation that is very
  close to the original (which is at platform/libcore/luni/src/main/java/...) is injected.
  Since these classees were in part of the java package, where we can't inject classes,
  all references to these have been updated (See strategy 4- Refactoring Classes).


2- Overriding methods
@@ -189,7 +212,15 @@ we don't control object creation.
This won't rename/replace the inner static methods of a given class.


4- Method erasure based on return type
4- Refactoring classes

This is very similar to the Renaming classes except that it also updates the reference in
all classes. This is done for classes which are added to the Dalvik VM for performance
reasons but are not present in the Standard Java VM. An implementation for these classes
is also injected.


5- Method erasure based on return type

This is mostly an implementation detail of the bridge: in the Paint class
mentioned above, some inner static classes are used to pass around
@@ -201,7 +232,7 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation.


5- Method Delegates
6- Method Delegates

This strategy is used to override method implementations.
Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
@@ -233,7 +264,7 @@ Bytecode opcode list:
  http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

ASM user guide:
  http://download.forge.objectweb.org/asm/asm-guide.pdf
  http://download.forge.objectweb.org/asm/asm4-guide.pdf


--
+418 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.tools.layoutlib.create;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;

/**
 * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It
 * goes through the complete class and finds references to other classes. It
 * then calls {@link #renameInternalType(String)} to convert the className to
 * the new value, if need be.
 */
public abstract class AbstractClassAdapter extends ClassVisitor {

    /**
     * Returns the new FQCN for the class, if the reference to this class needs
     * to be updated. Else, it returns the same string.
     * @param name Old FQCN
     * @return New FQCN if it needs to be renamed, else the old FQCN
     */
    abstract String renameInternalType(String name);

    public AbstractClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    /**
     * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
     * If the type doesn't need to be renamed, returns the input string as-is.
     */
    String renameTypeDesc(String desc) {
        if (desc == null) {
            return null;
        }

        return renameType(Type.getType(desc));
    }

    /**
     * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
     * object element, e.g. "[Lcom.package.MyClass;"
     * If the type doesn't need to be renamed, returns the internal name of the input type.
     */
    String renameType(Type type) {
        if (type == null) {
            return null;
        }

        if (type.getSort() == Type.OBJECT) {
            String in = type.getInternalName();
            return "L" + renameInternalType(in) + ";";
        } else if (type.getSort() == Type.ARRAY) {
            StringBuilder sb = new StringBuilder();
            for (int n = type.getDimensions(); n > 0; n--) {
                sb.append('[');
            }
            sb.append(renameType(type.getElementType()));
            return sb.toString();
        }
        return type.getDescriptor();
    }

    /**
     * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
     * object element, e.g. "[Lcom.package.MyClass;".
     * This is like renameType() except that it returns a Type object.
     * If the type doesn't need to be renamed, returns the input type object.
     */
    Type renameTypeAsType(Type type) {
        if (type == null) {
            return null;
        }

        if (type.getSort() == Type.OBJECT) {
            String in = type.getInternalName();
            String newIn = renameInternalType(in);
            if (newIn != in) {
                return Type.getType("L" + newIn + ";");
            }
        } else if (type.getSort() == Type.ARRAY) {
            StringBuilder sb = new StringBuilder();
            for (int n = type.getDimensions(); n > 0; n--) {
                sb.append('[');
            }
            sb.append(renameType(type.getElementType()));
            return Type.getType(sb.toString());
        }
        return type;
    }

    /**
     * Renames a method descriptor, i.e. applies renameType to all arguments and to the
     * return value.
     */
    String renameMethodDesc(String desc) {
        if (desc == null) {
            return null;
        }

        Type[] args = Type.getArgumentTypes(desc);

        StringBuilder sb = new StringBuilder("(");
        for (Type arg : args) {
            String name = renameType(arg);
            sb.append(name);
        }
        sb.append(')');

        Type ret = Type.getReturnType(desc);
        String name = renameType(ret);
        sb.append(name);

        return sb.toString();
    }


    /**
     * Renames the ClassSignature handled by ClassVisitor.visit
     * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
     */
    String renameTypeSignature(String sig) {
        if (sig == null) {
            return null;
        }
        SignatureReader reader = new SignatureReader(sig);
        SignatureWriter writer = new SignatureWriter();
        reader.accept(new RenameSignatureAdapter(writer));
        sig = writer.toString();
        return sig;
    }


    /**
     * Renames the FieldTypeSignature handled by ClassVisitor.visitField
     * or MethodVisitor.visitLocalVariable.
     */
    String renameFieldSignature(String sig) {
        return renameTypeSignature(sig);
    }


    //----------------------------------
    // Methods from the ClassAdapter

    @Override
    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        name = renameInternalType(name);
        superName = renameInternalType(superName);
        signature = renameTypeSignature(signature);
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; ++i) {
                interfaces[i] = renameInternalType(interfaces[i]);
            }
        }

        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public void visitInnerClass(String name, String outerName, String innerName, int access) {
        name = renameInternalType(name);
        outerName = renameInternalType(outerName);
        super.visitInnerClass(name, outerName, innerName, access);
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
        super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc));
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        desc = renameMethodDesc(desc);
        signature = renameTypeSignature(signature);
        MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
        return new RenameMethodAdapter(mw);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        desc = renameTypeDesc(desc);
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
            String signature, Object value) {
        desc = renameTypeDesc(desc);
        return super.visitField(access, name, desc, signature, value);
    }


    //----------------------------------

    /**
     * A method visitor that renames all references from an old class name to a new class name.
     */
    public class RenameMethodAdapter extends MethodVisitor {

        /**
         * Creates a method visitor that renames all references from a given old name to a given new
         * name. The method visitor will also rename all inner classes.
         * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
         */
        public RenameMethodAdapter(MethodVisitor mv) {
            super(Opcodes.ASM4, mv);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            desc = renameTypeDesc(desc);

            return super.visitAnnotation(desc, visible);
        }

        @Override
        public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
            desc = renameTypeDesc(desc);

            return super.visitParameterAnnotation(parameter, desc, visible);
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
            // The type sometimes turns out to be a type descriptor. We try to detect it and fix.
            if (type.indexOf(';') > 0) {
                type = renameTypeDesc(type);
            } else {
                type = renameInternalType(type);
            }
            super.visitTypeInsn(opcode, type);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            owner = renameInternalType(owner);
            desc = renameTypeDesc(desc);

            super.visitFieldInsn(opcode, owner, name, desc);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
            // The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
            if (owner.indexOf(';') > 0) {
                owner = renameTypeDesc(owner);
            } else {
                owner = renameInternalType(owner);
            }
            desc = renameMethodDesc(desc);

            super.visitMethodInsn(opcode, owner, name, desc);
        }

        @Override
        public void visitLdcInsn(Object cst) {
            // If cst is a Type, this means the code is trying to pull the .class constant
            // for this class, so it needs to be renamed too.
            if (cst instanceof Type) {
                cst = renameTypeAsType((Type) cst);
            }
            super.visitLdcInsn(cst);
        }

        @Override
        public void visitMultiANewArrayInsn(String desc, int dims) {
            desc = renameTypeDesc(desc);

            super.visitMultiANewArrayInsn(desc, dims);
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
            type = renameInternalType(type);

            super.visitTryCatchBlock(start, end, handler, type);
        }

        @Override
        public void visitLocalVariable(String name, String desc, String signature,
                Label start, Label end, int index) {
            desc = renameTypeDesc(desc);
            signature = renameFieldSignature(signature);

            super.visitLocalVariable(name, desc, signature, start, end, index);
        }

    }

    //----------------------------------

    public class RenameSignatureAdapter extends SignatureVisitor {

        private final SignatureVisitor mSv;

        public RenameSignatureAdapter(SignatureVisitor sv) {
            super(Opcodes.ASM4);
            mSv = sv;
        }

        @Override
        public void visitClassType(String name) {
            name = renameInternalType(name);
            mSv.visitClassType(name);
        }

        @Override
        public void visitInnerClassType(String name) {
            name = renameInternalType(name);
            mSv.visitInnerClassType(name);
        }

        @Override
        public SignatureVisitor visitArrayType() {
            SignatureVisitor sv = mSv.visitArrayType();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public void visitBaseType(char descriptor) {
            mSv.visitBaseType(descriptor);
        }

        @Override
        public SignatureVisitor visitClassBound() {
            SignatureVisitor sv = mSv.visitClassBound();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public void visitEnd() {
            mSv.visitEnd();
        }

        @Override
        public SignatureVisitor visitExceptionType() {
            SignatureVisitor sv = mSv.visitExceptionType();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public void visitFormalTypeParameter(String name) {
            mSv.visitFormalTypeParameter(name);
        }

        @Override
        public SignatureVisitor visitInterface() {
            SignatureVisitor sv = mSv.visitInterface();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public SignatureVisitor visitInterfaceBound() {
            SignatureVisitor sv = mSv.visitInterfaceBound();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public SignatureVisitor visitParameterType() {
            SignatureVisitor sv = mSv.visitParameterType();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public SignatureVisitor visitReturnType() {
            SignatureVisitor sv = mSv.visitReturnType();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public SignatureVisitor visitSuperclass() {
            SignatureVisitor sv = mSv.visitSuperclass();
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public void visitTypeArgument() {
            mSv.visitTypeArgument();
        }

        @Override
        public SignatureVisitor visitTypeArgument(char wildcard) {
            SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
            return new RenameSignatureAdapter(sv);
        }

        @Override
        public void visitTypeVariable(String name) {
            mSv.visitTypeVariable(name);
        }

    }
}
+17 −8
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
@@ -57,6 +58,8 @@ public class AsmAnalyzer {
    private final String[] mDeriveFrom;
    /** Glob patterns of classes to keep, e.g. "com.foo.*" */
    private final String[] mIncludeGlobs;
    /** The set of classes to exclude.*/
    private final Set<String> mExcludedClasses;

    /**
     * Creates a new analyzer.
@@ -69,12 +72,13 @@ public class AsmAnalyzer {
     *        ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
     */
    public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
            String[] deriveFrom, String[] includeGlobs) {
            String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
        mLog = log;
        mGen = gen;
        mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
        mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
        mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
        mExcludedClasses = excludeClasses;
    }

    /**
@@ -82,9 +86,6 @@ public class AsmAnalyzer {
     * Fills the generator with classes & dependencies found.
     */
    public void analyze() throws IOException, LogAbortException {

        AsmAnalyzer visitor = this;

        Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
        mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
                mOsSourceJar.size() > 1 ? "s" : "");
@@ -232,7 +233,7 @@ public class AsmAnalyzer {
     */
    void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
            Map<String, ClassReader> inOutFound) throws LogAbortException {
        ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
        findClass(super_name, zipClasses, inOutFound);

        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
            String className = entry.getKey();
@@ -363,11 +364,12 @@ public class AsmAnalyzer {

            className = internalToBinaryClassName(className);

            // exclude classes that have already been found
            // exclude classes that have already been found or are marked to be excluded
            if (mInKeep.containsKey(className) ||
                    mOutKeep.containsKey(className) ||
                    mInDeps.containsKey(className) ||
                    mOutDeps.containsKey(className)) {
                    mOutDeps.containsKey(className) ||
                    mExcludedClasses.contains(getBaseName(className))) {
                return;
            }

@@ -451,6 +453,13 @@ public class AsmAnalyzer {
            }
        }

        private String getBaseName(String className) {
            int pos = className.indexOf('$');
            if (pos > 0) {
                return className.substring(0, pos);
            }
            return className;
        }

        // ---------------------------------------------------
        // --- ClassVisitor, FieldVisitor
@@ -682,7 +691,7 @@ public class AsmAnalyzer {
            }

            @Override
            public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
            public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
                // pass -- table switch instruction

            }
+18 −4
Original line number Diff line number Diff line
@@ -65,6 +65,9 @@ public class AsmGenerator {
    /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
     *  The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
    private final HashMap<String, Set<String>> mDelegateMethods;
    /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
     * map old-FQCN => new-FQCN */
    private final HashMap<String, String> mRefactorClasses;

    /**
     * Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -119,6 +122,17 @@ public class AsmGenerator {
            mClassesNotRenamed.add(oldFqcn);
        }

        // Create a map of classes to be refactored.
        mRefactorClasses = new HashMap<String, String>();
        String[] refactorClasses = createInfo.getJavaPkgClasses();
        n = refactorClasses.length;
        for (int i = 0; i < n; i += 2) {
            assert i + 1 < n;
            String oldFqcn = binaryToInternalClassName(refactorClasses[i]);
            String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]);
            mRefactorClasses.put(oldFqcn, newFqcn);;
        }

        // create the map of renamed class -> return type of method to delete.
        mDeleteReturns = new HashMap<String, Set<String>>();
        String[] deleteReturns = createInfo.getDeleteReturns();
@@ -308,14 +322,14 @@ public class AsmGenerator {
        // original class reader.
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        ClassVisitor rv = cw;
        ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses);
        if (newName != className) {
            rv = new RenameClassAdapter(cw, className, newName);
            cv = new RenameClassAdapter(cv, className, newName);
        }

        ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
        cv = new TransformClassAdapter(mLog, mStubMethods,
                mDeleteReturns.get(className),
                newName, rv,
                newName, cv,
                stubNativesOnly, stubNativesOnly || hasNativeMethods);

        Set<String> delegateMethods = mDelegateMethods.get(className);
Loading