1 /*
   2  * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 8176537
  27  * @summary Check JDK modules have no qualified export to any upgradeable module
  28  * @modules java.base/jdk.internal.module
  29  * @run main JdkQualifiedExportTest
  30  */
  31 
  32 import jdk.internal.module.ModuleHashes;
  33 import jdk.internal.module.ModuleInfo;
  34 
  35 import java.io.IOException;
  36 import java.io.InputStream;
  37 import java.io.UncheckedIOException;
  38 import java.lang.module.Configuration;
  39 import java.lang.module.ModuleDescriptor;
  40 import java.lang.module.ModuleDescriptor.Exports;
  41 import java.lang.module.ModuleDescriptor.Opens;
  42 import java.lang.module.ModuleFinder;
  43 import java.lang.module.ModuleReference;
  44 import java.util.Collections;
  45 import java.util.Comparator;
  46 import java.util.HashMap;
  47 import java.util.HashSet;
  48 import java.util.Map;
  49 import java.util.Set;
  50 
  51 public class JdkQualifiedExportTest {
  52     public static void main(String... args) {
  53         // check all system modules
  54         ModuleFinder.ofSystem().findAll()
  55                     .stream()
  56                     .map(ModuleReference::descriptor)
  57                     .sorted(Comparator.comparing(ModuleDescriptor::name))
  58                     .forEach(JdkQualifiedExportTest::check);
  59     }
  60 
  61     static void check(ModuleDescriptor md) {
  62         // skip checking if this is an upgradeable module
  63         if (!HashedModules.contains(md.name())) {
  64             return;
  65         }
  66 
  67         checkExports(md);
  68         checkOpens(md);
  69     }
  70 
  71     static Set<String> KNOWN_EXCEPTIONS =
  72         Set.of("jdk.internal.vm.ci/jdk.vm.ci.services",
  73                "jdk.internal.vm.ci/jdk.vm.ci.runtime",
  74                "jdk.internal.vm.ci/jdk.vm.ci.hotspot",
  75                "jdk.internal.vm.ci/jdk.vm.ci.meta",
  76                "jdk.internal.vm.ci/jdk.vm.ci.code",
  77                "jdk.jsobject/jdk.internal.netscape.javascript.spi");
  78 
  79     static void checkExports(ModuleDescriptor md) {
  80         // build a map of upgradeable module to Exports that are qualified to it
  81         // skip the qualified exports
  82         Map<String, Set<Exports>> targetToExports = new HashMap<>();
  83         md.exports().stream()
  84           .filter(Exports::isQualified)
  85           .forEach(e -> e.targets().stream()
  86                          .filter(mn -> accept(md, mn))
  87                          .forEach(t -> targetToExports.computeIfAbsent(t, _k -> new HashSet<>())
  88                                                       .add(e)));
  89 
  90         if (targetToExports.size() > 0) {
  91             String mn = md.name();
  92 
  93             System.err.println(mn);
  94             targetToExports.entrySet().stream()
  95                 .sorted(Map.Entry.comparingByKey())
  96                 .forEach(e -> {
  97                     e.getValue().stream()
  98                      .forEach(exp -> System.err.format("    exports %s to %s%n",
  99                                                        exp.source(), e.getKey()));
 100                 });
 101 
 102             // no qualified exports to upgradeable modules are expected
 103             // except the known exception cases
 104             if (targetToExports.entrySet().stream()
 105                     .flatMap(e -> e.getValue().stream())
 106                     .anyMatch(e -> !KNOWN_EXCEPTIONS.contains(mn + "/" + e.source()))) {
 107                 throw new RuntimeException(mn + " can't export package to upgradeable modules");
 108             }
 109         }
 110     }
 111 
 112     static void checkOpens(ModuleDescriptor md) {
 113         // build a map of upgradeable module to Exports that are qualified to it
 114         // skip the qualified exports
 115         Map<String, Set<Opens>> targetToOpens = new HashMap<>();
 116         md.opens().stream()
 117             .filter(Opens::isQualified)
 118             .forEach(e -> e.targets().stream()
 119                            .filter(mn -> accept(md, mn))
 120                            .forEach(t -> targetToOpens.computeIfAbsent(t, _k -> new HashSet<>())
 121                                                       .add(e)));
 122 
 123         if (targetToOpens.size() > 0) {
 124             String mn = md.name();
 125 
 126             System.err.println(mn);
 127             targetToOpens.entrySet().stream()
 128                 .sorted(Map.Entry.comparingByKey())
 129                 .forEach(e -> {
 130                     e.getValue().stream()
 131                      .forEach(exp -> System.err.format("    opens %s to %s%n",
 132                                                        exp.source(), e.getKey()));
 133                 });
 134 
 135             throw new RuntimeException(mn + " can't open package to upgradeable modules");
 136         }
 137     }
 138 
 139     /**
 140      * Returns true if target is an upgradeable module but not required
 141      * by the source module directly and indirectly.
 142      */
 143     private static boolean accept(ModuleDescriptor source, String target) {
 144         if (HashedModules.contains(target))
 145             return false;
 146 
 147         if (!ModuleFinder.ofSystem().find(target).isPresent())
 148             return false;
 149 
 150         Configuration cf = Configuration.empty().resolve(ModuleFinder.of(),
 151                                                          ModuleFinder.ofSystem(),
 152                                                          Set.of(source.name()));
 153         return !cf.findModule(target).isPresent();
 154     }
 155 
 156     private static class HashedModules {
 157         static Set<String> HASHED_MODULES = hashedModules();
 158 
 159         static Set<String> hashedModules() {
 160             Module javaBase = Object.class.getModule();
 161             try (InputStream in = javaBase.getResourceAsStream("module-info.class")) {
 162                 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
 163                 ModuleHashes hashes = attrs.recordedHashes();
 164                 if (hashes == null)
 165                     return Collections.emptySet();
 166 
 167                 Set<String> names = new HashSet<>(hashes.names());
 168                 names.add(javaBase.getName());
 169                 return names;
 170             } catch (IOException e) {
 171                 throw new UncheckedIOException(e);
 172             }
 173         }
 174 
 175         /*
 176          * Returns true if the named module is tied with java.base,
 177          * i.e. non-upgradeable
 178          */
 179         static boolean contains(String mn) {
 180             return HASHED_MODULES.contains(mn);
 181         }
 182     }
 183 }