From 485c4374832af732e073c9656e5422ceb7d1635d Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Fri, 16 Dec 2016 10:09:41 -0500 Subject: Hacks to allow mocking of package private classes 2 things are required to allow mocking for package private classes. First is creating the mock in the same class as the base, that part is simple and seems to have no effect. The second is to have a matching ClassLoader as the source class. To make the classloader match, instead of generating a new ClassLoader, add the new dex to the existing one and share the base class's ClassLoader. Since this is a persistent change and could affect the process, it will be opt-in through setting a system property. This also appears to have unintended speedup in the tests I was testing, possible through cache fixes or brokenness. Test: Add System.setProperty("dexmaker.share_classloader", "true) to SysuiTestCase then runtest systemui (cherry picked from commit 31b974435499d3c9769ed6a746e946187f3be64f) Change-Id: Ic871c5f48f5d5e38946b77de10d71ca4589c5daa Merged-In: I566ff25dcdf53fbb8a0acd087724b068120f82c6 --- .../main/java/com/google/dexmaker/DexMaker.java | 29 +++++++++++++++----- .../com/google/dexmaker/stock/ProxyBuilder.java | 31 ++++++++++++++++------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/dexmaker/src/main/java/com/google/dexmaker/DexMaker.java b/dexmaker/src/main/java/com/google/dexmaker/DexMaker.java index 4e67433..a6a0999 100644 --- a/dexmaker/src/main/java/com/google/dexmaker/DexMaker.java +++ b/dexmaker/src/main/java/com/google/dexmaker/DexMaker.java @@ -32,6 +32,7 @@ import com.android.dx.rop.code.RopMethod; import com.android.dx.rop.cst.CstString; import com.android.dx.rop.cst.CstType; import com.android.dx.rop.type.StdTypeList; +import com.google.dexmaker.stock.ProxyBuilder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -40,6 +41,8 @@ import java.lang.reflect.Modifier; import static java.lang.reflect.Modifier.PRIVATE; import static java.lang.reflect.Modifier.STATIC; +import dalvik.system.DexClassLoader; + import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; @@ -355,11 +358,21 @@ public final class DexMaker { return "Generated_" + checksum +".jar"; } - private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) { + private ClassLoader generateClassLoader(ClassLoader classLoader, File result, File dexCache, + ClassLoader parent) { try { - return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") - .getConstructor(String.class, String.class, String.class, ClassLoader.class) - .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent); + boolean shareClassLoader = Boolean.parseBoolean(System.getProperty( + "dexmaker.share_classloader", "false")); + if (shareClassLoader) { + ClassLoader loader = parent != null ? parent : classLoader; + loader.getClass().getMethod("addDexPath", String.class).invoke(loader, + result.getPath()); + return loader; + } else { + return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") + .getConstructor(String.class, String.class, String.class, ClassLoader.class) + .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent); + } } catch (ClassNotFoundException e) { throw new UnsupportedOperationException("load() requires a Dalvik VM", e); } catch (InvocationTargetException e) { @@ -399,6 +412,10 @@ public final class DexMaker { * application's private data dir. */ public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException { + return generateAndLoad(parent, parent, dexCache); + } + + public ClassLoader generateAndLoad(ClassLoader classLoader, ClassLoader parent, File dexCache) throws IOException { if (dexCache == null) { String property = System.getProperty("dexmaker.dexcache"); if (property != null) { @@ -416,7 +433,7 @@ public final class DexMaker { // Check that the file exists. If it does, return a DexClassLoader and skip all // the dex bytecode generation. if (result.exists()) { - return generateClassLoader(result, dexCache, parent); + return generateClassLoader(classLoader, result, dexCache, parent); } byte[] dex = generate(); @@ -436,7 +453,7 @@ public final class DexMaker { jarOut.write(dex); jarOut.closeEntry(); jarOut.close(); - return generateClassLoader(result, dexCache, parent); + return generateClassLoader(classLoader, result, dexCache, parent); } private static class TypeDeclaration { diff --git a/dexmaker/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java b/dexmaker/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java index c42d24d..8ab06c1 100644 --- a/dexmaker/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java +++ b/dexmaker/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java @@ -245,10 +245,20 @@ public final class ProxyBuilder { // try the cache to see if we've generated this one before @SuppressWarnings("unchecked") // we only populate the map with matching types Class proxyClass = (Class) generatedProxyClasses.get(baseClass); - if (proxyClass != null - && proxyClass.getClassLoader().getParent() == parentClassLoader - && interfaces.equals(asSet(proxyClass.getInterfaces()))) { - return proxyClass; // cache hit! + if (proxyClass != null) { + boolean shareClassLoader = Boolean.parseBoolean(System.getProperty( + "dexmaker.share_classloader", "false")); + boolean validClassLoader; + if (shareClassLoader) { + ClassLoader parent = parentClassLoader != null ? parentClassLoader + : baseClass.getClassLoader(); + validClassLoader = proxyClass.getClassLoader() == parent; + } else { + validClassLoader = proxyClass.getClassLoader().getParent() == parentClassLoader; + } + if (validClassLoader && interfaces.equals(asSet(proxyClass.getInterfaces()))) { + return proxyClass; // cache hit! + } } // the cache missed; generate the class @@ -261,7 +271,8 @@ public final class ProxyBuilder { generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType); dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds()); - ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache); + ClassLoader classLoader = dexMaker.generateAndLoad(baseClass.getClassLoader(), + parentClassLoader, dexCache); try { proxyClass = loadClass(classLoader, generatedName); } catch (IllegalAccessError e) { @@ -655,6 +666,8 @@ public final class ProxyBuilder { private void getMethodsToProxy(Set sink, Set seenFinalMethods, Class c) { + boolean shareClassLoader = Boolean.parseBoolean(System.getProperty( + "dexmaker.share_classloader", "false")); for (Method method : c.getDeclaredMethods()) { if ((method.getModifiers() & Modifier.FINAL) != 0) { // Skip final methods, we can't override them. We @@ -672,13 +685,15 @@ public final class ProxyBuilder { continue; } if (!Modifier.isPublic(method.getModifiers()) - && !Modifier.isProtected(method.getModifiers())) { + && !Modifier.isProtected(method.getModifiers()) + && (!shareClassLoader || Modifier.isPrivate(method.getModifiers()))) { // Skip private methods, since they are invoked through direct // invocation (as opposed to virtual). Therefore, it would not // be possible to intercept any private method defined inside // the proxy class except through reflection. - // Skip package-private methods as well. The proxy class does + // Skip package-private methods as well (for non-shared class + // loaders). The proxy class does // not actually inherit package-private methods from the parent // class because it is not a member of the parent's package. // This is even true if the two classes have the same package @@ -708,7 +723,7 @@ public final class ProxyBuilder { } private static String getMethodNameForProxyOf(Class clazz) { - return clazz.getSimpleName() + "_Proxy"; + return clazz.getName().replace(".", "/") + "_Proxy"; } private static TypeId[] classArrayToTypeArray(Class[] input) { -- cgit v1.2.3