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 JdkQualifierExportTest 
  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.ModuleDescriptor;
  39 import java.lang.module.ModuleDescriptor.Exports;
  40 import java.lang.module.ModuleDescriptor.Opens;
  41 import java.lang.module.ModuleFinder;
  42 import java.lang.module.ModuleReference;
  43 import java.lang.reflect.Module;
  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 JdkQualifierExportTest {
  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(JdkQualifierExportTest::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.jsobject/jdk.internal.netscape.javascript.spi");
  74     static Set<String> DEPLOY_MODULES =
  75         Set.of("jdk.deploy", "jdk.plugin", "jdk.javaws");
  76 
  77     static void checkExports(ModuleDescriptor md) {
  78         // build a map of upgradeable module to Exports that are qualified to it
  79         // skip the qualified exports
  80         Map<String, Set<Exports>> targetToExports = new HashMap<>();
  81         md.exports().stream()
  82           .filter(Exports::isQualified)
  83           .forEach(e -> e.targets().stream()
  84                          .filter(mn -> !HashedModules.contains(mn) &&
  85                                            ModuleFinder.ofSystem().find(mn).isPresent())
  86                          .forEach(t -> targetToExports.computeIfAbsent(t, _k -> new HashSet<>())
  87                                                       .add(e)));
  88 
  89         if (targetToExports.size() > 0) {
  90             String mn = md.name();
  91 
  92             System.err.println(mn);
  93             targetToExports.entrySet().stream()
  94                 .sorted(Map.Entry.comparingByKey())
  95                 .forEach(e -> {
  96                     e.getValue().stream()
  97                      .forEach(exp -> System.err.format("    exports %s to %s%n",
  98                                                        exp.source(), e.getKey()));
  99                 });
 100 
 101             // workaround until all qualified exports to upgradeable modules
 102             // are eliminated
 103             if (targetToExports.entrySet().stream()
 104                     .filter(e -> !DEPLOY_MODULES.contains(e.getKey()))
 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 -> !HashedModules.contains(mn) &&
 120                                             ModuleFinder.ofSystem().find(mn).isPresent())
 121                            .forEach(t -> targetToOpens.computeIfAbsent(t, _k -> new HashSet<>())
 122                                                       .add(e)));
 123 
 124         if (targetToOpens.size() > 0) {
 125             String mn = md.name();
 126 
 127             System.err.println(mn);
 128             targetToOpens.entrySet().stream()
 129                 .sorted(Map.Entry.comparingByKey())
 130                 .forEach(e -> {
 131                     e.getValue().stream()
 132                      .forEach(exp -> System.err.format("    opens %s to %s%n",
 133                                                        exp.source(), e.getKey()));
 134                 });
 135 
 136             throw new RuntimeException(mn + " can't open package to upgradeable modules");
 137         }
 138     }
 139 
 140     private static class HashedModules {
 141         static Set<String> HASHED_MODULES = hashedModules();
 142 
 143         static Set<String> hashedModules() {
 144             Module javaBase = Object.class.getModule();
 145             try (InputStream in = javaBase.getResourceAsStream("module-info.class")) {
 146                 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
 147                 ModuleHashes hashes = attrs.recordedHashes();
 148                 if (hashes == null)
 149                     return Collections.emptySet();
 150 
 151                 Set<String> names = new HashSet<>(hashes.names());
 152                 names.add(javaBase.getName());
 153                 return names;
 154             } catch (IOException e) {
 155                 throw new UncheckedIOException(e);
 156             }
 157         }
 158 
 159         /*
 160          * Returns true if the named module is tied with java.base,
 161          * i.e. non-upgradeable
 162          */
 163         static boolean contains(String mn) {
 164             return HASHED_MODULES.contains(mn);
 165         }
 166     }
 167 }