1 /*
   2  * Copyright (c) 2015, 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.  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.ByteArrayInputStream;
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.lang.module.ModuleDescriptor.*;
  32 import java.lang.module.ModuleDescriptor;
  33 import java.util.ArrayList;
  34 import java.util.Collections;
  35 import java.util.EnumSet;
  36 import java.util.HashMap;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Set;
  40 import java.util.stream.Collectors;
  41 
  42 import jdk.internal.module.Checks;
  43 import jdk.internal.module.ModuleInfoExtender;
  44 import jdk.internal.module.SystemModules;
  45 import jdk.internal.org.objectweb.asm.ClassWriter;
  46 import jdk.internal.org.objectweb.asm.MethodVisitor;
  47 import jdk.internal.org.objectweb.asm.Opcodes;
  48 
  49 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  50 import jdk.tools.jlink.plugin.PluginException;
  51 import jdk.tools.jlink.plugin.Pool;
  52 import jdk.tools.jlink.plugin.TransformerPlugin;
  53 
  54 /**
  55  * Jlink plugin to reconstitute module descriptors for installed modules.
  56  * It will extend module-info.class with ConcealedPackages attribute,
  57  * if not present. It also determines the number of packages of
  58  * the boot layer at link time.
  59  *
  60  * This plugin will override jdk.internal.module.SystemModules class
  61  *
  62  * @see java.lang.module.SystemModuleFinder
  63  * @see SystemModules
  64  */
  65 public final class SystemModuleDescriptorPlugin implements TransformerPlugin {
  66     // TODO: packager has the dependency on the plugin name
  67     // Keep it as "--installed-modules" until packager removes such
  68     // dependency (should not need to specify this plugin since it
  69     // is enabled by default)
  70     private static final String NAME = "installed-modules";
  71     private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
  72     private boolean enabled;
  73 
  74     public SystemModuleDescriptorPlugin() {
  75         this.enabled = true;
  76     }
  77 
  78     @Override
  79     public Set<PluginType> getType() {
  80         return Collections.singleton(CATEGORY.TRANSFORMER);
  81     }
  82 
  83     @Override
  84     public String getName() {
  85         return NAME;
  86     }
  87 
  88     @Override
  89     public String getDescription() {
  90         return DESCRIPTION;
  91     }
  92 
  93     @Override
  94     public Set<STATE> getState() {
  95         return enabled ? EnumSet.of(STATE.AUTO_ENABLED, STATE.FUNCTIONAL)
  96                        : EnumSet.of(STATE.DISABLED);
  97     }
  98 
  99     @Override
 100     public void configure(Map<String, String> config) {
 101         if (config.containsKey(NAME)) {
 102             enabled = false;
 103         }
 104     }
 105 
 106 
 107     @Override
 108     public void visit(Pool in, Pool out) {
 109         if (!enabled) {
 110             throw new PluginException(NAME + " was set");
 111         }
 112 
 113         Builder builder = new Builder();
 114 
 115         // generate the byte code to create ModuleDescriptors
 116         // skip parsing module-info.class and skip name check
 117         for (Pool.Module module : in.getModules()) {
 118             Pool.ModuleData data = module.get("module-info.class");
 119             if (data == null) {
 120                 // automatic module not supported yet
 121                 throw new PluginException("module-info.class not found for " + module.getName() + " module");
 122             }
 123             assert module.getName().equals(data.getModule());
 124             try {
 125                 ByteArrayInputStream bain = new ByteArrayInputStream(data.getBytes());
 126                 ModuleDescriptor md = ModuleDescriptor.read(bain);
 127                 validateNames(md);
 128 
 129                 Builder.ModuleDescriptorBuilder mbuilder = builder.module(md, module.getAllPackages());
 130                 if (md.conceals().isEmpty() &&
 131                         (md.exports().size() + md.conceals().size()) != module.getAllPackages().size()) {
 132                     // add ConcealedPackages attribute if not exist
 133                     bain.reset();
 134                     ModuleInfoRewriter minfoWriter = new ModuleInfoRewriter(bain, mbuilder.conceals());
 135                     // replace with the overridden version
 136                     data = new Pool.ModuleData(data.getModule(), data.getPath(), data.getType(),
 137                                                minfoWriter.stream(), minfoWriter.size());
 138                 }
 139                 out.add(data);
 140             } catch (IOException e) {
 141                 throw new PluginException(e);
 142             }
 143 
 144         }
 145 
 146         // Generate the new class
 147         ClassWriter cwriter = builder.build();
 148         for (Pool.ModuleData data : in.getContent()) {
 149             if (data.getPath().endsWith("module-info.class"))
 150                 continue;
 151 
 152             if (builder.isOverriddenClass(data.getPath())) {
 153                 byte[] bytes = cwriter.toByteArray();
 154                 Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(), data.getPath(), data.getType(),
 155                                                             new ByteArrayInputStream(bytes), bytes.length);
 156                 out.add(ndata);
 157             } else {
 158                 out.add(data);
 159             }
 160         }
 161     }
 162 
 163     /*
 164      * Add ConcealedPackages attribute
 165      */
 166     class ModuleInfoRewriter extends ByteArrayOutputStream {
 167         final ModuleInfoExtender extender;
 168         ModuleInfoRewriter(InputStream in, Set<String> conceals) throws IOException {
 169             this.extender = ModuleInfoExtender.newExtender(in);
 170             // Add ConcealedPackages attribute
 171             this.extender.conceals(conceals);
 172             this.extender.write(this);
 173         }
 174 
 175         InputStream stream() {
 176             return new ByteArrayInputStream(buf);
 177         }
 178     }
 179 
 180     void validateNames(ModuleDescriptor md) {
 181         Checks.requireModuleName(md.name());
 182         for (Requires req : md.requires()) {
 183             Checks.requireModuleName(req.name());
 184         }
 185         for (Exports e : md.exports()) {
 186             Checks.requirePackageName(e.source());
 187             if (e.isQualified())
 188                e.targets().forEach(Checks::requireModuleName);
 189         }
 190         for (Map.Entry<String, Provides> e : md.provides().entrySet()) {
 191             String service = e.getKey();
 192             Provides provides = e.getValue();
 193             Checks.requireServiceTypeName(service);
 194             Checks.requireServiceTypeName(provides.service());
 195             provides.providers().forEach(Checks::requireServiceProviderName);
 196         }
 197         for (String service : md.uses()) {
 198             Checks.requireServiceTypeName(service);
 199         }
 200         for (String pn : md.conceals()) {
 201             Checks.requirePackageName(pn);
 202         }
 203     }
 204 
 205     /*
 206      * Returns the initial capacity for a new Set or Map of the given size
 207      * to avoid resizing.
 208      */
 209     static final int initialCapacity(int size) {
 210         if (size == 0) {
 211             return 0;
 212         } else {
 213             // Adjust to try and get size/capacity as close to the
 214             // HashSet/HashMap default load factor without going over.
 215             return (int)(Math.ceil((double)size / 0.75));
 216         }
 217     }
 218 
 219     /**
 220      * Builder of a new jdk.internal.module.SystemModules class
 221      * to reconstitute ModuleDescriptor of the installed modules.
 222      */
 223     static class Builder {
 224         private static final String CLASSNAME =
 225             "jdk/internal/module/SystemModules";
 226         private static final String MODULE_DESCRIPTOR_BUILDER =
 227             "jdk/internal/module/Builder";
 228         private static final String MODULE_DESCRIPTOR_ARRAY_SIGNATURE =
 229             "[Ljava/lang/module/ModuleDescriptor;";
 230 
 231         // static variables in SystemModules class
 232         private static final String MODULE_NAMES = "MODULE_NAMES";
 233         private static final String PACKAGE_COUNT = "PACKAGES_IN_BOOT_LAYER";
 234 
 235         private static final int BUILDER_VAR    = 0;
 236         private static final int MD_VAR         = 1;   // variable for ModuleDescriptor
 237         private static final int MODS_VAR       = 2;   // variable for Set<Modifier>
 238         private static final int STRING_SET_VAR = 3;   // variable for Set<String>
 239         private static final int MAX_LOCAL_VARS = 256;
 240 
 241         private final ClassWriter cw;
 242         private MethodVisitor mv;
 243         private int nextLocalVar = 4;
 244         private int nextModulesIndex = 0;
 245 
 246         // list of all ModuleDescriptorBuilders, invoked in turn when building.
 247         private final List<ModuleDescriptorBuilder> builders = new ArrayList<>();
 248 
 249         // map Set<String> to a specialized builder to allow them to be
 250         // deduplicated as they are requested
 251         private final Map<Set<String>, StringSetBuilder> stringSets = new HashMap<>();
 252 
 253         public Builder() {
 254             this.cw = new ClassWriter(ClassWriter.COMPUTE_MAXS+ClassWriter.COMPUTE_FRAMES);
 255         }
 256 
 257         /*
 258          * static initializer initializing the static fields
 259          *
 260          * static Map<String, ModuleDescriptor> map = new HashMap<>();
 261          */
 262         private void clinit(int numModules, int numPackages) {
 263             cw.visit(Opcodes.V1_8, ACC_PUBLIC+ACC_FINAL+ACC_SUPER, CLASSNAME,
 264                     null, "java/lang/Object", null);
 265 
 266             // public static String[] MODULE_NAMES = new String[] {....};
 267             cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, MODULE_NAMES,
 268                     "[Ljava/lang/String;", null, null)
 269                     .visitEnd();
 270 
 271             // public static int PACKAGES_IN_BOOT_LAYER;
 272             cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, PACKAGE_COUNT,
 273                     "I", null, numPackages)
 274                     .visitEnd();
 275 
 276             this.mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V",
 277                     null, null);
 278             mv.visitCode();
 279 
 280             // create the MODULE_NAMES array
 281             pushInt(numModules);
 282             mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
 283 
 284             int index = 0;
 285             for (ModuleDescriptorBuilder builder : builders) {
 286                 mv.visitInsn(DUP);       // arrayref
 287                 pushInt(index++);
 288                 mv.visitLdcInsn(builder.md.name());      // value
 289                 mv.visitInsn(AASTORE);
 290             }
 291 
 292             mv.visitFieldInsn(PUTSTATIC, CLASSNAME, MODULE_NAMES,
 293                     "[Ljava/lang/String;");
 294 
 295             mv.visitInsn(RETURN);
 296             mv.visitMaxs(0, 0);
 297             mv.visitEnd();
 298 
 299         }
 300 
 301         /*
 302          * Adds the given ModuleDescriptor to the installed module list, and
 303          * prepares mapping from Set<String> to StringSetBuilders to emit an
 304          * optimized number of string sets during build.
 305          */
 306         public ModuleDescriptorBuilder module(ModuleDescriptor md, Set<String> packages) {
 307             ModuleDescriptorBuilder builder = new ModuleDescriptorBuilder(md, packages);
 308             builders.add(builder);
 309 
 310             // exports
 311             for (ModuleDescriptor.Exports e : md.exports()) {
 312                 if (e.isQualified()) {
 313                     stringSets.computeIfAbsent(e.targets(), s -> new StringSetBuilder(s))
 314                               .increment();
 315                 }
 316             }
 317 
 318             // provides
 319             for (ModuleDescriptor.Provides p : md.provides().values()) {
 320                 stringSets.computeIfAbsent(p.providers(), s -> new StringSetBuilder(s))
 321                           .increment();
 322             }
 323 
 324             // uses
 325             stringSets.computeIfAbsent(md.uses(), s -> new StringSetBuilder(s))
 326                       .increment();
 327             return builder;
 328         }
 329 
 330         /*
 331          * Generate bytecode for SystemModules
 332          */
 333         public ClassWriter build() {
 334             int numModules = builders.size();
 335             int numPackages = 0;
 336             for (ModuleDescriptorBuilder builder : builders) {
 337                 numPackages += builder.md.packages().size();
 338             }
 339 
 340             this.clinit(numModules, numPackages);
 341             this.mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC,
 342                                      "modules", "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE,
 343                                      "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE, null);
 344             mv.visitCode();
 345             pushInt(numModules);
 346             mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor");
 347             mv.visitVarInsn(ASTORE, MD_VAR);
 348 
 349             for (ModuleDescriptorBuilder builder : builders) {
 350                 builder.build();
 351             }
 352             mv.visitVarInsn(ALOAD, MD_VAR);
 353             mv.visitInsn(ARETURN);
 354             mv.visitMaxs(0, 0);
 355             mv.visitEnd();
 356             return cw;
 357         }
 358 
 359         public boolean isOverriddenClass(String path) {
 360             return path.equals("/java.base/" + CLASSNAME + ".class");
 361         }
 362 
 363         void pushInt(int num) {
 364             if (num <= 5) {
 365                 mv.visitInsn(ICONST_0 + num);
 366             } else if (num < Byte.MAX_VALUE) {
 367                 mv.visitIntInsn(BIPUSH, num);
 368             } else if (num < Short.MAX_VALUE) {
 369                 mv.visitIntInsn(SIPUSH, num);
 370             } else {
 371                 throw new IllegalArgumentException("exceed limit: " + num);
 372             }
 373         }
 374 
 375         class ModuleDescriptorBuilder {
 376             static final String REQUIRES_MODIFIER_CLASSNAME =
 377                     "java/lang/module/ModuleDescriptor$Requires$Modifier";
 378             static final String REQUIRES_MODIFIER_TYPE =
 379                 "Ljava/lang/module/ModuleDescriptor$Requires$Modifier;";
 380             static final String BUILDER_TYPE = "Ljdk/internal/module/Builder;";
 381             static final String REQUIRES_MODIFIER_STRING_SIG =
 382                 "(" + REQUIRES_MODIFIER_TYPE + "Ljava/lang/String;)" + BUILDER_TYPE;
 383             static final String STRING_SET_SIG =
 384                 "(Ljava/lang/String;Ljava/util/Set;)" + BUILDER_TYPE;
 385             static final String SET_STRING_SIG =
 386                 "(Ljava/util/Set;Ljava/lang/String;)" + BUILDER_TYPE;
 387             static final String SET_SIG =
 388                 "(Ljava/util/Set;)" + BUILDER_TYPE;
 389             static final String STRING_SIG = "(Ljava/lang/String;)" + BUILDER_TYPE;
 390             static final String STRING_STRING_SIG =
 391                 "(Ljava/lang/String;Ljava/lang/String;)" + BUILDER_TYPE;
 392 
 393             final ModuleDescriptor md;
 394             final Set<String> packages;
 395 
 396             ModuleDescriptorBuilder(ModuleDescriptor md, Set<String> packages) {
 397                 this.md = md;
 398                 this.packages = packages;
 399             }
 400 
 401             void newBuilder(String name, int reqs, int exports, int provides,
 402                             int conceals, int packages) {
 403                 mv.visitTypeInsn(NEW, MODULE_DESCRIPTOR_BUILDER);
 404                 mv.visitInsn(DUP);
 405                 mv.visitLdcInsn(name);
 406                 pushInt(initialCapacity(reqs));
 407                 pushInt(initialCapacity(exports));
 408                 pushInt(initialCapacity(provides));
 409                 pushInt(initialCapacity(conceals));
 410                 pushInt(initialCapacity(packages));
 411                 mv.visitMethodInsn(INVOKESPECIAL, MODULE_DESCRIPTOR_BUILDER,
 412                                    "<init>", "(Ljava/lang/String;IIIII)V", false);
 413                 mv.visitVarInsn(ASTORE, BUILDER_VAR);
 414                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 415             }
 416 
 417             /*
 418              * Returns the set of concealed packages from ModuleDescriptor, if present
 419              * or compute it if the module oes not have ConcealedPackages attribute
 420              */
 421             Set<String> conceals() {
 422                 Set<String> conceals = md.conceals();
 423                 if (md.conceals().isEmpty() &&
 424                         (md.exports().size() + md.conceals().size()) != packages.size()) {
 425                     Set<String> exports = md.exports().stream()
 426                                             .map(Exports::source)
 427                                             .collect(Collectors.toSet());
 428                     conceals = packages.stream()
 429                                        .filter(pn -> !exports.contains(pn))
 430                                        .collect(Collectors.toSet());
 431                 }
 432 
 433                 if (conceals.size() + md.exports().size() != packages.size() &&
 434                     // jdk.localedata may have concealed packages that don't exist
 435                     !md.name().equals("jdk.localedata")) {
 436                     throw new AssertionError(md.name() + ": conceals=" + conceals.size() +
 437                             ", exports=" + md.exports().size() + ", packages=" + packages.size());
 438                 }
 439                 return conceals;
 440             }
 441 
 442             void build() {
 443                 newBuilder(md.name(), md.requires().size(),
 444                            md.exports().size(),
 445                            md.provides().size(),
 446                            conceals().size(),
 447                            conceals().size() + md.exports().size());
 448 
 449                 // requires
 450                 for (ModuleDescriptor.Requires req : md.requires()) {
 451                     switch (req.modifiers().size()) {
 452                         case 0:
 453                             requires(req.name());
 454                             break;
 455                         case 1:
 456                             ModuleDescriptor.Requires.Modifier mod =
 457                                 req.modifiers().iterator().next();
 458                             requires(mod, req.name());
 459                             break;
 460                         default:
 461                             requires(req.modifiers(), req.name());
 462                     }
 463                 }
 464 
 465                 // exports
 466                 for (ModuleDescriptor.Exports e : md.exports()) {
 467                     if (e.isQualified()) {
 468                         exports(e.source(), e.targets());
 469                     } else {
 470                         exports(e.source());
 471                     }
 472                 }
 473 
 474                 // uses
 475                 uses(md.uses());
 476 
 477                 // provides
 478                 for (ModuleDescriptor.Provides p : md.provides().values()) {
 479                     provides(p.service(), p.providers());
 480                 }
 481 
 482                 // concealed packages
 483                 for (String pn : conceals()) {
 484                     conceals(pn);
 485                 }
 486 
 487                 if (md.version().isPresent()) {
 488                     version(md.version().get());
 489                 }
 490 
 491                 if (md.mainClass().isPresent()) {
 492                     mainClass(md.mainClass().get());
 493                 }
 494 
 495                 putModuleDescriptor();
 496             }
 497 
 498             /*
 499              * Put ModuleDescriptor into the modules array
 500              */
 501             void putModuleDescriptor() {
 502                 mv.visitVarInsn(ALOAD, MD_VAR);
 503                 pushInt(nextModulesIndex++);
 504                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 505                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 506                     "build", "()Ljava/lang/module/ModuleDescriptor;", false);
 507                 mv.visitInsn(AASTORE);
 508             }
 509 
 510             /*
 511              * Invoke Builder.requires(String mn)
 512              */
 513             void requires(String name) {
 514                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 515                 mv.visitLdcInsn(name);
 516                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 517                                    "requires", STRING_SIG, false);
 518                 mv.visitInsn(POP);
 519             }
 520 
 521             /*
 522              * Invoke Builder.requires(Modifier mod, String mn)
 523              */
 524             void requires(ModuleDescriptor.Requires.Modifier mod, String name) {
 525                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 526                 mv.visitFieldInsn(GETSTATIC, REQUIRES_MODIFIER_CLASSNAME, mod.name(),
 527                                   REQUIRES_MODIFIER_TYPE);
 528                 mv.visitLdcInsn(name);
 529                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 530                                    "requires", REQUIRES_MODIFIER_STRING_SIG, false);
 531                 mv.visitInsn(POP);
 532             }
 533 
 534             /*
 535              * Invoke Builder.requires(Set<Modifier> mods, String mn)
 536              *
 537              * EnumSet<Modifier> mods = EnumSet.of(mod,....);
 538              * Buidler.requires(mods, mn);
 539              */
 540             void requires(Set<ModuleDescriptor.Requires.Modifier> mods, String name) {
 541                 mv.visitVarInsn(ALOAD, MODS_VAR);
 542                 String signature = "(";
 543                 for (ModuleDescriptor.Requires.Modifier m : mods) {
 544                     mv.visitFieldInsn(GETSTATIC, REQUIRES_MODIFIER_CLASSNAME, m.name(),
 545                                       REQUIRES_MODIFIER_TYPE);
 546                     signature += "Ljava/util/Enum;";
 547                 }
 548                 signature += ")Ljava/util/EnumSet;";
 549                 mv.visitMethodInsn(INVOKESTATIC, "java/util/EnumSet", "of",
 550                                    signature, false);
 551                 mv.visitVarInsn(ASTORE, MODS_VAR);
 552                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 553                 mv.visitVarInsn(ALOAD, MODS_VAR);
 554                 mv.visitLdcInsn(name);
 555                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 556                                    "requires", SET_STRING_SIG, false);
 557                 mv.visitInsn(POP);
 558             }
 559 
 560             /*
 561              * Invoke Builder.exports(String pn)
 562              */
 563             void exports(String pn) {
 564                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 565 
 566                 mv.visitLdcInsn(pn);
 567                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 568                         "exports", STRING_SIG, false);
 569                 mv.visitInsn(POP);
 570             }
 571 
 572             /*
 573              * Invoke Builder.exports(String pn, Set<String> targets)
 574              *
 575              * Set<String> targets = new HashSet<>();
 576              * targets.add(t);
 577              * :
 578              * :
 579              * Builder.exports(pn, targets);
 580              */
 581             void exports(String pn, Set<String> targets) {
 582                 int varIndex = stringSets.get(targets).build();
 583                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 584                 mv.visitLdcInsn(pn);
 585                 mv.visitVarInsn(ALOAD, varIndex);
 586                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 587                                    "exports", STRING_SET_SIG, false);
 588                 mv.visitInsn(POP);
 589             }
 590 
 591             /*
 592              * Invokes Builder.uses(Set<String> uses)
 593              */
 594             void uses(Set<String> uses) {
 595                 int varIndex = stringSets.get(uses).build();
 596                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 597                 mv.visitVarInsn(ALOAD, varIndex);
 598                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 599                         "uses", SET_SIG, false);
 600                 mv.visitInsn(POP);
 601             }
 602 
 603             /*
 604              * Invoke Builder.provides(String service, Set<String> providers)
 605              *
 606              * Set<String> providers = new HashSet<>();
 607              * providers.add(impl);
 608              * :
 609              * :
 610              * Builder.exports(service, providers);
 611              */
 612             void provides(String service, Set<String> providers) {
 613                 int varIndex = stringSets.get(providers).build();
 614                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 615                 mv.visitLdcInsn(service);
 616                 mv.visitVarInsn(ALOAD, varIndex);
 617                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 618                                    "provides", STRING_SET_SIG, false);
 619                 mv.visitInsn(POP);
 620             }
 621 
 622             /*
 623              * Invoke Builder.conceals(String pn)
 624              */
 625             void conceals(String pn) {
 626                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 627                 mv.visitLdcInsn(pn);
 628                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 629                                    "conceals", STRING_SIG, false);
 630                 mv.visitInsn(POP);
 631             }
 632 
 633             /*
 634              * Invoke Builder.mainClass(String cn)
 635              */
 636             void mainClass(String cn) {
 637                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 638                 mv.visitLdcInsn(cn);
 639                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 640                                    "mainClass", STRING_SIG, false);
 641                 mv.visitInsn(POP);
 642             }
 643 
 644             /*
 645              * Invoke Builder.version(Version v);
 646              */
 647             void version(ModuleDescriptor.Version v) {
 648                 mv.visitVarInsn(ALOAD, BUILDER_VAR);
 649                 mv.visitLdcInsn(v.toString());
 650                 mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
 651                                    "version", STRING_SIG, false);
 652                 mv.visitInsn(POP);
 653             }
 654 
 655         }
 656 
 657         /*
 658          * StringSetBuilder generates bytecode to create one single instance
 659          * of HashSet for a given set of names and assign to a local variable
 660          * slot.  When there is only one single reference to a Set<String>,
 661          * it will reuse STRING_SET_VAR for reference.  For Set<String> with
 662          * multiple references, it will use a new local variable.
 663          */
 664         class StringSetBuilder {
 665             final Set<String> names;
 666             int refCount;
 667             int localVarIndex;
 668             StringSetBuilder(Set<String> names) {
 669                 this.names = names;
 670             }
 671 
 672             void increment() {
 673                 refCount++;
 674             }
 675 
 676             /*
 677              * Build bytecode for the Set<String> represented by this builder,
 678              * or get the local variable index of a previously generated set
 679              * (in the local scope).
 680              *
 681              * @return local variable index of the generated set.
 682              */
 683             int build() {
 684                 int index = localVarIndex;
 685                 if (localVarIndex == 0) {
 686                     // if non-empty and more than one set reference this builder,
 687                     // emit to a unique local
 688                     index = refCount == 1 ? STRING_SET_VAR
 689                                           : nextLocalVar++;
 690                     if (index < MAX_LOCAL_VARS) {
 691                         localVarIndex = index;
 692                     } else {
 693                         // overflow: disable optimization and keep localVarIndex = 0
 694                         index = STRING_SET_VAR;
 695                     }
 696 
 697                     if (names.isEmpty()) {
 698                         mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections",
 699                                 "emptySet", "()Ljava/util/Set;", false);
 700                         mv.visitVarInsn(ASTORE, index);
 701                     } else if (names.size() == 1) {
 702                         mv.visitLdcInsn(names.iterator().next());
 703                         mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections",
 704                                 "singleton", "(Ljava/lang/Object;)Ljava/util/Set;", false);
 705                         mv.visitVarInsn(ASTORE, index);
 706                     } else {
 707                         mv.visitTypeInsn(NEW, "java/util/HashSet");
 708                         mv.visitInsn(DUP);
 709                         pushInt(initialCapacity(names.size()));
 710                         mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashSet",
 711                                 "<init>", "(I)V", false);
 712 
 713                         mv.visitVarInsn(ASTORE, index);
 714                         for (String t : names) {
 715                             mv.visitVarInsn(ALOAD, index);
 716                             mv.visitLdcInsn(t);
 717                             mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Set",
 718                                     "add", "(Ljava/lang/Object;)Z", true);
 719                             mv.visitInsn(POP);
 720                         }
 721                     }
 722                 }
 723                 return index;
 724             }
 725         }
 726     }
 727 }