1/*
2 * Copyright (c) 2001, 2013, 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
26package com.sun.java.util.jar.pack;
27
28import com.sun.java.util.jar.pack.ConstantPool.ClassEntry;
29import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
30import com.sun.java.util.jar.pack.ConstantPool.Entry;
31import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry;
32import com.sun.java.util.jar.pack.ConstantPool.MemberEntry;
33import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
34import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
35import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry;
36import com.sun.java.util.jar.pack.Package.Class;
37import com.sun.java.util.jar.pack.Package.InnerClass;
38import java.io.DataInputStream;
39import java.io.FilterInputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import java.util.ArrayList;
43import java.util.Arrays;
44import java.util.Map;
45import static com.sun.java.util.jar.pack.Constants.*;
46
47/**
48 * Reader for a class file that is being incorporated into a package.
49 * @author John Rose
50 */
51class ClassReader {
52    int verbose;
53
54    Package pkg;
55    Class cls;
56    long inPos;
57    long constantPoolLimit = -1;
58    DataInputStream in;
59    Map<Attribute.Layout, Attribute> attrDefs;
60    Map<Attribute.Layout, String> attrCommands;
61    String unknownAttrCommand = "error";;
62
63    ClassReader(Class cls, InputStream in) throws IOException {
64        this.pkg = cls.getPackage();
65        this.cls = cls;
66        this.verbose = pkg.verbose;
67        this.in = new DataInputStream(new FilterInputStream(in) {
68            public int read(byte b[], int off, int len) throws IOException {
69                int nr = super.read(b, off, len);
70                if (nr >= 0)  inPos += nr;
71                return nr;
72            }
73            public int read() throws IOException {
74                int ch = super.read();
75                if (ch >= 0)  inPos += 1;
76                return ch;
77            }
78            public long skip(long n) throws IOException {
79                long ns = super.skip(n);
80                if (ns >= 0)  inPos += ns;
81                return ns;
82            }
83        });
84    }
85
86    public void setAttrDefs(Map<Attribute.Layout, Attribute> attrDefs) {
87        this.attrDefs = attrDefs;
88    }
89
90    public void setAttrCommands(Map<Attribute.Layout, String> attrCommands) {
91        this.attrCommands = attrCommands;
92    }
93
94    private void skip(int n, String what) throws IOException {
95        Utils.log.warning("skipping "+n+" bytes of "+what);
96        long skipped = 0;
97        while (skipped < n) {
98            long j = in.skip(n - skipped);
99            assert(j > 0);
100            skipped += j;
101        }
102        assert(skipped == n);
103    }
104
105    private int readUnsignedShort() throws IOException {
106        return in.readUnsignedShort();
107    }
108
109    private int readInt() throws IOException {
110        return in.readInt();
111    }
112
113    /** Read a 2-byte int, and return the <em>global</em> CP entry for it. */
114    private Entry readRef() throws IOException {
115        int i = in.readUnsignedShort();
116        return i == 0 ? null : cls.cpMap[i];
117    }
118
119    private Entry readRef(byte tag) throws IOException {
120        Entry e = readRef();
121        assert(!(e instanceof UnresolvedEntry));
122        checkTag(e, tag);
123        return e;
124    }
125
126    /** Throw a ClassFormatException if the entry does not match the expected tag type. */
127    private Entry checkTag(Entry e, byte tag) throws ClassFormatException {
128        if (e == null || !e.tagMatches(tag)) {
129            String where = (inPos == constantPoolLimit
130                                ? " in constant pool"
131                                : " at pos: " + inPos);
132            String got = (e == null
133                            ? "null CP index"
134                            : "type=" + ConstantPool.tagName(e.tag));
135            throw new ClassFormatException("Bad constant, expected type=" +
136                    ConstantPool.tagName(tag) +
137                    " got "+ got + ", in File: " + cls.file.nameString + where);
138        }
139        return e;
140    }
141    private Entry checkTag(Entry e, byte tag, boolean nullOK) throws ClassFormatException {
142        return nullOK && e == null ? null : checkTag(e, tag);
143    }
144
145    private Entry readRefOrNull(byte tag) throws IOException {
146        Entry e = readRef();
147        checkTag(e, tag, true);
148        return e;
149    }
150
151    private Utf8Entry readUtf8Ref() throws IOException {
152        return (Utf8Entry) readRef(CONSTANT_Utf8);
153    }
154
155    private ClassEntry readClassRef() throws IOException {
156        return (ClassEntry) readRef(CONSTANT_Class);
157    }
158
159    private ClassEntry readClassRefOrNull() throws IOException {
160        return (ClassEntry) readRefOrNull(CONSTANT_Class);
161    }
162
163    private SignatureEntry readSignatureRef() throws IOException {
164        // The class file stores a Utf8, but we want a Signature.
165        Entry e = readRef(CONSTANT_Signature);
166        return (e != null && e.getTag() == CONSTANT_Utf8)
167                ? ConstantPool.getSignatureEntry(e.stringValue())
168                : (SignatureEntry) e;
169    }
170
171    void read() throws IOException {
172        boolean ok = false;
173        try {
174            readMagicNumbers();
175            readConstantPool();
176            readHeader();
177            readMembers(false);  // fields
178            readMembers(true);   // methods
179            readAttributes(ATTR_CONTEXT_CLASS, cls);
180            fixUnresolvedEntries();
181            cls.finishReading();
182            assert(0 >= in.read(new byte[1]));
183            ok = true;
184        } finally {
185            if (!ok) {
186                if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file);
187            }
188        }
189    }
190
191    void readMagicNumbers() throws IOException {
192        cls.magic = in.readInt();
193        if (cls.magic != JAVA_MAGIC)
194            throw new Attribute.FormatException
195                ("Bad magic number in class file "
196                 +Integer.toHexString(cls.magic),
197                 ATTR_CONTEXT_CLASS, "magic-number", "pass");
198        int minver = (short) readUnsignedShort();
199        int majver = (short) readUnsignedShort();
200        cls.version = Package.Version.of(majver, minver);
201
202        //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver);
203        String bad = checkVersion(cls.version);
204        if (bad != null) {
205            throw new Attribute.FormatException
206                ("classfile version too "+bad+": "
207                 +cls.version+" in "+cls.file,
208                 ATTR_CONTEXT_CLASS, "version", "pass");
209        }
210    }
211
212    private String checkVersion(Package.Version ver) {
213        int majver = ver.major;
214        int minver = ver.minor;
215        if (majver < pkg.minClassVersion.major ||
216            (majver == pkg.minClassVersion.major &&
217             minver < pkg.minClassVersion.minor)) {
218            return "small";
219        }
220        if (majver > pkg.maxClassVersion.major ||
221            (majver == pkg.maxClassVersion.major &&
222             minver > pkg.maxClassVersion.minor)) {
223            return "large";
224        }
225        return null;  // OK
226    }
227
228    void readConstantPool() throws IOException {
229        int length = in.readUnsignedShort();
230        //System.err.println("reading CP, length="+length);
231
232        int[] fixups = new int[length*4];
233        int fptr = 0;
234
235        Entry[] cpMap = new Entry[length];
236        cpMap[0] = null;
237        for (int i = 1; i < length; i++) {
238            //System.err.println("reading CP elt, i="+i);
239            int tag = in.readByte();
240            switch (tag) {
241                case CONSTANT_Utf8:
242                    cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF());
243                    break;
244                case CONSTANT_Integer:
245                    {
246                        cpMap[i] = ConstantPool.getLiteralEntry(in.readInt());
247                    }
248                    break;
249                case CONSTANT_Float:
250                    {
251                        cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat());
252                    }
253                    break;
254                case CONSTANT_Long:
255                    {
256                        cpMap[i] = ConstantPool.getLiteralEntry(in.readLong());
257                        cpMap[++i] = null;
258                    }
259                    break;
260                case CONSTANT_Double:
261                    {
262                        cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble());
263                        cpMap[++i] = null;
264                    }
265                    break;
266
267                // just read the refs; do not attempt to resolve while reading
268                case CONSTANT_Class:
269                case CONSTANT_String:
270                case CONSTANT_MethodType:
271                    fixups[fptr++] = i;
272                    fixups[fptr++] = tag;
273                    fixups[fptr++] = in.readUnsignedShort();
274                    fixups[fptr++] = -1;  // empty ref2
275                    break;
276                case CONSTANT_Fieldref:
277                case CONSTANT_Methodref:
278                case CONSTANT_InterfaceMethodref:
279                case CONSTANT_NameandType:
280                    fixups[fptr++] = i;
281                    fixups[fptr++] = tag;
282                    fixups[fptr++] = in.readUnsignedShort();
283                    fixups[fptr++] = in.readUnsignedShort();
284                    break;
285                case CONSTANT_InvokeDynamic:
286                    fixups[fptr++] = i;
287                    fixups[fptr++] = tag;
288                    fixups[fptr++] = -1 ^ in.readUnsignedShort();  // not a ref
289                    fixups[fptr++] = in.readUnsignedShort();
290                    break;
291                case CONSTANT_MethodHandle:
292                    fixups[fptr++] = i;
293                    fixups[fptr++] = tag;
294                    fixups[fptr++] = -1 ^ in.readUnsignedByte();
295                    fixups[fptr++] = in.readUnsignedShort();
296                    break;
297                default:
298                    throw new ClassFormatException("Bad constant pool tag " +
299                            tag + " in File: " + cls.file.nameString +
300                            " at pos: " + inPos);
301            }
302        }
303        constantPoolLimit = inPos;
304
305        // Fix up refs, which might be out of order.
306        while (fptr > 0) {
307            if (verbose > 3)
308                Utils.log.fine("CP fixups ["+fptr/4+"]");
309            int flimit = fptr;
310            fptr = 0;
311            for (int fi = 0; fi < flimit; ) {
312                int cpi = fixups[fi++];
313                int tag = fixups[fi++];
314                int ref = fixups[fi++];
315                int ref2 = fixups[fi++];
316                if (verbose > 3)
317                    Utils.log.fine("  cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}");
318                if (ref >= 0 && cpMap[ref] == null || ref2 >= 0 && cpMap[ref2] == null) {
319                    // Defer.
320                    fixups[fptr++] = cpi;
321                    fixups[fptr++] = tag;
322                    fixups[fptr++] = ref;
323                    fixups[fptr++] = ref2;
324                    continue;
325                }
326                switch (tag) {
327                case CONSTANT_Class:
328                    cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue());
329                    break;
330                case CONSTANT_String:
331                    cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue());
332                    break;
333                case CONSTANT_Fieldref:
334                case CONSTANT_Methodref:
335                case CONSTANT_InterfaceMethodref:
336                    ClassEntry      mclass = (ClassEntry)      checkTag(cpMap[ref],  CONSTANT_Class);
337                    DescriptorEntry mdescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
338                    cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr);
339                    break;
340                case CONSTANT_NameandType:
341                    Utf8Entry mname = (Utf8Entry) checkTag(cpMap[ref],  CONSTANT_Utf8);
342                    Utf8Entry mtype = (Utf8Entry) checkTag(cpMap[ref2], CONSTANT_Signature);
343                    cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype);
344                    break;
345                case CONSTANT_MethodType:
346                    cpMap[cpi] = ConstantPool.getMethodTypeEntry((Utf8Entry) checkTag(cpMap[ref], CONSTANT_Signature));
347                    break;
348                case CONSTANT_MethodHandle:
349                    byte refKind = (byte)(-1 ^ ref);
350                    MemberEntry memRef = (MemberEntry) checkTag(cpMap[ref2], CONSTANT_AnyMember);
351                    cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef);
352                    break;
353                case CONSTANT_InvokeDynamic:
354                    DescriptorEntry idescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
355                    cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr);
356                    // Note that ref must be resolved later, using the BootstrapMethods attribute.
357                    break;
358                default:
359                    assert(false);
360                }
361            }
362            assert(fptr < flimit);  // Must make progress.
363        }
364
365        cls.cpMap = cpMap;
366    }
367
368    private /*non-static*/
369    class UnresolvedEntry extends Entry {
370        final Object[] refsOrIndexes;
371        UnresolvedEntry(byte tag, Object... refsOrIndexes) {
372            super(tag);
373            this.refsOrIndexes = refsOrIndexes;
374            ClassReader.this.haveUnresolvedEntry = true;
375        }
376        Entry resolve() {
377            Class cls = ClassReader.this.cls;
378            Entry res;
379            switch (tag) {
380            case CONSTANT_InvokeDynamic:
381                BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]);
382                DescriptorEntry         idescr = (DescriptorEntry) refsOrIndexes[1];
383                res = ConstantPool.getInvokeDynamicEntry(iboots, idescr);
384                break;
385            default:
386                throw new AssertionError();
387            }
388            return res;
389        }
390        private void unresolved() { throw new RuntimeException("unresolved entry has no string"); }
391        public int compareTo(Object x) { unresolved(); return 0; }
392        public boolean equals(Object x) { unresolved(); return false; }
393        protected int computeValueHash() { unresolved(); return 0; }
394        public String stringValue() { unresolved(); return toString(); }
395        public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; }
396    }
397
398    boolean haveUnresolvedEntry;
399    private void fixUnresolvedEntries() {
400        if (!haveUnresolvedEntry)  return;
401        Entry[] cpMap = cls.getCPMap();
402        for (int i = 0; i < cpMap.length; i++) {
403            Entry e = cpMap[i];
404            if (e instanceof UnresolvedEntry) {
405                cpMap[i] = e = ((UnresolvedEntry)e).resolve();
406                assert(!(e instanceof UnresolvedEntry));
407            }
408        }
409        haveUnresolvedEntry = false;
410    }
411
412    void readHeader() throws IOException {
413        cls.flags = readUnsignedShort();
414        cls.thisClass = readClassRef();
415        cls.superClass = readClassRefOrNull();
416        int ni = readUnsignedShort();
417        cls.interfaces = new ClassEntry[ni];
418        for (int i = 0; i < ni; i++) {
419            cls.interfaces[i] = readClassRef();
420        }
421    }
422
423    void readMembers(boolean doMethods) throws IOException {
424        int nm = readUnsignedShort();
425        for (int i = 0; i < nm; i++) {
426            readMember(doMethods);
427        }
428    }
429
430    void readMember(boolean doMethod) throws IOException {
431        int    mflags = readUnsignedShort();
432        Utf8Entry       mname = readUtf8Ref();
433        SignatureEntry  mtype = readSignatureRef();
434        DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype);
435        Class.Member m;
436        if (!doMethod)
437            m = cls.new Field(mflags, descr);
438        else
439            m = cls.new Method(mflags, descr);
440        readAttributes(!doMethod ? ATTR_CONTEXT_FIELD : ATTR_CONTEXT_METHOD,
441                       m);
442    }
443    void readAttributes(int ctype, Attribute.Holder h) throws IOException {
444        int na = readUnsignedShort();
445        if (na == 0)  return;  // nothing to do here
446        if (verbose > 3)
447            Utils.log.fine("readAttributes "+h+" ["+na+"]");
448        for (int i = 0; i < na; i++) {
449            String name = readUtf8Ref().stringValue();
450            int length = readInt();
451            // See if there is a special command that applies.
452            if (attrCommands != null) {
453                Attribute.Layout lkey = Attribute.keyForLookup(ctype, name);
454                String cmd = attrCommands.get(lkey);
455                if (cmd != null) {
456                    switch (cmd) {
457                        case "pass":
458                            String message1 = "passing attribute bitwise in " + h;
459                            throw new Attribute.FormatException(message1, ctype, name, cmd);
460                        case "error":
461                            String message2 = "attribute not allowed in " + h;
462                            throw new Attribute.FormatException(message2, ctype, name, cmd);
463                        case "strip":
464                            skip(length, name + " attribute in " + h);
465                            continue;
466                    }
467                }
468            }
469            // Find canonical instance of the requested attribute.
470            Attribute a = Attribute.lookup(Package.attrDefs, ctype, name);
471            if (verbose > 4 && a != null)
472                Utils.log.fine("pkg_attribute_lookup "+name+" = "+a);
473            if (a == null) {
474                a = Attribute.lookup(this.attrDefs, ctype, name);
475                if (verbose > 4 && a != null)
476                    Utils.log.fine("this "+name+" = "+a);
477            }
478            if (a == null) {
479                a = Attribute.lookup(null, ctype, name);
480                if (verbose > 4 && a != null)
481                    Utils.log.fine("null_attribute_lookup "+name+" = "+a);
482            }
483            if (a == null && length == 0) {
484                // Any zero-length attr is "known"...
485                // We can assume an empty attr. has an empty layout.
486                // Handles markers like Enum, Bridge, Synthetic, Deprecated.
487                a = Attribute.find(ctype, name, "");
488            }
489            boolean isStackMap = (ctype == ATTR_CONTEXT_CODE
490                                  && (name.equals("StackMap") ||
491                                      name.equals("StackMapX")));
492            if (isStackMap) {
493                // Known attribute but with a corner case format, "pass" it.
494                Code code = (Code) h;
495                final int TOO_BIG = 0x10000;
496                if (code.max_stack   >= TOO_BIG ||
497                    code.max_locals  >= TOO_BIG ||
498                    code.getLength() >= TOO_BIG ||
499                    name.endsWith("X")) {
500                    // No, we don't really know what to do with this one.
501                    // Do not compress the rare and strange "u4" and "X" cases.
502                    a = null;
503                }
504            }
505            if (a == null) {
506                if (isStackMap) {
507                    // Known attribute but w/o a format; pass it.
508                    String message = "unsupported StackMap variant in "+h;
509                    throw new Attribute.FormatException(message, ctype, name,
510                                                        "pass");
511                } else if ("strip".equals(unknownAttrCommand)) {
512                    // Skip the unknown attribute.
513                    skip(length, "unknown "+name+" attribute in "+h);
514                    continue;
515                } else {
516                    String message = " is unknown attribute in class " + h;
517                    throw new Attribute.FormatException(message, ctype, name,
518                                                        unknownAttrCommand);
519                }
520            }
521            long pos0 = inPos;  // in case we want to check it
522            if (a.layout() == Package.attrCodeEmpty) {
523                // These are hardwired.
524                Class.Method m = (Class.Method) h;
525                m.code = new Code(m);
526                try {
527                    readCode(m.code);
528                } catch (Instruction.FormatException iie) {
529                    String message = iie.getMessage() + " in " + h;
530                    throw new ClassReader.ClassFormatException(message, iie);
531                }
532                assert(length == inPos - pos0);
533                // Keep empty attribute a...
534            } else if (a.layout() == Package.attrBootstrapMethodsEmpty) {
535                assert(h == cls);
536                readBootstrapMethods(cls);
537                assert(length == inPos - pos0);
538                // Delete the attribute; it is logically part of the constant pool.
539                continue;
540            } else if (a.layout() == Package.attrInnerClassesEmpty) {
541                // These are hardwired also.
542                assert(h == cls);
543                readInnerClasses(cls);
544                assert(length == inPos - pos0);
545                // Keep empty attribute a...
546            } else if (length > 0) {
547                byte[] bytes = new byte[length];
548                in.readFully(bytes);
549                a = a.addContent(bytes);
550            }
551            if (a.size() == 0 && !a.layout().isEmpty()) {
552                throw new ClassFormatException(name +
553                        ": attribute length cannot be zero, in " + h);
554            }
555            h.addAttribute(a);
556            if (verbose > 2)
557                Utils.log.fine("read "+a);
558        }
559    }
560
561    void readCode(Code code) throws IOException {
562        code.max_stack = readUnsignedShort();
563        code.max_locals = readUnsignedShort();
564        code.bytes = new byte[readInt()];
565        in.readFully(code.bytes);
566        Entry[] cpMap = cls.getCPMap();
567        Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version);
568        int nh = readUnsignedShort();
569        code.setHandlerCount(nh);
570        for (int i = 0; i < nh; i++) {
571            code.handler_start[i] = readUnsignedShort();
572            code.handler_end[i]   = readUnsignedShort();
573            code.handler_catch[i] = readUnsignedShort();
574            code.handler_class[i] = readClassRefOrNull();
575        }
576        readAttributes(ATTR_CONTEXT_CODE, code);
577    }
578
579    void readBootstrapMethods(Class cls) throws IOException {
580        BootstrapMethodEntry[] bsms = new BootstrapMethodEntry[readUnsignedShort()];
581        for (int i = 0; i < bsms.length; i++) {
582            MethodHandleEntry bsmRef = (MethodHandleEntry) readRef(CONSTANT_MethodHandle);
583            Entry[] argRefs = new Entry[readUnsignedShort()];
584            for (int j = 0; j < argRefs.length; j++) {
585                argRefs[j] = readRef(CONSTANT_LoadableValue);
586            }
587            bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs);
588        }
589        cls.setBootstrapMethods(Arrays.asList(bsms));
590    }
591
592    void readInnerClasses(Class cls) throws IOException {
593        int nc = readUnsignedShort();
594        ArrayList<InnerClass> ics = new ArrayList<>(nc);
595        for (int i = 0; i < nc; i++) {
596            InnerClass ic =
597                new InnerClass(readClassRef(),
598                               readClassRefOrNull(),
599                               (Utf8Entry)readRefOrNull(CONSTANT_Utf8),
600                               readUnsignedShort());
601            ics.add(ic);
602        }
603        cls.innerClasses = ics;  // set directly; do not use setInnerClasses.
604        // (Later, ics may be transferred to the pkg.)
605    }
606
607    static class ClassFormatException extends IOException {
608        private static final long serialVersionUID = -3564121733989501833L;
609
610        public ClassFormatException(String message) {
611            super(message);
612        }
613
614        public ClassFormatException(String message, Throwable cause) {
615            super(message, cause);
616        }
617    }
618}
619