/* * 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.Collections; 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 static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); // supplies the set of packages when ModulePackages attribute not present private final Supplier> packageFinder; // indicates if the ModuleHashes attribute should be parsed private final boolean parseHashes; private ModuleInfo(Supplier> pf, boolean ph) { packageFinder = pf; parseHashes = ph; } private ModuleInfo(Supplier> 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 ModuleHashes recordedHashes; private final ModuleResolution moduleResolution; Attributes(ModuleDescriptor descriptor, ModuleHashes recordedHashes, ModuleResolution moduleResolution) { this.descriptor = descriptor; this.recordedHashes = recordedHashes; this.moduleResolution = moduleResolution; } public ModuleDescriptor descriptor() { return descriptor; } 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> 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> 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> 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 < 53) { throw invalidModuleDescriptor("Must be >= 53.0"); } 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 attributes = new HashSet<>(); Builder builder = null; Set allPackages = null; String mainClass = null; String[] osValues = null; ModuleHashes hashes = 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); break; case MODULE_PACKAGES : allPackages = readModulePackagesAttribute(in, cpool); break; case MODULE_MAIN_CLASS : mainClass = readModuleMainClassAttribute(in, cpool); break; case MODULE_TARGET : osValues = readModuleTargetAttribute(in, cpool); break; case MODULE_HASHES : if (parseHashes) { hashes = 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 and ModuleTarget attributes if (mainClass != null) { builder.mainClass(mainClass); } if (osValues != null) { if (osValues[0] != null) builder.osName(osValues[0]); if (osValues[1] != null) builder.osArch(osValues[1]); if (osValues[2] != null) builder.osVersion(osValues[2]); } // 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 knownPackages = JLMA.packages(builder); if (!allPackages.containsAll(knownPackages)) { Set 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, hashes, moduleResolution); } /** * Reads the Module attribute, returning the ModuleDescriptor.Builder to * build the corresponding ModuleDescriptor. */ private Builder readModuleAttribute(DataInput in, ConstantPool cpool) throws IOException { // module_name int module_name_index = in.readUnsignedShort(); String mn = cpool.getModuleName(module_name_index); int module_flags = in.readUnsignedShort(); Set 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 mods; if (requires_flags == 0) { mods = Collections.emptySet(); } 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")) 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 mods; int exports_flags = in.readUnsignedShort(); if (exports_flags == 0) { mods = Collections.emptySet(); } 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 targets = new HashSet<>(exports_to_count); for (int j=0; j 0) { if (open) { throw invalidModuleDescriptor("The opens table for an open" + " module must be 0 length"); } for (int i=0; i mods; int opens_flags = in.readUnsignedShort(); if (opens_flags == 0) { mods = Collections.emptySet(); } 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 targets = new HashSet<>(open_to_count); for (int j=0; j 0) { for (int i=0; i 0) { for (int i=0; i providers = new ArrayList<>(with_count); for (int j=0; j readModulePackagesAttribute(DataInput in, ConstantPool cpool) throws IOException { int package_count = in.readUnsignedShort(); Set packages = new HashSet<>(package_count); for (int i=0; i map = new HashMap<>(hash_count); for (int i=0; i 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 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) { 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"); } }