| /* |
| * Copyright (C) 2008 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. |
| */ |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| |
| /** |
| * Class loader test. |
| */ |
| public class Main { |
| /** |
| * Main entry point. |
| */ |
| public static void main(String[] args) throws Exception { |
| FancyLoader loader; |
| |
| loader = new FancyLoader(ClassLoader.getSystemClassLoader()); |
| //System.out.println("SYSTEM: " + ClassLoader.getSystemClassLoader()); |
| //System.out.println("ALTERN: " + loader); |
| |
| /* |
| * This statement has no effect on this program, but it can |
| * change the point where a LinkageException is thrown in |
| * testImplement(). When this is present the "reference |
| * implementation" throws an exception from Class.newInstance(), |
| * when it's absent the exception is deferred until the first time |
| * we call a method that isn't actually implemented. |
| * |
| * This isn't the class that fails -- it's a class with the same |
| * name in the "fancy" class loader -- but the VM thinks it has a |
| * reference to one of these; presumably the difference is that |
| * without this the VM finds itself holding a reference to an |
| * instance of an uninitialized class. |
| */ |
| System.out.println("base: " + DoubledImplement.class); |
| System.out.println("base2: " + DoubledImplement2.class); |
| |
| /* |
| * Run tests. |
| */ |
| testAccess1(loader); |
| testAccess2(loader); |
| testAccess3(loader); |
| |
| testExtend(loader); |
| testExtendOkay(loader); |
| testInterface(loader); |
| testAbstract(loader); |
| testImplement(loader); |
| testIfaceImplement(loader); |
| |
| testSeparation(); |
| |
| testClassForName(); |
| |
| testNullClassLoader(); |
| } |
| |
| static void testNullClassLoader() { |
| try { |
| /* this is the "alternate" DEX/Jar file */ |
| String DEX_FILE = System.getenv("DEX_LOCATION") + "/068-classloader-ex.jar"; |
| /* on Dalvik, this is a DexFile; otherwise, it's null */ |
| Class<?> mDexClass = Class.forName("dalvik.system.DexFile"); |
| Constructor<?> ctor = mDexClass.getConstructor(String.class); |
| Object mDexFile = ctor.newInstance(DEX_FILE); |
| Method meth = mDexClass.getMethod("loadClass", String.class, ClassLoader.class); |
| Object klass = meth.invoke(mDexFile, "Mutator", null); |
| if (klass == null) { |
| throw new AssertionError("loadClass with nullclass loader failed"); |
| } |
| } catch (Exception e) { |
| System.out.println(e); |
| } |
| System.out.println("Loaded class into null class loader"); |
| } |
| |
| static void testSeparation() { |
| FancyLoader loader1 = new FancyLoader(ClassLoader.getSystemClassLoader()); |
| FancyLoader loader2 = new FancyLoader(ClassLoader.getSystemClassLoader()); |
| |
| try { |
| Class<?> target1 = loader1.loadClass("MutationTarget"); |
| Class<?> target2 = loader2.loadClass("MutationTarget"); |
| |
| if (target1 == target2) { |
| throw new RuntimeException("target1 should not be equal to target2"); |
| } |
| |
| Class<?> mutator1 = loader1.loadClass("Mutator"); |
| Class<?> mutator2 = loader2.loadClass("Mutator"); |
| |
| if (mutator1 == mutator2) { |
| throw new RuntimeException("mutator1 should not be equal to mutator2"); |
| } |
| |
| runMutator(mutator1, 1); |
| |
| int value = getMutationTargetValue(target1); |
| if (value != 1) { |
| throw new RuntimeException("target 1 has unexpected value " + value); |
| } |
| value = getMutationTargetValue(target2); |
| if (value != 0) { |
| throw new RuntimeException("target 2 has unexpected value " + value); |
| } |
| |
| runMutator(mutator2, 2); |
| |
| value = getMutationTargetValue(target1); |
| if (value != 1) { |
| throw new RuntimeException("target 1 has unexpected value " + value); |
| } |
| value = getMutationTargetValue(target2); |
| if (value != 2) { |
| throw new RuntimeException("target 2 has unexpected value " + value); |
| } |
| } catch (Exception ex) { |
| ex.printStackTrace(System.out); |
| } |
| } |
| |
| private static void runMutator(Class<?> c, int v) throws Exception { |
| java.lang.reflect.Method m = c.getDeclaredMethod("mutate", int.class); |
| m.invoke(null, v); |
| } |
| |
| private static int getMutationTargetValue(Class<?> c) throws Exception { |
| java.lang.reflect.Field f = c.getDeclaredField("value"); |
| return f.getInt(null); |
| } |
| |
| /** |
| * See if we can load a class that isn't public to us. We should be |
| * able to load it but not instantiate it. |
| */ |
| static void testAccess1(ClassLoader loader) { |
| Class<?> altClass; |
| |
| try { |
| altClass = loader.loadClass("Inaccessible1"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed"); |
| cnfe.printStackTrace(System.out); |
| return; |
| } |
| |
| /* instantiate */ |
| Object obj; |
| try { |
| obj = altClass.newInstance(); |
| System.out.println("ERROR: Inaccessible1 was accessible"); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("Got expected access exception #1"); |
| //System.out.println("+++ " + iae); |
| return; |
| } |
| } |
| |
| /** |
| * See if we can load a class whose base class is not accessible to it |
| * (though the base *is* accessible to us). |
| */ |
| static void testAccess2(ClassLoader loader) { |
| Class<?> altClass; |
| |
| try { |
| altClass = loader.loadClass("Inaccessible2"); |
| System.out.println("ERROR: Inaccessible2 was accessible: " + altClass); |
| } catch (ClassNotFoundException cnfe) { |
| Throwable cause = cnfe.getCause(); |
| if (cause instanceof IllegalAccessError) { |
| System.out.println("Got expected CNFE/IAE #2"); |
| } else { |
| System.out.println("Got unexpected CNFE/IAE #2"); |
| cnfe.printStackTrace(System.out); |
| } |
| } |
| } |
| |
| /** |
| * See if we can load a class with an inaccessible interface. |
| */ |
| static void testAccess3(ClassLoader loader) { |
| Class<?> altClass; |
| |
| try { |
| altClass = loader.loadClass("Inaccessible3"); |
| System.out.println("ERROR: Inaccessible3 was accessible: " + altClass); |
| } catch (ClassNotFoundException cnfe) { |
| Throwable cause = cnfe.getCause(); |
| if (cause instanceof IllegalAccessError) { |
| System.out.println("Got expected CNFE/IAE #3"); |
| } else { |
| System.out.println("Got unexpected CNFE/IAE #3"); |
| cnfe.printStackTrace(System.out); |
| } |
| } |
| } |
| |
| /** |
| * Test a doubled class that extends the base class. |
| */ |
| static void testExtend(ClassLoader loader) { |
| Class<?> doubledExtendClass; |
| Object obj; |
| |
| /* get the "alternate" version of DoubledExtend */ |
| try { |
| doubledExtendClass = loader.loadClass("DoubledExtend"); |
| //System.out.println("+++ DoubledExtend is " + doubledExtendClass |
| // + " in " + doubledExtendClass.getClassLoader()); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = doubledExtendClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| System.out.println("Got expected LinkageError on DE"); |
| return; |
| } |
| |
| /* use the base class reference to get a CL-specific instance */ |
| Base baseRef = (Base) obj; |
| DoubledExtend de = baseRef.getExtended(); |
| |
| /* try to call through it */ |
| try { |
| String result; |
| |
| result = Base.doStuff(de); |
| System.out.println("ERROR: did not get LinkageError on DE"); |
| System.out.println("(result=" + result + ")"); |
| } catch (LinkageError le) { |
| System.out.println("Got expected LinkageError on DE"); |
| return; |
| } |
| } |
| |
| /** |
| * Test a doubled class that extends the base class, but is okay since |
| * it doesn't override the base class method. |
| */ |
| static void testExtendOkay(ClassLoader loader) { |
| Class<?> doubledExtendOkayClass; |
| Object obj; |
| |
| /* get the "alternate" version of DoubledExtendOkay */ |
| try { |
| doubledExtendOkayClass = loader.loadClass("DoubledExtendOkay"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = doubledExtendOkayClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| System.out.println("Got unexpected LinkageError on DEO"); |
| le.printStackTrace(System.out); |
| return; |
| } |
| |
| /* use the base class reference to get a CL-specific instance */ |
| BaseOkay baseRef = (BaseOkay) obj; |
| DoubledExtendOkay de = baseRef.getExtended(); |
| |
| /* try to call through it */ |
| try { |
| String result; |
| |
| result = BaseOkay.doStuff(de); |
| System.out.println("Got DEO result " + result); |
| } catch (LinkageError le) { |
| System.out.println("Got unexpected LinkageError on DEO"); |
| le.printStackTrace(System.out); |
| return; |
| } |
| } |
| |
| /** |
| * Try to access a doubled class through a class that implements |
| * an interface declared in a different class. |
| */ |
| static void testInterface(ClassLoader loader) { |
| Class<?> getDoubledClass; |
| Object obj; |
| |
| /* get GetDoubled from the "alternate" class loader */ |
| try { |
| getDoubledClass = loader.loadClass("GetDoubled"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = getDoubledClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| // Dalvik bails here |
| System.out.println("Got LinkageError on GD"); |
| return; |
| } |
| |
| /* |
| * Cast the object to the interface, and try to use it. |
| */ |
| IGetDoubled iface = (IGetDoubled) obj; |
| try { |
| /* "de" will be the wrong variety of DoubledExtendOkay */ |
| DoubledExtendOkay de = iface.getDoubled(); |
| // reference impl bails here |
| String str = de.getStr(); |
| } catch (LinkageError le) { |
| System.out.println("Got LinkageError on GD"); |
| return; |
| } |
| System.out.println("Should have failed by now on GetDoubled"); |
| } |
| |
| /** |
| * Throw an abstract class into the middle and see what happens. |
| */ |
| static void testAbstract(ClassLoader loader) { |
| Class<?> abstractGetClass; |
| Object obj; |
| |
| /* get AbstractGet from the "alternate" loader */ |
| try { |
| abstractGetClass = loader.loadClass("AbstractGet"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass ta failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = abstractGetClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| System.out.println("Got LinkageError on TA"); |
| return; |
| } |
| |
| /* use the base class reference to get a CL-specific instance */ |
| BaseOkay baseRef = (BaseOkay) obj; |
| DoubledExtendOkay de = baseRef.getExtended(); |
| |
| /* try to call through it */ |
| try { |
| String result; |
| |
| result = BaseOkay.doStuff(de); |
| } catch (LinkageError le) { |
| System.out.println("Got LinkageError on TA"); |
| return; |
| } |
| System.out.println("Should have failed by now in testAbstract"); |
| } |
| |
| /** |
| * Test a doubled class that implements a common interface. |
| */ |
| static void testImplement(ClassLoader loader) { |
| Class<?> doubledImplementClass; |
| Object obj; |
| |
| useImplement(new DoubledImplement(), true); |
| |
| /* get the "alternate" version of DoubledImplement */ |
| try { |
| doubledImplementClass = loader.loadClass("DoubledImplement"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = doubledImplementClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| System.out.println("Got LinkageError on DI (early)"); |
| return; |
| } |
| |
| /* if we lived this long, try to do something with it */ |
| ICommon icommon = (ICommon) obj; |
| useImplement(icommon.getDoubledInstance(), false); |
| } |
| |
| /** |
| * Do something with a DoubledImplement instance. |
| */ |
| static void useImplement(DoubledImplement di, boolean isOne) { |
| //System.out.println("useObject: " + di.toString() + " -- " |
| // + di.getClass().getClassLoader()); |
| try { |
| di.one(); |
| if (!isOne) { |
| System.out.println("ERROR: did not get LinkageError on DI"); |
| } |
| } catch (LinkageError le) { |
| if (!isOne) { |
| System.out.println("Got LinkageError on DI (late)"); |
| } else { |
| throw le; |
| } |
| } |
| } |
| |
| |
| /** |
| * Test a class that implements an interface with a super-interface |
| * that refers to a doubled class. |
| */ |
| static void testIfaceImplement(ClassLoader loader) { |
| Class<?> ifaceImplClass; |
| Object obj; |
| |
| /* |
| * Create an instance of IfaceImpl. We also pull in |
| * DoubledImplement2 from the other class loader; without this |
| * we don't fail in some implementations. |
| */ |
| try { |
| ifaceImplClass = loader.loadClass("IfaceImpl"); |
| ifaceImplClass = loader.loadClass("DoubledImplement2"); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("loadClass failed: " + cnfe); |
| return; |
| } |
| |
| /* instantiate */ |
| try { |
| obj = ifaceImplClass.newInstance(); |
| } catch (InstantiationException ie) { |
| System.out.println("newInstance failed: " + ie); |
| return; |
| } catch (IllegalAccessException iae) { |
| System.out.println("newInstance failed: " + iae); |
| return; |
| } catch (LinkageError le) { |
| System.out.println("Got LinkageError on IDI (early)"); |
| //System.out.println(le); |
| return; |
| } |
| |
| /* |
| * Without the pre-load of FancyLoader->DoubledImplement2, some |
| * implementations will happily execute through this part. "obj" |
| * comes from FancyLoader, but the di2 returned from ifaceSuper |
| * comes from the application class loader. |
| */ |
| IfaceSuper ifaceSuper = (IfaceSuper) obj; |
| DoubledImplement2 di2 = ifaceSuper.getDoubledInstance2(); |
| di2.one(); |
| } |
| |
| static void testClassForName() throws Exception { |
| System.out.println(Class.forName("Main").toString()); |
| try { |
| System.out.println(Class.forName("Main", false, null).toString()); |
| } catch (ClassNotFoundException expected) { |
| System.out.println("Got expected ClassNotFoundException"); |
| } |
| } |
| } |