am afedbc47: Make Context.getClassLoader() work. [DO NOT MERGE]
* commit 'afedbc47b3c8f3fa9955496e6ceb7f5702dd90a3': Make Context.getClassLoader() work. [DO NOT MERGE]
This commit is contained in:
@@ -53,9 +53,14 @@ public final class BridgeInflater extends LayoutInflater {
|
|||||||
*/
|
*/
|
||||||
private static final String[] sClassPrefixList = {
|
private static final String[] sClassPrefixList = {
|
||||||
"android.widget.",
|
"android.widget.",
|
||||||
"android.webkit."
|
"android.webkit.",
|
||||||
|
"android.app."
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static String[] getClassPrefixList() {
|
||||||
|
return sClassPrefixList;
|
||||||
|
}
|
||||||
|
|
||||||
protected BridgeInflater(LayoutInflater original, Context newContext) {
|
protected BridgeInflater(LayoutInflater original, Context newContext) {
|
||||||
super(original, newContext);
|
super(original, newContext);
|
||||||
newContext = getBaseContext(newContext);
|
newContext = getBaseContext(newContext);
|
||||||
|
|||||||
@@ -137,8 +137,9 @@ public final class BridgeContext extends Context {
|
|||||||
|
|
||||||
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
|
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
|
||||||
private SharedPreferences mSharedPreferences;
|
private SharedPreferences mSharedPreferences;
|
||||||
|
private ClassLoader mClassLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
|
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
|
||||||
* @param metrics the {@link DisplayMetrics}.
|
* @param metrics the {@link DisplayMetrics}.
|
||||||
* @param renderResources the configured resources (both framework and projects) for this
|
* @param renderResources the configured resources (both framework and projects) for this
|
||||||
@@ -461,7 +462,21 @@ public final class BridgeContext extends Context {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClassLoader getClassLoader() {
|
public ClassLoader getClassLoader() {
|
||||||
return this.getClass().getClassLoader();
|
if (mClassLoader == null) {
|
||||||
|
mClassLoader = new ClassLoader(getClass().getClassLoader()) {
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
for (String prefix : BridgeInflater.getClassPrefixList()) {
|
||||||
|
if (name.startsWith(prefix)) {
|
||||||
|
// These are framework classes and should not be loaded from the app.
|
||||||
|
throw new ClassNotFoundException(name + " not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BridgeContext.this.mLayoutlibCallback.findClass(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return mClassLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
|
|||||||
mContext.getRenderResources().setLogger(null);
|
mContext.getRenderResources().setLogger(null);
|
||||||
}
|
}
|
||||||
ParserFactory.setParserFactory(null);
|
ParserFactory.setParserFactory(null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BridgeContext getCurrentContext() {
|
public static BridgeContext getCurrentContext() {
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ public class AsmAnalyzer {
|
|||||||
|
|
||||||
|
|
||||||
// Check if method needs to replaced by a call to a different method.
|
// Check if method needs to replaced by a call to a different method.
|
||||||
if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
|
if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) {
|
||||||
mReplaceMethodCallClasses.add(mOwnerClass);
|
mReplaceMethodCallClasses.add(mOwnerClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ public class AsmGenerator {
|
|||||||
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
|
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
|
||||||
* map old-FQCN => new-FQCN */
|
* map old-FQCN => new-FQCN */
|
||||||
private final HashMap<String, String> mRefactorClasses;
|
private final HashMap<String, String> mRefactorClasses;
|
||||||
|
/** Methods to inject. FQCN of class in which method should be injected => runnable that does
|
||||||
|
* the injection. */
|
||||||
|
private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new generator that can generate the output JAR with the stubbed classes.
|
* Creates a new generator that can generate the output JAR with the stubbed classes.
|
||||||
@@ -165,6 +168,8 @@ public class AsmGenerator {
|
|||||||
}
|
}
|
||||||
returnTypes.add(binaryToInternalClassName(className));
|
returnTypes.add(binaryToInternalClassName(className));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,7 +342,7 @@ public class AsmGenerator {
|
|||||||
ClassVisitor cv = cw;
|
ClassVisitor cv = cw;
|
||||||
|
|
||||||
if (mReplaceMethodCallsClasses.contains(className)) {
|
if (mReplaceMethodCallsClasses.contains(className)) {
|
||||||
cv = new ReplaceMethodCallsAdapter(cv);
|
cv = new ReplaceMethodCallsAdapter(cv, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
cv = new RefactorClassAdapter(cv, mRefactorClasses);
|
cv = new RefactorClassAdapter(cv, mRefactorClasses);
|
||||||
@@ -345,6 +350,10 @@ public class AsmGenerator {
|
|||||||
cv = new RenameClassAdapter(cv, className, newName);
|
cv = new RenameClassAdapter(cv, className, newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String binaryNewName = newName.replace('/', '.');
|
||||||
|
if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
|
||||||
|
cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
|
||||||
|
}
|
||||||
cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
|
cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
|
||||||
newName, cv, stubNativesOnly);
|
newName, cv, stubNativesOnly);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import com.android.tools.layoutlib.java.System_Delegate;
|
|||||||
import com.android.tools.layoutlib.java.UnsafeByteSequence;
|
import com.android.tools.layoutlib.java.UnsafeByteSequence;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,6 +107,7 @@ public final class CreateInfo implements ICreateInfo {
|
|||||||
return JAVA_PKG_CLASSES;
|
return JAVA_PKG_CLASSES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Set<String> getExcludedClasses() {
|
public Set<String> getExcludedClasses() {
|
||||||
String[] refactoredClasses = getJavaPkgClasses();
|
String[] refactoredClasses = getJavaPkgClasses();
|
||||||
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
|
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
|
||||||
@@ -115,6 +118,12 @@ public final class CreateInfo implements ICreateInfo {
|
|||||||
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
|
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
|
||||||
return excludedClasses;
|
return excludedClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
|
||||||
|
return INJECTED_METHODS;
|
||||||
|
}
|
||||||
|
|
||||||
//-----
|
//-----
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,5 +297,11 @@ public final class CreateInfo implements ICreateInfo {
|
|||||||
private final static String[] DELETE_RETURNS =
|
private final static String[] DELETE_RETURNS =
|
||||||
new String[] {
|
new String[] {
|
||||||
null }; // separator, for next class/methods list.
|
null }; // separator, for next class/methods list.
|
||||||
|
|
||||||
|
private final static Map<String, InjectMethodRunnable> INJECTED_METHODS =
|
||||||
|
new HashMap<String, InjectMethodRunnable>(1) {{
|
||||||
|
put("android.content.Context",
|
||||||
|
InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.tools.layoutlib.create;
|
package com.android.tools.layoutlib.create;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,33 +30,33 @@ public interface ICreateInfo {
|
|||||||
* Returns the list of class from layoutlib_create to inject in layoutlib.
|
* Returns the list of class from layoutlib_create to inject in layoutlib.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract Class<?>[] getInjectedClasses();
|
Class<?>[] getInjectedClasses();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of methods to rewrite as delegates.
|
* Returns the list of methods to rewrite as delegates.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getDelegateMethods();
|
String[] getDelegateMethods();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of classes on which to delegate all native methods.
|
* Returns the list of classes on which to delegate all native methods.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getDelegateClassNatives();
|
String[] getDelegateClassNatives();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns The list of methods to stub out. Each entry must be in the form
|
* Returns The list of methods to stub out. Each entry must be in the form
|
||||||
* "package.package.OuterClass$InnerClass#MethodName".
|
* "package.package.OuterClass$InnerClass#MethodName".
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getOverriddenMethods();
|
String[] getOverriddenMethods();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of classes to rename, must be an even list: the binary FQCN
|
* Returns the list of classes to rename, must be an even list: the binary FQCN
|
||||||
* of class to replace followed by the new FQCN.
|
* of class to replace followed by the new FQCN.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getRenamedClasses();
|
String[] getRenamedClasses();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of classes for which the methods returning them should be deleted.
|
* Returns the list of classes for which the methods returning them should be deleted.
|
||||||
@@ -62,7 +65,7 @@ public interface ICreateInfo {
|
|||||||
* the methods to delete.
|
* the methods to delete.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getDeleteReturns();
|
String[] getDeleteReturns();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of classes to refactor, must be an even list: the
|
* Returns the list of classes to refactor, must be an even list: the
|
||||||
@@ -70,7 +73,18 @@ public interface ICreateInfo {
|
|||||||
* to the old class should be updated to the new class.
|
* to the old class should be updated to the new class.
|
||||||
* The list can be empty but must not be null.
|
* The list can be empty but must not be null.
|
||||||
*/
|
*/
|
||||||
public abstract String[] getJavaPkgClasses();
|
String[] getJavaPkgClasses();
|
||||||
|
|
||||||
public abstract Set<String> getExcludedClasses();
|
Set<String> getExcludedClasses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
|
||||||
|
* called to inject methods into a class.
|
||||||
|
* Can be empty but must not be null.
|
||||||
|
*/
|
||||||
|
Map<String, InjectMethodRunnable> getInjectedMethodsMap();
|
||||||
|
|
||||||
|
abstract class InjectMethodRunnable {
|
||||||
|
public abstract void generateMethods(ClassVisitor cv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.tools.layoutlib.create;
|
||||||
|
|
||||||
|
import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.ALOAD;
|
||||||
|
import static org.objectweb.asm.Opcodes.ARETURN;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
||||||
|
|
||||||
|
public class InjectMethodRunnables {
|
||||||
|
public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER
|
||||||
|
= new InjectMethodRunnable() {
|
||||||
|
@Override
|
||||||
|
public void generateMethods(ClassVisitor cv) {
|
||||||
|
// generated by compiling the class:
|
||||||
|
// class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } }
|
||||||
|
// and then running ASMifier on it:
|
||||||
|
// java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo
|
||||||
|
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader",
|
||||||
|
"()Ljava/lang/ClassLoader;", null, null);
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
|
||||||
|
"()Ljava/lang/Class;");
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
|
||||||
|
"()Ljava/lang/ClassLoader;");
|
||||||
|
mv.visitInsn(ARETURN);
|
||||||
|
mv.visitMaxs(1, 1);
|
||||||
|
mv.visitEnd();
|
||||||
|
// generated code ends.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.tools.layoutlib.create;
|
||||||
|
|
||||||
|
import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects methods into some classes.
|
||||||
|
*/
|
||||||
|
public class InjectMethodsAdapter extends ClassVisitor {
|
||||||
|
|
||||||
|
private final ICreateInfo.InjectMethodRunnable mRunnable;
|
||||||
|
|
||||||
|
public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
|
||||||
|
super(Opcodes.ASM4, cv);
|
||||||
|
mRunnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
mRunnable.generateMethods(this);
|
||||||
|
super.visitEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,14 +62,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
// Case 1: java.lang.System.arraycopy()
|
// Case 1: java.lang.System.arraycopy()
|
||||||
METHOD_REPLACERS.add(new MethodReplacer() {
|
METHOD_REPLACERS.add(new MethodReplacer() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeeded(String owner, String name, String desc) {
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
|
return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
|
||||||
ARRAYCOPY_DESCRIPTORS.contains(desc);
|
ARRAYCOPY_DESCRIPTORS.contains(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(MethodInformation mi) {
|
public void replace(MethodInformation mi) {
|
||||||
assert isNeeded(mi.owner, mi.name, mi.desc);
|
|
||||||
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
|
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -81,14 +80,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
|
Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeeded(String owner, String name, String desc) {
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
|
return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
|
||||||
("toLanguageTag".equals(name) || "getScript".equals(name));
|
("toLanguageTag".equals(name) || "getScript".equals(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(MethodInformation mi) {
|
public void replace(MethodInformation mi) {
|
||||||
assert isNeeded(mi.owner, mi.name, mi.desc);
|
|
||||||
mi.opcode = Opcodes.INVOKESTATIC;
|
mi.opcode = Opcodes.INVOKESTATIC;
|
||||||
mi.owner = ANDROID_LOCALE_CLASS;
|
mi.owner = ANDROID_LOCALE_CLASS;
|
||||||
mi.desc = LOCALE_TO_STRING;
|
mi.desc = LOCALE_TO_STRING;
|
||||||
@@ -103,7 +101,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
Type.getType(Locale.class), STRING);
|
Type.getType(Locale.class), STRING);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeeded(String owner, String name, String desc) {
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
return JAVA_LOCALE_CLASS.equals(owner) &&
|
return JAVA_LOCALE_CLASS.equals(owner) &&
|
||||||
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
|
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
|
||||||
"forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE));
|
"forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE));
|
||||||
@@ -111,7 +109,6 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(MethodInformation mi) {
|
public void replace(MethodInformation mi) {
|
||||||
assert isNeeded(mi.owner, mi.name, mi.desc);
|
|
||||||
mi.owner = ANDROID_LOCALE_CLASS;
|
mi.owner = ANDROID_LOCALE_CLASS;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -119,14 +116,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
// Case 4: java.lang.System.log?()
|
// Case 4: java.lang.System.log?()
|
||||||
METHOD_REPLACERS.add(new MethodReplacer() {
|
METHOD_REPLACERS.add(new MethodReplacer() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeeded(String owner, String name, String desc) {
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
|
return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
|
||||||
&& name.startsWith("log");
|
&& name.startsWith("log");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(MethodInformation mi) {
|
public void replace(MethodInformation mi) {
|
||||||
assert isNeeded(mi.owner, mi.name, mi.desc);
|
|
||||||
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|
||||||
|| mi.desc.equals("(Ljava/lang/String;)V");
|
|| mi.desc.equals("(Ljava/lang/String;)V");
|
||||||
mi.name = "log";
|
mi.name = "log";
|
||||||
@@ -142,7 +138,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
|
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeeded(String owner, String name, String desc) {
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
return LINKED_HASH_MAP.equals(owner) &&
|
return LINKED_HASH_MAP.equals(owner) &&
|
||||||
"eldest".equals(name) &&
|
"eldest".equals(name) &&
|
||||||
VOID_TO_MAP_ENTRY.equals(desc);
|
VOID_TO_MAP_ENTRY.equals(desc);
|
||||||
@@ -150,26 +146,64 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(MethodInformation mi) {
|
public void replace(MethodInformation mi) {
|
||||||
assert isNeeded(mi.owner, mi.name, mi.desc);
|
|
||||||
mi.opcode = Opcodes.INVOKESTATIC;
|
mi.opcode = Opcodes.INVOKESTATIC;
|
||||||
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
|
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
|
||||||
mi.desc = Type.getMethodDescriptor(
|
mi.desc = Type.getMethodDescriptor(
|
||||||
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
|
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Case 6: android.content.Context.getClassLoader() in LayoutInflater
|
||||||
|
METHOD_REPLACERS.add(new MethodReplacer() {
|
||||||
|
// When LayoutInflater asks for a class loader, we must return the class loader that
|
||||||
|
// cannot return app's custom views/classes. This is so that in case of any failure
|
||||||
|
// or exception when instantiating the views, the IDE can replace it with a mock view
|
||||||
|
// and have proper error handling. However, if a custom view asks for the class
|
||||||
|
// loader, we must return a class loader that can find app's custom views as well.
|
||||||
|
// Thus, we rewrite the call to get class loader in LayoutInflater to
|
||||||
|
// getFrameworkClassLoader and inject a new method in Context. This leaves the normal
|
||||||
|
// method: Context.getClassLoader() free to be used by the apps.
|
||||||
|
private final String VOID_TO_CLASS_LOADER =
|
||||||
|
Type.getMethodDescriptor(Type.getType(ClassLoader.class));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
|
||||||
|
return owner.equals("android/content/Context") &&
|
||||||
|
sourceClass.equals("android/view/LayoutInflater") &&
|
||||||
|
name.equals("getClassLoader") &&
|
||||||
|
desc.equals(VOID_TO_CLASS_LOADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replace(MethodInformation mi) {
|
||||||
|
mi.name = "getFrameworkClassLoader";
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isReplacementNeeded(String owner, String name, String desc) {
|
/**
|
||||||
|
* If a method some.package.Class.Method(args) is called from some.other.Class,
|
||||||
|
* @param owner some/package/Class
|
||||||
|
* @param name Method
|
||||||
|
* @param desc (args)returnType
|
||||||
|
* @param sourceClass some/other/Class
|
||||||
|
* @return if the method invocation needs to be replaced by some other class.
|
||||||
|
*/
|
||||||
|
public static boolean isReplacementNeeded(String owner, String name, String desc,
|
||||||
|
String sourceClass) {
|
||||||
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
||||||
if (replacer.isNeeded(owner, name, desc)) {
|
if (replacer.isNeeded(owner, name, desc, sourceClass)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplaceMethodCallsAdapter(ClassVisitor cv) {
|
private final String mOriginalClassName;
|
||||||
|
|
||||||
|
public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
|
||||||
super(Opcodes.ASM4, cv);
|
super(Opcodes.ASM4, cv);
|
||||||
|
mOriginalClassName = originalClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -187,7 +221,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||||
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
||||||
if (replacer.isNeeded(owner, name, desc)) {
|
if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
|
||||||
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
|
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
|
||||||
replacer.replace(mi);
|
replacer.replace(mi);
|
||||||
opcode = mi.opcode;
|
opcode = mi.opcode;
|
||||||
@@ -216,13 +250,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private interface MethodReplacer {
|
private interface MethodReplacer {
|
||||||
public boolean isNeeded(String owner, String name, String desc);
|
boolean isNeeded(String owner, String name, String desc, String sourceClass);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the MethodInformation with the new values of the method attributes -
|
* Updates the MethodInformation with the new values of the method attributes -
|
||||||
* opcode, owner, name and desc.
|
* opcode, owner, name and desc.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public void replace(MethodInformation mi);
|
void replace(MethodInformation mi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package com.android.tools.layoutlib.create;
|
|||||||
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -32,13 +34,17 @@ import org.objectweb.asm.MethodVisitor;
|
|||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -130,6 +136,11 @@ public class AsmGeneratorTest {
|
|||||||
// methods deleted from their return type.
|
// methods deleted from their return type.
|
||||||
return new String[0];
|
return new String[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
|
||||||
|
return new HashMap<String, InjectMethodRunnable>(0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
||||||
@@ -200,6 +211,11 @@ public class AsmGeneratorTest {
|
|||||||
// methods deleted from their return type.
|
// methods deleted from their return type.
|
||||||
return new String[0];
|
return new String[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
|
||||||
|
return new HashMap<String, InjectMethodRunnable>(0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
||||||
@@ -278,6 +294,11 @@ public class AsmGeneratorTest {
|
|||||||
// methods deleted from their return type.
|
// methods deleted from their return type.
|
||||||
return new String[0];
|
return new String[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
|
||||||
|
return new HashMap<String, InjectMethodRunnable>(0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
||||||
@@ -303,6 +324,118 @@ public class AsmGeneratorTest {
|
|||||||
filesFound.keySet().toArray());
|
filesFound.keySet().toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodInjection() throws IOException, LogAbortException,
|
||||||
|
ClassNotFoundException, IllegalAccessException, InstantiationException,
|
||||||
|
NoSuchMethodException, InvocationTargetException {
|
||||||
|
ICreateInfo ci = new ICreateInfo() {
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getInjectedClasses() {
|
||||||
|
return new Class<?>[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDelegateMethods() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDelegateClassNatives() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getOverriddenMethods() {
|
||||||
|
// methods to force override
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getRenamedClasses() {
|
||||||
|
// classes to rename (so that we can replace them)
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getJavaPkgClasses() {
|
||||||
|
// classes to refactor (so that we can replace them)
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getExcludedClasses() {
|
||||||
|
return new HashSet<String>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDeleteReturns() {
|
||||||
|
// methods deleted from their return type.
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
|
||||||
|
HashMap<String, InjectMethodRunnable> map =
|
||||||
|
new HashMap<String, InjectMethodRunnable>(1);
|
||||||
|
map.put("mock_android.util.EmptyArray",
|
||||||
|
InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
|
||||||
|
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
|
||||||
|
null, // derived from
|
||||||
|
new String[] { // include classes
|
||||||
|
"**"
|
||||||
|
},
|
||||||
|
ci.getExcludedClasses(),
|
||||||
|
new String[] { /* include files */
|
||||||
|
"mock_android/data/data*"
|
||||||
|
});
|
||||||
|
aa.analyze();
|
||||||
|
agen.generate();
|
||||||
|
Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
|
||||||
|
Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
|
||||||
|
parseZip(mOsDestJar, output, filesFound);
|
||||||
|
final String modifiedClass = "mock_android.util.EmptyArray";
|
||||||
|
final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
|
||||||
|
ZipFile zipFile = new ZipFile(mOsDestJar);
|
||||||
|
ZipEntry entry = zipFile.getEntry(modifiedClassPath);
|
||||||
|
assertNotNull(entry);
|
||||||
|
final byte[] bytes;
|
||||||
|
final InputStream inputStream = zipFile.getInputStream(entry);
|
||||||
|
try {
|
||||||
|
bytes = getByteArray(inputStream);
|
||||||
|
} finally {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
if (name.equals(modifiedClass)) {
|
||||||
|
return defineClass(null, bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
throw new ClassNotFoundException(name + " not found.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
|
||||||
|
Object emptyArrayInstance = emptyArrayClass.newInstance();
|
||||||
|
Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
|
||||||
|
Object cl = method.invoke(emptyArrayInstance);
|
||||||
|
assertEquals(classLoader, cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getByteArray(InputStream stream) throws IOException {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read;
|
||||||
|
while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
|
||||||
|
bos.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private void parseZip(String jarPath,
|
private void parseZip(String jarPath,
|
||||||
Map<String, ClassReader> classes,
|
Map<String, ClassReader> classes,
|
||||||
Map<String, InputStream> filesFound) throws IOException {
|
Map<String, InputStream> filesFound) throws IOException {
|
||||||
|
|||||||
Reference in New Issue
Block a user