1 /*
   2  * Copyright (c) 2014, 2020, 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.module;
  27 
  28 import java.io.File;
  29 import java.io.PrintStream;
  30 import java.lang.module.Configuration;
  31 import java.lang.module.ModuleDescriptor;
  32 import java.lang.module.ModuleFinder;
  33 import java.lang.module.ModuleReference;
  34 import java.lang.module.ResolvedModule;
  35 import java.net.URI;
  36 import java.nio.file.Path;
  37 import java.util.ArrayList;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.HashSet;
  41 import java.util.Iterator;
  42 import java.util.LinkedHashMap;
  43 import java.util.List;
  44 import java.util.Map;
  45 import java.util.Objects;
  46 import java.util.Optional;
  47 import java.util.Set;
  48 import java.util.function.Function;
  49 import java.util.stream.Collectors;
  50 
  51 import jdk.internal.access.JavaLangAccess;
  52 import jdk.internal.access.JavaLangModuleAccess;
  53 import jdk.internal.access.SharedSecrets;
  54 import jdk.internal.loader.BootLoader;
  55 import jdk.internal.loader.BuiltinClassLoader;
  56 import jdk.internal.loader.ClassLoaders;
  57 import jdk.internal.misc.VM;
  58 import jdk.internal.perf.PerfCounter;
  59 
  60 /**
  61  * Initializes/boots the module system.
  62  *
  63  * The {@link #boot() boot} method is called early in the startup to initialize
  64  * the module system. In summary, the boot method creates a Configuration by
  65  * resolving a set of module names specified via the launcher (or equivalent)
  66  * -m and --add-modules options. The modules are located on a module path that
  67  * is constructed from the upgrade module path, system modules, and application
  68  * module path. The Configuration is instantiated as the boot layer with each
  69  * module in the configuration defined to a class loader.
  70  */
  71 
  72 public final class ModuleBootstrap {
  73     private ModuleBootstrap() { }
  74 
  75     private static final String JAVA_BASE = "java.base";
  76 
  77     // the token for "all default modules"
  78     private static final String ALL_DEFAULT = "ALL-DEFAULT";
  79 
  80     // the token for "all unnamed modules"
  81     private static final String ALL_UNNAMED = "ALL-UNNAMED";
  82 
  83     // the token for "all system modules"
  84     private static final String ALL_SYSTEM = "ALL-SYSTEM";
  85 
  86     // the token for "all modules on the module path"
  87     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
  88 
  89     // access to java.lang/module
  90     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
  91     private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess();
  92 
  93     // The ModulePatcher for the initial configuration
  94     private static final ModulePatcher patcher = initModulePatcher();
  95 
  96     /**
  97      * Returns the ModulePatcher for the initial configuration.
  98      */
  99     public static ModulePatcher patcher() {
 100         return patcher;
 101     }
 102 
 103     // ModuleFinders for the initial configuration
 104     private static volatile ModuleFinder unlimitedFinder;
 105     private static volatile ModuleFinder limitedFinder;
 106 
 107     /**
 108      * Returns the ModuleFinder for the initial configuration before
 109      * observability is limited by the --limit-modules command line option.
 110      *
 111      * @apiNote Used to support locating modules {@code java.instrument} and
 112      * {@code jdk.management.agent} modules when they are loaded dynamically.
 113      */
 114     public static ModuleFinder unlimitedFinder() {
 115         ModuleFinder finder = unlimitedFinder;
 116         if (finder == null) {
 117             return ModuleFinder.ofSystem();
 118         } else {
 119             return finder;
 120         }
 121     }
 122 
 123     /**
 124      * Returns the ModuleFinder for the initial configuration.
 125      *
 126      * @apiNote Used to support "{@code java --list-modules}".
 127      */
 128     public static ModuleFinder limitedFinder() {
 129         ModuleFinder finder = limitedFinder;
 130         if (finder == null) {
 131             return unlimitedFinder();
 132         } else {
 133             return finder;
 134         }
 135     }
 136 
 137     /**
 138      * Returns true if the archived boot layer can be used. The system properties
 139      * are checked in the order that they are used by boot2.
 140      */
 141     private static boolean canUseArchivedBootLayer() {
 142         return getProperty("jdk.module.upgrade.path") == null &&
 143                getProperty("jdk.module.path") == null &&
 144                getProperty("jdk.module.patch.0") == null &&    // --patch-module
 145                getProperty("jdk.module.main") == null &&
 146                getProperty("jdk.module.addmods.0") == null  && // --add-modules
 147                getProperty("jdk.module.limitmods") == null &&
 148                getProperty("jdk.module.addreads.0") == null &&    // --add-reads
 149                getProperty("jdk.module.addexports.0") == null &&  // --add-exports
 150                getProperty("jdk.module.addopens.0") == null &&    // --add-opens
 151                getProperty("jdk.module.illegalAccess") == null;
 152     }
 153 
 154     /**
 155      * Initialize the module system, returning the boot layer. The boot layer
 156      * is obtained from the CDS archive if possible, otherwise it is generated
 157      * from the module graph.
 158      *
 159      * @see java.lang.System#initPhase2(boolean, boolean)
 160      */
 161     public static ModuleLayer boot() {
 162         Counters.start();
 163 
 164         ModuleLayer bootLayer;
 165         ArchivedBootLayer archivedBootLayer = ArchivedBootLayer.get();
 166         if (archivedBootLayer != null) {
 167             assert canUseArchivedBootLayer();
 168             bootLayer = archivedBootLayer.bootLayer();
 169             BootLoader.getUnnamedModule(); // trigger <clinit> of BootLoader.
 170             VM.defineArchivedModules(ClassLoaders.platformClassLoader(), ClassLoaders.appClassLoader());
 171 
 172             // assume boot layer has at least one module providing a service
 173             // that is mapped to the application class loader.
 174             JLA.bindToLoader(bootLayer, ClassLoaders.appClassLoader());
 175 
 176             // IllegalAccessLogger needs to be set
 177             var illegalAccessLoggerBuilder = archivedBootLayer.illegalAccessLoggerBuilder();
 178             if (illegalAccessLoggerBuilder != null) {
 179                 illegalAccessLoggerBuilder.complete();
 180             }
 181         } else {
 182             bootLayer = boot2();
 183         }
 184 
 185         Counters.publish("jdk.module.boot.totalTime");
 186         return bootLayer;
 187     }
 188 
 189     private static ModuleLayer boot2() {
 190         // Step 0: Command line options
 191 
 192         ModuleFinder upgradeModulePath = finderFor("jdk.module.upgrade.path");
 193         ModuleFinder appModulePath = finderFor("jdk.module.path");
 194         boolean isPatched = patcher.hasPatches();
 195 
 196         String mainModule = System.getProperty("jdk.module.main");
 197         Set<String> addModules = addModules();
 198         Set<String> limitModules = limitModules();
 199 
 200         PrintStream traceOutput = null;
 201         String trace = getAndRemoveProperty("jdk.module.showModuleResolution");
 202         if (trace != null && Boolean.parseBoolean(trace))
 203             traceOutput = System.out;
 204 
 205         Counters.add("jdk.module.boot.0.commandLineTime");
 206 
 207         // Step 1: The observable system modules, either all system modules
 208         // or the system modules pre-generated for the initial module (the
 209         // initial module may be the unnamed module). If the system modules
 210         // are pre-generated for the initial module then resolution can be
 211         // skipped.
 212 
 213         SystemModules systemModules = null;
 214         ModuleFinder systemModuleFinder;
 215 
 216         boolean haveModulePath = (appModulePath != null || upgradeModulePath != null);
 217         boolean needResolution = true;
 218         boolean canArchive = false;
 219         boolean hasSplitPackages;
 220         boolean hasIncubatorModules;
 221 
 222         // If the java heap was archived at CDS dump time and the environment
 223         // at dump time matches the current environment then use the archived
 224         // system modules and finder.
 225         ArchivedModuleGraph archivedModuleGraph = ArchivedModuleGraph.get(mainModule);
 226         if (archivedModuleGraph != null
 227                 && !haveModulePath
 228                 && addModules.isEmpty()
 229                 && limitModules.isEmpty()
 230                 && !isPatched) {
 231             systemModuleFinder = archivedModuleGraph.finder();
 232             hasSplitPackages = archivedModuleGraph.hasSplitPackages();
 233             hasIncubatorModules = archivedModuleGraph.hasIncubatorModules();
 234             needResolution = (traceOutput != null);
 235         } else {
 236             if (!haveModulePath && addModules.isEmpty() && limitModules.isEmpty()) {
 237                 systemModules = SystemModuleFinders.systemModules(mainModule);
 238                 if (systemModules != null && !isPatched) {
 239                     needResolution = (traceOutput != null);
 240                     canArchive = true;
 241                 }
 242             }
 243             if (systemModules == null) {
 244                 // all system modules are observable
 245                 systemModules = SystemModuleFinders.allSystemModules();
 246             }
 247             if (systemModules != null) {
 248                 // images build
 249                 systemModuleFinder = SystemModuleFinders.of(systemModules);
 250             } else {
 251                 // exploded build or testing
 252                 systemModules = new ExplodedSystemModules();
 253                 systemModuleFinder = SystemModuleFinders.ofSystem();
 254             }
 255 
 256             hasSplitPackages = systemModules.hasSplitPackages();
 257             hasIncubatorModules = systemModules.hasIncubatorModules();
 258             // not using the archived module graph - avoid accidental use
 259             archivedModuleGraph = null;
 260         }
 261 
 262         Counters.add("jdk.module.boot.1.systemModulesTime");
 263 
 264         // Step 2: Define and load java.base. This patches all classes loaded
 265         // to date so that they are members of java.base. Once java.base is
 266         // loaded then resources in java.base are available for error messages
 267         // needed from here on.
 268 
 269         ModuleReference base = systemModuleFinder.find(JAVA_BASE).orElse(null);
 270         if (base == null)
 271             throw new InternalError(JAVA_BASE + " not found");
 272         URI baseUri = base.location().orElse(null);
 273         if (baseUri == null)
 274             throw new InternalError(JAVA_BASE + " does not have a location");
 275         BootLoader.loadModule(base);
 276         Modules.defineModule(null, base.descriptor(), baseUri);
 277 
 278         // Step 2a: Scan all modules when --validate-modules specified
 279 
 280         if (getAndRemoveProperty("jdk.module.validation") != null) {
 281             int errors = ModulePathValidator.scanAllModules(System.out);
 282             if (errors > 0) {
 283                 fail("Validation of module path failed");
 284             }
 285         }
 286 
 287         Counters.add("jdk.module.boot.2.defineBaseTime");
 288 
 289         // Step 3: If resolution is needed then create the module finder and
 290         // the set of root modules to resolve.
 291 
 292         ModuleFinder savedModuleFinder = null;
 293         ModuleFinder finder;
 294         Set<String> roots;
 295         if (needResolution) {
 296 
 297             // upgraded modules override the modules in the run-time image
 298             if (upgradeModulePath != null)
 299                 systemModuleFinder = ModuleFinder.compose(upgradeModulePath,
 300                                                           systemModuleFinder);
 301 
 302             // The module finder: [--upgrade-module-path] system [--module-path]
 303             if (appModulePath != null) {
 304                 finder = ModuleFinder.compose(systemModuleFinder, appModulePath);
 305             } else {
 306                 finder = systemModuleFinder;
 307             }
 308 
 309             // The root modules to resolve
 310             roots = new HashSet<>();
 311 
 312             // launcher -m option to specify the main/initial module
 313             if (mainModule != null)
 314                 roots.add(mainModule);
 315 
 316             // additional module(s) specified by --add-modules
 317             boolean addAllDefaultModules = false;
 318             boolean addAllSystemModules = false;
 319             boolean addAllApplicationModules = false;
 320             for (String mod : addModules) {
 321                 switch (mod) {
 322                     case ALL_DEFAULT:
 323                         addAllDefaultModules = true;
 324                         break;
 325                     case ALL_SYSTEM:
 326                         addAllSystemModules = true;
 327                         break;
 328                     case ALL_MODULE_PATH:
 329                         addAllApplicationModules = true;
 330                         break;
 331                     default:
 332                         roots.add(mod);
 333                 }
 334             }
 335 
 336             // --limit-modules
 337             savedModuleFinder = finder;
 338             if (!limitModules.isEmpty()) {
 339                 finder = limitFinder(finder, limitModules, roots);
 340             }
 341 
 342             // If there is no initial module specified then assume that the initial
 343             // module is the unnamed module of the application class loader. This
 344             // is implemented by resolving all observable modules that export an
 345             // API. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT bit set in
 346             // their ModuleResolution attribute flags are excluded from the
 347             // default set of roots.
 348             if (mainModule == null || addAllDefaultModules) {
 349                 roots.addAll(DefaultRoots.compute(systemModuleFinder, finder));
 350             }
 351 
 352             // If `--add-modules ALL-SYSTEM` is specified then all observable system
 353             // modules will be resolved.
 354             if (addAllSystemModules) {
 355                 ModuleFinder f = finder;  // observable modules
 356                 systemModuleFinder.findAll()
 357                     .stream()
 358                     .map(ModuleReference::descriptor)
 359                     .map(ModuleDescriptor::name)
 360                     .filter(mn -> f.find(mn).isPresent())  // observable
 361                     .forEach(mn -> roots.add(mn));
 362             }
 363 
 364             // If `--add-modules ALL-MODULE-PATH` is specified then all observable
 365             // modules on the application module path will be resolved.
 366             if (appModulePath != null && addAllApplicationModules) {
 367                 ModuleFinder f = finder;  // observable modules
 368                 appModulePath.findAll()
 369                     .stream()
 370                     .map(ModuleReference::descriptor)
 371                     .map(ModuleDescriptor::name)
 372                     .filter(mn -> f.find(mn).isPresent())  // observable
 373                     .forEach(mn -> roots.add(mn));
 374             }
 375         } else {
 376             // no resolution case
 377             finder = systemModuleFinder;
 378             roots = null;
 379         }
 380 
 381         Counters.add("jdk.module.boot.3.optionsAndRootsTime");
 382 
 383         // Step 4: Resolve the root modules, with service binding, to create
 384         // the configuration for the boot layer. If resolution is not needed
 385         // then create the configuration for the boot layer from the
 386         // readability graph created at link time.
 387 
 388         Configuration cf;
 389         if (needResolution) {
 390             cf = Modules.newBootLayerConfiguration(finder, roots, traceOutput);
 391         } else {
 392             if (archivedModuleGraph != null) {
 393                 cf = archivedModuleGraph.configuration();
 394             } else {
 395                 Map<String, Set<String>> map = systemModules.moduleReads();
 396                 cf = JLMA.newConfiguration(systemModuleFinder, map);
 397             }
 398         }
 399 
 400         // check that modules specified to --patch-module are resolved
 401         if (isPatched) {
 402             patcher.patchedModules()
 403                     .stream()
 404                     .filter(mn -> !cf.findModule(mn).isPresent())
 405                     .forEach(mn -> warnUnknownModule(PATCH_MODULE, mn));
 406         }
 407 
 408         Counters.add("jdk.module.boot.4.resolveTime");
 409 
 410         // Step 5: Map the modules in the configuration to class loaders.
 411         // The static configuration provides the mapping of standard and JDK
 412         // modules to the boot and platform loaders. All other modules (JDK
 413         // tool modules, and both explicit and automatic modules on the
 414         // application module path) are defined to the application class
 415         // loader.
 416 
 417         // mapping of modules to class loaders
 418         Function<String, ClassLoader> clf;
 419         if (archivedModuleGraph != null) {
 420             clf = archivedModuleGraph.classLoaderFunction();
 421         } else {
 422             clf = ModuleLoaderMap.mappingFunction(cf);
 423         }
 424 
 425         // check that all modules to be mapped to the boot loader will be
 426         // loaded from the runtime image
 427         if (haveModulePath) {
 428             for (ResolvedModule resolvedModule : cf.modules()) {
 429                 ModuleReference mref = resolvedModule.reference();
 430                 String name = mref.descriptor().name();
 431                 ClassLoader cl = clf.apply(name);
 432                 if (cl == null) {
 433                     if (upgradeModulePath != null
 434                             && upgradeModulePath.find(name).isPresent())
 435                         fail(name + ": cannot be loaded from upgrade module path");
 436                     if (!systemModuleFinder.find(name).isPresent())
 437                         fail(name + ": cannot be loaded from application module path");
 438                 }
 439             }
 440         }
 441 
 442         // check for split packages in the modules mapped to the built-in loaders
 443         if (hasSplitPackages || isPatched || haveModulePath) {
 444             checkSplitPackages(cf, clf);
 445         }
 446 
 447         // load/register the modules with the built-in class loaders
 448         loadModules(cf, clf);
 449         Counters.add("jdk.module.boot.5.loadModulesTime");
 450 
 451         // Step 6: Define all modules to the VM
 452 
 453         ModuleLayer bootLayer = ModuleLayer.empty().defineModules(cf, clf);
 454         Counters.add("jdk.module.boot.6.layerCreateTime");
 455 
 456         // Step 7: Miscellaneous
 457 
 458         // check incubating status
 459         if (hasIncubatorModules || haveModulePath) {
 460             checkIncubatingStatus(cf);
 461         }
 462 
 463         // --add-reads, --add-exports/--add-opens, and --illegal-access
 464         addExtraReads(bootLayer);
 465         boolean extraExportsOrOpens = addExtraExportsAndOpens(bootLayer);
 466 
 467         Map<String, Set<String>> concealedPackagesToOpen;
 468         Map<String, Set<String>> exportedPackagesToOpen;
 469         if (archivedModuleGraph != null) {
 470             concealedPackagesToOpen = archivedModuleGraph.concealedPackagesToOpen();
 471             exportedPackagesToOpen = archivedModuleGraph.exportedPackagesToOpen();
 472         } else {
 473             concealedPackagesToOpen = systemModules.concealedPackagesToOpen();
 474             exportedPackagesToOpen = systemModules.exportedPackagesToOpen();
 475         }
 476         IllegalAccessLogger.Builder builder =
 477             addIllegalAccess(upgradeModulePath,
 478                              concealedPackagesToOpen,
 479                              exportedPackagesToOpen,
 480                              bootLayer,
 481                              extraExportsOrOpens);
 482         Counters.add("jdk.module.boot.7.adjustModulesTime");
 483 
 484         // save module finders for later use
 485         if (savedModuleFinder != null) {
 486             unlimitedFinder = new SafeModuleFinder(savedModuleFinder);
 487             if (savedModuleFinder != finder)
 488                 limitedFinder = new SafeModuleFinder(finder);
 489         }
 490 
 491         // Archive module graph and boot layer can be archived at CDS dump time.
 492         // Only allow the unnamed module case for now.
 493         if (canArchive && (mainModule == null)) {
 494             ArchivedModuleGraph.archive(hasSplitPackages,
 495                                         hasIncubatorModules,
 496                                         systemModuleFinder,
 497                                         cf,
 498                                         clf,
 499                                         concealedPackagesToOpen,
 500                                         exportedPackagesToOpen);
 501 
 502             if (!hasSplitPackages && !hasIncubatorModules) {
 503                 ArchivedBootLayer.archive(bootLayer, builder);
 504             }
 505         }
 506 
 507         return bootLayer;
 508     }
 509 
 510     /**
 511      * Load/register the modules to the built-in class loaders.
 512      */
 513     private static void loadModules(Configuration cf,
 514                                     Function<String, ClassLoader> clf) {
 515         for (ResolvedModule resolvedModule : cf.modules()) {
 516             ModuleReference mref = resolvedModule.reference();
 517             String name = resolvedModule.name();
 518             ClassLoader loader = clf.apply(name);
 519             if (loader == null) {
 520                 // skip java.base as it is already loaded
 521                 if (!name.equals(JAVA_BASE)) {
 522                     BootLoader.loadModule(mref);
 523                 }
 524             } else if (loader instanceof BuiltinClassLoader) {
 525                 ((BuiltinClassLoader) loader).loadModule(mref);
 526             }
 527         }
 528     }
 529 
 530     /**
 531      * Checks for split packages between modules defined to the built-in class
 532      * loaders.
 533      */
 534     private static void checkSplitPackages(Configuration cf,
 535                                            Function<String, ClassLoader> clf) {
 536         Map<String, String> packageToModule = new HashMap<>();
 537         for (ResolvedModule resolvedModule : cf.modules()) {
 538             ModuleDescriptor descriptor = resolvedModule.reference().descriptor();
 539             String name = descriptor.name();
 540             ClassLoader loader = clf.apply(name);
 541             if (loader == null || loader instanceof BuiltinClassLoader) {
 542                 for (String p : descriptor.packages()) {
 543                     String other = packageToModule.putIfAbsent(p, name);
 544                     if (other != null) {
 545                         String msg = "Package " + p + " in both module "
 546                                      + name + " and module " + other;
 547                         throw new LayerInstantiationException(msg);
 548                     }
 549                 }
 550             }
 551         }
 552     }
 553 
 554     /**
 555      * Returns a ModuleFinder that limits observability to the given root
 556      * modules, their transitive dependences, plus a set of other modules.
 557      */
 558     private static ModuleFinder limitFinder(ModuleFinder finder,
 559                                             Set<String> roots,
 560                                             Set<String> otherMods)
 561     {
 562         // resolve all root modules
 563         Configuration cf = Configuration.empty().resolve(finder,
 564                                                          ModuleFinder.of(),
 565                                                          roots);
 566 
 567         // module name -> reference
 568         Map<String, ModuleReference> map = new HashMap<>();
 569 
 570         // root modules and their transitive dependences
 571         cf.modules().stream()
 572             .map(ResolvedModule::reference)
 573             .forEach(mref -> map.put(mref.descriptor().name(), mref));
 574 
 575         // additional modules
 576         otherMods.stream()
 577             .map(finder::find)
 578             .flatMap(Optional::stream)
 579             .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
 580 
 581         // set of modules that are observable
 582         Set<ModuleReference> mrefs = new HashSet<>(map.values());
 583 
 584         return new ModuleFinder() {
 585             @Override
 586             public Optional<ModuleReference> find(String name) {
 587                 return Optional.ofNullable(map.get(name));
 588             }
 589             @Override
 590             public Set<ModuleReference> findAll() {
 591                 return mrefs;
 592             }
 593         };
 594     }
 595 
 596     /**
 597      * Creates a finder from the module path that is the value of the given
 598      * system property and optionally patched by --patch-module
 599      */
 600     private static ModuleFinder finderFor(String prop) {
 601         String s = System.getProperty(prop);
 602         if (s == null) {
 603             return null;
 604         } else {
 605             String[] dirs = s.split(File.pathSeparator);
 606             Path[] paths = new Path[dirs.length];
 607             int i = 0;
 608             for (String dir: dirs) {
 609                 paths[i++] = Path.of(dir);
 610             }
 611             return ModulePath.of(patcher, paths);
 612         }
 613     }
 614 
 615     /**
 616      * Initialize the module patcher for the initial configuration passed on the
 617      * value of the --patch-module options.
 618      */
 619     private static ModulePatcher initModulePatcher() {
 620         Map<String, List<String>> map = decode("jdk.module.patch.",
 621                                                File.pathSeparator,
 622                                                false);
 623         return new ModulePatcher(map);
 624     }
 625 
 626     /**
 627      * Returns the set of module names specified by --add-module options.
 628      */
 629     private static Set<String> addModules() {
 630         String prefix = "jdk.module.addmods.";
 631         int index = 0;
 632         // the system property is removed after decoding
 633         String value = getAndRemoveProperty(prefix + index);
 634         if (value == null) {
 635             return Set.of();
 636         } else {
 637             Set<String> modules = new HashSet<>();
 638             while (value != null) {
 639                 for (String s : value.split(",")) {
 640                     if (!s.isEmpty())
 641                         modules.add(s);
 642                 }
 643                 index++;
 644                 value = getAndRemoveProperty(prefix + index);
 645             }
 646             return modules;
 647         }
 648     }
 649 
 650     /**
 651      * Returns the set of module names specified by --limit-modules.
 652      */
 653     private static Set<String> limitModules() {
 654         String value = getAndRemoveProperty("jdk.module.limitmods");
 655         if (value == null) {
 656             return Set.of();
 657         } else {
 658             Set<String> names = new HashSet<>();
 659             for (String name : value.split(",")) {
 660                 if (name.length() > 0) names.add(name);
 661             }
 662             return names;
 663         }
 664     }
 665 
 666     /**
 667      * Process the --add-reads options to add any additional read edges that
 668      * are specified on the command-line.
 669      */
 670     private static void addExtraReads(ModuleLayer bootLayer) {
 671 
 672         // decode the command line options
 673         Map<String, List<String>> map = decode("jdk.module.addreads.");
 674         if (map.isEmpty())
 675             return;
 676 
 677         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 678 
 679             // the key is $MODULE
 680             String mn = e.getKey();
 681             Optional<Module> om = bootLayer.findModule(mn);
 682             if (!om.isPresent()) {
 683                 warnUnknownModule(ADD_READS, mn);
 684                 continue;
 685             }
 686             Module m = om.get();
 687 
 688             // the value is the set of other modules (by name)
 689             for (String name : e.getValue()) {
 690                 if (ALL_UNNAMED.equals(name)) {
 691                     Modules.addReadsAllUnnamed(m);
 692                 } else {
 693                     om = bootLayer.findModule(name);
 694                     if (om.isPresent()) {
 695                         Modules.addReads(m, om.get());
 696                     } else {
 697                         warnUnknownModule(ADD_READS, name);
 698                     }
 699                 }
 700             }
 701         }
 702     }
 703 
 704     /**
 705      * Process the --add-exports and --add-opens options to export/open
 706      * additional packages specified on the command-line.
 707      */
 708     private static boolean addExtraExportsAndOpens(ModuleLayer bootLayer) {
 709         boolean extraExportsOrOpens = false;
 710 
 711         // --add-exports
 712         String prefix = "jdk.module.addexports.";
 713         Map<String, List<String>> extraExports = decode(prefix);
 714         if (!extraExports.isEmpty()) {
 715             addExtraExportsOrOpens(bootLayer, extraExports, false);
 716             extraExportsOrOpens = true;
 717         }
 718 
 719 
 720         // --add-opens
 721         prefix = "jdk.module.addopens.";
 722         Map<String, List<String>> extraOpens = decode(prefix);
 723         if (!extraOpens.isEmpty()) {
 724             addExtraExportsOrOpens(bootLayer, extraOpens, true);
 725             extraExportsOrOpens = true;
 726         }
 727 
 728         return extraExportsOrOpens;
 729     }
 730 
 731     private static void addExtraExportsOrOpens(ModuleLayer bootLayer,
 732                                                Map<String, List<String>> map,
 733                                                boolean opens)
 734     {
 735         String option = opens ? ADD_OPENS : ADD_EXPORTS;
 736         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 737 
 738             // the key is $MODULE/$PACKAGE
 739             String key = e.getKey();
 740             String[] s = key.split("/");
 741             if (s.length != 2)
 742                 fail(unableToParse(option, "<module>/<package>", key));
 743 
 744             String mn = s[0];
 745             String pn = s[1];
 746             if (mn.isEmpty() || pn.isEmpty())
 747                 fail(unableToParse(option, "<module>/<package>", key));
 748 
 749             // The exporting module is in the boot layer
 750             Module m;
 751             Optional<Module> om = bootLayer.findModule(mn);
 752             if (!om.isPresent()) {
 753                 warnUnknownModule(option, mn);
 754                 continue;
 755             }
 756 
 757             m = om.get();
 758 
 759             if (!m.getDescriptor().packages().contains(pn)) {
 760                 warn("package " + pn + " not in " + mn);
 761                 continue;
 762             }
 763 
 764             // the value is the set of modules to export to (by name)
 765             for (String name : e.getValue()) {
 766                 boolean allUnnamed = false;
 767                 Module other = null;
 768                 if (ALL_UNNAMED.equals(name)) {
 769                     allUnnamed = true;
 770                 } else {
 771                     om = bootLayer.findModule(name);
 772                     if (om.isPresent()) {
 773                         other = om.get();
 774                     } else {
 775                         warnUnknownModule(option, name);
 776                         continue;
 777                     }
 778                 }
 779                 if (allUnnamed) {
 780                     if (opens) {
 781                         Modules.addOpensToAllUnnamed(m, pn);
 782                     } else {
 783                         Modules.addExportsToAllUnnamed(m, pn);
 784                     }
 785                 } else {
 786                     if (opens) {
 787                         Modules.addOpens(m, pn, other);
 788                     } else {
 789                         Modules.addExports(m, pn, other);
 790                     }
 791                 }
 792             }
 793         }
 794     }
 795 
 796     /**
 797      * Process the --illegal-access option (and its default) to open packages
 798      * of system modules in the boot layer to code in unnamed modules.
 799      */
 800     private static IllegalAccessLogger.Builder
 801         addIllegalAccess(ModuleFinder upgradeModulePath,
 802                          Map<String, Set<String>> concealedPackagesToOpen,
 803                          Map<String, Set<String>> exportedPackagesToOpen,
 804                          ModuleLayer bootLayer,
 805                          boolean extraExportsOrOpens) {
 806         String value = getAndRemoveProperty("jdk.module.illegalAccess");
 807         IllegalAccessLogger.Mode mode = IllegalAccessLogger.Mode.ONESHOT;
 808         if (value != null) {
 809             switch (value) {
 810                 case "deny":
 811                     return null;
 812                 case "permit":
 813                     break;
 814                 case "warn":
 815                     mode = IllegalAccessLogger.Mode.WARN;
 816                     break;
 817                 case "debug":
 818                     mode = IllegalAccessLogger.Mode.DEBUG;
 819                     break;
 820                 default:
 821                     fail("Value specified to --illegal-access not recognized:"
 822                             + " '" + value + "'");
 823                     return null;
 824             }
 825         }
 826         IllegalAccessLogger.Builder builder
 827             = new IllegalAccessLogger.Builder(mode, System.err);
 828 
 829         if (concealedPackagesToOpen.isEmpty() && exportedPackagesToOpen.isEmpty()) {
 830             // need to generate (exploded build)
 831             IllegalAccessMaps maps = IllegalAccessMaps.generate(limitedFinder());
 832             concealedPackagesToOpen = maps.concealedPackagesToOpen();
 833             exportedPackagesToOpen = maps.exportedPackagesToOpen();
 834         }
 835 
 836         // open specific packages in the system modules
 837         Set<String> emptySet = Set.of();
 838         for (Module m : bootLayer.modules()) {
 839             ModuleDescriptor descriptor = m.getDescriptor();
 840             String name = m.getName();
 841 
 842             // skip open modules
 843             if (descriptor.isOpen()) {
 844                 continue;
 845             }
 846 
 847             // skip modules loaded from the upgrade module path
 848             if (upgradeModulePath != null
 849                 && upgradeModulePath.find(name).isPresent()) {
 850                 continue;
 851             }
 852 
 853             Set<String> concealedPackages = concealedPackagesToOpen.getOrDefault(name, emptySet);
 854             Set<String> exportedPackages = exportedPackagesToOpen.getOrDefault(name, emptySet);
 855 
 856             // refresh the set of concealed and exported packages if needed
 857             if (extraExportsOrOpens) {
 858                 concealedPackages = new HashSet<>(concealedPackages);
 859                 exportedPackages = new HashSet<>(exportedPackages);
 860                 Iterator<String> iterator = concealedPackages.iterator();
 861                 while (iterator.hasNext()) {
 862                     String pn = iterator.next();
 863                     if (m.isExported(pn, BootLoader.getUnnamedModule())) {
 864                         // concealed package is exported to ALL-UNNAMED
 865                         iterator.remove();
 866                         exportedPackages.add(pn);
 867                     }
 868                 }
 869                 iterator = exportedPackages.iterator();
 870                 while (iterator.hasNext()) {
 871                     String pn = iterator.next();
 872                     if (m.isOpen(pn, BootLoader.getUnnamedModule())) {
 873                         // exported package is opened to ALL-UNNAMED
 874                         iterator.remove();
 875                     }
 876                 }
 877             }
 878 
 879             // log reflective access to all types in concealed packages
 880             builder.logAccessToConcealedPackages(m, concealedPackages);
 881 
 882             // log reflective access to non-public members/types in exported packages
 883             builder.logAccessToExportedPackages(m, exportedPackages);
 884 
 885             // open the packages to unnamed modules
 886             JLA.addOpensToAllUnnamed(m, concealedPackages, exportedPackages);
 887         }
 888 
 889         builder.complete();
 890         return builder;
 891     }
 892 
 893     /**
 894      * Decodes the values of --add-reads, -add-exports, --add-opens or
 895      * --patch-modules options that are encoded in system properties.
 896      *
 897      * @param prefix the system property prefix
 898      * @praam regex the regex for splitting the RHS of the option value
 899      */
 900     private static Map<String, List<String>> decode(String prefix,
 901                                                     String regex,
 902                                                     boolean allowDuplicates) {
 903         int index = 0;
 904         // the system property is removed after decoding
 905         String value = getAndRemoveProperty(prefix + index);
 906         if (value == null)
 907             return Map.of();
 908 
 909         Map<String, List<String>> map = new HashMap<>();
 910 
 911         while (value != null) {
 912 
 913             int pos = value.indexOf('=');
 914             if (pos == -1)
 915                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 916             if (pos == 0)
 917                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 918 
 919             // key is <module> or <module>/<package>
 920             String key = value.substring(0, pos);
 921 
 922             String rhs = value.substring(pos+1);
 923             if (rhs.isEmpty())
 924                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 925 
 926             // value is <module>(,<module>)* or <file>(<pathsep><file>)*
 927             if (!allowDuplicates && map.containsKey(key))
 928                 fail(key + " specified more than once to " + option(prefix));
 929             List<String> values = map.computeIfAbsent(key, k -> new ArrayList<>());
 930             int ntargets = 0;
 931             for (String s : rhs.split(regex)) {
 932                 if (!s.isEmpty()) {
 933                     values.add(s);
 934                     ntargets++;
 935                 }
 936             }
 937             if (ntargets == 0)
 938                 fail("Target must be specified: " + option(prefix) + " " + value);
 939 
 940             index++;
 941             value = getAndRemoveProperty(prefix + index);
 942         }
 943 
 944         return map;
 945     }
 946 
 947     /**
 948      * Decodes the values of --add-reads, -add-exports or --add-opens
 949      * which use the "," to separate the RHS of the option value.
 950      */
 951     private static Map<String, List<String>> decode(String prefix) {
 952         return decode(prefix, ",", true);
 953     }
 954 
 955 
 956     /**
 957      * Gets the named system property
 958      */
 959     private static String getProperty(String key) {
 960         return System.getProperty(key);
 961     }
 962 
 963     /**
 964      * Gets and remove the named system property
 965      */
 966     private static String getAndRemoveProperty(String key) {
 967         return (String) System.getProperties().remove(key);
 968     }
 969 
 970     /**
 971      * Checks incubating status of modules in the configuration
 972      */
 973     private static void checkIncubatingStatus(Configuration cf) {
 974         String incubating = null;
 975         for (ResolvedModule resolvedModule : cf.modules()) {
 976             ModuleReference mref = resolvedModule.reference();
 977 
 978             // emit warning if the WARN_INCUBATING module resolution bit set
 979             if (ModuleResolution.hasIncubatingWarning(mref)) {
 980                 String mn = mref.descriptor().name();
 981                 if (incubating == null) {
 982                     incubating = mn;
 983                 } else {
 984                     incubating += ", " + mn;
 985                 }
 986             }
 987         }
 988         if (incubating != null)
 989             warn("Using incubator modules: " + incubating);
 990     }
 991 
 992     /**
 993      * Throws a RuntimeException with the given message
 994      */
 995     static void fail(String m) {
 996         throw new RuntimeException(m);
 997     }
 998 
 999     static void warn(String m) {
1000         System.err.println("WARNING: " + m);
1001     }
1002 
1003     static void warnUnknownModule(String option, String mn) {
1004         warn("Unknown module: " + mn + " specified to " + option);
1005     }
1006 
1007     static String unableToParse(String option, String text, String value) {
1008         return "Unable to parse " +  option + " " + text + ": " + value;
1009     }
1010 
1011     private static final String ADD_MODULES  = "--add-modules";
1012     private static final String ADD_EXPORTS  = "--add-exports";
1013     private static final String ADD_OPENS    = "--add-opens";
1014     private static final String ADD_READS    = "--add-reads";
1015     private static final String PATCH_MODULE = "--patch-module";
1016 
1017 
1018     /*
1019      * Returns the command-line option name corresponds to the specified
1020      * system property prefix.
1021      */
1022     static String option(String prefix) {
1023         switch (prefix) {
1024             case "jdk.module.addexports.":
1025                 return ADD_EXPORTS;
1026             case "jdk.module.addopens.":
1027                 return ADD_OPENS;
1028             case "jdk.module.addreads.":
1029                 return ADD_READS;
1030             case "jdk.module.patch.":
1031                 return PATCH_MODULE;
1032             case "jdk.module.addmods.":
1033                 return ADD_MODULES;
1034             default:
1035                 throw new IllegalArgumentException(prefix);
1036         }
1037     }
1038 
1039     /**
1040      * Wraps a (potentially not thread safe) ModuleFinder created during startup
1041      * for use after startup.
1042      */
1043     static class SafeModuleFinder implements ModuleFinder {
1044         private final Set<ModuleReference> mrefs;
1045         private volatile Map<String, ModuleReference> nameToModule;
1046 
1047         SafeModuleFinder(ModuleFinder finder) {
1048             this.mrefs = Collections.unmodifiableSet(finder.findAll());
1049         }
1050         @Override
1051         public Optional<ModuleReference> find(String name) {
1052             Objects.requireNonNull(name);
1053             Map<String, ModuleReference> nameToModule = this.nameToModule;
1054             if (nameToModule == null) {
1055                 this.nameToModule = nameToModule = mrefs.stream()
1056                         .collect(Collectors.toMap(m -> m.descriptor().name(),
1057                                                   Function.identity()));
1058             }
1059             return Optional.ofNullable(nameToModule.get(name));
1060         }
1061         @Override
1062         public Set<ModuleReference> findAll() {
1063             return mrefs;
1064         }
1065     }
1066 
1067     /**
1068      * Counters for startup performance analysis.
1069      */
1070     static class Counters {
1071         private static final boolean PUBLISH_COUNTERS;
1072         private static final boolean PRINT_COUNTERS;
1073         private static Map<String, Long> counters;
1074         private static long startTime;
1075         private static long previousTime;
1076 
1077         static {
1078             String s = System.getProperty("jdk.module.boot.usePerfData");
1079             if (s == null) {
1080                 PUBLISH_COUNTERS = false;
1081                 PRINT_COUNTERS = false;
1082             } else {
1083                 PUBLISH_COUNTERS = true;
1084                 PRINT_COUNTERS = s.equals("debug");
1085                 counters = new LinkedHashMap<>();  // preserve insert order
1086             }
1087         }
1088 
1089         /**
1090          * Start counting time.
1091          */
1092         static void start() {
1093             if (PUBLISH_COUNTERS) {
1094                 startTime = previousTime = System.nanoTime();
1095             }
1096         }
1097 
1098         /**
1099          * Add a counter - storing the time difference between now and the
1100          * previous add or the start.
1101          */
1102         static void add(String name) {
1103             if (PUBLISH_COUNTERS) {
1104                 long current = System.nanoTime();
1105                 long elapsed = current - previousTime;
1106                 previousTime = current;
1107                 counters.put(name, elapsed);
1108             }
1109         }
1110 
1111         /**
1112          * Publish the counters to the instrumentation buffer or stdout.
1113          */
1114         static void publish(String totalTimeName) {
1115             if (PUBLISH_COUNTERS) {
1116                 long currentTime = System.nanoTime();
1117                 for (Map.Entry<String, Long> e : counters.entrySet()) {
1118                     String name = e.getKey();
1119                     long value = e.getValue();
1120                     PerfCounter.newPerfCounter(name).set(value);
1121                     if (PRINT_COUNTERS)
1122                         System.out.println(name + " = " + value);
1123                 }
1124                 long elapsedTotal = currentTime - startTime;
1125                 PerfCounter.newPerfCounter(totalTimeName).set(elapsedTotal);
1126                 if (PRINT_COUNTERS)
1127                     System.out.println(totalTimeName + " = " + elapsedTotal);
1128             }
1129         }
1130     }
1131 }