1 /*
   2  * Copyright (c) 2015, 2016, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.loader;
  27 
  28 import java.io.File;
  29 import java.io.FilePermission;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.lang.module.ModuleReference;
  33 import java.lang.module.ModuleReader;
  34 import java.net.MalformedURLException;
  35 import java.net.URI;
  36 import java.net.URL;
  37 import java.nio.ByteBuffer;
  38 import java.security.AccessController;
  39 import java.security.CodeSigner;
  40 import java.security.CodeSource;
  41 import java.security.Permission;
  42 import java.security.PermissionCollection;
  43 import java.security.PrivilegedAction;
  44 import java.security.PrivilegedActionException;
  45 import java.security.PrivilegedExceptionAction;
  46 import java.security.SecureClassLoader;
  47 import java.util.ArrayList;
  48 import java.util.Collections;
  49 import java.util.Enumeration;
  50 import java.util.List;
  51 import java.util.Map;
  52 import java.util.Optional;
  53 import java.util.concurrent.ConcurrentHashMap;
  54 import java.util.jar.Attributes;
  55 import java.util.jar.Manifest;
  56 import java.util.stream.Stream;
  57 
  58 import jdk.internal.module.ModulePatcher.PatchedModuleReader;
  59 import jdk.internal.misc.VM;
  60 
  61 
  62 /**
  63  * The platform or application class loader. Resources loaded from modules
  64  * defined to the boot class loader are also loaded via an instance of this
  65  * ClassLoader type.
  66  *
  67  * <p> This ClassLoader supports loading of classes and resources from modules.
  68  * Modules are defined to the ClassLoader by invoking the {@link #loadModule}
  69  * method. Defining a module to this ClassLoader has the effect of making the
  70  * types in the module visible. </p>
  71  *
  72  * <p> This ClassLoader also supports loading of classes and resources from a
  73  * class path of URLs that are specified to the ClassLoader at construction
  74  * time. The class path may expand at runtime (the Class-Path attribute in JAR
  75  * files or via instrumentation agents). </p>
  76  *
  77  * <p> The delegation model used by this ClassLoader differs to the regular
  78  * delegation model. When requested to load a class then this ClassLoader first
  79  * maps the class name to its package name. If there is a module defined to a
  80  * BuiltinClassLoader containing this package then the class loader delegates
  81  * directly to that class loader. If there isn't a module containing the
  82  * package then it delegates the search to the parent class loader and if not
  83  * found in the parent then it searches the class path. The main difference
  84  * between this and the usual delegation model is that it allows the platform
  85  * class loader to delegate to the application class loader, important with
  86  * upgraded modules defined to the platform class loader.
  87  */
  88 
  89 public class BuiltinClassLoader
  90     extends SecureClassLoader
  91 {
  92     static {
  93         if (!ClassLoader.registerAsParallelCapable())
  94             throw new InternalError();
  95     }
  96 
  97     // parent ClassLoader
  98     private final BuiltinClassLoader parent;
  99 
 100     // the URL class path or null if there is no class path
 101     private final URLClassPath ucp;
 102 
 103 
 104     /**
 105      * A module defined/loaded by a built-in class loader.
 106      *
 107      * A LoadedModule encapsulates a ModuleReference along with its CodeSource
 108      * URL to avoid needing to create this URL when defining classes.
 109      */
 110     private static class LoadedModule {
 111         private final BuiltinClassLoader loader;
 112         private final ModuleReference mref;
 113         private final URL codeSourceURL;          // may be null
 114 
 115         LoadedModule(BuiltinClassLoader loader, ModuleReference mref) {
 116             URL url = null;
 117             if (mref.location().isPresent()) {
 118                 try {
 119                     url = mref.location().get().toURL();
 120                 } catch (MalformedURLException e) { }
 121             }
 122             this.loader = loader;
 123             this.mref = mref;
 124             this.codeSourceURL = url;
 125         }
 126 
 127         BuiltinClassLoader loader() { return loader; }
 128         ModuleReference mref() { return mref; }
 129         String name() { return mref.descriptor().name(); }
 130         URL codeSourceURL() { return codeSourceURL; }
 131     }
 132 
 133 
 134     // maps package name to loaded module for modules in the boot layer
 135     private static final Map<String, LoadedModule> packageToModule
 136         = new ConcurrentHashMap<>(1024);
 137 
 138     // maps a module name to a module reference
 139     private final Map<String, ModuleReference> nameToModule;
 140 
 141     // maps a module reference to a module reader
 142     private final Map<ModuleReference, ModuleReader> moduleToReader;
 143 
 144 
 145     /**
 146      * Create a new instance.
 147      */
 148     BuiltinClassLoader(String name, BuiltinClassLoader parent, URLClassPath ucp) {
 149         // ensure getParent() returns null when the parent is the boot loader
 150         super(name, parent == null || parent == ClassLoaders.bootLoader() ? null : parent);
 151 
 152         this.parent = parent;
 153         this.ucp = ucp;
 154 
 155         this.nameToModule = new ConcurrentHashMap<>();
 156         this.moduleToReader = new ConcurrentHashMap<>();
 157     }
 158 
 159     /**
 160      * Register a module this this class loader. This has the effect of making
 161      * the types in the module visible.
 162      */
 163     public void loadModule(ModuleReference mref) {
 164         String mn = mref.descriptor().name();
 165         if (nameToModule.putIfAbsent(mn, mref) != null) {
 166             throw new InternalError(mn + " already defined to this loader");
 167         }
 168 
 169         LoadedModule loadedModule = new LoadedModule(this, mref);
 170         for (String pn : mref.descriptor().packages()) {
 171             LoadedModule other = packageToModule.putIfAbsent(pn, loadedModule);
 172             if (other != null) {
 173                 throw new InternalError(pn + " in modules " + mn + " and "
 174                                         + other.mref().descriptor().name());
 175             }
 176         }
 177     }
 178 
 179     /**
 180      * Returns the {@code ModuleReference} for the named module defined to
 181      * this class loader; or {@code null} if not defined.
 182      *
 183      * @param name The name of the module to find
 184      */
 185     protected ModuleReference findModule(String name) {
 186         return nameToModule.get(name);
 187     }
 188 
 189 
 190     // -- finding resources
 191 
 192     /**
 193      * Returns a URL to a resource of the given name in a module defined to
 194      * this class loader.
 195      */
 196     @Override
 197     public URL findResource(String mn, String name) throws IOException {
 198         ModuleReference mref = nameToModule.get(mn);
 199         if (mref == null)
 200             return null;   // not defined to this class loader
 201 
 202         URL url;
 203 
 204         try {
 205             url = AccessController.doPrivileged(
 206                 new PrivilegedExceptionAction<URL>() {
 207                     @Override
 208                     public URL run() throws IOException {
 209                         URI u = moduleReaderFor(mref).find(name).orElse(null);
 210                         if (u != null) {
 211                             try {
 212                                 return u.toURL();
 213                             } catch (MalformedURLException e) { }
 214                         }
 215                         return null;
 216                     }
 217                 });
 218         } catch (PrivilegedActionException pae) {
 219             throw (IOException) pae.getCause();
 220         }
 221 
 222         // check access to the URL
 223         return checkURL(url);
 224     }
 225 
 226     /**
 227      * Returns an input stream to a resource of the given name in a module
 228      * defined to this class loader.
 229      */
 230     public InputStream findResourceAsStream(String mn, String name)
 231         throws IOException
 232     {
 233         // Need URL to resource when running with a security manager so that
 234         // the right permission check is done.
 235         SecurityManager sm = System.getSecurityManager();
 236         if (sm != null) {
 237 
 238             URL url = findResource(mn, name);
 239             return (url != null) ? url.openStream() : null;
 240 
 241         } else {
 242 
 243             ModuleReference mref = nameToModule.get(mn);
 244             if (mref == null)
 245                 return null;   // not defined to this class loader
 246 
 247             try {
 248                 return AccessController.doPrivileged(
 249                     new PrivilegedExceptionAction<InputStream>() {
 250                         @Override
 251                         public InputStream run() throws IOException {
 252                             return moduleReaderFor(mref).open(name).orElse(null);
 253                         }
 254                     });
 255             } catch (PrivilegedActionException pae) {
 256                 throw (IOException) pae.getCause();
 257             }
 258         }
 259     }
 260 
 261     /**
 262      * Finds the resource with the given name on the class path of this class
 263      * loader.
 264      */
 265     @Override
 266     public URL findResource(String name) {
 267         if (ucp != null) {
 268             PrivilegedAction<URL> pa = () -> ucp.findResource(name, false);
 269             URL url = AccessController.doPrivileged(pa);
 270             return checkURL(url);
 271         } else {
 272             return null;
 273         }
 274     }
 275 
 276     /**
 277      * Returns an enumeration of URL objects to all the resources with the
 278      * given name on the class path of this class loader.
 279      */
 280     @Override
 281     public Enumeration<URL> findResources(String name) throws IOException {
 282         if (ucp != null) {
 283             List<URL> result = new ArrayList<>();
 284             PrivilegedAction<Enumeration<URL>> pa = () -> ucp.findResources(name, false);
 285             Enumeration<URL> e = AccessController.doPrivileged(pa);
 286             while (e.hasMoreElements()) {
 287                 URL url = checkURL(e.nextElement());
 288                 if (url != null) {
 289                     result.add(url);
 290                 }
 291             }
 292             return Collections.enumeration(result); // checked URLs
 293         } else {
 294             return Collections.emptyEnumeration();
 295         }
 296     }
 297 
 298 
 299     // -- finding/loading classes
 300 
 301     /**
 302      * Finds the class with the specified binary name.
 303      */
 304     @Override
 305     protected Class<?> findClass(String cn) throws ClassNotFoundException {
 306         // no class loading until VM is fully initialized
 307         if (!VM.isModuleSystemInited())
 308             throw new ClassNotFoundException(cn);
 309 
 310         // find the candidate module for this class
 311         LoadedModule loadedModule = findLoadedModule(cn);
 312 
 313         Class<?> c = null;
 314         if (loadedModule != null) {
 315 
 316             // attempt to load class in module defined to this loader
 317             if (loadedModule.loader() == this) {
 318                 c = findClassInModuleOrNull(loadedModule, cn);
 319             }
 320 
 321         } else {
 322 
 323             // search class path
 324             if (ucp != null) {
 325                 c = findClassOnClassPathOrNull(cn);
 326             }
 327 
 328         }
 329 
 330         // not found
 331         if (c == null)
 332             throw new ClassNotFoundException(cn);
 333 
 334         return c;
 335     }
 336 
 337     /**
 338      * Finds the class with the specified binary name in a given module.
 339      * This method returns {@code null} if the class cannot be found.
 340      */
 341     @Override
 342     protected Class<?> findClass(String mn, String cn) {
 343         ModuleReference mref = nameToModule.get(mn);
 344         if (mref == null)
 345             return null;   // not defined to this class loader
 346 
 347         // find the candidate module for this class
 348         LoadedModule loadedModule = findLoadedModule(cn);
 349         if (loadedModule == null || !loadedModule.name().equals(mn)) {
 350             return null;   // module name does not match
 351         }
 352 
 353         // attempt to load class in module defined to this loader
 354         assert loadedModule.loader() == this;
 355         return findClassInModuleOrNull(loadedModule, cn);
 356     }
 357 
 358     /**
 359      * Loads the class with the specified binary name.
 360      */
 361     @Override
 362     protected Class<?> loadClass(String cn, boolean resolve)
 363         throws ClassNotFoundException
 364     {
 365         Class<?> c = loadClassOrNull(cn, resolve);
 366         if (c == null)
 367             throw new ClassNotFoundException(cn);
 368         return c;
 369     }
 370 
 371     /**
 372      * A variation of {@code loadCass} to load a class with the specified
 373      * binary name. This method returns {@code null} when the class is not
 374      * found.
 375      */
 376     protected Class<?> loadClassOrNull(String cn, boolean resolve) {
 377         synchronized (getClassLoadingLock(cn)) {
 378             // check if already loaded
 379             Class<?> c = findLoadedClass(cn);
 380 
 381             if (c == null) {
 382 
 383                 // find the candidate module for this class
 384                 LoadedModule loadedModule = findLoadedModule(cn);
 385                 if (loadedModule != null) {
 386 
 387                     // package is in a module
 388                     BuiltinClassLoader loader = loadedModule.loader();
 389                     if (loader == this) {
 390                         if (VM.isModuleSystemInited()) {
 391                             c = findClassInModuleOrNull(loadedModule, cn);
 392                         }
 393                     } else {
 394                         // delegate to the other loader
 395                         c = loader.loadClassOrNull(cn);
 396                     }
 397 
 398                 } else {
 399 
 400                     // check parent
 401                     if (parent != null) {
 402                         c = parent.loadClassOrNull(cn);
 403                     }
 404 
 405                     // check class path
 406                     if (c == null && ucp != null && VM.isModuleSystemInited()) {
 407                         c = findClassOnClassPathOrNull(cn);
 408                     }
 409                 }
 410 
 411             }
 412 
 413             if (resolve && c != null)
 414                 resolveClass(c);
 415 
 416             return c;
 417         }
 418     }
 419 
 420     /**
 421      * A variation of {@code loadCass} to load a class with the specified
 422      * binary name. This method returns {@code null} when the class is not
 423      * found.
 424      */
 425     protected  Class<?> loadClassOrNull(String cn) {
 426         return loadClassOrNull(cn, false);
 427     }
 428 
 429     /**
 430      * Find the candidate loaded module for the given class name.
 431      * Returns {@code null} if none of the modules defined to this
 432      * class loader contain the API package for the class.
 433      */
 434     private LoadedModule findLoadedModule(String cn) {
 435         int pos = cn.lastIndexOf('.');
 436         if (pos < 0)
 437             return null; // unnamed package
 438 
 439         String pn = cn.substring(0, pos);
 440         return packageToModule.get(pn);
 441     }
 442 
 443     /**
 444      * Finds the class with the specified binary name if in a module
 445      * defined to this ClassLoader.
 446      *
 447      * @return the resulting Class or {@code null} if not found
 448      */
 449     private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
 450         PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
 451         return AccessController.doPrivileged(pa);
 452     }
 453 
 454     /**
 455      * Finds the class with the specified binary name on the class path.
 456      *
 457      * @return the resulting Class or {@code null} if not found
 458      */
 459     private Class<?> findClassOnClassPathOrNull(String cn) {
 460         return AccessController.doPrivileged(
 461             new PrivilegedAction<Class<?>>() {
 462                 public Class<?> run() {
 463                     String path = cn.replace('.', '/').concat(".class");
 464                     Resource res = ucp.getResource(path, false);
 465                     if (res != null) {
 466                         try {
 467                             return defineClass(cn, res);
 468                         } catch (IOException ioe) {
 469                             // TBD on how I/O errors should be propagated
 470                         }
 471                     }
 472                     return null;
 473                 }
 474             });
 475     }
 476 
 477     /**
 478      * Defines the given binary class name to the VM, loading the class
 479      * bytes from the given module.
 480      *
 481      * @return the resulting Class or {@code null} if an I/O error occurs
 482      */
 483     private Class<?> defineClass(String cn, LoadedModule loadedModule) {
 484         ModuleReference mref = loadedModule.mref();
 485         ModuleReader reader = moduleReaderFor(mref);
 486 
 487         try {
 488             ByteBuffer bb = null;
 489             URL csURL = null;
 490 
 491             // locate class file, special handling for patched modules to
 492             // avoid locating the resource twice
 493             String rn = cn.replace('.', '/').concat(".class");
 494             if (reader instanceof PatchedModuleReader) {
 495                 Resource r = ((PatchedModuleReader)reader).findResource(rn);
 496                 if (r != null) {
 497                     bb = r.getByteBuffer();
 498                     csURL = r.getCodeSourceURL();
 499                 }
 500             } else {
 501                 bb = reader.read(rn).orElse(null);
 502                 csURL = loadedModule.codeSourceURL();
 503             }
 504 
 505             if (bb == null) {
 506                 // class not found
 507                 return null;
 508             }
 509 
 510             CodeSource cs = new CodeSource(csURL, (CodeSigner[]) null);
 511             try {
 512                 // define class to VM
 513                 return defineClass(cn, bb, cs);
 514 
 515             } finally {
 516                 reader.release(bb);
 517             }
 518 
 519         } catch (IOException ioe) {
 520             // TBD on how I/O errors should be propagated
 521             return null;
 522         }
 523     }
 524 
 525     /**
 526      * Defines the given binary class name to the VM, loading the class
 527      * bytes via the given Resource object.
 528      *
 529      * @return the resulting Class
 530      * @throws IOException if reading the resource fails
 531      * @throws SecurityException if there is a sealing violation (JAR spec)
 532      */
 533     private Class<?> defineClass(String cn, Resource res) throws IOException {
 534         URL url = res.getCodeSourceURL();
 535 
 536         // if class is in a named package then ensure that the package is defined
 537         int pos = cn.lastIndexOf('.');
 538         if (pos != -1) {
 539             String pn = cn.substring(0, pos);
 540             Manifest man = res.getManifest();
 541             defineOrCheckPackage(pn, man, url);
 542         }
 543 
 544         // defines the class to the runtime
 545         ByteBuffer bb = res.getByteBuffer();
 546         if (bb != null) {
 547             CodeSigner[] signers = res.getCodeSigners();
 548             CodeSource cs = new CodeSource(url, signers);
 549             return defineClass(cn, bb, cs);
 550         } else {
 551             byte[] b = res.getBytes();
 552             CodeSigner[] signers = res.getCodeSigners();
 553             CodeSource cs = new CodeSource(url, signers);
 554             return defineClass(cn, b, 0, b.length, cs);
 555         }
 556     }
 557 
 558 
 559     // -- packages
 560 
 561     /**
 562      * Defines a package in this ClassLoader. If the package is already defined
 563      * then its sealing needs to be checked if sealed by the legacy sealing
 564      * mechanism.
 565      *
 566      * @throws SecurityException if there is a sealing violation (JAR spec)
 567      */
 568     protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {
 569         Package pkg = getAndVerifyPackage(pn, man, url);
 570         if (pkg == null) {
 571             try {
 572                 if (man != null) {
 573                     pkg = definePackage(pn, man, url);
 574                 } else {
 575                     pkg = definePackage(pn, null, null, null, null, null, null, null);
 576                 }
 577             } catch (IllegalArgumentException iae) {
 578                 // defined by another thread so need to re-verify
 579                 pkg = getAndVerifyPackage(pn, man, url);
 580                 if (pkg == null)
 581                     throw new InternalError("Cannot find package: " + pn);
 582             }
 583         }
 584         return pkg;
 585     }
 586 
 587     /**
 588      * Get the Package with the specified package name. If defined
 589      * then verify that it against the manifest and code source.
 590      *
 591      * @throws SecurityException if there is a sealing violation (JAR spec)
 592      */
 593     private Package getAndVerifyPackage(String pn, Manifest man, URL url) {
 594         Package pkg = getDefinedPackage(pn);
 595         if (pkg != null) {
 596             if (pkg.isSealed()) {
 597                 if (!pkg.isSealed(url)) {
 598                     throw new SecurityException(
 599                         "sealing violation: package " + pn + " is sealed");
 600                 }
 601             } else {
 602                 // can't seal package if already defined without sealing
 603                 if ((man != null) && isSealed(pn, man)) {
 604                     throw new SecurityException(
 605                         "sealing violation: can't seal package " + pn +
 606                         ": already defined");
 607                 }
 608             }
 609         }
 610         return pkg;
 611     }
 612 
 613     /**
 614      * Defines a new package in this ClassLoader. The attributes in the specified
 615      * Manifest are use to get the package version and sealing information.
 616      *
 617      * @throws IllegalArgumentException if the package name duplicates an
 618      * existing package either in this class loader or one of its ancestors
 619      */
 620     private Package definePackage(String pn, Manifest man, URL url) {
 621         String specTitle = null;
 622         String specVersion = null;
 623         String specVendor = null;
 624         String implTitle = null;
 625         String implVersion = null;
 626         String implVendor = null;
 627         String sealed = null;
 628         URL sealBase = null;
 629 
 630         if (man != null) {
 631             Attributes attr = man.getAttributes(pn.replace('.', '/').concat("/"));
 632             if (attr != null) {
 633                 specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
 634                 specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
 635                 specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR);
 636                 implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
 637                 implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
 638                 implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
 639                 sealed = attr.getValue(Attributes.Name.SEALED);
 640             }
 641 
 642             attr = man.getMainAttributes();
 643             if (attr != null) {
 644                 if (specTitle == null)
 645                     specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
 646                 if (specVersion == null)
 647                     specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
 648                 if (specVendor == null)
 649                     specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR);
 650                 if (implTitle == null)
 651                     implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
 652                 if (implVersion == null)
 653                     implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
 654                 if (implVendor == null)
 655                     implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
 656                 if (sealed == null)
 657                     sealed = attr.getValue(Attributes.Name.SEALED);
 658             }
 659 
 660             // package is sealed
 661             if ("true".equalsIgnoreCase(sealed))
 662                 sealBase = url;
 663         }
 664         return definePackage(pn,
 665                              specTitle,
 666                              specVersion,
 667                              specVendor,
 668                              implTitle,
 669                              implVersion,
 670                              implVendor,
 671                              sealBase);
 672     }
 673 
 674     /**
 675      * Returns {@code true} if the specified package name is sealed according to
 676      * the given manifest.
 677      */
 678     private boolean isSealed(String pn, Manifest man) {
 679         String path = pn.replace('.', '/').concat("/");
 680         Attributes attr = man.getAttributes(path);
 681         String sealed = null;
 682         if (attr != null)
 683             sealed = attr.getValue(Attributes.Name.SEALED);
 684         if (sealed == null && (attr = man.getMainAttributes()) != null)
 685             sealed = attr.getValue(Attributes.Name.SEALED);
 686         return "true".equalsIgnoreCase(sealed);
 687     }
 688 
 689     // -- permissions
 690 
 691     /**
 692      * Returns the permissions for the given CodeSource.
 693      */
 694     @Override
 695     protected PermissionCollection getPermissions(CodeSource cs) {
 696         PermissionCollection perms = super.getPermissions(cs);
 697 
 698         // add the permission to access the resource
 699         URL url = cs.getLocation();
 700         if (url == null)
 701             return perms;
 702         Permission p = null;
 703         try {
 704             p = url.openConnection().getPermission();
 705             if (p != null) {
 706                 // for directories then need recursive access
 707                 if (p instanceof FilePermission) {
 708                     String path = p.getName();
 709                     if (path.endsWith(File.separator)) {
 710                         path += "-";
 711                         p = new FilePermission(path, "read");
 712                     }
 713                 }
 714                 perms.add(p);
 715             }
 716         } catch (IOException ioe) { }
 717 
 718         return perms;
 719     }
 720 
 721 
 722     // -- miscellaneous supporting methods
 723 
 724     /**
 725      * Returns the ModuleReader for the given module.
 726      */
 727     private ModuleReader moduleReaderFor(ModuleReference mref) {
 728         return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));
 729     }
 730 
 731     /**
 732      * Creates a ModuleReader for the given module.
 733      */
 734     private ModuleReader createModuleReader(ModuleReference mref) {
 735         try {
 736             return mref.open();
 737         } catch (IOException e) {
 738             // Return a null module reader to avoid a future class load
 739             // attempting to open the module again.
 740             return new NullModuleReader();
 741         }
 742     }
 743 
 744     /**
 745      * A ModuleReader that doesn't read any resources.
 746      */
 747     private static class NullModuleReader implements ModuleReader {
 748         @Override
 749         public Optional<URI> find(String name) {
 750             return Optional.empty();
 751         }
 752         @Override
 753         public Stream<String> list() {
 754             return Stream.empty();
 755         }
 756         @Override
 757         public void close() {
 758             throw new InternalError("Should not get here");
 759         }
 760     };
 761 
 762     /**
 763      * Checks access to the given URL. We use URLClassPath for consistent
 764      * checking with java.net.URLClassLoader.
 765      */
 766     private static URL checkURL(URL url) {
 767         return URLClassPath.checkURL(url);
 768     }
 769 }