1 /*
   2  * Copyright (c) 1999, 2016, 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 com.sun.tools.javac.jvm;
  26 
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 
  32 import javax.tools.JavaFileObject;
  33 
  34 import static com.sun.tools.javac.jvm.ClassFile.*;
  35 
  36 
  37 /**
  38  * Stripped down ClassReader, just sufficient to read module names from module-info.class files
  39  * while analyzing the module path.
  40  *
  41  * <p>
  42  * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
  43  * your own risk. This code and its internal interfaces are subject to change or deletion without
  44  * notice.</b>
  45  */
  46 public class ModuleNameReader {
  47     public static class BadClassFile extends Exception {
  48         private static final long serialVersionUID = 0;
  49         BadClassFile(String msg) {
  50             super(msg);
  51         }
  52     }
  53 
  54     private static final int INITIAL_BUFFER_SIZE = 0x0fff0;
  55 
  56     /** The buffer containing the currently read class file.
  57      */
  58     private byte[] buf = new byte[INITIAL_BUFFER_SIZE];
  59 
  60     /** The current input pointer.
  61      */
  62     private int bp;
  63 
  64     /** For every constant pool entry, an index into buf where the
  65      *  defining section of the entry is found.
  66      */
  67     private int[] poolIdx;
  68 
  69     public ModuleNameReader() {
  70     }
  71 
  72     public String readModuleName(Path p) throws IOException, BadClassFile {
  73         try (InputStream in = Files.newInputStream(p)) {
  74             return readModuleName(in);
  75         }
  76     }
  77 
  78     public String readModuleName(JavaFileObject jfo) throws IOException, BadClassFile {
  79         try (InputStream in = jfo.openInputStream()) {
  80             return readModuleName(in);
  81         }
  82     }
  83 
  84     public String readModuleName(InputStream in) throws IOException, BadClassFile {
  85         bp = 0;
  86         buf = readInputStream(buf, in);
  87 
  88         int magic = nextInt();
  89         if (magic != JAVA_MAGIC)
  90             throw new BadClassFile("illegal.start.of.class.file");
  91 
  92         int minorVersion = nextChar();
  93         int majorVersion = nextChar();
  94         if (majorVersion < 53)
  95             throw new BadClassFile("bad major version number for module: " + majorVersion);
  96 
  97         indexPool();
  98 
  99         int access_flags = nextChar();
 100         if (access_flags != 0x8000)
 101             throw new BadClassFile("invalid access flags for module: 0x" + Integer.toHexString(access_flags));
 102 
 103         int this_class = nextChar();
 104         // could, should, check this_class == CONSTANT_Class("mdoule-info")
 105         checkZero(nextChar(), "super_class");
 106         checkZero(nextChar(), "interface_count");
 107         checkZero(nextChar(), "fields_count");
 108         checkZero(nextChar(), "methods_count");
 109         int attributes_count = nextChar();
 110         for (int i = 0; i < attributes_count; i++) {
 111             int attr_name = nextChar();
 112             int attr_length = nextInt();
 113             if (getUtf8Value(attr_name, false).equals("Module") && attr_length > 2) {
 114                 return getModuleName(nextChar());
 115             } else {
 116                 // skip over unknown attributes
 117                 bp += attr_length;
 118             }
 119         }
 120         throw new BadClassFile("no Module attribute");
 121     }
 122 
 123     void checkZero(int count, String name) throws BadClassFile {
 124         if (count != 0)
 125             throw new BadClassFile("invalid " + name + " for module: " + count);
 126     }
 127 
 128     /** Extract a character at position bp from buf.
 129      */
 130     char getChar(int bp) {
 131         return
 132             (char)(((buf[bp] & 0xFF) << 8) + (buf[bp+1] & 0xFF));
 133     }
 134 
 135     /** Read a character.
 136      */
 137     char nextChar() {
 138         return (char)(((buf[bp++] & 0xFF) << 8) + (buf[bp++] & 0xFF));
 139     }
 140 
 141     /** Read an integer.
 142      */
 143     int nextInt() {
 144         return
 145             ((buf[bp++] & 0xFF) << 24) +
 146             ((buf[bp++] & 0xFF) << 16) +
 147             ((buf[bp++] & 0xFF) << 8) +
 148             (buf[bp++] & 0xFF);
 149     }
 150 
 151     /** Index all constant pool entries, writing their start addresses into
 152      *  poolIdx.
 153      */
 154     void indexPool() throws BadClassFile {
 155         poolIdx = new int[nextChar()];
 156         int i = 1;
 157         while (i < poolIdx.length) {
 158             poolIdx[i++] = bp;
 159             byte tag = buf[bp++];
 160             switch (tag) {
 161             case CONSTANT_Utf8: case CONSTANT_Unicode: {
 162                 int len = nextChar();
 163                 bp = bp + len;
 164                 break;
 165             }
 166             case CONSTANT_Class:
 167             case CONSTANT_String:
 168             case CONSTANT_MethodType:
 169             case CONSTANT_Module:
 170             case CONSTANT_Package:
 171                 bp = bp + 2;
 172                 break;
 173             case CONSTANT_MethodHandle:
 174                 bp = bp + 3;
 175                 break;
 176             case CONSTANT_Fieldref:
 177             case CONSTANT_Methodref:
 178             case CONSTANT_InterfaceMethodref:
 179             case CONSTANT_NameandType:
 180             case CONSTANT_Integer:
 181             case CONSTANT_Float:
 182             case CONSTANT_InvokeDynamic:
 183                 bp = bp + 4;
 184                 break;
 185             case CONSTANT_Long:
 186             case CONSTANT_Double:
 187                 bp = bp + 8;
 188                 i++;
 189                 break;
 190             default:
 191                 throw new BadClassFile("malformed constant pool");
 192             }
 193         }
 194     }
 195 
 196     String getUtf8Value(int index, boolean internalize) throws BadClassFile {
 197         int utf8Index = poolIdx[index];
 198         if (buf[utf8Index] == CONSTANT_Utf8) {
 199             int len = getChar(utf8Index + 1);
 200             int start = utf8Index + 3;
 201             if (internalize) {
 202                 return new String(ClassFile.internalize(buf, start, len));
 203             } else {
 204                 return new String(buf, start, len);
 205             }
 206         }
 207         throw new BadClassFile("bad name at index " + index);
 208     }
 209 
 210     String getModuleName(int index) throws BadClassFile {
 211         int infoIndex = poolIdx[index];
 212         if (buf[infoIndex] == CONSTANT_Module) {
 213             return getUtf8Value(getChar(infoIndex + 1), true);
 214         } else {
 215             throw new BadClassFile("bad module name at index " + index);
 216         }
 217     }
 218 
 219     private static byte[] readInputStream(byte[] buf, InputStream s) throws IOException {
 220         try {
 221             buf = ensureCapacity(buf, s.available());
 222             int r = s.read(buf);
 223             int bp = 0;
 224             while (r != -1) {
 225                 bp += r;
 226                 buf = ensureCapacity(buf, bp);
 227                 r = s.read(buf, bp, buf.length - bp);
 228             }
 229             return buf;
 230         } finally {
 231             try {
 232                 s.close();
 233             } catch (IOException e) {
 234                 /* Ignore any errors, as this stream may have already
 235                  * thrown a related exception which is the one that
 236                  * should be reported.
 237                  */
 238             }
 239         }
 240     }
 241 
 242     /*
 243      * ensureCapacity will increase the buffer as needed, taking note that
 244      * the new buffer will always be greater than the needed and never
 245      * exactly equal to the needed size or bp. If equal then the read (above)
 246      * will infinitely loop as buf.length - bp == 0.
 247      */
 248     private static byte[] ensureCapacity(byte[] buf, int needed) {
 249         if (buf.length <= needed) {
 250             byte[] old = buf;
 251             buf = new byte[Integer.highestOneBit(needed) << 1];
 252             System.arraycopy(old, 0, buf, 0, old.length);
 253         }
 254         return buf;
 255     }
 256 }