< prev index next >

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java

Print this page
rev 16725 : 8175026: Capture build-time parameters to --generate-jli-classes
Reviewed-by: mchung


   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 package jdk.tools.jlink.internal.plugins;
  26 

  27 import java.io.File;
  28 import java.io.IOException;


  29 import java.lang.invoke.MethodType;

  30 import java.nio.file.Files;
  31 import java.util.ArrayList;
  32 import java.util.EnumSet;
  33 import java.util.List;
  34 import java.util.Map;

  35 import java.util.Set;
  36 import java.util.TreeMap;
  37 import java.util.TreeSet;
  38 import java.util.stream.Collectors;
  39 import java.util.stream.Stream;
  40 import jdk.internal.misc.SharedSecrets;
  41 import jdk.internal.misc.JavaLangInvokeAccess;
  42 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  43 import jdk.tools.jlink.plugin.PluginException;
  44 import jdk.tools.jlink.plugin.ResourcePool;
  45 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
  46 import jdk.tools.jlink.plugin.Plugin;
  47 
  48 /**
  49  * Plugin to generate java.lang.invoke classes.
  50  */
  51 public final class GenerateJLIClassesPlugin implements Plugin {
  52 
  53     private static final String NAME = "generate-jli-classes";

  54 
  55     private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);




  56 
  57     private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
  58     private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
  59     private static final String DMH_INVOKE_STATIC = "invokeStatic";
  60     private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
  61     private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
  62     private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
  63     private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
  64 
  65     private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
  66     private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
  67     private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
  68 
  69     private static final JavaLangInvokeAccess JLIA
  70             = SharedSecrets.getJavaLangInvokeAccess();
  71 
  72     Set<String> speciesTypes;


  73 
  74     Set<String> invokerTypes;
  75 
  76     Map<String, Set<String>> dmhMethods;


  77 
  78     public GenerateJLIClassesPlugin() {
  79     }
  80 
  81     @Override
  82     public String getName() {
  83         return NAME;
  84     }
  85 
  86     @Override
  87     public String getDescription() {
  88         return DESCRIPTION;
  89     }
  90 
  91     @Override
  92     public Set<State> getState() {
  93         return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
  94     }
  95 
  96     @Override


 140                 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L",
 141                 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L",
 142                 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L",
 143                 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L")
 144         );
 145     }
 146 
 147     // Map from DirectMethodHandle method type to internal ID
 148     private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
 149             Map.of(
 150                 DMH_INVOKE_VIRTUAL,     0,
 151                 DMH_INVOKE_STATIC,      1,
 152                 DMH_INVOKE_SPECIAL,     2,
 153                 DMH_NEW_INVOKE_SPECIAL, 3,
 154                 DMH_INVOKE_INTERFACE,   4,
 155                 DMH_INVOKE_STATIC_INIT, 5
 156             );
 157 
 158     @Override
 159     public void configure(Map<String, String> config) {
 160         String mainArgument = config.get(NAME);


 161 

 162         // Start with the default configuration
 163         Set<String> defaultBMHSpecies = defaultSpecies();
 164         // Expand BMH species signatures
 165         defaultBMHSpecies = defaultBMHSpecies.stream()
 166                 .map(type -> expandSignature(type))
 167                 .collect(Collectors.toSet());
 168 
 169         Set<String> defaultInvokerTypes = defaultInvokers();
 170         validateMethodTypes(defaultInvokerTypes);
 171 
 172         Map<String, Set<String>> defaultDmhMethods = defaultDMHMethods();
 173         for (Set<String> dmhMethodTypes : defaultDmhMethods.values()) {
 174             validateMethodTypes(dmhMethodTypes);
 175         }
 176 
 177         // Extend the default configuration with the contents in the supplied
 178         // input file
 179         if (mainArgument == null || !mainArgument.startsWith("@")) {
 180             speciesTypes = defaultBMHSpecies;
 181             invokerTypes = defaultInvokerTypes;
 182             dmhMethods = defaultDmhMethods;







 183         } else {
 184             File file = new File(mainArgument.substring(1));
 185             if (file.exists()) {


























 186                 // Use TreeSet/TreeMap to keep things sorted in a deterministic
 187                 // order to avoid scrambling the layout on small changes and to
 188                 // ease finding methods in the generated code
 189                 speciesTypes = new TreeSet<>(defaultBMHSpecies);
 190                 invokerTypes = new TreeSet<>(defaultInvokerTypes);
 191                 dmhMethods = new TreeMap<>();
 192                 for (Map.Entry<String, Set<String>> entry : defaultDmhMethods.entrySet()) {
 193                     dmhMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
 194                 }
 195                 fileLines(file)
 196                     .map(line -> line.split(" "))
 197                     .forEach(parts -> {
 198                         switch (parts[0]) {
 199                             case "[BMH_RESOLVE]":
 200                                 speciesTypes.add(expandSignature(parts[1]));
 201                                 break;
 202                             case "[LF_RESOLVE]":
 203                                 String methodType = parts[3];
 204                                 validateMethodType(methodType);
 205                                 if (parts[1].contains("Invokers")) {
 206                                     invokerTypes.add(methodType);
 207                                 } else if (parts[1].contains("DirectMethodHandle")) {
 208                                     String dmh = parts[2];
 209                                     // ignore getObject etc for now (generated
 210                                     // by default)
 211                                     if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
 212                                         addDMHMethodType(dmh, methodType);
 213                                     }
 214                                 }
 215                                 break;
 216                             default: break; // ignore
 217                         }
 218                 });
 219             }
 220         }
 221     }
 222 
 223     private void addDMHMethodType(String dmh, String methodType) {
 224         validateMethodType(methodType);
 225         Set<String> methodTypes = dmhMethods.get(dmh);
 226         if (methodTypes == null) {
 227             methodTypes = new TreeSet<>();
 228             dmhMethods.put(dmh, methodTypes);
 229         }
 230         methodTypes.add(methodType);
 231     }
 232 
 233     private Stream<String> fileLines(File file) {
 234         try {
 235             return Files.lines(file.toPath());
 236         } catch (IOException io) {
 237             throw new PluginException("Couldn't read file");
 238         }
 239     }
 240 
 241     private void validateMethodTypes(Set<String> dmhMethodTypes) {


 248         String[] typeParts = type.split("_");
 249         // check return type (second part)
 250         if (typeParts.length != 2 || typeParts[1].length() != 1
 251                 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
 252             throw new PluginException(
 253                     "Method type signature must be of form [LJIFD]*_[LJIFDV]");
 254         }
 255         // expand and check arguments (first part)
 256         expandSignature(typeParts[0]);
 257     }
 258 
 259     private static void requireBasicType(char c) {
 260         if ("LIJFD".indexOf(c) < 0) {
 261             throw new PluginException(
 262                     "Character " + c + " must correspond to a basic field type: LIJFD");
 263         }
 264     }
 265 
 266     @Override
 267     public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {



















 268         // Copy all but DMH_ENTRY to out
 269         in.transformAndCopy(entry -> {
 270                 // filter out placeholder entries
 271                 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
 272                     entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
 273                     entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
 274                     entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
 275                     return null;
 276                 } else {
 277                     return entry;
 278                 }
 279             }, out);


 280         speciesTypes.forEach(types -> generateBMHClass(types, out));


 281         generateHolderClasses(out);






 282         return out.build();
 283     }
 284 
 285     @SuppressWarnings("unchecked")
 286     private void generateBMHClass(String types, ResourcePoolBuilder out) {
 287         try {
 288             // Generate class
 289             Map.Entry<String, byte[]> result =
 290                     JLIA.generateConcreteBMHClassBytes(types);
 291             String className = result.getKey();
 292             byte[] bytes = result.getValue();
 293 
 294             // Add class to pool
 295             ResourcePoolEntry ndata = ResourcePoolEntry.create(
 296                     "/java.base/" + className + ".class",
 297                     bytes);
 298             out.add(ndata);
 299         } catch (Exception ex) {
 300             throw new PluginException(ex);
 301         }




   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 package jdk.tools.jlink.internal.plugins;
  26 
  27 import java.io.BufferedReader;
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.InputStreamReader;
  32 import java.lang.invoke.MethodType;
  33 import java.lang.module.ModuleDescriptor;
  34 import java.nio.file.Files;

  35 import java.util.EnumSet;

  36 import java.util.Map;
  37 import java.util.Optional;
  38 import java.util.Set;
  39 import java.util.TreeMap;
  40 import java.util.TreeSet;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.Stream;
  43 import jdk.internal.misc.SharedSecrets;
  44 import jdk.internal.misc.JavaLangInvokeAccess;
  45 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  46 import jdk.tools.jlink.plugin.PluginException;
  47 import jdk.tools.jlink.plugin.ResourcePool;
  48 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
  49 import jdk.tools.jlink.plugin.Plugin;
  50 
  51 /**
  52  * Plugin to generate java.lang.invoke classes.
  53  */
  54 public final class GenerateJLIClassesPlugin implements Plugin {
  55 
  56     private static final String NAME = "generate-jli-classes";
  57     private static final String IGNORE_VERSION = "ignore-version";
  58 
  59     private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
  60     private static final String IGNORE_VERSION_WARNING = NAME + ".ignore.version.warn";
  61     private static final String VERSION_MISMATCH_WARNING = NAME + ".version.mismatch.warn";
  62 
  63     private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt";
  64 
  65     private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
  66     private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
  67     private static final String DMH_INVOKE_STATIC = "invokeStatic";
  68     private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
  69     private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
  70     private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
  71     private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
  72 
  73     private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
  74     private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
  75     private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
  76 
  77     private static final JavaLangInvokeAccess JLIA
  78             = SharedSecrets.getJavaLangInvokeAccess();
  79 
  80     Set<String> speciesTypes = Set.of();
  81 
  82     Set<String> invokerTypes = Set.of();
  83 
  84     Map<String, Set<String>> dmhMethods = Map.of();
  85 
  86     String mainArgument;
  87 
  88     boolean ignoreVersion;
  89 
  90     public GenerateJLIClassesPlugin() {
  91     }
  92 
  93     @Override
  94     public String getName() {
  95         return NAME;
  96     }
  97 
  98     @Override
  99     public String getDescription() {
 100         return DESCRIPTION;
 101     }
 102 
 103     @Override
 104     public Set<State> getState() {
 105         return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
 106     }
 107 
 108     @Override


 152                 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L",
 153                 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L",
 154                 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L",
 155                 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L")
 156         );
 157     }
 158 
 159     // Map from DirectMethodHandle method type to internal ID
 160     private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
 161             Map.of(
 162                 DMH_INVOKE_VIRTUAL,     0,
 163                 DMH_INVOKE_STATIC,      1,
 164                 DMH_INVOKE_SPECIAL,     2,
 165                 DMH_NEW_INVOKE_SPECIAL, 3,
 166                 DMH_INVOKE_INTERFACE,   4,
 167                 DMH_INVOKE_STATIC_INIT, 5
 168             );
 169 
 170     @Override
 171     public void configure(Map<String, String> config) {
 172         mainArgument = config.get(NAME);
 173         ignoreVersion = Boolean.parseBoolean(config.get(IGNORE_VERSION));
 174     }
 175 
 176     public void initialize(ResourcePool in) {
 177         // Start with the default configuration
 178         speciesTypes = defaultSpecies().stream()


 179                 .map(type -> expandSignature(type))
 180                 .collect(Collectors.toSet());
 181 
 182         invokerTypes = defaultInvokers();
 183         validateMethodTypes(invokerTypes);
 184 
 185         dmhMethods = defaultDMHMethods();
 186         for (Set<String> dmhMethodTypes : dmhMethods.values()) {
 187             validateMethodTypes(dmhMethodTypes);
 188         }
 189 
 190         // Extend the default configuration with the contents in the supplied
 191         // input file - if none was supplied we look for the default file
 192         if (mainArgument == null || !mainArgument.startsWith("@")) {
 193             try (InputStream traceFile =
 194                     this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) {
 195                 if (traceFile != null) {
 196                     readTraceConfig(
 197                         new BufferedReader(
 198                             new InputStreamReader(traceFile)).lines());
 199                 }
 200             } catch (Exception e) {
 201                 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e);
 202             }
 203         } else {
 204             File file = new File(mainArgument.substring(1));
 205             if (file.exists()) {
 206                 readTraceConfig(fileLines(file));
 207             }
 208         }
 209     }
 210 
 211     private boolean checkVersion(Runtime.Version linkedVersion) {
 212         Runtime.Version baseVersion = Runtime.version();
 213         if (baseVersion.major() != linkedVersion.major() ||
 214                 baseVersion.minor() != linkedVersion.minor()) {
 215             return false;
 216         }
 217         return true;
 218     }
 219 
 220     private Runtime.Version getLinkedVersion(ResourcePool in) {
 221         ModuleDescriptor.Version version = in.moduleView()
 222                 .findModule("java.base")
 223                 .get()
 224                 .descriptor()
 225                 .version()
 226                 .orElseThrow(() -> new PluginException("No version defined in "
 227                         + "the java.base being linked"));
 228          return Runtime.Version.parse(version.toString());
 229     }
 230 
 231     private void readTraceConfig(Stream<String> lines) {
 232         // Use TreeSet/TreeMap to keep things sorted in a deterministic
 233         // order to avoid scrambling the layout on small changes and to
 234         // ease finding methods in the generated code
 235         speciesTypes = new TreeSet<>(speciesTypes);
 236         invokerTypes = new TreeSet<>(invokerTypes);
 237         TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>();
 238         for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
 239             newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
 240         }
 241         dmhMethods = newDMHMethods;
 242         lines.map(line -> line.split(" "))
 243              .forEach(parts -> {
 244                 switch (parts[0]) {
 245                     case "[BMH_RESOLVE]":
 246                         speciesTypes.add(expandSignature(parts[1]));
 247                         break;
 248                     case "[LF_RESOLVE]":
 249                         String methodType = parts[3];
 250                         validateMethodType(methodType);
 251                         if (parts[1].contains("Invokers")) {
 252                             invokerTypes.add(methodType);
 253                         } else if (parts[1].contains("DirectMethodHandle")) {
 254                             String dmh = parts[2];
 255                             // ignore getObject etc for now (generated
 256                             // by default)
 257                             if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
 258                                 addDMHMethodType(dmh, methodType);
 259                             }
 260                         }
 261                         break;
 262                     default: break; // ignore
 263                 }
 264             });
 265     }


 266 
 267     private void addDMHMethodType(String dmh, String methodType) {
 268         validateMethodType(methodType);
 269         Set<String> methodTypes = dmhMethods.get(dmh);
 270         if (methodTypes == null) {
 271             methodTypes = new TreeSet<>();
 272             dmhMethods.put(dmh, methodTypes);
 273         }
 274         methodTypes.add(methodType);
 275     }
 276 
 277     private Stream<String> fileLines(File file) {
 278         try {
 279             return Files.lines(file.toPath());
 280         } catch (IOException io) {
 281             throw new PluginException("Couldn't read file");
 282         }
 283     }
 284 
 285     private void validateMethodTypes(Set<String> dmhMethodTypes) {


 292         String[] typeParts = type.split("_");
 293         // check return type (second part)
 294         if (typeParts.length != 2 || typeParts[1].length() != 1
 295                 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
 296             throw new PluginException(
 297                     "Method type signature must be of form [LJIFD]*_[LJIFDV]");
 298         }
 299         // expand and check arguments (first part)
 300         expandSignature(typeParts[0]);
 301     }
 302 
 303     private static void requireBasicType(char c) {
 304         if ("LIJFD".indexOf(c) < 0) {
 305             throw new PluginException(
 306                     "Character " + c + " must correspond to a basic field type: LIJFD");
 307         }
 308     }
 309 
 310     @Override
 311     public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
 312         if (ignoreVersion) {
 313             System.out.println(
 314                     PluginsResourceBundle
 315                             .getMessage(IGNORE_VERSION_WARNING));
 316         } else if (!checkVersion(getLinkedVersion(in))) {
 317             // The linked images are not version compatible
 318             if (mainArgument != null) {
 319                 // Log a mismatch warning if an argument was specified
 320                 System.out.println(
 321                         PluginsResourceBundle
 322                                 .getMessage(VERSION_MISMATCH_WARNING,
 323                                             getLinkedVersion(in),
 324                                             Runtime.version()));
 325             }
 326             in.transformAndCopy(entry -> entry, out);
 327             return out.build();
 328         }
 329 
 330         initialize(in);
 331         // Copy all but DMH_ENTRY to out
 332         in.transformAndCopy(entry -> {
 333                 // filter out placeholder entries
 334                 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
 335                     entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
 336                     entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
 337                     entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
 338                     return null;
 339                 } else {
 340                     return entry;
 341                 }
 342             }, out);
 343 
 344         // Generate BMH Species classes
 345         speciesTypes.forEach(types -> generateBMHClass(types, out));
 346 
 347         // Generate LambdaForm Holder classes
 348         generateHolderClasses(out);
 349 
 350         // Let it go
 351         speciesTypes = null;
 352         invokerTypes = null;
 353         dmhMethods = null;
 354 
 355         return out.build();
 356     }
 357 
 358     @SuppressWarnings("unchecked")
 359     private void generateBMHClass(String types, ResourcePoolBuilder out) {
 360         try {
 361             // Generate class
 362             Map.Entry<String, byte[]> result =
 363                     JLIA.generateConcreteBMHClassBytes(types);
 364             String className = result.getKey();
 365             byte[] bytes = result.getValue();
 366 
 367             // Add class to pool
 368             ResourcePoolEntry ndata = ResourcePoolEntry.create(
 369                     "/java.base/" + className + ".class",
 370                     bytes);
 371             out.add(ndata);
 372         } catch (Exception ex) {
 373             throw new PluginException(ex);
 374         }


< prev index next >