| /* | |
| * 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"); | |
| } | |
| } | |
| } |