/*
 * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.tools.jlink.internal.plugins;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleDescriptor.Version;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import jdk.internal.module.Checks;
import jdk.internal.module.DefaultRoots;
import jdk.internal.module.IllegalAccessMaps;
import jdk.internal.module.Modules;
import jdk.internal.module.ModuleHashes;
import jdk.internal.module.ModuleInfo.Attributes;
import jdk.internal.module.ModuleInfoExtender;
import jdk.internal.module.ModuleReferenceImpl;
import jdk.internal.module.ModuleResolution;
import jdk.internal.module.ModuleTarget;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.ModuleVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import static jdk.internal.org.objectweb.asm.Opcodes.*;

import jdk.tools.jlink.internal.ModuleSorter;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;

Jlink plugin to reconstitute module descriptors and other attributes for system modules. The plugin generates implementations of SystemModules to avoid parsing module-info.class files at startup. It also generates SystemModulesMap to return the SystemModules implementation for a specific initial module. As a side effect, the plugin adds the ModulePackages class file attribute to the module-info.class files that don't have the attribute.
See Also:
/** * Jlink plugin to reconstitute module descriptors and other attributes for system * modules. The plugin generates implementations of SystemModules to avoid parsing * module-info.class files at startup. It also generates SystemModulesMap to return * the SystemModules implementation for a specific initial module. * * As a side effect, the plugin adds the ModulePackages class file attribute to the * module-info.class files that don't have the attribute. * * @see jdk.internal.module.SystemModuleFinders * @see jdk.internal.module.SystemModules */
public final class SystemModulesPlugin implements Plugin { private static final String NAME = "system-modules"; private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); private static final String SYSTEM_MODULES_MAP_CLASS = "jdk/internal/module/SystemModulesMap"; private static final String SYSTEM_MODULES_CLASS_PREFIX = "jdk/internal/module/SystemModules$"; private static final String ALL_SYSTEM_MODULES_CLASS = SYSTEM_MODULES_CLASS_PREFIX + "all"; private static final String DEFAULT_SYSTEM_MODULES_CLASS = SYSTEM_MODULES_CLASS_PREFIX + "default"; private boolean enabled; public SystemModulesPlugin() { this.enabled = true; } @Override public String getName() { return NAME; } @Override public String getDescription() { return DESCRIPTION; } @Override public Set<State> getState() { return enabled ? EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL) : EnumSet.of(State.DISABLED); } @Override public boolean hasArguments() { return true; } @Override public String getArgumentsDescription() { return PluginsResourceBundle.getArgument(NAME); } @Override public void configure(Map<String, String> config) { String arg = config.get(NAME); if (arg != null) { throw new IllegalArgumentException(NAME + ": " + arg); } } @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { if (!enabled) { throw new PluginException(NAME + " was set"); } // validate, transform (if needed), and add the module-info.class files List<ModuleInfo> moduleInfos = transformModuleInfos(in, out); // generate and add the SystemModuleMap and SystemModules classes Set<String> generated = genSystemModulesClasses(moduleInfos, out); // pass through all other resources in.entries() .filter(data -> !data.path().endsWith("/module-info.class") && !generated.contains(data.path())) .forEach(data -> out.add(data)); return out.build(); }
Validates and transforms the module-info.class files in the modules, adding the ModulePackages class file attribute if needed.
Returns:the list of ModuleInfo objects, the first element is java.base
/** * Validates and transforms the module-info.class files in the modules, adding * the ModulePackages class file attribute if needed. * * @return the list of ModuleInfo objects, the first element is java.base */
List<ModuleInfo> transformModuleInfos(ResourcePool in, ResourcePoolBuilder out) { List<ModuleInfo> moduleInfos = new ArrayList<>(); // Sort modules in the topological order so that java.base is always first. new ModuleSorter(in.moduleView()).sorted().forEach(module -> { ResourcePoolEntry data = module.findEntry("module-info.class").orElseThrow( // automatic modules not supported () -> new PluginException("module-info.class not found for " + module.name() + " module") ); assert module.name().equals(data.moduleName()); try { byte[] content = data.contentBytes(); Set<String> packages = module.packages(); ModuleInfo moduleInfo = new ModuleInfo(content, packages); // link-time validation moduleInfo.validateNames(); // check if any exported or open package is not present moduleInfo.validatePackages(); // module-info.class may be overridden to add ModulePackages if (moduleInfo.shouldRewrite()) { data = data.copyWithContent(moduleInfo.getBytes()); } moduleInfos.add(moduleInfo); // add resource pool entry out.add(data); } catch (IOException e) { throw new PluginException(e); } }); return moduleInfos; }
Generates the SystemModules classes (at least one) and the SystemModulesMap class to map initial modules to a SystemModules class.
Returns:the resource names of the resources added to the pool
/** * Generates the SystemModules classes (at least one) and the SystemModulesMap * class to map initial modules to a SystemModules class. * * @return the resource names of the resources added to the pool */
private Set<String> genSystemModulesClasses(List<ModuleInfo> moduleInfos, ResourcePoolBuilder out) { int moduleCount = moduleInfos.size(); ModuleFinder finder = finderOf(moduleInfos); assert finder.findAll().size() == moduleCount; // map of initial module name to SystemModules class name Map<String, String> map = new LinkedHashMap<>(); // the names of resources written to the pool Set<String> generated = new HashSet<>(); // generate the SystemModules implementation to reconstitute all modules Set<String> allModuleNames = moduleInfos.stream() .map(ModuleInfo::moduleName) .collect(Collectors.toSet()); String rn = genSystemModulesClass(moduleInfos, resolve(finder, allModuleNames), ALL_SYSTEM_MODULES_CLASS, out); generated.add(rn); // generate, if needed, a SystemModules class to reconstitute the modules // needed for the case that the initial module is the unnamed module. String defaultSystemModulesClassName; Configuration cf = resolve(finder, DefaultRoots.compute(finder)); if (cf.modules().size() == moduleCount) { // all modules are resolved so no need to generate a class defaultSystemModulesClassName = ALL_SYSTEM_MODULES_CLASS; } else { defaultSystemModulesClassName = DEFAULT_SYSTEM_MODULES_CLASS; rn = genSystemModulesClass(sublist(moduleInfos, cf), cf, defaultSystemModulesClassName, out); generated.add(rn); } // Generate a SystemModules class for each module with a main class int suffix = 0; for (ModuleInfo mi : moduleInfos) { if (mi.descriptor().mainClass().isPresent()) { String moduleName = mi.moduleName(); cf = resolve(finder, Set.of(moduleName)); if (cf.modules().size() == moduleCount) { // resolves all modules so no need to generate a class map.put(moduleName, ALL_SYSTEM_MODULES_CLASS); } else { String cn = SYSTEM_MODULES_CLASS_PREFIX + (suffix++); rn = genSystemModulesClass(sublist(moduleInfos, cf), cf, cn, out); map.put(moduleName, cn); generated.add(rn); } } } // generate SystemModulesMap rn = genSystemModulesMapClass(ALL_SYSTEM_MODULES_CLASS, defaultSystemModulesClassName, map, out); generated.add(rn); // return the resource names of the generated classes return generated; }
Resolves a collection of root modules, with service binding, to create a Configuration for the boot layer.
/** * Resolves a collection of root modules, with service binding, to create * a Configuration for the boot layer. */
private Configuration resolve(ModuleFinder finder, Set<String> roots) { return Modules.newBootLayerConfiguration(finder, roots, null); }
Returns the list of ModuleInfo objects that correspond to the modules in the given configuration.
/** * Returns the list of ModuleInfo objects that correspond to the modules in * the given configuration. */
private List<ModuleInfo> sublist(List<ModuleInfo> moduleInfos, Configuration cf) { Set<String> names = cf.modules() .stream() .map(ResolvedModule::name) .collect(Collectors.toSet()); return moduleInfos.stream() .filter(mi -> names.contains(mi.moduleName())) .collect(Collectors.toList()); }
Generate a SystemModules implementation class and add it as a resource.
Returns:the name of the class resource added to the pool
/** * Generate a SystemModules implementation class and add it as a resource. * * @return the name of the class resource added to the pool */
private String genSystemModulesClass(List<ModuleInfo> moduleInfos, Configuration cf, String className, ResourcePoolBuilder out) { SystemModulesClassGenerator generator = new SystemModulesClassGenerator(className, moduleInfos); byte[] bytes = generator.getClassWriter(cf).toByteArray(); String rn = "/java.base/" + className + ".class"; ResourcePoolEntry e = ResourcePoolEntry.create(rn, bytes); out.add(e); return rn; } static class ModuleInfo { private final ByteArrayInputStream bais; private final Attributes attrs; private final Set<String> packages; private final boolean addModulePackages; private ModuleDescriptor descriptor; // may be different that the original one ModuleInfo(byte[] bytes, Set<String> packages) throws IOException { this.bais = new ByteArrayInputStream(bytes); this.packages = packages; this.attrs = jdk.internal.module.ModuleInfo.read(bais, null); // If ModulePackages attribute is present, the packages from this // module descriptor returns the packages in that attribute. // If it's not present, ModuleDescriptor::packages only contains // the exported and open packages from module-info.class this.descriptor = attrs.descriptor(); if (descriptor.isAutomatic()) { throw new InternalError("linking automatic module is not supported"); } // add ModulePackages attribute if this module contains some packages // and ModulePackages is not present this.addModulePackages = packages.size() > 0 && !hasModulePackages(); } String moduleName() { return attrs.descriptor().name(); } ModuleDescriptor descriptor() { return descriptor; } Set<String> packages() { return packages; } ModuleTarget target() { return attrs.target(); } ModuleHashes recordedHashes() { return attrs.recordedHashes(); } ModuleResolution moduleResolution() { return attrs.moduleResolution(); }
Validates names in ModuleDescriptor
/** * Validates names in ModuleDescriptor */
void validateNames() { Checks.requireModuleName(descriptor.name()); for (Requires req : descriptor.requires()) { Checks.requireModuleName(req.name()); } for (Exports e : descriptor.exports()) { Checks.requirePackageName(e.source()); if (e.isQualified()) e.targets().forEach(Checks::requireModuleName); } for (Opens opens : descriptor.opens()) { Checks.requirePackageName(opens.source()); if (opens.isQualified()) opens.targets().forEach(Checks::requireModuleName); } for (Provides provides : descriptor.provides()) { Checks.requireServiceTypeName(provides.service()); provides.providers().forEach(Checks::requireServiceProviderName); } for (String service : descriptor.uses()) { Checks.requireServiceTypeName(service); } for (String pn : descriptor.packages()) { Checks.requirePackageName(pn); } for (String pn : packages) { Checks.requirePackageName(pn); } }
Validates if exported and open packages are present
/** * Validates if exported and open packages are present */
void validatePackages() { Set<String> nonExistPackages = new TreeSet<>(); descriptor.exports().stream() .map(Exports::source) .filter(pn -> !packages.contains(pn)) .forEach(nonExistPackages::add); descriptor.opens().stream() .map(Opens::source) .filter(pn -> !packages.contains(pn)) .forEach(nonExistPackages::add); if (!nonExistPackages.isEmpty()) { throw new PluginException("Packages that are exported or open in " + descriptor.name() + " are not present: " + nonExistPackages); } } boolean hasModulePackages() throws IOException { Set<String> packages = new HashSet<>(); ClassVisitor cv = new ClassVisitor(Opcodes.ASM7) { @Override public ModuleVisitor visitModule(String name, int flags, String version) { return new ModuleVisitor(Opcodes.ASM7) { @Override public void visitPackage(String pn) { packages.add(pn); } }; } }; try (InputStream in = getInputStream()) { // parse module-info.class ClassReader cr = new ClassReader(in); cr.accept(cv, 0); return packages.size() > 0; } }
Returns true if module-info.class should be rewritten to add the ModulePackages attribute.
/** * Returns true if module-info.class should be rewritten to add the * ModulePackages attribute. */
boolean shouldRewrite() { return addModulePackages; }
Returns the bytes for the (possibly updated) module-info.class.
/** * Returns the bytes for the (possibly updated) module-info.class. */
byte[] getBytes() throws IOException { try (InputStream in = getInputStream()) { if (shouldRewrite()) { ModuleInfoRewriter rewriter = new ModuleInfoRewriter(in); if (addModulePackages) { rewriter.addModulePackages(packages); } // rewritten module descriptor byte[] bytes = rewriter.getBytes(); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { this.descriptor = ModuleDescriptor.read(bais); } return bytes; } else { return in.readAllBytes(); } } } /* * Returns the input stream of the module-info.class */ InputStream getInputStream() { bais.reset(); return bais; } class ModuleInfoRewriter extends ByteArrayOutputStream { final ModuleInfoExtender extender; ModuleInfoRewriter(InputStream in) { this.extender = ModuleInfoExtender.newExtender(in); } void addModulePackages(Set<String> packages) { // Add ModulePackages attribute if (packages.size() > 0) { extender.packages(packages); } } byte[] getBytes() throws IOException { extender.write(this); return buf; } } }
Generates a SystemModules class to reconstitute the ModuleDescriptor and other attributes of system modules.
/** * Generates a SystemModules class to reconstitute the ModuleDescriptor * and other attributes of system modules. */
static class SystemModulesClassGenerator { private static final String MODULE_DESCRIPTOR_BUILDER = "jdk/internal/module/Builder"; private static final String MODULE_DESCRIPTOR_ARRAY_SIGNATURE = "[Ljava/lang/module/ModuleDescriptor;"; private static final String REQUIRES_MODIFIER_CLASSNAME = "java/lang/module/ModuleDescriptor$Requires$Modifier"; private static final String EXPORTS_MODIFIER_CLASSNAME = "java/lang/module/ModuleDescriptor$Exports$Modifier"; private static final String OPENS_MODIFIER_CLASSNAME = "java/lang/module/ModuleDescriptor$Opens$Modifier"; private static final String MODULE_TARGET_CLASSNAME = "jdk/internal/module/ModuleTarget"; private static final String MODULE_TARGET_ARRAY_SIGNATURE = "[Ljdk/internal/module/ModuleTarget;"; private static final String MODULE_HASHES_ARRAY_SIGNATURE = "[Ljdk/internal/module/ModuleHashes;"; private static final String MODULE_RESOLUTION_CLASSNAME = "jdk/internal/module/ModuleResolution"; private static final String MODULE_RESOLUTIONS_ARRAY_SIGNATURE = "[Ljdk/internal/module/ModuleResolution;"; private static final int MAX_LOCAL_VARS = 256; private final int BUILDER_VAR = 0; private final int MD_VAR = 1; // variable for ModuleDescriptor private final int MT_VAR = 1; // variable for ModuleTarget private final int MH_VAR = 1; // variable for ModuleHashes private int nextLocalVar = 2; // index to next local variable // Method visitor for generating the SystemModules::modules() method private MethodVisitor mv; // name of class to generate private final String className; // list of all ModuleDescriptorBuilders, invoked in turn when building. private final List<ModuleInfo> moduleInfos; // A builder to create one single Set instance for a given set of // names or modifiers to reduce the footprint // e.g. target modules of qualified exports private final DedupSetBuilder dedupSetBuilder = new DedupSetBuilder(this::getNextLocalVar); public SystemModulesClassGenerator(String className, List<ModuleInfo> moduleInfos) { this.className = className; this.moduleInfos = moduleInfos; moduleInfos.forEach(mi -> dedups(mi.descriptor())); } private int getNextLocalVar() { return nextLocalVar++; } /* * Adds the given ModuleDescriptor to the system module list. * It performs link-time validation and prepares mapping from various * Sets to SetBuilders to emit an optimized number of sets during build. */ private void dedups(ModuleDescriptor md) { // exports for (Exports e : md.exports()) { dedupSetBuilder.stringSet(e.targets()); dedupSetBuilder.exportsModifiers(e.modifiers()); } // opens for (Opens opens : md.opens()) { dedupSetBuilder.stringSet(opens.targets()); dedupSetBuilder.opensModifiers(opens.modifiers()); } // requires for (Requires r : md.requires()) { dedupSetBuilder.requiresModifiers(r.modifiers()); } // uses dedupSetBuilder.stringSet(md.uses()); }
Generate SystemModules class
/** * Generate SystemModules class */
public ClassWriter getClassWriter(Configuration cf) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, ACC_FINAL+ACC_SUPER, className, null, "java/lang/Object", new String[] { "jdk/internal/module/SystemModules" }); // generate <init> genConstructor(cw); // generate hasSplitPackages genHasSplitPackages(cw); // generate hasIncubatorModules genIncubatorModules(cw); // generate moduleDescriptors genModuleDescriptorsMethod(cw); // generate moduleTargets genModuleTargetsMethod(cw); // generate moduleHashes genModuleHashesMethod(cw); // generate moduleResolutions genModuleResolutionsMethod(cw); // generate moduleReads genModuleReads(cw, cf); // generate concealedPackagesToOpen and exportedPackagesToOpen genXXXPackagesToOpenMethods(cw); return cw; }
Generate byteccode for no-arg constructor
/** * Generate byteccode for no-arg constructor */
private void genConstructor(ClassWriter cw) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate bytecode for hasSplitPackages method
/** * Generate bytecode for hasSplitPackages method */
private void genHasSplitPackages(ClassWriter cw) { boolean distinct = moduleInfos.stream() .map(ModuleInfo::packages) .flatMap(Set::stream) .allMatch(new HashSet<>()::add); boolean hasSplitPackages = !distinct; mv = cw.visitMethod(ACC_PUBLIC, "hasSplitPackages", "()Z", "()Z", null); mv.visitCode(); if (hasSplitPackages) { mv.visitInsn(ICONST_1); } else { mv.visitInsn(ICONST_0); } mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate bytecode for hasIncubatorModules method
/** * Generate bytecode for hasIncubatorModules method */
private void genIncubatorModules(ClassWriter cw) { boolean hasIncubatorModules = moduleInfos.stream() .map(ModuleInfo::moduleResolution) .filter(mres -> (mres != null && mres.hasIncubatingWarning())) .findFirst() .isPresent(); mv = cw.visitMethod(ACC_PUBLIC, "hasIncubatorModules", "()Z", "()Z", null); mv.visitCode(); if (hasIncubatorModules) { mv.visitInsn(ICONST_1); } else { mv.visitInsn(ICONST_0); } mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate bytecode for moduleDescriptors method
/** * Generate bytecode for moduleDescriptors method */
private void genModuleDescriptorsMethod(ClassWriter cw) { this.mv = cw.visitMethod(ACC_PUBLIC, "moduleDescriptors", "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE, "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE, null); mv.visitCode(); pushInt(mv, moduleInfos.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor"); mv.visitVarInsn(ASTORE, MD_VAR); for (int index = 0; index < moduleInfos.size(); index++) { ModuleInfo minfo = moduleInfos.get(index); new ModuleDescriptorBuilder(minfo.descriptor(), minfo.packages(), index).build(); } mv.visitVarInsn(ALOAD, MD_VAR); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate bytecode for moduleTargets method
/** * Generate bytecode for moduleTargets method */
private void genModuleTargetsMethod(ClassWriter cw) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "moduleTargets", "()" + MODULE_TARGET_ARRAY_SIGNATURE, "()" + MODULE_TARGET_ARRAY_SIGNATURE, null); mv.visitCode(); pushInt(mv, moduleInfos.size()); mv.visitTypeInsn(ANEWARRAY, MODULE_TARGET_CLASSNAME); mv.visitVarInsn(ASTORE, MT_VAR); // if java.base has a ModuleTarget attribute then generate the array // with one element, all other elements will be null. ModuleInfo base = moduleInfos.get(0); if (!base.moduleName().equals("java.base")) throw new InternalError("java.base should be first module in list"); ModuleTarget target = base.target(); int count; if (target != null && target.targetPlatform() != null) { count = 1; } else { count = moduleInfos.size(); } for (int index = 0; index < count; index++) { ModuleInfo minfo = moduleInfos.get(index); if (minfo.target() != null) { mv.visitVarInsn(ALOAD, MT_VAR); pushInt(mv, index); // new ModuleTarget(String) mv.visitTypeInsn(NEW, MODULE_TARGET_CLASSNAME); mv.visitInsn(DUP); mv.visitLdcInsn(minfo.target().targetPlatform()); mv.visitMethodInsn(INVOKESPECIAL, MODULE_TARGET_CLASSNAME, "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(AASTORE); } } mv.visitVarInsn(ALOAD, MT_VAR); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate bytecode for moduleHashes method
/** * Generate bytecode for moduleHashes method */
private void genModuleHashesMethod(ClassWriter cw) { MethodVisitor hmv = cw.visitMethod(ACC_PUBLIC, "moduleHashes", "()" + MODULE_HASHES_ARRAY_SIGNATURE, "()" + MODULE_HASHES_ARRAY_SIGNATURE, null); hmv.visitCode(); pushInt(hmv, moduleInfos.size()); hmv.visitTypeInsn(ANEWARRAY, "jdk/internal/module/ModuleHashes"); hmv.visitVarInsn(ASTORE, MH_VAR); for (int index = 0; index < moduleInfos.size(); index++) { ModuleInfo minfo = moduleInfos.get(index); if (minfo.recordedHashes() != null) { new ModuleHashesBuilder(minfo.recordedHashes(), index, hmv).build(); } } hmv.visitVarInsn(ALOAD, MH_VAR); hmv.visitInsn(ARETURN); hmv.visitMaxs(0, 0); hmv.visitEnd(); }
Generate bytecode for moduleResolutions method
/** * Generate bytecode for moduleResolutions method */
private void genModuleResolutionsMethod(ClassWriter cw) { MethodVisitor mresmv = cw.visitMethod(ACC_PUBLIC, "moduleResolutions", "()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE, "()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE, null); mresmv.visitCode(); pushInt(mresmv, moduleInfos.size()); mresmv.visitTypeInsn(ANEWARRAY, MODULE_RESOLUTION_CLASSNAME); mresmv.visitVarInsn(ASTORE, 0); for (int index=0; index < moduleInfos.size(); index++) { ModuleInfo minfo = moduleInfos.get(index); if (minfo.moduleResolution() != null) { mresmv.visitVarInsn(ALOAD, 0); pushInt(mresmv, index); mresmv.visitTypeInsn(NEW, MODULE_RESOLUTION_CLASSNAME); mresmv.visitInsn(DUP); mresmv.visitLdcInsn(minfo.moduleResolution().value()); mresmv.visitMethodInsn(INVOKESPECIAL, MODULE_RESOLUTION_CLASSNAME, "<init>", "(I)V", false); mresmv.visitInsn(AASTORE); } } mresmv.visitVarInsn(ALOAD, 0); mresmv.visitInsn(ARETURN); mresmv.visitMaxs(0, 0); mresmv.visitEnd(); }
Generate bytecode for moduleReads method
/** * Generate bytecode for moduleReads method */
private void genModuleReads(ClassWriter cw, Configuration cf) { // module name -> names of modules that it reads Map<String, Set<String>> map = cf.modules().stream() .collect(Collectors.toMap( ResolvedModule::name, m -> m.reads().stream() .map(ResolvedModule::name) .collect(Collectors.toSet()))); generate(cw, "moduleReads", map, true); }
Generate concealedPackagesToOpen and exportedPackagesToOpen methods.
/** * Generate concealedPackagesToOpen and exportedPackagesToOpen methods. */
private void genXXXPackagesToOpenMethods(ClassWriter cw) { ModuleFinder finder = finderOf(moduleInfos); IllegalAccessMaps maps = IllegalAccessMaps.generate(finder); generate(cw, "concealedPackagesToOpen", maps.concealedPackagesToOpen(), false); generate(cw, "exportedPackagesToOpen", maps.exportedPackagesToOpen(), false); }
Generate method to return Map<String, Set<String>>. If dedup is true then the values are de-duplicated.
/** * Generate method to return {@code Map<String, Set<String>>}. * * If {@code dedup} is true then the values are de-duplicated. */
private void generate(ClassWriter cw, String methodName, Map<String, Set<String>> map, boolean dedup) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "()Ljava/util/Map;", "()Ljava/util/Map;", null); mv.visitCode(); // map of Set -> local Map<Set<String>, Integer> locals; // generate code to create the sets that are duplicated if (dedup) { Collection<Set<String>> values = map.values(); Set<Set<String>> duplicateSets = values.stream() .distinct() .filter(s -> Collections.frequency(values, s) > 1) .collect(Collectors.toSet()); locals = new HashMap<>(); int index = 1; for (Set<String> s : duplicateSets) { genImmutableSet(mv, s); mv.visitVarInsn(ASTORE, index); locals.put(s, index); if (++index >= MAX_LOCAL_VARS) { break; } } } else { locals = Map.of(); } // new Map$Entry[size] pushInt(mv, map.size()); mv.visitTypeInsn(ANEWARRAY, "java/util/Map$Entry"); int index = 0; for (var e : new TreeMap<>(map).entrySet()) { String name = e.getKey(); Set<String> s = e.getValue(); mv.visitInsn(DUP); pushInt(mv, index); mv.visitLdcInsn(name); // if de-duplicated then load the local, otherwise generate code Integer varIndex = locals.get(s); if (varIndex == null) { genImmutableSet(mv, s); } else { mv.visitVarInsn(ALOAD, varIndex); } String desc = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map$Entry;"; mv.visitMethodInsn(INVOKESTATIC, "java/util/Map", "entry", desc, true); mv.visitInsn(AASTORE); index++; } // invoke Map.ofEntries(Map$Entry[]) mv.visitMethodInsn(INVOKESTATIC, "java/util/Map", "ofEntries", "([Ljava/util/Map$Entry;)Ljava/util/Map;", true); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); }
Generate code to generate an immutable set.
/** * Generate code to generate an immutable set. */
private void genImmutableSet(MethodVisitor mv, Set<String> set) { int size = set.size(); // use Set.of(Object[]) when there are more than 2 elements // use Set.of(Object) or Set.of(Object, Object) when fewer if (size > 2) { pushInt(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); int i = 0; for (String element : sorted(set)) { mv.visitInsn(DUP); pushInt(mv, i); mv.visitLdcInsn(element); mv.visitInsn(AASTORE); i++; } mv.visitMethodInsn(INVOKESTATIC, "java/util/Set", "of", "([Ljava/lang/Object;)Ljava/util/Set;", true); } else { StringBuilder sb = new StringBuilder("("); for (String element : sorted(set)) { mv.visitLdcInsn(element); sb.append("Ljava/lang/Object;"); } sb.append(")Ljava/util/Set;"); mv.visitMethodInsn(INVOKESTATIC, "java/util/Set", "of", sb.toString(), true); } } class ModuleDescriptorBuilder { static final String BUILDER_TYPE = "Ljdk/internal/module/Builder;"; static final String EXPORTS_TYPE = "Ljava/lang/module/ModuleDescriptor$Exports;"; static final String OPENS_TYPE = "Ljava/lang/module/ModuleDescriptor$Opens;"; static final String PROVIDES_TYPE = "Ljava/lang/module/ModuleDescriptor$Provides;"; static final String REQUIRES_TYPE = "Ljava/lang/module/ModuleDescriptor$Requires;"; // method signature for static Builder::newExports, newOpens, // newProvides, newRequires methods static final String EXPORTS_MODIFIER_SET_STRING_SET_SIG = "(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)" + EXPORTS_TYPE; static final String EXPORTS_MODIFIER_SET_STRING_SIG = "(Ljava/util/Set;Ljava/lang/String;)" + EXPORTS_TYPE; static final String OPENS_MODIFIER_SET_STRING_SET_SIG = "(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)" + OPENS_TYPE; static final String OPENS_MODIFIER_SET_STRING_SIG = "(Ljava/util/Set;Ljava/lang/String;)" + OPENS_TYPE; static final String PROVIDES_STRING_LIST_SIG = "(Ljava/lang/String;Ljava/util/List;)" + PROVIDES_TYPE; static final String REQUIRES_SET_STRING_SIG = "(Ljava/util/Set;Ljava/lang/String;)" + REQUIRES_TYPE; static final String REQUIRES_SET_STRING_STRING_SIG = "(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)" + REQUIRES_TYPE; // method signature for Builder instance methods that // return this Builder instance static final String EXPORTS_ARRAY_SIG = "([" + EXPORTS_TYPE + ")" + BUILDER_TYPE; static final String OPENS_ARRAY_SIG = "([" + OPENS_TYPE + ")" + BUILDER_TYPE; static final String PROVIDES_ARRAY_SIG = "([" + PROVIDES_TYPE + ")" + BUILDER_TYPE; static final String REQUIRES_ARRAY_SIG = "([" + REQUIRES_TYPE + ")" + BUILDER_TYPE; static final String SET_SIG = "(Ljava/util/Set;)" + BUILDER_TYPE; static final String STRING_SIG = "(Ljava/lang/String;)" + BUILDER_TYPE; static final String BOOLEAN_SIG = "(Z)" + BUILDER_TYPE; final ModuleDescriptor md; final Set<String> packages; final int index; ModuleDescriptorBuilder(ModuleDescriptor md, Set<String> packages, int index) { if (md.isAutomatic()) { throw new InternalError("linking automatic module is not supported"); } this.md = md; this.packages = packages; this.index = index; } void build() { // new jdk.internal.module.Builder newBuilder(); // requires requires(md.requires()); // exports exports(md.exports()); // opens opens(md.opens()); // uses uses(md.uses()); // provides provides(md.provides()); // all packages packages(packages); // version md.version().ifPresent(this::version); // main class md.mainClass().ifPresent(this::mainClass); putModuleDescriptor(); } void newBuilder() { mv.visitTypeInsn(NEW, MODULE_DESCRIPTOR_BUILDER); mv.visitInsn(DUP); mv.visitLdcInsn(md.name()); mv.visitMethodInsn(INVOKESPECIAL, MODULE_DESCRIPTOR_BUILDER, "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ASTORE, BUILDER_VAR); mv.visitVarInsn(ALOAD, BUILDER_VAR); if (md.isOpen()) { setModuleBit("open", true); } if (md.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC)) { setModuleBit("synthetic", true); } if (md.modifiers().contains(ModuleDescriptor.Modifier.MANDATED)) { setModuleBit("mandated", true); } } /* * Invoke Builder.<methodName>(boolean value) */ void setModuleBit(String methodName, boolean value) { mv.visitVarInsn(ALOAD, BUILDER_VAR); if (value) { mv.visitInsn(ICONST_1); } else { mv.visitInsn(ICONST_0); } mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, methodName, BOOLEAN_SIG, false); mv.visitInsn(POP); } /* * Put ModuleDescriptor into the modules array */ void putModuleDescriptor() { mv.visitVarInsn(ALOAD, MD_VAR); pushInt(mv, index); mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitLdcInsn(md.hashCode()); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "build", "(I)Ljava/lang/module/ModuleDescriptor;", false); mv.visitInsn(AASTORE); } /* * Call Builder::newRequires to create Requires instances and * then pass it to the builder by calling: * Builder.requires(Requires[]) * */ void requires(Set<Requires> requires) { mv.visitVarInsn(ALOAD, BUILDER_VAR); pushInt(mv, requires.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Requires"); int arrayIndex = 0; for (Requires require : sorted(requires)) { String compiledVersion = null; if (require.compiledVersion().isPresent()) { compiledVersion = require.compiledVersion().get().toString(); } mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex++); newRequires(require.modifiers(), require.name(), compiledVersion); mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "requires", REQUIRES_ARRAY_SIG, false); } /* * Invoke Builder.newRequires(Set<Modifier> mods, String mn, String compiledVersion) * * Set<Modifier> mods = ... * Builder.newRequires(mods, mn, compiledVersion); */ void newRequires(Set<Requires.Modifier> mods, String name, String compiledVersion) { int varIndex = dedupSetBuilder.indexOfRequiresModifiers(mods); mv.visitVarInsn(ALOAD, varIndex); mv.visitLdcInsn(name); if (compiledVersion != null) { mv.visitLdcInsn(compiledVersion); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newRequires", REQUIRES_SET_STRING_STRING_SIG, false); } else { mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newRequires", REQUIRES_SET_STRING_SIG, false); } } /* * Call Builder::newExports to create Exports instances and * then pass it to the builder by calling: * Builder.exports(Exports[]) * */ void exports(Set<Exports> exports) { mv.visitVarInsn(ALOAD, BUILDER_VAR); pushInt(mv, exports.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Exports"); int arrayIndex = 0; for (Exports export : sorted(exports)) { mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex++); newExports(export.modifiers(), export.source(), export.targets()); mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "exports", EXPORTS_ARRAY_SIG, false); } /* * Invoke * Builder.newExports(Set<Exports.Modifier> ms, String pn, * Set<String> targets) * or * Builder.newExports(Set<Exports.Modifier> ms, String pn) * * Set<String> targets = new HashSet<>(); * targets.add(t); * : * : * * Set<Modifier> mods = ... * Builder.newExports(mods, pn, targets); */ void newExports(Set<Exports.Modifier> ms, String pn, Set<String> targets) { int modifiersSetIndex = dedupSetBuilder.indexOfExportsModifiers(ms); if (!targets.isEmpty()) { int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets); mv.visitVarInsn(ALOAD, modifiersSetIndex); mv.visitLdcInsn(pn); mv.visitVarInsn(ALOAD, stringSetIndex); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newExports", EXPORTS_MODIFIER_SET_STRING_SET_SIG, false); } else { mv.visitVarInsn(ALOAD, modifiersSetIndex); mv.visitLdcInsn(pn); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newExports", EXPORTS_MODIFIER_SET_STRING_SIG, false); } }
Call Builder::newOpens to create Opens instances and then pass it to the builder by calling: Builder.opens(Opens[])
/** * Call Builder::newOpens to create Opens instances and * then pass it to the builder by calling: * Builder.opens(Opens[]) */
void opens(Set<Opens> opens) { mv.visitVarInsn(ALOAD, BUILDER_VAR); pushInt(mv, opens.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Opens"); int arrayIndex = 0; for (Opens open : sorted(opens)) { mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex++); newOpens(open.modifiers(), open.source(), open.targets()); mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "opens", OPENS_ARRAY_SIG, false); } /* * Invoke * Builder.newOpens(Set<Opens.Modifier> ms, String pn, * Set<String> targets) * or * Builder.newOpens(Set<Opens.Modifier> ms, String pn) * * Set<String> targets = new HashSet<>(); * targets.add(t); * : * : * * Set<Modifier> mods = ... * Builder.newOpens(mods, pn, targets); */ void newOpens(Set<Opens.Modifier> ms, String pn, Set<String> targets) { int modifiersSetIndex = dedupSetBuilder.indexOfOpensModifiers(ms); if (!targets.isEmpty()) { int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets); mv.visitVarInsn(ALOAD, modifiersSetIndex); mv.visitLdcInsn(pn); mv.visitVarInsn(ALOAD, stringSetIndex); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newOpens", OPENS_MODIFIER_SET_STRING_SET_SIG, false); } else { mv.visitVarInsn(ALOAD, modifiersSetIndex); mv.visitLdcInsn(pn); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newOpens", OPENS_MODIFIER_SET_STRING_SIG, false); } } /* * Invoke Builder.uses(Set<String> uses) */ void uses(Set<String> uses) { int varIndex = dedupSetBuilder.indexOfStringSet(uses); mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitVarInsn(ALOAD, varIndex); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "uses", SET_SIG, false); mv.visitInsn(POP); } /* * Call Builder::newProvides to create Provides instances and * then pass it to the builder by calling: * Builder.provides(Provides[] provides) * */ void provides(Collection<Provides> provides) { mv.visitVarInsn(ALOAD, BUILDER_VAR); pushInt(mv, provides.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Provides"); int arrayIndex = 0; for (Provides provide : sorted(provides)) { mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex++); newProvides(provide.service(), provide.providers()); mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "provides", PROVIDES_ARRAY_SIG, false); } /* * Invoke Builder.newProvides(String service, Set<String> providers) * * Set<String> providers = new HashSet<>(); * providers.add(impl); * : * : * Builder.newProvides(service, providers); */ void newProvides(String service, List<String> providers) { mv.visitLdcInsn(service); pushInt(mv, providers.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); int arrayIndex = 0; for (String provider : providers) { mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex++); mv.visitLdcInsn(provider); mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKESTATIC, "java/util/List", "of", "([Ljava/lang/Object;)Ljava/util/List;", true); mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER, "newProvides", PROVIDES_STRING_LIST_SIG, false); } /* * Invoke Builder.packages(String pn) */ void packages(Set<String> packages) { int varIndex = dedupSetBuilder.newStringSet(packages); mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitVarInsn(ALOAD, varIndex); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "packages", SET_SIG, false); mv.visitInsn(POP); } /* * Invoke Builder.mainClass(String cn) */ void mainClass(String cn) { mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitLdcInsn(cn); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "mainClass", STRING_SIG, false); mv.visitInsn(POP); } /* * Invoke Builder.version(Version v); */ void version(Version v) { mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitLdcInsn(v.toString()); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, "version", STRING_SIG, false); mv.visitInsn(POP); } void invokeBuilderMethod(String methodName, String value) { mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitLdcInsn(value); mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, methodName, STRING_SIG, false); mv.visitInsn(POP); } } class ModuleHashesBuilder { private static final String MODULE_HASHES_BUILDER = "jdk/internal/module/ModuleHashes$Builder"; private static final String MODULE_HASHES_BUILDER_TYPE = "L" + MODULE_HASHES_BUILDER + ";"; static final String STRING_BYTE_ARRAY_SIG = "(Ljava/lang/String;[B)" + MODULE_HASHES_BUILDER_TYPE; final ModuleHashes recordedHashes; final MethodVisitor hmv; final int index; ModuleHashesBuilder(ModuleHashes hashes, int index, MethodVisitor hmv) { this.recordedHashes = hashes; this.hmv = hmv; this.index = index; }
Build ModuleHashes
/** * Build ModuleHashes */
void build() { if (recordedHashes == null) return; // new jdk.internal.module.ModuleHashes.Builder newModuleHashesBuilder(); // Invoke ModuleHashes.Builder::hashForModule recordedHashes .names() .stream() .sorted() .forEach(mn -> hashForModule(mn, recordedHashes.hashFor(mn))); // Put ModuleHashes into the hashes array pushModuleHashes(); } /* * Create ModuleHashes.Builder instance */ void newModuleHashesBuilder() { hmv.visitTypeInsn(NEW, MODULE_HASHES_BUILDER); hmv.visitInsn(DUP); hmv.visitLdcInsn(recordedHashes.algorithm()); pushInt(hmv, ((4 * recordedHashes.names().size()) / 3) + 1); hmv.visitMethodInsn(INVOKESPECIAL, MODULE_HASHES_BUILDER, "<init>", "(Ljava/lang/String;I)V", false); hmv.visitVarInsn(ASTORE, BUILDER_VAR); hmv.visitVarInsn(ALOAD, BUILDER_VAR); } /* * Invoke ModuleHashes.Builder::build and put the returned * ModuleHashes to the hashes array */ void pushModuleHashes() { hmv.visitVarInsn(ALOAD, MH_VAR); pushInt(hmv, index); hmv.visitVarInsn(ALOAD, BUILDER_VAR); hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER, "build", "()Ljdk/internal/module/ModuleHashes;", false); hmv.visitInsn(AASTORE); } /* * Invoke ModuleHashes.Builder.hashForModule(String name, byte[] hash); */ void hashForModule(String name, byte[] hash) { hmv.visitVarInsn(ALOAD, BUILDER_VAR); hmv.visitLdcInsn(name); pushInt(hmv, hash.length); hmv.visitIntInsn(NEWARRAY, T_BYTE); for (int i = 0; i < hash.length; i++) { hmv.visitInsn(DUP); // arrayref pushInt(hmv, i); hmv.visitIntInsn(BIPUSH, hash[i]); hmv.visitInsn(BASTORE); } hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER, "hashForModule", STRING_BYTE_ARRAY_SIG, false); hmv.visitInsn(POP); } } /* * Wraps set creation, ensuring identical sets are properly deduplicated. */ class DedupSetBuilder { // map Set<String> to a specialized builder to allow them to be // deduplicated as they are requested final Map<Set<String>, SetBuilder<String>> stringSets = new HashMap<>(); // map Set<Requires.Modifier> to a specialized builder to allow them to be // deduplicated as they are requested final Map<Set<Requires.Modifier>, EnumSetBuilder<Requires.Modifier>> requiresModifiersSets = new HashMap<>(); // map Set<Exports.Modifier> to a specialized builder to allow them to be // deduplicated as they are requested final Map<Set<Exports.Modifier>, EnumSetBuilder<Exports.Modifier>> exportsModifiersSets = new HashMap<>(); // map Set<Opens.Modifier> to a specialized builder to allow them to be // deduplicated as they are requested final Map<Set<Opens.Modifier>, EnumSetBuilder<Opens.Modifier>> opensModifiersSets = new HashMap<>(); private final int stringSetVar; private final int enumSetVar; private final IntSupplier localVarSupplier; DedupSetBuilder(IntSupplier localVarSupplier) { this.stringSetVar = localVarSupplier.getAsInt(); this.enumSetVar = localVarSupplier.getAsInt(); this.localVarSupplier = localVarSupplier; } /* * Add the given set of strings to this builder. */ void stringSet(Set<String> strings) { stringSets.computeIfAbsent(strings, s -> new SetBuilder<>(s, stringSetVar, localVarSupplier) ).increment(); } /* * Add the given set of Exports.Modifiers */ void exportsModifiers(Set<Exports.Modifier> mods) { exportsModifiersSets.computeIfAbsent(mods, s -> new EnumSetBuilder<>(s, EXPORTS_MODIFIER_CLASSNAME, enumSetVar, localVarSupplier) ).increment(); } /* * Add the given set of Opens.Modifiers */ void opensModifiers(Set<Opens.Modifier> mods) { opensModifiersSets.computeIfAbsent(mods, s -> new EnumSetBuilder<>(s, OPENS_MODIFIER_CLASSNAME, enumSetVar, localVarSupplier) ).increment(); } /* * Add the given set of Requires.Modifiers */ void requiresModifiers(Set<Requires.Modifier> mods) { requiresModifiersSets.computeIfAbsent(mods, s -> new EnumSetBuilder<>(s, REQUIRES_MODIFIER_CLASSNAME, enumSetVar, localVarSupplier) ).increment(); } /* * Retrieve the index to the given set of Strings. Emit code to * generate it when SetBuilder::build is called. */ int indexOfStringSet(Set<String> names) { return stringSets.get(names).build(); } /* * Retrieve the index to the given set of Exports.Modifier. * Emit code to generate it when EnumSetBuilder::build is called. */ int indexOfExportsModifiers(Set<Exports.Modifier> mods) { return exportsModifiersSets.get(mods).build(); }
Retrieve the index to the given set of Opens.Modifier. Emit code to generate it when EnumSetBuilder::build is called.
/** * Retrieve the index to the given set of Opens.Modifier. * Emit code to generate it when EnumSetBuilder::build is called. */
int indexOfOpensModifiers(Set<Opens.Modifier> mods) { return opensModifiersSets.get(mods).build(); } /* * Retrieve the index to the given set of Requires.Modifier. * Emit code to generate it when EnumSetBuilder::build is called. */ int indexOfRequiresModifiers(Set<Requires.Modifier> mods) { return requiresModifiersSets.get(mods).build(); } /* * Build a new string set without any attempt to deduplicate it. */ int newStringSet(Set<String> names) { int index = new SetBuilder<>(names, stringSetVar, localVarSupplier).build(); assert index == stringSetVar; return index; } } /* * SetBuilder generates bytecode to create one single instance of Set * for a given set of elements and assign to a local variable slot. * When there is only one single reference to a Set<T>, * it will reuse defaultVarIndex. For a Set with multiple references, * it will use a new local variable retrieved from the nextLocalVar */ class SetBuilder<T extends Comparable<T>> { private final Set<T> elements; private final int defaultVarIndex; private final IntSupplier nextLocalVar; private int refCount; private int localVarIndex; SetBuilder(Set<T> elements, int defaultVarIndex, IntSupplier nextLocalVar) { this.elements = elements; this.defaultVarIndex = defaultVarIndex; this.nextLocalVar = nextLocalVar; } /* * Increments the number of references to this particular set. */ final void increment() { refCount++; }
Generate the appropriate instructions to load an object reference to the element onto the stack.
/** * Generate the appropriate instructions to load an object reference * to the element onto the stack. */
void visitElement(T element, MethodVisitor mv) { mv.visitLdcInsn(element); } /* * Build bytecode for the Set represented by this builder, * or get the local variable index of a previously generated set * (in the local scope). * * @return local variable index of the generated set. */ final int build() { int index = localVarIndex; if (localVarIndex == 0) { // if non-empty and more than one set reference this builder, // emit to a unique local index = refCount <= 1 ? defaultVarIndex : nextLocalVar.getAsInt(); if (index < MAX_LOCAL_VARS) { localVarIndex = index; } else { // overflow: disable optimization by using localVarIndex = 0 index = defaultVarIndex; } generateSetOf(index); } return index; } private void generateSetOf(int index) { if (elements.size() <= 10) { // call Set.of(e1, e2, ...) StringBuilder sb = new StringBuilder("("); for (T t : sorted(elements)) { sb.append("Ljava/lang/Object;"); visitElement(t, mv); } sb.append(")Ljava/util/Set;"); mv.visitMethodInsn(INVOKESTATIC, "java/util/Set", "of", sb.toString(), true); } else { // call Set.of(E... elements) pushInt(mv, elements.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); int arrayIndex = 0; for (T t : sorted(elements)) { mv.visitInsn(DUP); // arrayref pushInt(mv, arrayIndex); visitElement(t, mv); // value mv.visitInsn(AASTORE); arrayIndex++; } mv.visitMethodInsn(INVOKESTATIC, "java/util/Set", "of", "([Ljava/lang/Object;)Ljava/util/Set;", true); } mv.visitVarInsn(ASTORE, index); } } /* * Generates bytecode to create one single instance of EnumSet * for a given set of modifiers and assign to a local variable slot. */ class EnumSetBuilder<T extends Comparable<T>> extends SetBuilder<T> { private final String className; EnumSetBuilder(Set<T> modifiers, String className, int defaultVarIndex, IntSupplier nextLocalVar) { super(modifiers, defaultVarIndex, nextLocalVar); this.className = className; }
Loads an Enum field.
/** * Loads an Enum field. */
@Override void visitElement(T t, MethodVisitor mv) { mv.visitFieldInsn(GETSTATIC, className, t.toString(), "L" + className + ";"); } } }
Generate SystemModulesMap and add it as a resource.
Returns:the name of the class resource added to the pool
/** * Generate SystemModulesMap and add it as a resource. * * @return the name of the class resource added to the pool */
private String genSystemModulesMapClass(String allSystemModulesClassName, String defaultSystemModulesClassName, Map<String, String> map, ResourcePoolBuilder out) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, ACC_FINAL+ACC_SUPER, SYSTEM_MODULES_MAP_CLASS, null, "java/lang/Object", null); // <init> MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // allSystemModules() mv = cw.visitMethod(ACC_STATIC, "allSystemModules", "()Ljdk/internal/module/SystemModules;", "()Ljdk/internal/module/SystemModules;", null); mv.visitCode(); mv.visitTypeInsn(NEW, allSystemModulesClassName); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, allSystemModulesClassName, "<init>", "()V", false); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // defaultSystemModules() mv = cw.visitMethod(ACC_STATIC, "defaultSystemModules", "()Ljdk/internal/module/SystemModules;", "()Ljdk/internal/module/SystemModules;", null); mv.visitCode(); mv.visitTypeInsn(NEW, defaultSystemModulesClassName); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, defaultSystemModulesClassName, "<init>", "()V", false); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // moduleNames() mv = cw.visitMethod(ACC_STATIC, "moduleNames", "()[Ljava/lang/String;", "()[Ljava/lang/String;", null); mv.visitCode(); pushInt(mv, map.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); int index = 0; for (String moduleName : sorted(map.keySet())) { mv.visitInsn(DUP); // arrayref pushInt(mv, index); mv.visitLdcInsn(moduleName); mv.visitInsn(AASTORE); index++; } mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // classNames() mv = cw.visitMethod(ACC_STATIC, "classNames", "()[Ljava/lang/String;", "()[Ljava/lang/String;", null); mv.visitCode(); pushInt(mv, map.size()); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); index = 0; for (String className : sorted(map.values())) { mv.visitInsn(DUP); // arrayref pushInt(mv, index); mv.visitLdcInsn(className.replace('/', '.')); mv.visitInsn(AASTORE); index++; } mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // write the class file to the pool as a resource String rn = "/java.base/" + SYSTEM_MODULES_MAP_CLASS + ".class"; ResourcePoolEntry e = ResourcePoolEntry.create(rn, cw.toByteArray()); out.add(e); return rn; }
Returns a sorted copy of a collection. This is useful to ensure a deterministic iteration order.
Returns:a sorted copy of the given collection.
/** * Returns a sorted copy of a collection. * * This is useful to ensure a deterministic iteration order. * * @return a sorted copy of the given collection. */
private static <T extends Comparable<T>> List<T> sorted(Collection<T> c) { var l = new ArrayList<T>(c); Collections.sort(l); return l; }
Pushes an int constant
/** * Pushes an int constant */
private static void pushInt(MethodVisitor mv, int value) { if (value <= 5) { mv.visitInsn(ICONST_0 + value); } else if (value < Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, value); } else if (value < Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, value); } else { throw new IllegalArgumentException("exceed limit: " + value); } }
Returns a module finder that finds all modules in the given list
/** * Returns a module finder that finds all modules in the given list */
private static ModuleFinder finderOf(Collection<ModuleInfo> moduleInfos) { Supplier<ModuleReader> readerSupplier = () -> null; Map<String, ModuleReference> namesToReference = new HashMap<>(); for (ModuleInfo mi : moduleInfos) { String name = mi.moduleName(); ModuleReference mref = new ModuleReferenceImpl(mi.descriptor(), URI.create("jrt:/" + name), readerSupplier, null, mi.target(), null, null, mi.moduleResolution()); namesToReference.put(name, mref); } return new ModuleFinder() { @Override public Optional<ModuleReference> find(String name) { Objects.requireNonNull(name); return Optional.ofNullable(namesToReference.get(name)); } @Override public Set<ModuleReference> findAll() { return new HashSet<>(namesToReference.values()); } }; } }