1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Class loader test.
19 */
20public class Main {
21    /**
22     * Main entry point.
23     */
24    public static void main(String[] args) throws Exception {
25        FancyLoader loader;
26
27        loader = new FancyLoader(ClassLoader.getSystemClassLoader());
28        //System.out.println("SYSTEM: " + ClassLoader.getSystemClassLoader());
29        //System.out.println("ALTERN: " + loader);
30
31        /*
32         * This statement has no effect on this program, but it can
33         * change the point where a LinkageException is thrown in
34         * testImplement().  When this is present the "reference
35         * implementation" throws an exception from Class.newInstance(),
36         * when it's absent the exception is deferred until the first time
37         * we call a method that isn't actually implemented.
38         *
39         * This isn't the class that fails -- it's a class with the same
40         * name in the "fancy" class loader --  but the VM thinks it has a
41         * reference to one of these; presumably the difference is that
42         * without this the VM finds itself holding a reference to an
43         * instance of an uninitialized class.
44         */
45        System.out.println("base: " + DoubledImplement.class);
46        System.out.println("base2: " + DoubledImplement2.class);
47
48        /*
49         * Run tests.
50         */
51        testAccess1(loader);
52        testAccess2(loader);
53        testAccess3(loader);
54
55        testExtend(loader);
56        testExtendOkay(loader);
57        testInterface(loader);
58        testAbstract(loader);
59        testImplement(loader);
60        testIfaceImplement(loader);
61
62        testSeparation();
63
64        testClassForName();
65    }
66
67    static void testSeparation() {
68        FancyLoader loader1 = new FancyLoader(ClassLoader.getSystemClassLoader());
69        FancyLoader loader2 = new FancyLoader(ClassLoader.getSystemClassLoader());
70
71        try {
72            Class target1 = loader1.loadClass("MutationTarget");
73            Class target2 = loader2.loadClass("MutationTarget");
74
75            if (target1 == target2) {
76                throw new RuntimeException("target1 should not be equal to target2");
77            }
78
79            Class mutator1 = loader1.loadClass("Mutator");
80            Class mutator2 = loader2.loadClass("Mutator");
81
82            if (mutator1 == mutator2) {
83                throw new RuntimeException("mutator1 should not be equal to mutator2");
84            }
85
86            runMutator(mutator1, 1);
87
88            int value = getMutationTargetValue(target1);
89            if (value != 1) {
90                throw new RuntimeException("target 1 has unexpected value " + value);
91            }
92            value = getMutationTargetValue(target2);
93            if (value != 0) {
94                throw new RuntimeException("target 2 has unexpected value " + value);
95            }
96
97            runMutator(mutator2, 2);
98
99            value = getMutationTargetValue(target1);
100            if (value != 1) {
101                throw new RuntimeException("target 1 has unexpected value " + value);
102            }
103            value = getMutationTargetValue(target2);
104            if (value != 2) {
105                throw new RuntimeException("target 2 has unexpected value " + value);
106            }
107        } catch (Exception ex) {
108            ex.printStackTrace();
109        }
110    }
111
112    private static void runMutator(Class c, int v) throws Exception {
113        java.lang.reflect.Method m = c.getDeclaredMethod("mutate", int.class);
114        m.invoke(null, v);
115    }
116
117    private static int getMutationTargetValue(Class c) throws Exception {
118        java.lang.reflect.Field f = c.getDeclaredField("value");
119        return f.getInt(null);
120    }
121
122    /**
123     * See if we can load a class that isn't public to us.  We should be
124     * able to load it but not instantiate it.
125     */
126    static void testAccess1(ClassLoader loader) {
127        Class altClass;
128
129        try {
130            altClass = loader.loadClass("Inaccessible1");
131        } catch (ClassNotFoundException cnfe) {
132            System.err.println("loadClass failed");
133            cnfe.printStackTrace();
134            return;
135        }
136
137        /* instantiate */
138        Object obj;
139        try {
140            obj = altClass.newInstance();
141            System.err.println("ERROR: Inaccessible1 was accessible");
142        } catch (InstantiationException ie) {
143            System.err.println("newInstance failed: " + ie);
144            return;
145        } catch (IllegalAccessException iae) {
146            System.out.println("Got expected access exception #1");
147            //System.out.println("+++ " + iae);
148            return;
149        }
150    }
151
152    /**
153     * See if we can load a class whose base class is not accessible to it
154     * (though the base *is* accessible to us).
155     */
156    static void testAccess2(ClassLoader loader) {
157        Class altClass;
158
159        try {
160            altClass = loader.loadClass("Inaccessible2");
161            System.err.println("ERROR: Inaccessible2 was accessible: " + altClass);
162        } catch (ClassNotFoundException cnfe) {
163            Throwable cause = cnfe.getCause();
164            if (cause instanceof IllegalAccessError) {
165                System.out.println("Got expected CNFE/IAE #2");
166            } else {
167                System.err.println("Got unexpected CNFE/IAE #2");
168                cnfe.printStackTrace();
169            }
170        }
171    }
172
173    /**
174     * See if we can load a class with an inaccessible interface.
175     */
176    static void testAccess3(ClassLoader loader) {
177        Class altClass;
178
179        try {
180            altClass = loader.loadClass("Inaccessible3");
181            System.err.println("ERROR: Inaccessible3 was accessible: " + altClass);
182        } catch (ClassNotFoundException cnfe) {
183            Throwable cause = cnfe.getCause();
184            if (cause instanceof IllegalAccessError) {
185                System.out.println("Got expected CNFE/IAE #3");
186            } else {
187                System.err.println("Got unexpected CNFE/IAE #3");
188                cnfe.printStackTrace();
189            }
190        }
191    }
192
193    /**
194     * Test a doubled class that extends the base class.
195     */
196    static void testExtend(ClassLoader loader) {
197        Class doubledExtendClass;
198        Object obj;
199
200        /* get the "alternate" version of DoubledExtend */
201        try {
202            doubledExtendClass = loader.loadClass("DoubledExtend");
203            //System.out.println("+++ DoubledExtend is " + doubledExtendClass
204            //    + " in " + doubledExtendClass.getClassLoader());
205        } catch (ClassNotFoundException cnfe) {
206            System.err.println("loadClass failed: " + cnfe);
207            return;
208        }
209
210        /* instantiate */
211        try {
212            obj = doubledExtendClass.newInstance();
213        } catch (InstantiationException ie) {
214            System.err.println("newInstance failed: " + ie);
215            return;
216        } catch (IllegalAccessException iae) {
217            System.err.println("newInstance failed: " + iae);
218            return;
219        } catch (LinkageError le) {
220            System.out.println("Got expected LinkageError on DE");
221            return;
222        }
223
224        /* use the base class reference to get a CL-specific instance */
225        Base baseRef = (Base) obj;
226        DoubledExtend de = baseRef.getExtended();
227
228        /* try to call through it */
229        try {
230            String result;
231
232            result = Base.doStuff(de);
233            System.err.println("ERROR: did not get LinkageError on DE");
234            System.err.println("(result=" + result + ")");
235        } catch (LinkageError le) {
236            System.out.println("Got expected LinkageError on DE");
237            return;
238        }
239    }
240
241    /**
242     * Test a doubled class that extends the base class, but is okay since
243     * it doesn't override the base class method.
244     */
245    static void testExtendOkay(ClassLoader loader) {
246        Class doubledExtendOkayClass;
247        Object obj;
248
249        /* get the "alternate" version of DoubledExtendOkay */
250        try {
251            doubledExtendOkayClass = loader.loadClass("DoubledExtendOkay");
252        } catch (ClassNotFoundException cnfe) {
253            System.err.println("loadClass failed: " + cnfe);
254            return;
255        }
256
257        /* instantiate */
258        try {
259            obj = doubledExtendOkayClass.newInstance();
260        } catch (InstantiationException ie) {
261            System.err.println("newInstance failed: " + ie);
262            return;
263        } catch (IllegalAccessException iae) {
264            System.err.println("newInstance failed: " + iae);
265            return;
266        } catch (LinkageError le) {
267            System.err.println("Got unexpected LinkageError on DEO");
268            le.printStackTrace();
269            return;
270        }
271
272        /* use the base class reference to get a CL-specific instance */
273        BaseOkay baseRef = (BaseOkay) obj;
274        DoubledExtendOkay de = baseRef.getExtended();
275
276        /* try to call through it */
277        try {
278            String result;
279
280            result = BaseOkay.doStuff(de);
281            System.out.println("Got DEO result " + result);
282        } catch (LinkageError le) {
283            System.err.println("Got unexpected LinkageError on DEO");
284            le.printStackTrace();
285            return;
286        }
287    }
288
289    /**
290     * Try to access a doubled class through a class that implements
291     * an interface declared in a different class.
292     */
293    static void testInterface(ClassLoader loader) {
294        Class getDoubledClass;
295        Object obj;
296
297        /* get GetDoubled from the "alternate" class loader */
298        try {
299            getDoubledClass = loader.loadClass("GetDoubled");
300        } catch (ClassNotFoundException cnfe) {
301            System.err.println("loadClass failed: " + cnfe);
302            return;
303        }
304
305        /* instantiate */
306        try {
307            obj = getDoubledClass.newInstance();
308        } catch (InstantiationException ie) {
309            System.err.println("newInstance failed: " + ie);
310            return;
311        } catch (IllegalAccessException iae) {
312            System.err.println("newInstance failed: " + iae);
313            return;
314        } catch (LinkageError le) {
315            // Dalvik bails here
316            System.out.println("Got LinkageError on GD");
317            return;
318        }
319
320        /*
321         * Cast the object to the interface, and try to use it.
322         */
323        IGetDoubled iface = (IGetDoubled) obj;
324        try {
325            /* "de" will be the wrong variety of DoubledExtendOkay */
326            DoubledExtendOkay de = iface.getDoubled();
327            // reference impl bails here
328            String str = de.getStr();
329        } catch (LinkageError le) {
330            System.out.println("Got LinkageError on GD");
331            return;
332        }
333        System.err.println("Should have failed by now on GetDoubled");
334    }
335
336    /**
337     * Throw an abstract class into the middle and see what happens.
338     */
339    static void testAbstract(ClassLoader loader) {
340        Class abstractGetClass;
341        Object obj;
342
343        /* get AbstractGet from the "alternate" loader */
344        try {
345            abstractGetClass = loader.loadClass("AbstractGet");
346        } catch (ClassNotFoundException cnfe) {
347            System.err.println("loadClass ta failed: " + cnfe);
348            return;
349        }
350
351        /* instantiate */
352        try {
353            obj = abstractGetClass.newInstance();
354        } catch (InstantiationException ie) {
355            System.err.println("newInstance failed: " + ie);
356            return;
357        } catch (IllegalAccessException iae) {
358            System.err.println("newInstance failed: " + iae);
359            return;
360        } catch (LinkageError le) {
361            System.out.println("Got LinkageError on TA");
362            return;
363        }
364
365        /* use the base class reference to get a CL-specific instance */
366        BaseOkay baseRef = (BaseOkay) obj;
367        DoubledExtendOkay de = baseRef.getExtended();
368
369        /* try to call through it */
370        try {
371            String result;
372
373            result = BaseOkay.doStuff(de);
374        } catch (LinkageError le) {
375            System.out.println("Got LinkageError on TA");
376            return;
377        }
378        System.err.println("Should have failed by now in testAbstract");
379    }
380
381    /**
382     * Test a doubled class that implements a common interface.
383     */
384    static void testImplement(ClassLoader loader) {
385        Class doubledImplementClass;
386        Object obj;
387
388        useImplement(new DoubledImplement(), true);
389
390        /* get the "alternate" version of DoubledImplement */
391        try {
392            doubledImplementClass = loader.loadClass("DoubledImplement");
393        } catch (ClassNotFoundException cnfe) {
394            System.err.println("loadClass failed: " + cnfe);
395            return;
396        }
397
398        /* instantiate */
399        try {
400            obj = doubledImplementClass.newInstance();
401        } catch (InstantiationException ie) {
402            System.err.println("newInstance failed: " + ie);
403            return;
404        } catch (IllegalAccessException iae) {
405            System.err.println("newInstance failed: " + iae);
406            return;
407        } catch (LinkageError le) {
408            System.out.println("Got LinkageError on DI (early)");
409            return;
410        }
411
412        /* if we lived this long, try to do something with it */
413        ICommon icommon = (ICommon) obj;
414        useImplement(icommon.getDoubledInstance(), false);
415    }
416
417    /**
418     * Do something with a DoubledImplement instance.
419     */
420    static void useImplement(DoubledImplement di, boolean isOne) {
421        //System.out.println("useObject: " + di.toString() + " -- "
422        //    + di.getClass().getClassLoader());
423        try {
424            di.one();
425            if (!isOne) {
426                System.err.println("ERROR: did not get LinkageError on DI");
427            }
428        } catch (LinkageError le) {
429            if (!isOne) {
430                System.out.println("Got LinkageError on DI (late)");
431            } else {
432                throw le;
433            }
434        }
435    }
436
437
438    /**
439     * Test a class that implements an interface with a super-interface
440     * that refers to a doubled class.
441     */
442    static void testIfaceImplement(ClassLoader loader) {
443        Class ifaceImplClass;
444        Object obj;
445
446        /*
447         * Create an instance of IfaceImpl.  We also pull in
448         * DoubledImplement2 from the other class loader; without this
449         * we don't fail in some implementations.
450         */
451        try {
452            ifaceImplClass = loader.loadClass("IfaceImpl");
453            ifaceImplClass = loader.loadClass("DoubledImplement2");
454        } catch (ClassNotFoundException cnfe) {
455            System.err.println("loadClass failed: " + cnfe);
456            return;
457        }
458
459        /* instantiate */
460        try {
461            obj = ifaceImplClass.newInstance();
462        } catch (InstantiationException ie) {
463            System.err.println("newInstance failed: " + ie);
464            return;
465        } catch (IllegalAccessException iae) {
466            System.err.println("newInstance failed: " + iae);
467            return;
468        } catch (LinkageError le) {
469            System.out.println("Got LinkageError on IDI (early)");
470            //System.out.println(le);
471            return;
472        }
473
474        /*
475         * Without the pre-load of FancyLoader->DoubledImplement2, some
476         * implementations will happily execute through this part.  "obj"
477         * comes from FancyLoader, but the di2 returned from ifaceSuper
478         * comes from the application class loader.
479         */
480        IfaceSuper ifaceSuper = (IfaceSuper) obj;
481        DoubledImplement2 di2 = ifaceSuper.getDoubledInstance2();
482        di2.one();
483    }
484
485    static void testClassForName() throws Exception {
486        System.out.println(Class.forName("Main").toString());
487        try {
488            System.out.println(Class.forName("Main", false, null).toString());
489        } catch (ClassNotFoundException expected) {
490            System.out.println("Got expected ClassNotFoundException");
491        }
492    }
493}
494