| /* |
| * Copyright (c) 2014, 2017, 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.internal.module; |
| |
| import java.io.DataInput; |
| import java.io.DataInputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UncheckedIOException; |
| import java.lang.module.InvalidModuleDescriptorException; |
| import java.lang.module.ModuleDescriptor; |
| import java.lang.module.ModuleDescriptor.Builder; |
| import java.lang.module.ModuleDescriptor.Requires; |
| import java.lang.module.ModuleDescriptor.Exports; |
| import java.lang.module.ModuleDescriptor.Opens; |
| import java.nio.ByteBuffer; |
| import java.nio.BufferUnderflowException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Supplier; |
| |
| import jdk.internal.misc.JavaLangModuleAccess; |
| import jdk.internal.misc.SharedSecrets; |
| |
| import static jdk.internal.module.ClassFileConstants.*; |
| |
| |
| /** |
| * Read module information from a {@code module-info} class file. |
| * |
| * @implNote The rationale for the hand-coded reader is startup performance |
| * and fine control over the throwing of InvalidModuleDescriptorException. |
| */ |
| |
| public final class ModuleInfo { |
| |
| private final int JAVA_MIN_SUPPORTED_VERSION = 53; |
| private final int JAVA_MAX_SUPPORTED_VERSION = 55; |
| |
| private static final JavaLangModuleAccess JLMA |
| = SharedSecrets.getJavaLangModuleAccess(); |
| |
| // supplies the set of packages when ModulePackages attribute not present |
| private final Supplier<Set<String>> packageFinder; |
| |
| // indicates if the ModuleHashes attribute should be parsed |
| private final boolean parseHashes; |
| |
| private ModuleInfo(Supplier<Set<String>> pf, boolean ph) { |
| packageFinder = pf; |
| parseHashes = ph; |
| } |
| |
| private ModuleInfo(Supplier<Set<String>> pf) { |
| this(pf, true); |
| } |
| |
| /** |
| * A holder class for the ModuleDescriptor that is created by reading the |
| * Module and other standard class file attributes. It also holds the objects |
| * that represent the non-standard class file attributes that are read from |
| * the class file. |
| */ |
| public static final class Attributes { |
| private final ModuleDescriptor descriptor; |
| private final ModuleTarget target; |
| private final ModuleHashes recordedHashes; |
| private final ModuleResolution moduleResolution; |
| Attributes(ModuleDescriptor descriptor, |
| ModuleTarget target, |
| ModuleHashes recordedHashes, |
| ModuleResolution moduleResolution) { |
| this.descriptor = descriptor; |
| this.target = target; |
| this.recordedHashes = recordedHashes; |
| this.moduleResolution = moduleResolution; |
| } |
| public ModuleDescriptor descriptor() { |
| return descriptor; |
| } |
| public ModuleTarget target() { |
| return target; |
| } |
| public ModuleHashes recordedHashes() { |
| return recordedHashes; |
| } |
| public ModuleResolution moduleResolution() { |
| return moduleResolution; |
| } |
| } |
| |
| |
| /** |
| * Reads a {@code module-info.class} from the given input stream. |
| * |
| * @throws InvalidModuleDescriptorException |
| * @throws IOException |
| */ |
| public static Attributes read(InputStream in, Supplier<Set<String>> pf) |
| throws IOException |
| { |
| try { |
| return new ModuleInfo(pf).doRead(new DataInputStream(in)); |
| } catch (IllegalArgumentException | IllegalStateException e) { |
| throw invalidModuleDescriptor(e.getMessage()); |
| } catch (EOFException x) { |
| throw truncatedModuleDescriptor(); |
| } |
| } |
| |
| /** |
| * Reads a {@code module-info.class} from the given byte buffer. |
| * |
| * @throws InvalidModuleDescriptorException |
| * @throws UncheckedIOException |
| */ |
| public static Attributes read(ByteBuffer bb, Supplier<Set<String>> pf) { |
| try { |
| return new ModuleInfo(pf).doRead(new DataInputWrapper(bb)); |
| } catch (IllegalArgumentException | IllegalStateException e) { |
| throw invalidModuleDescriptor(e.getMessage()); |
| } catch (EOFException x) { |
| throw truncatedModuleDescriptor(); |
| } catch (IOException ioe) { |
| throw new UncheckedIOException(ioe); |
| } |
| } |
| |
| /** |
| * Reads a {@code module-info.class} from the given byte buffer |
| * but ignore the {@code ModuleHashes} attribute. |
| * |
| * @throws InvalidModuleDescriptorException |
| * @throws UncheckedIOException |
| */ |
| public static Attributes readIgnoringHashes(ByteBuffer bb, Supplier<Set<String>> pf) { |
| try { |
| return new ModuleInfo(pf, false).doRead(new DataInputWrapper(bb)); |
| } catch (IllegalArgumentException | IllegalStateException e) { |
| throw invalidModuleDescriptor(e.getMessage()); |
| } catch (EOFException x) { |
| throw truncatedModuleDescriptor(); |
| } catch (IOException ioe) { |
| throw new UncheckedIOException(ioe); |
| } |
| } |
| |
| /** |
| * Reads the input as a module-info class file. |
| * |
| * @throws IOException |
| * @throws InvalidModuleDescriptorException |
| * @throws IllegalArgumentException if thrown by the ModuleDescriptor.Builder |
| * because an identifier is not a legal Java identifier, duplicate |
| * exports, and many other reasons |
| */ |
| private Attributes doRead(DataInput in) throws IOException { |
| |
| int magic = in.readInt(); |
| if (magic != 0xCAFEBABE) |
| throw invalidModuleDescriptor("Bad magic number"); |
| |
| int minor_version = in.readUnsignedShort(); |
| int major_version = in.readUnsignedShort(); |
| if (major_version < JAVA_MIN_SUPPORTED_VERSION || |
| major_version > JAVA_MAX_SUPPORTED_VERSION) { |
| throw invalidModuleDescriptor("Unsupported major.minor version " |
| + major_version + "." + minor_version); |
| } |
| |
| ConstantPool cpool = new ConstantPool(in); |
| |
| int access_flags = in.readUnsignedShort(); |
| if (access_flags != ACC_MODULE) |
| throw invalidModuleDescriptor("access_flags should be ACC_MODULE"); |
| |
| int this_class = in.readUnsignedShort(); |
| String mn = cpool.getClassName(this_class); |
| if (!"module-info".equals(mn)) |
| throw invalidModuleDescriptor("this_class should be module-info"); |
| |
| int super_class = in.readUnsignedShort(); |
| if (super_class > 0) |
| throw invalidModuleDescriptor("bad #super_class"); |
| |
| int interfaces_count = in.readUnsignedShort(); |
| if (interfaces_count > 0) |
| throw invalidModuleDescriptor("Bad #interfaces"); |
| |
| int fields_count = in.readUnsignedShort(); |
| if (fields_count > 0) |
| throw invalidModuleDescriptor("Bad #fields"); |
| |
| int methods_count = in.readUnsignedShort(); |
| if (methods_count > 0) |
| throw invalidModuleDescriptor("Bad #methods"); |
| |
| int attributes_count = in.readUnsignedShort(); |
| |
| // the names of the attributes found in the class file |
| Set<String> attributes = new HashSet<>(); |
| |
| Builder builder = null; |
| Set<String> allPackages = null; |
| String mainClass = null; |
| ModuleTarget moduleTarget = null; |
| ModuleHashes moduelHashes = null; |
| ModuleResolution moduleResolution = null; |
| |
| for (int i = 0; i < attributes_count ; i++) { |
| int name_index = in.readUnsignedShort(); |
| String attribute_name = cpool.getUtf8(name_index); |
| int length = in.readInt(); |
| |
| boolean added = attributes.add(attribute_name); |
| if (!added && isAttributeAtMostOnce(attribute_name)) { |
| throw invalidModuleDescriptor("More than one " |
| + attribute_name + " attribute"); |
| } |
| |
| switch (attribute_name) { |
| |
| case MODULE : |
| builder = readModuleAttribute(in, cpool, major_version); |
| break; |
| |
| case MODULE_PACKAGES : |
| allPackages = readModulePackagesAttribute(in, cpool); |
| break; |
| |
| case MODULE_MAIN_CLASS : |
| mainClass = readModuleMainClassAttribute(in, cpool); |
| break; |
| |
| case MODULE_TARGET : |
| moduleTarget = readModuleTargetAttribute(in, cpool); |
| break; |
| |
| case MODULE_HASHES : |
| if (parseHashes) { |
| moduelHashes = readModuleHashesAttribute(in, cpool); |
| } else { |
| in.skipBytes(length); |
| } |
| break; |
| |
| case MODULE_RESOLUTION : |
| moduleResolution = readModuleResolution(in, cpool); |
| break; |
| |
| default: |
| if (isAttributeDisallowed(attribute_name)) { |
| throw invalidModuleDescriptor(attribute_name |
| + " attribute not allowed"); |
| } else { |
| in.skipBytes(length); |
| } |
| |
| } |
| } |
| |
| // the Module attribute is required |
| if (builder == null) { |
| throw invalidModuleDescriptor(MODULE + " attribute not found"); |
| } |
| |
| // ModuleMainClass attribute |
| if (mainClass != null) { |
| builder.mainClass(mainClass); |
| } |
| |
| // If the ModulePackages attribute is not present then the packageFinder |
| // is used to find the set of packages |
| boolean usedPackageFinder = false; |
| if (allPackages == null && packageFinder != null) { |
| try { |
| allPackages = packageFinder.get(); |
| } catch (UncheckedIOException x) { |
| throw x.getCause(); |
| } |
| usedPackageFinder = true; |
| } |
| if (allPackages != null) { |
| Set<String> knownPackages = JLMA.packages(builder); |
| if (!allPackages.containsAll(knownPackages)) { |
| Set<String> missingPackages = new HashSet<>(knownPackages); |
| missingPackages.removeAll(allPackages); |
| assert !missingPackages.isEmpty(); |
| String missingPackage = missingPackages.iterator().next(); |
| String tail; |
| if (usedPackageFinder) { |
| tail = " not found in module"; |
| } else { |
| tail = " missing from ModulePackages class file attribute"; |
| } |
| throw invalidModuleDescriptor("Package " + missingPackage + tail); |
| |
| } |
| builder.packages(allPackages); |
| } |
| |
| ModuleDescriptor descriptor = builder.build(); |
| return new Attributes(descriptor, |
| moduleTarget, |
| moduelHashes, |
| moduleResolution); |
| } |
| |
| /** |
| * Reads the Module attribute, returning the ModuleDescriptor.Builder to |
| * build the corresponding ModuleDescriptor. |
| */ |
| private Builder readModuleAttribute(DataInput in, ConstantPool cpool, int major) |
| throws IOException |
| { |
| // module_name |
| int module_name_index = in.readUnsignedShort(); |
| String mn = cpool.getModuleName(module_name_index); |
| |
| int module_flags = in.readUnsignedShort(); |
| |
| Set<ModuleDescriptor.Modifier> modifiers = new HashSet<>(); |
| boolean open = ((module_flags & ACC_OPEN) != 0); |
| if (open) |
| modifiers.add(ModuleDescriptor.Modifier.OPEN); |
| if ((module_flags & ACC_SYNTHETIC) != 0) |
| modifiers.add(ModuleDescriptor.Modifier.SYNTHETIC); |
| if ((module_flags & ACC_MANDATED) != 0) |
| modifiers.add(ModuleDescriptor.Modifier.MANDATED); |
| |
| Builder builder = JLMA.newModuleBuilder(mn, false, modifiers); |
| |
| int module_version_index = in.readUnsignedShort(); |
| if (module_version_index != 0) { |
| String vs = cpool.getUtf8(module_version_index); |
| builder.version(vs); |
| } |
| |
| int requires_count = in.readUnsignedShort(); |
| boolean requiresJavaBase = false; |
| for (int i=0; i<requires_count; i++) { |
| int requires_index = in.readUnsignedShort(); |
| String dn = cpool.getModuleName(requires_index); |
| |
| int requires_flags = in.readUnsignedShort(); |
| Set<Requires.Modifier> mods; |
| if (requires_flags == 0) { |
| mods = Set.of(); |
| } else { |
| mods = new HashSet<>(); |
| if ((requires_flags & ACC_TRANSITIVE) != 0) |
| mods.add(Requires.Modifier.TRANSITIVE); |
| if ((requires_flags & ACC_STATIC_PHASE) != 0) |
| mods.add(Requires.Modifier.STATIC); |
| if ((requires_flags & ACC_SYNTHETIC) != 0) |
| mods.add(Requires.Modifier.SYNTHETIC); |
| if ((requires_flags & ACC_MANDATED) != 0) |
| mods.add(Requires.Modifier.MANDATED); |
| } |
| |
| int requires_version_index = in.readUnsignedShort(); |
| if (requires_version_index == 0) { |
| builder.requires(mods, dn); |
| } else { |
| String vs = cpool.getUtf8(requires_version_index); |
| JLMA.requires(builder, mods, dn, vs); |
| } |
| |
| if (dn.equals("java.base")) { |
| if (major >= 54 |
| && (mods.contains(Requires.Modifier.TRANSITIVE) |
| || mods.contains(Requires.Modifier.STATIC))) { |
| String flagName; |
| if (mods.contains(Requires.Modifier.TRANSITIVE)) { |
| flagName = "ACC_TRANSITIVE"; |
| } else { |
| flagName = "ACC_STATIC_PHASE"; |
| } |
| throw invalidModuleDescriptor("The requires entry for java.base" |
| + " has " + flagName + " set"); |
| } |
| requiresJavaBase = true; |
| } |
| } |
| if (mn.equals("java.base")) { |
| if (requires_count > 0) { |
| throw invalidModuleDescriptor("The requires table for java.base" |
| + " must be 0 length"); |
| } |
| } else if (!requiresJavaBase) { |
| throw invalidModuleDescriptor("The requires table must have" |
| + " an entry for java.base"); |
| } |
| |
| int exports_count = in.readUnsignedShort(); |
| if (exports_count > 0) { |
| for (int i=0; i<exports_count; i++) { |
| int exports_index = in.readUnsignedShort(); |
| String pkg = cpool.getPackageName(exports_index); |
| |
| Set<Exports.Modifier> mods; |
| int exports_flags = in.readUnsignedShort(); |
| if (exports_flags == 0) { |
| mods = Set.of(); |
| } else { |
| mods = new HashSet<>(); |
| if ((exports_flags & ACC_SYNTHETIC) != 0) |
| mods.add(Exports.Modifier.SYNTHETIC); |
| if ((exports_flags & ACC_MANDATED) != 0) |
| mods.add(Exports.Modifier.MANDATED); |
| } |
| |
| int exports_to_count = in.readUnsignedShort(); |
| if (exports_to_count > 0) { |
| Set<String> targets = new HashSet<>(exports_to_count); |
| for (int j=0; j<exports_to_count; j++) { |
| int exports_to_index = in.readUnsignedShort(); |
| String target = cpool.getModuleName(exports_to_index); |
| if (!targets.add(target)) { |
| throw invalidModuleDescriptor(pkg + " exported to " |
| + target + " more than once"); |
| } |
| } |
| builder.exports(mods, pkg, targets); |
| } else { |
| builder.exports(mods, pkg); |
| } |
| } |
| } |
| |
| int opens_count = in.readUnsignedShort(); |
| if (opens_count > 0) { |
| if (open) { |
| throw invalidModuleDescriptor("The opens table for an open" |
| + " module must be 0 length"); |
| } |
| for (int i=0; i<opens_count; i++) { |
| int opens_index = in.readUnsignedShort(); |
| String pkg = cpool.getPackageName(opens_index); |
| |
| Set<Opens.Modifier> mods; |
| int opens_flags = in.readUnsignedShort(); |
| if (opens_flags == 0) { |
| mods = Set.of(); |
| } else { |
| mods = new HashSet<>(); |
| if ((opens_flags & ACC_SYNTHETIC) != 0) |
| mods.add(Opens.Modifier.SYNTHETIC); |
| if ((opens_flags & ACC_MANDATED) != 0) |
| mods.add(Opens.Modifier.MANDATED); |
| } |
| |
| int open_to_count = in.readUnsignedShort(); |
| if (open_to_count > 0) { |
| Set<String> targets = new HashSet<>(open_to_count); |
| for (int j=0; j<open_to_count; j++) { |
| int opens_to_index = in.readUnsignedShort(); |
| String target = cpool.getModuleName(opens_to_index); |
| if (!targets.add(target)) { |
| throw invalidModuleDescriptor(pkg + " opened to " |
| + target + " more than once"); |
| } |
| } |
| builder.opens(mods, pkg, targets); |
| } else { |
| builder.opens(mods, pkg); |
| } |
| } |
| } |
| |
| int uses_count = in.readUnsignedShort(); |
| if (uses_count > 0) { |
| for (int i=0; i<uses_count; i++) { |
| int index = in.readUnsignedShort(); |
| String sn = cpool.getClassName(index); |
| builder.uses(sn); |
| } |
| } |
| |
| int provides_count = in.readUnsignedShort(); |
| if (provides_count > 0) { |
| for (int i=0; i<provides_count; i++) { |
| int index = in.readUnsignedShort(); |
| String sn = cpool.getClassName(index); |
| int with_count = in.readUnsignedShort(); |
| List<String> providers = new ArrayList<>(with_count); |
| for (int j=0; j<with_count; j++) { |
| index = in.readUnsignedShort(); |
| String pn = cpool.getClassName(index); |
| if (!providers.add(pn)) { |
| throw invalidModuleDescriptor(sn + " provides " + pn |
| + " more than once"); |
| } |
| } |
| builder.provides(sn, providers); |
| } |
| } |
| |
| return builder; |
| } |
| |
| /** |
| * Reads the ModulePackages attribute |
| */ |
| private Set<String> readModulePackagesAttribute(DataInput in, ConstantPool cpool) |
| throws IOException |
| { |
| int package_count = in.readUnsignedShort(); |
| Set<String> packages = new HashSet<>(package_count); |
| for (int i=0; i<package_count; i++) { |
| int index = in.readUnsignedShort(); |
| String pn = cpool.getPackageName(index); |
| boolean added = packages.add(pn); |
| if (!added) { |
| throw invalidModuleDescriptor("Package " + pn + " in ModulePackages" |
| + "attribute more than once"); |
| } |
| } |
| return packages; |
| } |
| |
| /** |
| * Reads the ModuleMainClass attribute |
| */ |
| private String readModuleMainClassAttribute(DataInput in, ConstantPool cpool) |
| throws IOException |
| { |
| int index = in.readUnsignedShort(); |
| return cpool.getClassName(index); |
| } |
| |
| /** |
| * Reads the ModuleTarget attribute |
| */ |
| private ModuleTarget readModuleTargetAttribute(DataInput in, ConstantPool cpool) |
| throws IOException |
| { |
| String targetPlatform = null; |
| |
| int index = in.readUnsignedShort(); |
| if (index != 0) |
| targetPlatform = cpool.getUtf8(index); |
| |
| return new ModuleTarget(targetPlatform); |
| } |
| |
| /** |
| * Reads the ModuleHashes attribute |
| */ |
| private ModuleHashes readModuleHashesAttribute(DataInput in, ConstantPool cpool) |
| throws IOException |
| { |
| int algorithm_index = in.readUnsignedShort(); |
| String algorithm = cpool.getUtf8(algorithm_index); |
| |
| int hash_count = in.readUnsignedShort(); |
| Map<String, byte[]> map = new HashMap<>(hash_count); |
| for (int i=0; i<hash_count; i++) { |
| int module_name_index = in.readUnsignedShort(); |
| String mn = cpool.getModuleName(module_name_index); |
| int hash_length = in.readUnsignedShort(); |
| if (hash_length == 0) { |
| throw invalidModuleDescriptor("hash_length == 0"); |
| } |
| byte[] hash = new byte[hash_length]; |
| in.readFully(hash); |
| map.put(mn, hash); |
| } |
| |
| return new ModuleHashes(algorithm, map); |
| } |
| |
| /** |
| * Reads the ModuleResolution attribute. |
| */ |
| private ModuleResolution readModuleResolution(DataInput in, |
| ConstantPool cpool) |
| throws IOException |
| { |
| int flags = in.readUnsignedShort(); |
| |
| int reason = 0; |
| if ((flags & WARN_DEPRECATED) != 0) |
| reason = WARN_DEPRECATED; |
| if ((flags & WARN_DEPRECATED_FOR_REMOVAL) != 0) { |
| if (reason != 0) |
| throw invalidModuleDescriptor("Bad module resolution flags:" + flags); |
| reason = WARN_DEPRECATED_FOR_REMOVAL; |
| } |
| if ((flags & WARN_INCUBATING) != 0) { |
| if (reason != 0) |
| throw invalidModuleDescriptor("Bad module resolution flags:" + flags); |
| } |
| |
| return new ModuleResolution(flags); |
| } |
| |
| /** |
| * Returns true if the given attribute can be present at most once |
| * in the class file. Returns false otherwise. |
| */ |
| private static boolean isAttributeAtMostOnce(String name) { |
| |
| if (name.equals(MODULE) || |
| name.equals(SOURCE_FILE) || |
| name.equals(SDE) || |
| name.equals(MODULE_PACKAGES) || |
| name.equals(MODULE_MAIN_CLASS) || |
| name.equals(MODULE_TARGET) || |
| name.equals(MODULE_HASHES) || |
| name.equals(MODULE_RESOLUTION)) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * Return true if the given attribute name is the name of a pre-defined |
| * attribute in JVMS 4.7 that is not allowed in a module-info class. |
| */ |
| private static boolean isAttributeDisallowed(String name) { |
| Set<String> notAllowed = predefinedNotAllowed; |
| if (notAllowed == null) { |
| notAllowed = Set.of( |
| "ConstantValue", |
| "Code", |
| "Deprecated", |
| "StackMapTable", |
| "Exceptions", |
| "EnclosingMethod", |
| "Signature", |
| "LineNumberTable", |
| "LocalVariableTable", |
| "LocalVariableTypeTable", |
| "RuntimeVisibleParameterAnnotations", |
| "RuntimeInvisibleParameterAnnotations", |
| "RuntimeVisibleTypeAnnotations", |
| "RuntimeInvisibleTypeAnnotations", |
| "Synthetic", |
| "AnnotationDefault", |
| "BootstrapMethods", |
| "MethodParameters"); |
| predefinedNotAllowed = notAllowed; |
| } |
| return notAllowed.contains(name); |
| } |
| |
| // lazily created set the pre-defined attributes that are not allowed |
| private static volatile Set<String> predefinedNotAllowed; |
| |
| |
| /** |
| * The constant pool in a class file. |
| */ |
| private static class ConstantPool { |
| static final int CONSTANT_Utf8 = 1; |
| static final int CONSTANT_Integer = 3; |
| static final int CONSTANT_Float = 4; |
| static final int CONSTANT_Long = 5; |
| static final int CONSTANT_Double = 6; |
| static final int CONSTANT_Class = 7; |
| static final int CONSTANT_String = 8; |
| static final int CONSTANT_Fieldref = 9; |
| static final int CONSTANT_Methodref = 10; |
| static final int CONSTANT_InterfaceMethodref = 11; |
| static final int CONSTANT_NameAndType = 12; |
| static final int CONSTANT_MethodHandle = 15; |
| static final int CONSTANT_MethodType = 16; |
| static final int CONSTANT_InvokeDynamic = 18; |
| static final int CONSTANT_Module = 19; |
| static final int CONSTANT_Package = 20; |
| |
| private static class Entry { |
| protected Entry(int tag) { |
| this.tag = tag; |
| } |
| final int tag; |
| } |
| |
| private static class IndexEntry extends Entry { |
| IndexEntry(int tag, int index) { |
| super(tag); |
| this.index = index; |
| } |
| final int index; |
| } |
| |
| private static class Index2Entry extends Entry { |
| Index2Entry(int tag, int index1, int index2) { |
| super(tag); |
| this.index1 = index1; |
| this.index2 = index2; |
| } |
| final int index1, index2; |
| } |
| |
| private static class ValueEntry extends Entry { |
| ValueEntry(int tag, Object value) { |
| super(tag); |
| this.value = value; |
| } |
| final Object value; |
| } |
| |
| final Entry[] pool; |
| |
| ConstantPool(DataInput in) throws IOException { |
| int count = in.readUnsignedShort(); |
| pool = new Entry[count]; |
| |
| for (int i = 1; i < count; i++) { |
| int tag = in.readUnsignedByte(); |
| switch (tag) { |
| |
| case CONSTANT_Utf8: |
| String svalue = in.readUTF(); |
| pool[i] = new ValueEntry(tag, svalue); |
| break; |
| |
| case CONSTANT_Class: |
| case CONSTANT_Package: |
| case CONSTANT_Module: |
| case CONSTANT_String: |
| int index = in.readUnsignedShort(); |
| pool[i] = new IndexEntry(tag, index); |
| break; |
| |
| case CONSTANT_Double: |
| double dvalue = in.readDouble(); |
| pool[i] = new ValueEntry(tag, dvalue); |
| i++; |
| break; |
| |
| case CONSTANT_Fieldref: |
| case CONSTANT_InterfaceMethodref: |
| case CONSTANT_Methodref: |
| case CONSTANT_InvokeDynamic: |
| case CONSTANT_NameAndType: |
| int index1 = in.readUnsignedShort(); |
| int index2 = in.readUnsignedShort(); |
| pool[i] = new Index2Entry(tag, index1, index2); |
| break; |
| |
| case CONSTANT_MethodHandle: |
| int refKind = in.readUnsignedByte(); |
| index = in.readUnsignedShort(); |
| pool[i] = new Index2Entry(tag, refKind, index); |
| break; |
| |
| case CONSTANT_MethodType: |
| index = in.readUnsignedShort(); |
| pool[i] = new IndexEntry(tag, index); |
| break; |
| |
| case CONSTANT_Float: |
| float fvalue = in.readFloat(); |
| pool[i] = new ValueEntry(tag, fvalue); |
| break; |
| |
| case CONSTANT_Integer: |
| int ivalue = in.readInt(); |
| pool[i] = new ValueEntry(tag, ivalue); |
| break; |
| |
| case CONSTANT_Long: |
| long lvalue = in.readLong(); |
| pool[i] = new ValueEntry(tag, lvalue); |
| i++; |
| break; |
| |
| default: |
| throw invalidModuleDescriptor("Bad constant pool entry: " |
| + i); |
| } |
| } |
| } |
| |
| String getClassName(int index) { |
| checkIndex(index); |
| Entry e = pool[index]; |
| if (e.tag != CONSTANT_Class) { |
| throw invalidModuleDescriptor("CONSTANT_Class expected at entry: " |
| + index); |
| } |
| String value = getUtf8(((IndexEntry) e).index); |
| checkUnqualifiedName("CONSTANT_Class", index, value); |
| return value.replace('/', '.'); // internal form -> binary name |
| } |
| |
| String getPackageName(int index) { |
| checkIndex(index); |
| Entry e = pool[index]; |
| if (e.tag != CONSTANT_Package) { |
| throw invalidModuleDescriptor("CONSTANT_Package expected at entry: " |
| + index); |
| } |
| String value = getUtf8(((IndexEntry) e).index); |
| checkUnqualifiedName("CONSTANT_Package", index, value); |
| return value.replace('/', '.'); // internal form -> binary name |
| } |
| |
| String getModuleName(int index) { |
| checkIndex(index); |
| Entry e = pool[index]; |
| if (e.tag != CONSTANT_Module) { |
| throw invalidModuleDescriptor("CONSTANT_Module expected at entry: " |
| + index); |
| } |
| String value = getUtf8(((IndexEntry) e).index); |
| return decodeModuleName(index, value); |
| } |
| |
| String getUtf8(int index) { |
| checkIndex(index); |
| Entry e = pool[index]; |
| if (e.tag != CONSTANT_Utf8) { |
| throw invalidModuleDescriptor("CONSTANT_Utf8 expected at entry: " |
| + index); |
| } |
| return (String) (((ValueEntry) e).value); |
| } |
| |
| void checkIndex(int index) { |
| if (index < 1 || index >= pool.length) |
| throw invalidModuleDescriptor("Index into constant pool out of range"); |
| } |
| |
| void checkUnqualifiedName(String what, int index, String value) { |
| int len = value.length(); |
| if (len == 0) { |
| throw invalidModuleDescriptor(what + " at entry " + index |
| + " has zero length"); |
| } |
| for (int i=0; i<len; i++) { |
| char c = value.charAt(i); |
| if (c == '.' || c == ';' || c == '[') { |
| throw invalidModuleDescriptor(what + " at entry " + index |
| + " has illegal character: '" |
| + c + "'"); |
| } |
| } |
| } |
| |
| /** |
| * "Decode" a module name that has been read from the constant pool. |
| */ |
| String decodeModuleName(int index, String value) { |
| int len = value.length(); |
| if (len == 0) { |
| throw invalidModuleDescriptor("CONSTANT_Module at entry " |
| + index + " is zero length"); |
| } |
| int i = 0; |
| while (i < len) { |
| int cp = value.codePointAt(i); |
| if (cp == ':' || cp == '@' || cp < 0x20) { |
| throw invalidModuleDescriptor("CONSTANT_Module at entry " |
| + index + " has illegal character: " |
| + Character.getName(cp)); |
| } |
| |
| // blackslash is the escape character |
| if (cp == '\\') |
| return decodeModuleName(index, i, value); |
| |
| i += Character.charCount(cp); |
| } |
| return value; |
| } |
| |
| /** |
| * "Decode" a module name that has been read from the constant pool and |
| * partly checked for illegal characters (up to position {@code i}). |
| */ |
| String decodeModuleName(int index, int i, String value) { |
| StringBuilder sb = new StringBuilder(); |
| |
| // copy the code points that have been checked |
| int j = 0; |
| while (j < i) { |
| int cp = value.codePointAt(j); |
| sb.appendCodePoint(cp); |
| j += Character.charCount(cp); |
| } |
| |
| // decode from position {@code i} to end |
| int len = value.length(); |
| while (i < len) { |
| int cp = value.codePointAt(i); |
| if (cp == ':' || cp == '@' || cp < 0x20) { |
| throw invalidModuleDescriptor("CONSTANT_Module at entry " |
| + index + " has illegal character: " |
| + Character.getName(cp)); |
| } |
| |
| // blackslash is the escape character |
| if (cp == '\\') { |
| j = i + Character.charCount(cp); |
| if (j >= len) { |
| throw invalidModuleDescriptor("CONSTANT_Module at entry " |
| + index + " has illegal " |
| + "escape sequence"); |
| } |
| int next = value.codePointAt(j); |
| if (next != '\\' && next != ':' && next != '@') { |
| throw invalidModuleDescriptor("CONSTANT_Module at entry " |
| + index + " has illegal " |
| + "escape sequence"); |
| } |
| sb.appendCodePoint(next); |
| i += Character.charCount(next); |
| } else { |
| sb.appendCodePoint(cp); |
| } |
| |
| i += Character.charCount(cp); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * A DataInput implementation that reads from a ByteBuffer. |
| */ |
| private static class DataInputWrapper implements DataInput { |
| private final ByteBuffer bb; |
| |
| DataInputWrapper(ByteBuffer bb) { |
| this.bb = bb; |
| } |
| |
| @Override |
| public void readFully(byte b[]) throws IOException { |
| readFully(b, 0, b.length); |
| } |
| |
| @Override |
| public void readFully(byte b[], int off, int len) throws IOException { |
| try { |
| bb.get(b, off, len); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public int skipBytes(int n) { |
| int skip = Math.min(n, bb.remaining()); |
| bb.position(bb.position() + skip); |
| return skip; |
| } |
| |
| @Override |
| public boolean readBoolean() throws IOException { |
| try { |
| int ch = bb.get(); |
| return (ch != 0); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public byte readByte() throws IOException { |
| try { |
| return bb.get(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public int readUnsignedByte() throws IOException { |
| try { |
| return ((int) bb.get()) & 0xff; |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public short readShort() throws IOException { |
| try { |
| return bb.getShort(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public int readUnsignedShort() throws IOException { |
| try { |
| return ((int) bb.getShort()) & 0xffff; |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public char readChar() throws IOException { |
| try { |
| return bb.getChar(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public int readInt() throws IOException { |
| try { |
| return bb.getInt(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public long readLong() throws IOException { |
| try { |
| return bb.getLong(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public float readFloat() throws IOException { |
| try { |
| return bb.getFloat(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public double readDouble() throws IOException { |
| try { |
| return bb.getDouble(); |
| } catch (BufferUnderflowException e) { |
| throw new EOFException(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public String readLine() { |
| throw new RuntimeException("not implemented"); |
| } |
| |
| @Override |
| public String readUTF() throws IOException { |
| // ### Need to measure the performance and feasibility of using |
| // the UTF-8 decoder instead. |
| return DataInputStream.readUTF(this); |
| } |
| } |
| |
| /** |
| * Returns an InvalidModuleDescriptorException with the given detail |
| * message |
| */ |
| private static InvalidModuleDescriptorException |
| invalidModuleDescriptor(String msg) { |
| return new InvalidModuleDescriptorException(msg); |
| } |
| |
| /** |
| * Returns an InvalidModuleDescriptorException with a detail message to |
| * indicate that the class file is truncated. |
| */ |
| private static InvalidModuleDescriptorException truncatedModuleDescriptor() { |
| return invalidModuleDescriptor("Truncated module-info.class"); |
| } |
| |
| } |