1 /*
   2  * Copyright (c) 2014, 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.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.lang.reflect.Layer;
  36 import java.lang.reflect.Module;
  37 import java.net.URI;
  38 import java.nio.file.Path;
  39 import java.nio.file.Paths;
  40 import java.util.ArrayList;
  41 import java.util.Collections;
  42 import java.util.HashMap;
  43 import java.util.HashSet;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.Optional;
  47 import java.util.Set;
  48 import java.util.function.Function;
  49 
  50 import jdk.internal.loader.BootLoader;
  51 import jdk.internal.loader.BuiltinClassLoader;
  52 import jdk.internal.misc.JavaLangModuleAccess;
  53 import jdk.internal.misc.SharedSecrets;
  54 import jdk.internal.perf.PerfCounter;
  55 import static jdk.internal.module.WarnIfResolvedReason.*;
  56 
  57 /**
  58  * Initializes/boots the module system.
  59  *
  60  * The {@link #boot() boot} method is called early in the startup to initialize
  61  * the module system. In summary, the boot method creates a Configuration by
  62  * resolving a set of module names specified via the launcher (or equivalent)
  63  * -m and --add-modules options. The modules are located on a module path that
  64  * is constructed from the upgrade module path, system modules, and application
  65  * module path. The Configuration is instantiated as the boot Layer with each
  66  * module in the the configuration defined to one of the built-in class loaders.
  67  */
  68 
  69 public final class ModuleBootstrap {
  70     private ModuleBootstrap() { }
  71 
  72     private static final JavaLangModuleAccess JLMA
  73             = SharedSecrets.getJavaLangModuleAccess();
  74 
  75     private static final String JAVA_BASE = "java.base";
  76 
  77     private static final String JAVA_SE = "java.se";
  78 
  79     // the token for "all default modules"
  80     private static final String ALL_DEFAULT = "ALL-DEFAULT";
  81 
  82     // the token for "all unnamed modules"
  83     private static final String ALL_UNNAMED = "ALL-UNNAMED";
  84 
  85     // the token for "all system modules"
  86     private static final String ALL_SYSTEM = "ALL-SYSTEM";
  87 
  88     // the token for "all modules on the module path"
  89     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
  90 
  91     // The ModulePatcher for the initial configuration
  92     private static final ModulePatcher patcher = initModulePatcher();
  93 
  94     // ModuleFinder for the initial configuration
  95     private static ModuleFinder initialFinder;
  96 
  97     /**
  98      * Returns the ModulePatcher for the initial configuration.
  99      */
 100     public static ModulePatcher patcher() {
 101         return patcher;
 102     }
 103 
 104     /**
 105      * Returns the ModuleFinder for the initial configuration
 106      */
 107     public static ModuleFinder finder() {
 108         assert initialFinder != null;
 109         return initialFinder;
 110     }
 111 
 112     /**
 113      * Initialize the module system, returning the boot Layer.
 114      *
 115      * @see java.lang.System#initPhase2()
 116      */
 117     public static Layer boot() {
 118 
 119         long t0 = System.nanoTime();
 120 
 121         // system modules (may be patched)
 122         ModuleFinder systemModules = ModuleFinder.ofSystem();
 123 
 124         PerfCounters.systemModulesTime.addElapsedTimeFrom(t0);
 125 
 126 
 127         long t1 = System.nanoTime();
 128 
 129         // Once we have the system modules then we define the base module to
 130         // the VM. We do this here so that java.base is defined as early as
 131         // possible and also that resources in the base module can be located
 132         // for error messages that may happen from here on.
 133         ModuleReference base = systemModules.find(JAVA_BASE).orElse(null);
 134         if (base == null)
 135             throw new InternalError(JAVA_BASE + " not found");
 136         URI baseUri = base.location().orElse(null);
 137         if (baseUri == null)
 138             throw new InternalError(JAVA_BASE + " does not have a location");
 139         BootLoader.loadModule(base);
 140         Modules.defineModule(null, base.descriptor(), baseUri);
 141 
 142         PerfCounters.defineBaseTime.addElapsedTimeFrom(t1);
 143 
 144 
 145         long t2 = System.nanoTime();
 146 
 147         // --upgrade-module-path option specified to launcher
 148         ModuleFinder upgradeModulePath
 149             = createModulePathFinder("jdk.module.upgrade.path");
 150         if (upgradeModulePath != null)
 151             systemModules = ModuleFinder.compose(upgradeModulePath, systemModules);
 152 
 153         // --module-path option specified to the launcher
 154         ModuleFinder appModulePath = createModulePathFinder("jdk.module.path");
 155 
 156         // The module finder: [--upgrade-module-path] system [--module-path]
 157         ModuleFinder finder = systemModules;
 158         if (appModulePath != null)
 159             finder = ModuleFinder.compose(finder, appModulePath);
 160 
 161         // The root modules to resolve
 162         Set<String> roots = new HashSet<>();
 163 
 164         // launcher -m option to specify the main/initial module
 165         String mainModule = System.getProperty("jdk.module.main");
 166         if (mainModule != null)
 167             roots.add(mainModule);
 168 
 169         // additional module(s) specified by --add-modules
 170         boolean addAllDefaultModules = false;
 171         boolean addAllSystemModules = false;
 172         boolean addAllApplicationModules = false;
 173         for (String mod: getExtraAddModules()) {
 174             switch (mod) {
 175                 case ALL_DEFAULT:
 176                     addAllDefaultModules = true;
 177                     break;
 178                 case ALL_SYSTEM:
 179                     addAllSystemModules = true;
 180                     break;
 181                 case ALL_MODULE_PATH:
 182                     addAllApplicationModules = true;
 183                     break;
 184                 default :
 185                     roots.add(mod);
 186             }
 187         }
 188 
 189         // --limit-modules
 190         String propValue = getAndRemoveProperty("jdk.module.limitmods");
 191         if (propValue != null) {
 192             Set<String> mods = new HashSet<>();
 193             for (String mod: propValue.split(",")) {
 194                 mods.add(mod);
 195             }
 196             finder = limitFinder(finder, mods, roots);
 197         }
 198 
 199         // If there is no initial module specified then assume that the initial
 200         // module is the unnamed module of the application class loader. This
 201         // is implemented by resolving "java.se" and all (non-java.*) modules
 202         // that export an API. If "java.se" is not observable then all java.*
 203         // modules are resolved. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT
 204         // bit set in their ModuleResolution attribute flags are excluded from
 205         // the detault set of roots.
 206         if (mainModule == null || addAllDefaultModules) {
 207             boolean hasJava = false;
 208             if (systemModules.find(JAVA_SE).isPresent()) {
 209                 // java.se is a system module
 210                 if (finder == systemModules || finder.find(JAVA_SE).isPresent()) {
 211                     // java.se is observable
 212                     hasJava = true;
 213                     roots.add(JAVA_SE);
 214                 }
 215             }
 216 
 217             for (ModuleReference mref : systemModules.findAll()) {
 218                 ModuleDescriptor descriptor = mref.descriptor();
 219                 String mn = descriptor.name();
 220                 if (hasJava && mn.startsWith("java."))
 221                     continue;
 222 
 223                 if (JLMA.doNotResolveByDefault(descriptor)) {
 224                     continue;
 225                 }
 226 
 227                 // add as root if observable and exports at least one package
 228                 if ((finder == systemModules || finder.find(mn).isPresent())) {
 229                     for (ModuleDescriptor.Exports e : descriptor.exports()) {
 230                         if (!e.isQualified()) {
 231                             roots.add(mn);
 232                             break;
 233                         }
 234                     }
 235                 }
 236             }
 237         }
 238 
 239         // If `--add-modules ALL-SYSTEM` is specified then all observable system
 240         // modules will be resolved.
 241         if (addAllSystemModules) {
 242             ModuleFinder f = finder;  // observable modules
 243             systemModules.findAll()
 244                 .stream()
 245                 .map(ModuleReference::descriptor)
 246                 .filter(md -> !JLMA.doNotResolveByDefault(md))
 247                 .map(ModuleDescriptor::name)
 248                 .filter(mn -> f.find(mn).isPresent())  // observable
 249                 .forEach(mn -> roots.add(mn));
 250         }
 251 
 252         // If `--add-modules ALL-MODULE-PATH` is specified then all observable
 253         // modules on the application module path will be resolved.
 254         if (appModulePath != null && addAllApplicationModules) {
 255             ModuleFinder f = finder;  // observable modules
 256             appModulePath.findAll()
 257                 .stream()
 258                 .map(ModuleReference::descriptor)
 259                 .map(ModuleDescriptor::name)
 260                 .filter(mn -> f.find(mn).isPresent())  // observable
 261                 .forEach(mn -> roots.add(mn));
 262         }
 263 
 264         PerfCounters.optionsAndRootsTime.addElapsedTimeFrom(t2);
 265 
 266 
 267         long t3 = System.nanoTime();
 268 
 269         // determine if post resolution checks are needed
 270         boolean needPostResolutionChecks = true;
 271         if (baseUri.getScheme().equals("jrt")   // toLowerCase not needed here
 272                 && (upgradeModulePath == null)
 273                 && (appModulePath == null)
 274                 && (patcher.isEmpty())) {
 275             needPostResolutionChecks = false;
 276         }
 277 
 278         PrintStream traceOutput = null;
 279         if (Boolean.getBoolean("jdk.launcher.traceResolver"))
 280             traceOutput = System.out;
 281 
 282         // run the resolver to create the configuration
 283         Configuration cf = SharedSecrets.getJavaLangModuleAccess()
 284                 .resolveRequiresAndUses(finder,
 285                                         roots,
 286                                         needPostResolutionChecks,
 287                                         traceOutput);
 288 
 289         // time to create configuration
 290         PerfCounters.resolveTime.addElapsedTimeFrom(t3);
 291 
 292         // issue a warning for any incubator modules in the configuration
 293         String incubating = null;
 294         for (ResolvedModule rm : cf.modules()) {
 295             ModuleDescriptor descriptor = rm.reference().descriptor();
 296             if (JLMA.warnIfResolvedReason(descriptor) == INCUBATING)
 297                 if (incubating == null)
 298                     incubating = descriptor.name();
 299                 else
 300                     incubating += ", " + descriptor.name();
 301             }
 302 
 303         if (incubating != null)
 304             warn("using incubating module(s): " + incubating);
 305 
 306         // mapping of modules to class loaders
 307         Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
 308 
 309         // check that all modules to be mapped to the boot loader will be
 310         // loaded from the runtime image
 311         if (needPostResolutionChecks) {
 312             for (ResolvedModule resolvedModule : cf.modules()) {
 313                 ModuleReference mref = resolvedModule.reference();
 314                 String name = mref.descriptor().name();
 315                 ClassLoader cl = clf.apply(name);
 316                 if (cl == null) {
 317 
 318                     if (upgradeModulePath != null
 319                             && upgradeModulePath.find(name).isPresent())
 320                         fail(name + ": cannot be loaded from upgrade module path");
 321 
 322                     if (!systemModules.find(name).isPresent())
 323                         fail(name + ": cannot be loaded from application module path");
 324                 }
 325             }
 326         }
 327 
 328 
 329         long t4 = System.nanoTime();
 330 
 331         // define modules to VM/runtime
 332         Layer bootLayer = Layer.empty().defineModules(cf, clf);
 333 
 334         PerfCounters.layerCreateTime.addElapsedTimeFrom(t4);
 335 
 336 
 337         long t5 = System.nanoTime();
 338 
 339         // define the module to its class loader, except java.base
 340         for (ResolvedModule resolvedModule : cf.modules()) {
 341             ModuleReference mref = resolvedModule.reference();
 342             String name = mref.descriptor().name();
 343             ClassLoader cl = clf.apply(name);
 344             if (cl == null) {
 345                 if (!name.equals(JAVA_BASE)) BootLoader.loadModule(mref);
 346             } else {
 347                 ((BuiltinClassLoader)cl).loadModule(mref);
 348             }
 349         }
 350 
 351         PerfCounters.loadModulesTime.addElapsedTimeFrom(t5);
 352 
 353 
 354         // --add-reads, -add-exports/-add-opens
 355         addExtraReads(bootLayer);
 356         addExtraExportsAndOpens(bootLayer);
 357 
 358         // total time to initialize
 359         PerfCounters.bootstrapTime.addElapsedTimeFrom(t0);
 360 
 361         // remember the ModuleFinder
 362         initialFinder = finder;
 363 
 364         return bootLayer;
 365     }
 366 
 367     /**
 368      * Returns a ModuleFinder that limits observability to the given root
 369      * modules, their transitive dependences, plus a set of other modules.
 370      */
 371     private static ModuleFinder limitFinder(ModuleFinder finder,
 372                                             Set<String> roots,
 373                                             Set<String> otherMods)
 374     {
 375         // resolve all root modules
 376         Configuration cf = Configuration.empty()
 377                 .resolveRequires(finder,
 378                                  ModuleFinder.of(),
 379                                  roots);
 380 
 381         // module name -> reference
 382         Map<String, ModuleReference> map = new HashMap<>();
 383 
 384         // root modules and their transitive dependences
 385         cf.modules().stream()
 386             .map(ResolvedModule::reference)
 387             .forEach(mref -> map.put(mref.descriptor().name(), mref));
 388 
 389         // additional modules
 390         otherMods.stream()
 391             .map(finder::find)
 392             .flatMap(Optional::stream)
 393             .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
 394 
 395         // set of modules that are observable
 396         Set<ModuleReference> mrefs = new HashSet<>(map.values());
 397 
 398         return new ModuleFinder() {
 399             @Override
 400             public Optional<ModuleReference> find(String name) {
 401                 return Optional.ofNullable(map.get(name));
 402             }
 403             @Override
 404             public Set<ModuleReference> findAll() {
 405                 return mrefs;
 406             }
 407         };
 408     }
 409 
 410     /**
 411      * Creates a finder from the module path that is the value of the given
 412      * system property.
 413      */
 414     private static ModuleFinder createModulePathFinder(String prop) {
 415         String s = System.getProperty(prop);
 416         if (s == null) {
 417             return null;
 418         } else {
 419             String[] dirs = s.split(File.pathSeparator);
 420             Path[] paths = new Path[dirs.length];
 421             int i = 0;
 422             for (String dir: dirs) {
 423                 paths[i++] = Paths.get(dir);
 424             }
 425             return ModuleFinder.of(paths);
 426         }
 427     }
 428 
 429 
 430     /**
 431      * Initialize the module patcher for the initial configuration passed on the
 432      * value of the --patch-module options.
 433      */
 434     private static ModulePatcher initModulePatcher() {
 435         Map<String, List<String>> map = decode("jdk.module.patch.",
 436                                                File.pathSeparator,
 437                                                false);
 438         return new ModulePatcher(map);
 439     }
 440 
 441     /**
 442      * Returns the set of module names specified via --add-modules options
 443      * on the command line
 444      */
 445     private static Set<String> getExtraAddModules() {
 446         String prefix = "jdk.module.addmods.";
 447         int index = 0;
 448 
 449         // the system property is removed after decoding
 450         String value = getAndRemoveProperty(prefix + index);
 451         if (value == null) {
 452             return Collections.emptySet();
 453         }
 454 
 455         Set<String> modules = new HashSet<>();
 456         while (value != null) {
 457             for (String s : value.split(",")) {
 458                 if (s.length() > 0) modules.add(s);
 459             }
 460             index++;
 461             value = getAndRemoveProperty(prefix + index);
 462         }
 463 
 464         return modules;
 465     }
 466 
 467     /**
 468      * Process the --add-reads options to add any additional read edges that
 469      * are specified on the command-line.
 470      */
 471     private static void addExtraReads(Layer bootLayer) {
 472 
 473         // decode the command line options
 474         Map<String, List<String>> map = decode("jdk.module.addreads.");
 475         if (map.isEmpty())
 476             return;
 477 
 478         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 479 
 480             // the key is $MODULE
 481             String mn = e.getKey();
 482             Optional<Module> om = bootLayer.findModule(mn);
 483             if (!om.isPresent()) {
 484                 warn("Unknown module: " + mn);
 485                 continue;
 486             }
 487             Module m = om.get();
 488 
 489             // the value is the set of other modules (by name)
 490             for (String name : e.getValue()) {
 491                 if (ALL_UNNAMED.equals(name)) {
 492                     Modules.addReadsAllUnnamed(m);
 493                 } else {
 494                     om = bootLayer.findModule(name);
 495                     if (om.isPresent()) {
 496                         Modules.addReads(m, om.get());
 497                     } else {
 498                         warn("Unknown module: " + name);
 499                     }
 500                 }
 501             }
 502         }
 503     }
 504 
 505     /**
 506      * Process the --add-exports and --add-opens options to export/open
 507      * additional packages specified on the command-line.
 508      */
 509     private static void addExtraExportsAndOpens(Layer bootLayer) {
 510 
 511         // --add-exports
 512         String prefix = "jdk.module.addexports.";
 513         Map<String, List<String>> extraExports = decode(prefix);
 514         if (!extraExports.isEmpty()) {
 515             addExtraExportsOrOpens(bootLayer, extraExports, false);
 516         }
 517 
 518         // --add-opens
 519         prefix = "jdk.module.addopens.";
 520         Map<String, List<String>> extraOpens = decode(prefix);
 521         if (!extraOpens.isEmpty()) {
 522             addExtraExportsOrOpens(bootLayer, extraOpens, true);
 523         }
 524     }
 525 
 526     private static void addExtraExportsOrOpens(Layer bootLayer,
 527                                                Map<String, List<String>> map,
 528                                                boolean opens)
 529     {
 530         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 531 
 532             // the key is $MODULE/$PACKAGE
 533             String key = e.getKey();
 534             String[] s = key.split("/");
 535             if (s.length != 2)
 536                 fail("Unable to parse: " + key);
 537 
 538             String mn = s[0];
 539             String pn = s[1];
 540             if (mn.isEmpty() || pn.isEmpty())
 541                 fail("Module and package name must be specified:" + key);
 542 
 543             // The exporting module is in the boot layer
 544             Module m;
 545             Optional<Module> om = bootLayer.findModule(mn);
 546             if (!om.isPresent()) {
 547                 warn("Unknown module: " + mn);
 548                 continue;
 549             }
 550 
 551             m = om.get();
 552 
 553             if (!m.getDescriptor().packages().contains(pn)) {
 554                 warn("package " + pn + " not in " + mn);
 555                 continue;
 556             }
 557 
 558             // the value is the set of modules to export to (by name)
 559             for (String name : e.getValue()) {
 560                 boolean allUnnamed = false;
 561                 Module other = null;
 562                 if (ALL_UNNAMED.equals(name)) {
 563                     allUnnamed = true;
 564                 } else {
 565                     om = bootLayer.findModule(name);
 566                     if (om.isPresent()) {
 567                         other = om.get();
 568                     } else {
 569                         warn("Unknown module: " + name);
 570                         continue;
 571                     }
 572                 }
 573                 if (allUnnamed) {
 574                     if (opens) {
 575                         Modules.addOpensToAllUnnamed(m, pn);
 576                     } else {
 577                         Modules.addExportsToAllUnnamed(m, pn);
 578                     }
 579                 } else {
 580                     if (opens) {
 581                         Modules.addOpens(m, pn, other);
 582                     } else {
 583                         Modules.addExports(m, pn, other);
 584                     }
 585                 }
 586 
 587             }
 588         }
 589     }
 590 
 591     /**
 592      * Decodes the values of --add-reads, -add-exports, --add-opens or
 593      * --patch-modules options that are encoded in system properties.
 594      *
 595      * @param prefix the system property prefix
 596      * @praam regex the regex for splitting the RHS of the option value
 597      */
 598     private static Map<String, List<String>> decode(String prefix,
 599                                                     String regex,
 600                                                     boolean allowDuplicates) {
 601         int index = 0;
 602         // the system property is removed after decoding
 603         String value = getAndRemoveProperty(prefix + index);
 604         if (value == null)
 605             return Collections.emptyMap();
 606 
 607         Map<String, List<String>> map = new HashMap<>();
 608 
 609         while (value != null) {
 610 
 611             int pos = value.indexOf('=');
 612             if (pos == -1)
 613                 fail("Unable to parse: " + value);
 614             if (pos == 0)
 615                 fail("Missing module name in: " + value);
 616 
 617             // key is <module> or <module>/<package>
 618             String key = value.substring(0, pos);
 619 
 620             String rhs = value.substring(pos+1);
 621             if (rhs.isEmpty())
 622                 fail("Unable to parse: " + value);
 623 
 624             // value is <module>(,<module>)* or <file>(<pathsep><file>)*
 625             if (!allowDuplicates && map.containsKey(key))
 626                 fail(key + " specified more than once");
 627             List<String> values = map.computeIfAbsent(key, k -> new ArrayList<>());
 628             for (String s : rhs.split(regex)) {
 629                 if (s.length() > 0) values.add(s);
 630             }
 631 
 632             index++;
 633             value = getAndRemoveProperty(prefix + index);
 634         }
 635 
 636         return map;
 637     }
 638 
 639     /**
 640      * Decodes the values of --add-reads, -add-exports or --add-opens
 641      * which use the "," to separate the RHS of the option value.
 642      */
 643     private static Map<String, List<String>> decode(String prefix) {
 644         return decode(prefix, ",", true);
 645     }
 646 
 647     /**
 648      * Gets and remove the named system property
 649      */
 650     private static String getAndRemoveProperty(String key) {
 651         return (String)System.getProperties().remove(key);
 652     }
 653 
 654     /**
 655      * Throws a RuntimeException with the given message
 656      */
 657     static void fail(String m) {
 658         throw new RuntimeException(m);
 659     }
 660 
 661     static void warn(String m) {
 662         System.err.println("WARNING: " + m);
 663     }
 664 
 665     static class PerfCounters {
 666 
 667         static PerfCounter systemModulesTime
 668             = PerfCounter.newPerfCounter("jdk.module.bootstrap.systemModulesTime");
 669         static PerfCounter defineBaseTime
 670             = PerfCounter.newPerfCounter("jdk.module.bootstrap.defineBaseTime");
 671         static PerfCounter optionsAndRootsTime
 672             = PerfCounter.newPerfCounter("jdk.module.bootstrap.optionsAndRootsTime");
 673         static PerfCounter resolveTime
 674             = PerfCounter.newPerfCounter("jdk.module.bootstrap.resolveTime");
 675         static PerfCounter layerCreateTime
 676             = PerfCounter.newPerfCounter("jdk.module.bootstrap.layerCreateTime");
 677         static PerfCounter loadModulesTime
 678             = PerfCounter.newPerfCounter("jdk.module.bootstrap.loadModulesTime");
 679         static PerfCounter bootstrapTime
 680             = PerfCounter.newPerfCounter("jdk.module.bootstrap.totalTime");
 681     }
 682 }