1 /*
   2  * Copyright (c) 2017, 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("java.xml/com.sun.xml.internal.stream.writers",
  73                "jdk.internal.vm.ci/jdk.vm.ci.services",
  74                "jdk.jsobject/jdk.internal.netscape.javascript.spi");
  75 
  76     static void checkExports(ModuleDescriptor md) {
  77         // build a map of upgradeable module to Exports that are qualified to it
  78         // skip the qualified exports
  79         Map<String, Set<Exports>> targetToExports = new HashMap<>();
  80         md.exports().stream()
  81           .filter(Exports::isQualified)
  82           .forEach(e -> e.targets().stream()
  83                          .filter(mn -> accept(md, mn))
  84                          .forEach(t -> targetToExports.computeIfAbsent(t, _k -> new HashSet<>())
  85                                                       .add(e)));
  86 
  87         if (targetToExports.size() > 0) {
  88             String mn = md.name();
  89 
  90             System.err.println(mn);
  91             targetToExports.entrySet().stream()
  92                 .sorted(Map.Entry.comparingByKey())
  93                 .forEach(e -> {
  94                     e.getValue().stream()
  95                      .forEach(exp -> System.err.format("    exports %s to %s%n",
  96                                                        exp.source(), e.getKey()));
  97                 });
  98 
  99             // no qualified exports to upgradeable modules are expected
 100             // except the known exception cases
 101             if (targetToExports.entrySet().stream()
 102                     .flatMap(e -> e.getValue().stream())
 103                     .anyMatch(e -> !KNOWN_EXCEPTIONS.contains(mn + "/" + e.source()))) {
 104                 throw new RuntimeException(mn + " can't export package to upgradeable modules");
 105             }
 106         }
 107     }
 108 
 109     static void checkOpens(ModuleDescriptor md) {
 110         // build a map of upgradeable module to Exports that are qualified to it
 111         // skip the qualified exports
 112         Map<String, Set<Opens>> targetToOpens = new HashMap<>();
 113         md.opens().stream()
 114             .filter(Opens::isQualified)
 115             .forEach(e -> e.targets().stream()
 116                            .filter(mn -> accept(md, mn))
 117                            .forEach(t -> targetToOpens.computeIfAbsent(t, _k -> new HashSet<>())
 118                                                       .add(e)));
 119 
 120         if (targetToOpens.size() > 0) {
 121             String mn = md.name();
 122 
 123             System.err.println(mn);
 124             targetToOpens.entrySet().stream()
 125                 .sorted(Map.Entry.comparingByKey())
 126                 .forEach(e -> {
 127                     e.getValue().stream()
 128                      .forEach(exp -> System.err.format("    opens %s to %s%n",
 129                                                        exp.source(), e.getKey()));
 130                 });
 131 
 132             throw new RuntimeException(mn + " can't open package to upgradeable modules");
 133         }
 134     }
 135 
 136     /**
 137      * Returns true if target is an upgradeable module but not required
 138      * by the source module directly and indirectly.
 139      */
 140     private static boolean accept(ModuleDescriptor source, String target) {
 141         if (HashedModules.contains(target))
 142             return false;
 143 
 144         if (!ModuleFinder.ofSystem().find(target).isPresent())
 145             return false;
 146 
 147         Configuration cf = Configuration.empty().resolve(ModuleFinder.of(),
 148                                                          ModuleFinder.ofSystem(),
 149                                                          Set.of(source.name()));
 150         return !cf.findModule(target).isPresent();
 151     }
 152 
 153     private static class HashedModules {
 154         static Set<String> HASHED_MODULES = hashedModules();
 155 
 156         static Set<String> hashedModules() {
 157             Module javaBase = Object.class.getModule();
 158             try (InputStream in = javaBase.getResourceAsStream("module-info.class")) {
 159                 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
 160                 ModuleHashes hashes = attrs.recordedHashes();
 161                 if (hashes == null)
 162                     return Collections.emptySet();
 163 
 164                 Set<String> names = new HashSet<>(hashes.names());
 165                 names.add(javaBase.getName());
 166                 return names;
 167             } catch (IOException e) {
 168                 throw new UncheckedIOException(e);
 169             }
 170         }
 171 
 172         /*
 173          * Returns true if the named module is tied with java.base,
 174          * i.e. non-upgradeable
 175          */
 176         static boolean contains(String mn) {
 177             return HASHED_MODULES.contains(mn);
 178         }
 179     }
 180 }