1 /*
   2  * Copyright (c) 2014, 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 import java.io.File;
  24 import java.io.FilePermission;
  25 import java.io.IOException;
  26 import java.lang.reflect.Field;
  27 import java.lang.reflect.ReflectPermission;
  28 import java.security.CodeSource;
  29 import java.security.Permission;
  30 import java.security.PermissionCollection;
  31 import java.security.Permissions;
  32 import java.security.Policy;
  33 import java.security.ProtectionDomain;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.Collections;
  37 import java.util.Enumeration;
  38 import java.util.Iterator;
  39 import java.util.List;
  40 import java.util.PropertyPermission;
  41 import java.util.concurrent.atomic.AtomicBoolean;
  42 import java.util.concurrent.atomic.AtomicLong;
  43 import java.util.jar.JarEntry;
  44 import java.util.jar.JarFile;
  45 
  46 /**
  47  * @test
  48  * @bug 8065552
  49  * @summary test that all fields returned by getDeclaredFields() can be
  50  * set accessible if the right permission is granted; this test
  51  * loads all the classes in the BCL, get their declared fields,
  52  * and call setAccessible(false) followed by setAccessible(true);
  53  * This test has several (at)run main/othervm lines to work around the fact
  54  * that the default PermGen size is usually too small to load all classes.
  55  * So we split the list in batch of ~ 4000 and run the test
  56  * once for each batch. The first number is the start index,
  57  * the second is the maximum number of classes to load.
  58  *
  59  * @run main/othervm FieldSetAccessibleTest UNSECURE 0 4000
  60  * @run main/othervm FieldSetAccessibleTest SECURE   0 4000
  61  * @run main/othervm FieldSetAccessibleTest UNSECURE 4000 4000
  62  * @run main/othervm FieldSetAccessibleTest SECURE   4000 4000
  63  * @run main/othervm FieldSetAccessibleTest UNSECURE 8000 4000
  64  * @run main/othervm FieldSetAccessibleTest SECURE   8000 4000
  65  * @run main/othervm FieldSetAccessibleTest UNSECURE 12000 4000
  66  * @run main/othervm FieldSetAccessibleTest SECURE   12000 4000
  67  * @run main/othervm FieldSetAccessibleTest UNSECURE 16000 4000
  68  * @run main/othervm FieldSetAccessibleTest SECURE   16000 4000
  69  * @run main/othervm FieldSetAccessibleTest UNSECURE 20000
  70  * @run main/othervm FieldSetAccessibleTest SECURE   20000
  71  *
  72  * @author danielfuchs
  73  */
  74 
  75 
  76 public class FieldSetAccessibleTest {
  77 
  78     static final List<String> skipped = new ArrayList<>();
  79     static final List<String> cantread = new ArrayList<>();
  80     static final List<String> failed = new ArrayList<>();
  81     static final AtomicLong classCount = new AtomicLong();
  82     static final AtomicLong fieldCount = new AtomicLong();
  83     static long startIndex = 0;
  84     static long maxSize = Long.MAX_VALUE;
  85     static long maxIndex = Long.MAX_VALUE;
  86 
  87 
  88     static void testSetFieldsAccessible(Class<?> c) {
  89         for (Field f : c.getDeclaredFields()) {
  90             fieldCount.incrementAndGet();
  91             f.setAccessible(false);
  92             f.setAccessible(true);
  93         }
  94     }
  95 
  96 
  97     public static boolean test(Class<?> c) {
  98         //System.out.println(c.getName());
  99         classCount.incrementAndGet();
 100 
 101         // Call getDeclaredFields() and try to set their accessible flag.
 102         testSetFieldsAccessible(c);
 103 
 104         // add more tests here...
 105 
 106         return c == Class.class;
 107     }
 108 
 109     static ClassLoader getClassLoaderFor(String classFileName) {
 110         return null; // load all classes through the BCL.
 111     }
 112 
 113     static void checkClassLoaderFor(Class<?> c, ClassLoader loader) {
 114         if (loader == null) { // expects BCL
 115             if (c.getClassLoader() != loader) {
 116                 System.err.println("Unexpected loader for "+c+": "+c.getClassLoader());
 117             }
 118         }
 119     }
 120 
 121     static void printSummary(long secs, long millis, long nanos) {
 122         System.out.println("Tested " + fieldCount.get() + " fields of "
 123                 + classCount.get() + " classes in "
 124                 + secs + "s " + millis + "ms " + nanos + "ns");
 125     }
 126 
 127 
 128     /**
 129      * @param args the command line arguments:
 130      *
 131      *     SECURE|UNSECURE [startIndex (default=0)] [maxSize (default=Long.MAX_VALUE)]
 132      * 
 133      * @throws java.lang.Exception if the test fails
 134      */
 135     public static void main(String[] args) throws Exception {
 136         if (args == null || args.length == 0) {
 137             args = new String[] {"SECURE", "0"};
 138         } else if (args.length > 3) {
 139             throw new RuntimeException("Expected at most one argument. Found "
 140                     + Arrays.asList(args));
 141         }
 142         try {
 143             if (args.length > 1) {
 144                 startIndex = Long.parseLong(args[1]);
 145                 if (startIndex < 0) {
 146                     throw new IllegalArgumentException("startIndex args[1]: "
 147                             + startIndex);
 148                 }
 149             }
 150             if (args.length > 2) {
 151                 maxSize = Long.parseLong(args[2]);
 152                 if (maxSize <= 0) {
 153                     maxSize = Long.MAX_VALUE;
 154                 }
 155                 maxIndex = (Long.MAX_VALUE - startIndex) < maxSize
 156                         ? Long.MAX_VALUE : startIndex + maxSize;
 157             }
 158             TestCase.valueOf(args[0]).run();
 159         } catch (OutOfMemoryError oome) {
 160             System.err.println(classCount.get());
 161             throw oome;
 162         }
 163     }
 164 
 165     public static void run(TestCase test) {
 166         System.out.println("Testing " + test);
 167         test(listAllClassNames());
 168         System.out.println("Passed " + test);
 169     }
 170 
 171     static Iterable<String> listAllClassNames() {
 172         boolean sort = startIndex != 0 || maxIndex != Long.MAX_VALUE;
 173         return new ClassNameListBuilder(sort);
 174     }
 175 
 176     static void test(Iterable<String> iterable) {
 177         final long start = System.nanoTime();
 178         boolean classFound = false;
 179         int index = 0;
 180         for (String s: iterable) {
 181             if (index == maxIndex) break;
 182             try {
 183                 if (index < startIndex) continue;
 184                 if (test(getClassLoaderFor(s), s)) {
 185                     classFound = true;
 186                 }
 187             } finally {
 188                 index++;
 189             }
 190         }
 191         long elapsed = System.nanoTime() - start;
 192         long secs = elapsed / 1000_000_000;
 193         long millis = (elapsed % 1000_000_000) / 1000_000;
 194         long nanos  = elapsed % 1000_000;
 195         System.out.println("Unreadable path elements: " + cantread);
 196         System.out.println("Skipped path elements: " + skipped);
 197         System.out.println("Failed path elements: " + failed);
 198         printSummary(secs, millis, nanos);
 199 
 200         if (!failed.isEmpty()) {
 201             throw new RuntimeException("Test failed for the following classes: " + failed);
 202         }
 203         if (!classFound && startIndex == 0 && index < maxIndex) {
 204             // this is just to verify that we have indeed parsed rt.jar
 205             // (or the java.base module)
 206             throw  new RuntimeException("Test failed: Class.class not found...");
 207         }
 208         if (classCount.get() == 0 && startIndex < 20000) {
 209             throw  new RuntimeException("Test failed: no class found?");
 210         }
 211     }
 212 
 213     static boolean test(ClassLoader loader, String s) {
 214         try {
 215             if (s.startsWith("WrapperGenerator")) {
 216                 System.out.println("Skipping "+ s);
 217                 return false;
 218             }
 219             final Class<?> c = Class.forName(
 220                     s.replace('/', '.').substring(0, s.length() - 6),
 221                     false,
 222                     loader);
 223             checkClassLoaderFor(c, loader);
 224             return test(c);
 225         } catch (Exception t) {
 226             t.printStackTrace();
 227             failed.add(s);
 228         }
 229         return false;
 230     }
 231 
 232 
 233     static class ClassNameListBuilder implements Iterable<String> {
 234         String[] bcp;
 235         boolean sort;
 236         ClassNameListBuilder(boolean sort) {
 237             bcp = System.getProperty("sun.boot.class.path").split(File.pathSeparator);
 238             this.sort = sort;
 239         }
 240 
 241         public List<String> build() {
 242             return findClasses();
 243         }
 244 
 245         List<String> findClasses() {
 246             List<String> list = new ArrayList<>();
 247 
 248             for (String s : bcp) {
 249                 System.out.println("boot class path element: " + s);
 250                 if (s.endsWith(".jar")) {
 251                     addJarEntries(s, list);
 252                 } else {
 253                     addFolderEntries(new File(s), s, list);
 254                 }
 255             }
 256 
 257             if (sort) {
 258                 System.out.println("Sorting class names...");
 259                 Collections.sort(list);
 260                 System.out.println("\t... Sorted.");
 261             }
 262             return list;
 263         }
 264 
 265         void addJarEntries(String jarName, List<String> classNames) {
 266             File f = new File(jarName);
 267             if (f.canRead() && f.isFile()) {
 268                 try {
 269                     JarFile jarFile = new JarFile(f);
 270                     for (Enumeration<JarEntry> entries = jarFile.entries() ; entries.hasMoreElements() ;) {
 271                         JarEntry e = entries.nextElement();
 272                         if (e.isDirectory()) {
 273                             continue;
 274                         }
 275                         if (e.getName().endsWith(".class")) {
 276                             classNames.add(e.getName());
 277                         }
 278                     }
 279                 } catch (IOException e) {
 280                     e.printStackTrace();
 281                     skipped.add(jarName);
 282                 }
 283             } else {
 284                 cantread.add(jarName);
 285             }
 286         }
 287 
 288         void addFolderEntries(File root, String folderName, List<String> classNames) {
 289             File f = new File(folderName);
 290             if (f.canRead()) {
 291                if (f.isDirectory()) {
 292                    for (String child :  f.list()) {
 293                        addFolderEntries(root, new File(f,child).getPath(), classNames);
 294                    }
 295                } else if (f.isFile() && f.getName().endsWith(".class")) {
 296                    String className = root.toPath().relativize(f.toPath()).toString()
 297                            .replace(File.separatorChar, '/');
 298                    System.out.println("adding: " + className);
 299                    classNames.add(className);
 300                }
 301             } else {
 302                 cantread.add(folderName);
 303             }
 304         }
 305 
 306         @Override
 307         public Iterator<String> iterator() {
 308             return build().iterator();
 309         }
 310 
 311     }
 312 
 313     // Test with or without a security manager
 314     public static enum TestCase {
 315         UNSECURE, SECURE;
 316         public void run() throws Exception {
 317             System.out.println("Running test case: " + name());
 318             Configure.setUp(this);
 319             FieldSetAccessibleTest.run(this);
 320         }
 321     }
 322 
 323     // A helper class to configure the security manager for the test,
 324     // and bypass it when needed.
 325     static class Configure {
 326         static Policy policy = null;
 327         static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
 328             @Override
 329             protected AtomicBoolean initialValue() {
 330                 return  new AtomicBoolean(false);
 331             }
 332         };
 333         static void setUp(TestCase test) {
 334             switch (test) {
 335                 case SECURE:
 336                     if (policy == null && System.getSecurityManager() != null) {
 337                         throw new IllegalStateException("SecurityManager already set");
 338                     } else if (policy == null) {
 339                         policy = new SimplePolicy(TestCase.SECURE, allowAll);
 340                         Policy.setPolicy(policy);
 341                         System.setSecurityManager(new SecurityManager());
 342                     }
 343                     if (System.getSecurityManager() == null) {
 344                         throw new IllegalStateException("No SecurityManager.");
 345                     }
 346                     if (policy == null) {
 347                         throw new IllegalStateException("policy not configured");
 348                     }
 349                     break;
 350                 case UNSECURE:
 351                     if (System.getSecurityManager() != null) {
 352                         throw new IllegalStateException("SecurityManager already set");
 353                     }
 354                     break;
 355                 default:
 356                     throw new InternalError("No such testcase: " + test);
 357             }
 358         }
 359         static void doPrivileged(Runnable run) {
 360             allowAll.get().set(true);
 361             try {
 362                 run.run();
 363             } finally {
 364                 allowAll.get().set(false);
 365             }
 366         }
 367     }
 368 
 369     // A Helper class to build a set of permissions.
 370     final static class PermissionsBuilder {
 371         final Permissions perms;
 372         public PermissionsBuilder() {
 373             this(new Permissions());
 374         }
 375         public PermissionsBuilder(Permissions perms) {
 376             this.perms = perms;
 377         }
 378         public PermissionsBuilder add(Permission p) {
 379             perms.add(p);
 380             return this;
 381         }
 382         public PermissionsBuilder addAll(PermissionCollection col) {
 383             if (col != null) {
 384                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
 385                     perms.add(e.nextElement());
 386                 }
 387             }
 388             return this;
 389         }
 390         public Permissions toPermissions() {
 391             final PermissionsBuilder builder = new PermissionsBuilder();
 392             builder.addAll(perms);
 393             return builder.perms;
 394         }
 395     }
 396 
 397     // Policy for the test...
 398     public static class SimplePolicy extends Policy {
 399 
 400         final Permissions permissions;
 401         final Permissions allPermissions;
 402         final ThreadLocal<AtomicBoolean> allowAll;
 403         public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
 404             this.allowAll = allowAll;
 405 
 406             // Permission needed by the tested code exercised in the test
 407             permissions = new Permissions();
 408             permissions.add(new RuntimePermission("createClassLoader"));
 409             permissions.add(new RuntimePermission("closeClassLoader"));
 410             permissions.add(new RuntimePermission("getClassLoader"));
 411             permissions.add(new RuntimePermission("accessDeclaredMembers"));
 412             permissions.add(new ReflectPermission("suppressAccessChecks"));
 413             permissions.add(new PropertyPermission("sun.boot.class.path", "read"));
 414             permissions.add(new FilePermission("<<ALL FILES>>", "read"));
 415 
 416             // these are used for configuring the test itself...
 417             allPermissions = new Permissions();
 418             allPermissions.add(new java.security.AllPermission());
 419         }
 420 
 421         @Override
 422         public boolean implies(ProtectionDomain domain, Permission permission) {
 423             if (allowAll.get().get()) return allPermissions.implies(permission);
 424             if (permissions.implies(permission)) return true;
 425             if (permission instanceof java.lang.RuntimePermission) {
 426                 if (permission.getName().startsWith("accessClassInPackage.")) {
 427                     // add these along to the set of permission we have, when we
 428                     // discover that we need them.
 429                     permissions.add(permission);
 430                     return true;
 431                 }
 432             }
 433             return false;
 434         }
 435 
 436         @Override
 437         public PermissionCollection getPermissions(CodeSource codesource) {
 438             return new PermissionsBuilder().addAll(allowAll.get().get()
 439                     ? allPermissions : permissions).toPermissions();
 440         }
 441 
 442         @Override
 443         public PermissionCollection getPermissions(ProtectionDomain domain) {
 444             return new PermissionsBuilder().addAll(allowAll.get().get()
 445                     ? allPermissions : permissions).toPermissions();
 446         }
 447     }
 448 
 449 }