1/*
2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.FilePermission;
25import java.io.IOException;
26import java.io.UncheckedIOException;
27import java.lang.module.ModuleFinder;
28import java.lang.reflect.AccessibleObject;
29import java.lang.reflect.Field;
30import java.lang.reflect.Modifier;
31import java.lang.reflect.InaccessibleObjectException;
32import java.lang.reflect.ReflectPermission;
33import java.net.URI;
34import java.nio.file.FileSystem;
35import java.nio.file.FileSystems;
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.security.CodeSource;
39import java.security.Permission;
40import java.security.PermissionCollection;
41import java.security.Permissions;
42import java.security.Policy;
43import java.security.ProtectionDomain;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collections;
47import java.util.Enumeration;
48import java.util.Iterator;
49import java.util.List;
50import java.util.Set;
51import java.util.PropertyPermission;
52import java.util.concurrent.atomic.AtomicBoolean;
53import java.util.concurrent.atomic.AtomicLong;
54import java.util.stream.Collectors;
55import java.util.stream.Stream;
56
57import jdk.internal.module.Modules;
58
59/**
60 * @test
61 * @bug 8065552
62 * @summary test that all public fields returned by getDeclaredFields() can
63 *          be set accessible if the right permission is granted; this test
64 *          loads all classes and get their declared fields
65 *          and call setAccessible(false) followed by setAccessible(true);
66 * @modules java.base/jdk.internal.module
67 * @run main/othervm --add-modules=ALL-SYSTEM FieldSetAccessibleTest UNSECURE
68 * @run main/othervm --add-modules=ALL-SYSTEM FieldSetAccessibleTest SECURE
69 *
70 * @author danielfuchs
71 */
72public class FieldSetAccessibleTest {
73
74    static final List<String> cantread = new ArrayList<>();
75    static final List<String> failed = new ArrayList<>();
76    static final AtomicLong classCount = new AtomicLong();
77    static final AtomicLong fieldCount = new AtomicLong();
78    static long startIndex = 0;
79    static long maxSize = Long.MAX_VALUE;
80    static long maxIndex = Long.MAX_VALUE;
81    static final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
82
83
84    // Test that all fields for any given class can be made accessibles
85    static void testSetFieldsAccessible(Class<?> c) {
86        Module self = FieldSetAccessibleTest.class.getModule();
87        Module target = c.getModule();
88        String pn = c.getPackageName();
89        boolean exported = self.canRead(target) && target.isExported(pn, self);
90        for (Field f : c.getDeclaredFields()) {
91            fieldCount.incrementAndGet();
92
93            // setAccessible succeeds only if it's exported and the member
94            // is public and of a public class, or it's opened
95            // otherwise it would fail.
96            boolean isPublic = Modifier.isPublic(f.getModifiers()) &&
97                Modifier.isPublic(c.getModifiers());
98            boolean access = (exported && isPublic) || target.isOpen(pn, self);
99            try {
100                f.setAccessible(false);
101                f.setAccessible(true);
102                if (!access) {
103                    throw new RuntimeException(
104                        String.format("Expected InaccessibleObjectException is not thrown "
105                                      + "for field %s in class %s%n", f.getName(), c.getName()));
106                }
107            } catch (InaccessibleObjectException expected) {
108                if (access) {
109                    throw new RuntimeException(expected);
110                }
111            }
112        }
113    }
114
115    // Performs a series of test on the given class.
116    // At this time, we only call testSetFieldsAccessible(c)
117    public static boolean test(Class<?> c, boolean addExports) {
118        Module self = FieldSetAccessibleTest.class.getModule();
119        Module target = c.getModule();
120        String pn = c.getPackageName();
121        boolean exported = self.canRead(target) && target.isExported(pn, self);
122        if (addExports && !exported) {
123            Modules.addExports(target, pn, self);
124            exported = true;
125        }
126
127        classCount.incrementAndGet();
128
129        // Call getDeclaredFields() and try to set their accessible flag.
130        testSetFieldsAccessible(c);
131
132        // add more tests here...
133
134        return c == Class.class;
135    }
136
137    // Prints a summary at the end of the test.
138    static void printSummary(long secs, long millis, long nanos) {
139        System.out.println("Tested " + fieldCount.get() + " fields of "
140                + classCount.get() + " classes in "
141                + secs + "s " + millis + "ms " + nanos + "ns");
142    }
143
144
145    /**
146     * @param args the command line arguments:
147     *
148     *     SECURE|UNSECURE [startIndex (default=0)] [maxSize (default=Long.MAX_VALUE)]
149     *
150     * @throws java.lang.Exception if the test fails
151     */
152    public static void main(String[] args) throws Exception {
153        if (args == null || args.length == 0) {
154            args = new String[] {"SECURE", "0"};
155        } else if (args.length > 3) {
156            throw new RuntimeException("Expected at most one argument. Found "
157                    + Arrays.asList(args));
158        }
159        try {
160            if (args.length > 1) {
161                startIndex = Long.parseLong(args[1]);
162                if (startIndex < 0) {
163                    throw new IllegalArgumentException("startIndex args[1]: "
164                            + startIndex);
165                }
166            }
167            if (args.length > 2) {
168                maxSize = Long.parseLong(args[2]);
169                if (maxSize <= 0) {
170                    maxSize = Long.MAX_VALUE;
171                }
172                maxIndex = (Long.MAX_VALUE - startIndex) < maxSize
173                        ? Long.MAX_VALUE : startIndex + maxSize;
174            }
175            TestCase.valueOf(args[0]).run();
176        } catch (OutOfMemoryError oome) {
177            System.err.println(classCount.get());
178            throw oome;
179        }
180    }
181
182    public static void run(TestCase test) {
183        System.out.println("Testing " + test);
184        test(listAllClassNames());
185        System.out.println("Passed " + test);
186    }
187
188    static Iterable<String> listAllClassNames() {
189        return new ClassNameJrtStreamBuilder();
190    }
191
192    static void test(Iterable<String> iterable) {
193        final long start = System.nanoTime();
194        boolean classFound = false;
195        int index = 0;
196        for (String s : iterable) {
197            if (index == maxIndex) break;
198            try {
199                if (index < startIndex) continue;
200                if (test(s, false)) {
201                    classFound = true;
202                }
203            } finally {
204                index++;
205            }
206        }
207
208        // Re-test with all packages exported
209        for (String s : iterable) {
210            test(s, true);
211        }
212
213        classCount.set(classCount.get() / 2);
214        fieldCount.set(fieldCount.get() / 2);
215        long elapsed = System.nanoTime() - start;
216        long secs = elapsed / 1000_000_000;
217        long millis = (elapsed % 1000_000_000) / 1000_000;
218        long nanos  = elapsed % 1000_000;
219        System.out.println("Unreadable path elements: " + cantread);
220        System.out.println("Failed path elements: " + failed);
221        printSummary(secs, millis, nanos);
222
223        if (!failed.isEmpty()) {
224            throw new RuntimeException("Test failed for the following classes: " + failed);
225        }
226        if (!classFound && startIndex == 0 && index < maxIndex) {
227            // this is just to verify that we have indeed parsed rt.jar
228            // (or the java.base module)
229            throw  new RuntimeException("Test failed: Class.class not found...");
230        }
231        if (classCount.get() == 0 && startIndex == 0) {
232            throw  new RuntimeException("Test failed: no class found?");
233        }
234    }
235
236    static boolean test(String s, boolean addExports) {
237        String clsName = s.replace('/', '.').substring(0, s.length() - 6);
238        try {
239            System.out.println("Loading " + clsName);
240            final Class<?> c = Class.forName(
241                    clsName,
242                    false,
243                    systemClassLoader);
244            return test(c, addExports);
245        } catch (VerifyError ve) {
246            System.err.println("VerifyError for " + clsName);
247            ve.printStackTrace(System.err);
248            failed.add(s);
249        } catch (Exception t) {
250            t.printStackTrace(System.err);
251            failed.add(s);
252        } catch (NoClassDefFoundError e) {
253            e.printStackTrace(System.err);
254            failed.add(s);
255        }
256        return false;
257    }
258
259    static class ClassNameJrtStreamBuilder implements Iterable<String>{
260
261        final FileSystem jrt;
262        final Path root;
263        final Set<String> modules;
264        ClassNameJrtStreamBuilder() {
265            jrt = FileSystems.getFileSystem(URI.create("jrt:/"));
266            root = jrt.getPath("/modules");
267            modules = systemModules();
268        }
269
270        @Override
271        public Iterator<String> iterator() {
272            try {
273                return Files.walk(root)
274                        .filter(p -> p.getNameCount() > 2)
275                        .filter(p -> modules.contains(p.getName(1).toString()))
276                        .map(p -> p.subpath(2, p.getNameCount()))
277                        .map(p -> p.toString())
278                        .filter(s -> s.endsWith(".class") && !s.endsWith("module-info.class"))
279                    .iterator();
280            } catch(IOException x) {
281                throw new UncheckedIOException("Unable to walk \"/modules\"", x);
282            }
283        }
284
285        /*
286         * Filter deployment modules
287         */
288        static Set<String> systemModules() {
289            Set<String> mods = Set.of("javafx.deploy", "jdk.deploy", "jdk.plugin", "jdk.javaws",
290                // All JVMCI packages other than jdk.vm.ci.services are dynamically
291                // exported to jdk.internal.vm.compiler and jdk.aot
292                "jdk.internal.vm.compiler", "jdk.aot"
293            );
294            return ModuleFinder.ofSystem().findAll().stream()
295                               .map(mref -> mref.descriptor().name())
296                               .filter(mn -> !mods.contains(mn))
297                               .collect(Collectors.toSet());
298        }
299    }
300
301    // Test with or without a security manager
302    public static enum TestCase {
303        UNSECURE, SECURE;
304        public void run() throws Exception {
305            System.out.println("Running test case: " + name());
306            Configure.setUp(this);
307            FieldSetAccessibleTest.run(this);
308        }
309    }
310
311    // A helper class to configure the security manager for the test,
312    // and bypass it when needed.
313    static class Configure {
314        static Policy policy = null;
315        static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
316            @Override
317            protected AtomicBoolean initialValue() {
318                return  new AtomicBoolean(false);
319            }
320        };
321        static void setUp(TestCase test) {
322            switch (test) {
323                case SECURE:
324                    if (policy == null && System.getSecurityManager() != null) {
325                        throw new IllegalStateException("SecurityManager already set");
326                    } else if (policy == null) {
327                        policy = new SimplePolicy(TestCase.SECURE, allowAll);
328                        Policy.setPolicy(policy);
329                        System.setSecurityManager(new SecurityManager());
330                    }
331                    if (System.getSecurityManager() == null) {
332                        throw new IllegalStateException("No SecurityManager.");
333                    }
334                    if (policy == null) {
335                        throw new IllegalStateException("policy not configured");
336                    }
337                    break;
338                case UNSECURE:
339                    if (System.getSecurityManager() != null) {
340                        throw new IllegalStateException("SecurityManager already set");
341                    }
342                    break;
343                default:
344                    throw new InternalError("No such testcase: " + test);
345            }
346        }
347        static void doPrivileged(Runnable run) {
348            allowAll.get().set(true);
349            try {
350                run.run();
351            } finally {
352                allowAll.get().set(false);
353            }
354        }
355    }
356
357    // A Helper class to build a set of permissions.
358    static final class PermissionsBuilder {
359        final Permissions perms;
360        public PermissionsBuilder() {
361            this(new Permissions());
362        }
363        public PermissionsBuilder(Permissions perms) {
364            this.perms = perms;
365        }
366        public PermissionsBuilder add(Permission p) {
367            perms.add(p);
368            return this;
369        }
370        public PermissionsBuilder addAll(PermissionCollection col) {
371            if (col != null) {
372                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
373                    perms.add(e.nextElement());
374                }
375            }
376            return this;
377        }
378        public Permissions toPermissions() {
379            final PermissionsBuilder builder = new PermissionsBuilder();
380            builder.addAll(perms);
381            return builder.perms;
382        }
383    }
384
385    // Policy for the test...
386    public static class SimplePolicy extends Policy {
387
388        final Permissions permissions;
389        final Permissions allPermissions;
390        final ThreadLocal<AtomicBoolean> allowAll;
391        public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
392            this.allowAll = allowAll;
393
394            // Permission needed by the tested code exercised in the test
395            permissions = new Permissions();
396            permissions.add(new RuntimePermission("fileSystemProvider"));
397            permissions.add(new RuntimePermission("createClassLoader"));
398            permissions.add(new RuntimePermission("closeClassLoader"));
399            permissions.add(new RuntimePermission("getClassLoader"));
400            permissions.add(new RuntimePermission("accessDeclaredMembers"));
401            permissions.add(new RuntimePermission("accessSystemModules"));
402            permissions.add(new ReflectPermission("suppressAccessChecks"));
403            permissions.add(new PropertyPermission("*", "read"));
404            permissions.add(new FilePermission("<<ALL FILES>>", "read"));
405
406            // these are used for configuring the test itself...
407            allPermissions = new Permissions();
408            allPermissions.add(new java.security.AllPermission());
409        }
410
411        @Override
412        public boolean implies(ProtectionDomain domain, Permission permission) {
413            if (allowAll.get().get()) return allPermissions.implies(permission);
414            if (permissions.implies(permission)) return true;
415            if (permission instanceof java.lang.RuntimePermission) {
416                if (permission.getName().startsWith("accessClassInPackage.")) {
417                    // add these along to the set of permission we have, when we
418                    // discover that we need them.
419                    permissions.add(permission);
420                    return true;
421                }
422            }
423            return false;
424        }
425
426        @Override
427        public PermissionCollection getPermissions(CodeSource codesource) {
428            return new PermissionsBuilder().addAll(allowAll.get().get()
429                    ? allPermissions : permissions).toPermissions();
430        }
431
432        @Override
433        public PermissionCollection getPermissions(ProtectionDomain domain) {
434            return new PermissionsBuilder().addAll(allowAll.get().get()
435                    ? allPermissions : permissions).toPermissions();
436        }
437    }
438
439}
440