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