1/*
2 * Copyright (C) 2010 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
17import java.lang.reflect.Constructor;
18import java.lang.reflect.Method;
19import java.lang.reflect.InvocationTargetException;
20
21/**
22 * Class loader test.
23 */
24public class Main {
25    /**
26     * Thrown when an unexpected Exception is caught internally.
27     */
28    static class TestFailed extends Exception {
29        public TestFailed(Throwable cause) {
30            super(cause);
31        }
32    }
33
34    /**
35     * A class loader which loads classes from the dex file
36     * "test.jar". However, it will return null when asked to load the
37     * class InaccessibleSuper.
38     *
39     * When testing code calls BrokenDexLoader's findBrokenClass(),
40     * a BrokenDexLoader will be the defining loader for the class
41     * Inaccessible.  The VM will call the defining loader for
42     * "InaccessibleSuper", which will return null, which the VM
43     * should be able to deal with gracefully.
44     *
45     * Note that this depends heavily on the Dalvik test harness.
46     */
47    static class BrokenDexLoader extends ClassLoader {
48
49        /** We return null when asked to load InaccessibleSuper. */
50        private static class InaccessibleSuper {}
51        private static class Inaccessible extends InaccessibleSuper {}
52
53        private static final String SUPERCLASS_NAME =
54                "Main$BrokenDexLoader$InaccessibleSuper";
55        private static final String CLASS_NAME =
56                "Main$BrokenDexLoader$Inaccessible";
57
58        private static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/087-gc-after-link.jar";
59
60        public BrokenDexLoader(ClassLoader parent) {
61            super(parent);
62        }
63
64        /**
65         * Finds the class with the specified binary name, from DEX_FILE.
66         *
67         * If we don't find a match, we throw an exception.
68         */
69        private Class<?> findDexClass(String name)
70                throws TestFailed, InvocationTargetException
71        {
72            Object dexFile = null;
73            Class<?> dexClass = null;
74
75            try {
76                try {
77                    /*
78                     * Find the DexFile class, and construct a DexFile object
79                     * through reflection, then call loadClass on it.
80                     */
81                    dexClass = ClassLoader.getSystemClassLoader().
82                            loadClass("dalvik.system.DexFile");
83                    Constructor<?> ctor = dexClass.getConstructor(String.class);
84                    dexFile = ctor.newInstance(DEX_FILE);
85                    Method meth = dexClass.getMethod("loadClass", String.class, ClassLoader.class);
86                    /*
87                     * Invoking loadClass on CLASS_NAME is expected to
88                     * throw an InvocationTargetException. Anything else
89                     * is an error we can't recover from.
90                     */
91                    meth.invoke(dexFile, name, this);
92                    System.out.println("Unreachable");
93                } finally {
94                    if (dexFile != null) {
95                        /* close the DexFile to make CloseGuard happy */
96                        Method meth = dexClass.getMethod("close");
97                        meth.invoke(dexFile);
98                    }
99                }
100            } catch (NoSuchMethodException nsme) {
101                throw new TestFailed(nsme);
102            } catch (InstantiationException ie) {
103                throw new TestFailed(ie);
104            } catch (IllegalAccessException iae) {
105                throw new TestFailed(iae);
106            } catch (ClassNotFoundException cnfe) {
107                throw new TestFailed(cnfe);
108            }
109
110            return null;
111        }
112
113        /**
114         * Load a class.
115         *
116         * Return null if the class's name is SUPERCLASS_NAME;
117         * otherwise invoke the super's loadClass method.
118         */
119        public Class<?> loadClass(String name, boolean resolve)
120                throws ClassNotFoundException
121        {
122            if (SUPERCLASS_NAME.equals(name)) {
123                return null;
124            }
125
126            return super.loadClass(name, resolve);
127        }
128
129        /**
130         * Attempt to find the class with the superclass we refuse to
131         * load.  This is expected to throw an
132         * InvocationTargetException, with a NullPointerException as
133         * its cause.
134         */
135        public void findBrokenClass()
136                throws TestFailed, InvocationTargetException
137        {
138            findDexClass(CLASS_NAME);
139        }
140    }
141
142    /**
143     * Main entry point.
144     */
145    public static void main(String[] args)
146            throws TestFailed, ClassNotFoundException {
147        /*
148         * Run test.
149         */
150        testFailLoadAndGc();
151    }
152
153    /**
154     * See if we can GC after a failed load.
155     */
156    static void testFailLoadAndGc() throws TestFailed {
157        processFailLoadAndGc();
158        Runtime.getRuntime().gc();
159        System.out.println("GC complete.");
160    }
161
162    private static void processFailLoadAndGc() throws TestFailed {
163        try {
164            BrokenDexLoader loader;
165
166            loader = new BrokenDexLoader(ClassLoader.getSystemClassLoader());
167            loader.findBrokenClass();
168            System.out.println("ERROR: Inaccessible was accessible");
169        } catch (InvocationTargetException ite) {
170            Throwable cause = ite.getCause();
171            if (cause instanceof NullPointerException) {
172                System.out.println("Got expected ITE/NPE");
173            } else {
174                System.out.println("Got unexpected ITE");
175                ite.printStackTrace(System.out);
176            }
177        }
178    }
179}
180