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