1 /*
  2  * Copyright (c) 2022, 2024, 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 
 26 package jdk.internal.classfile.impl;
 27 
 28 import java.lang.classfile.*;
 29 import java.lang.classfile.attribute.BootstrapMethodsAttribute;
 30 import java.lang.classfile.constantpool.ClassEntry;
 31 import java.lang.classfile.constantpool.ConstantPoolException;
 32 import java.lang.classfile.constantpool.LoadableConstantEntry;
 33 import java.lang.classfile.constantpool.PoolEntry;
 34 import java.lang.classfile.constantpool.Utf8Entry;
 35 import java.util.ArrayList;
 36 import java.util.Arrays;
 37 import java.util.List;
 38 import java.util.Objects;
 39 import java.util.Optional;
 40 import java.util.function.Function;
 41 
 42 import static java.lang.classfile.constantpool.PoolEntry.*;
 43 
 44 public final class ClassReaderImpl
 45         implements ClassReader {
 46     static final int CP_ITEM_START = 10;
 47 
 48     private final byte[] buffer;
 49     private final int metadataStart;
 50     private final int classfileLength;
 51     private final Function<Utf8Entry, AttributeMapper<?>> attributeMapper;
 52     private final int flags;
 53     private final int thisClassPos;
 54     private ClassEntry thisClass;
 55     private Optional<ClassEntry> superclass;
 56     private final int constantPoolCount;
 57     private final int[] cpOffset;
 58 
 59     final ClassFileImpl context;
 60     final int interfacesPos;
 61     final PoolEntry[] cp;
 62 
 63     private ClassModel containedClass;
 64     private List<BootstrapMethodEntryImpl> bsmEntries;
 65     private BootstrapMethodsAttribute bootstrapMethodsAttribute;
 66 
 67     ClassReaderImpl(byte[] classfileBytes,
 68                     ClassFileImpl context) {
 69         this.buffer = classfileBytes;
 70         this.classfileLength = classfileBytes.length;
 71         this.context = context;
 72         this.attributeMapper = this.context.attributeMapper();
 73         if (classfileLength < 4 || readInt(0) != 0xCAFEBABE) {
 74             throw new IllegalArgumentException("Bad magic number");
 75         }
 76         if (readU2(6) > ClassFile.latestMajorVersion()) {
 77             throw new IllegalArgumentException("Unsupported class file version: " + readU2(6));
 78         }
 79         int constantPoolCount = readU2(8);
 80         int[] cpOffset = new int[constantPoolCount];
 81         int p = CP_ITEM_START;
 82         for (int i = 1; i < cpOffset.length; ++i) {
 83             cpOffset[i] = p;
 84             int tag = readU1(p);
 85             ++p;
 86             switch (tag) {
 87                 // 2
 88                 case TAG_CLASS, TAG_METHOD_TYPE, TAG_MODULE, TAG_STRING, TAG_PACKAGE -> p += 2;
 89 
 90                 // 3
 91                 case TAG_METHOD_HANDLE -> p += 3;
 92 
 93                 // 4
 94                 case TAG_DYNAMIC, TAG_FIELDREF, TAG_FLOAT, TAG_INTEGER,
 95                      TAG_INTERFACE_METHODREF, TAG_INVOKE_DYNAMIC, TAG_METHODREF,
 96                      TAG_NAME_AND_TYPE -> p += 4;
 97 
 98                 // 8
 99                 case TAG_DOUBLE, TAG_LONG -> {
100                     p += 8;
101                     ++i;
102                 }
103                 case TAG_UTF8 -> p += 2 + readU2(p);
104                 default -> throw new ConstantPoolException(
105                         "Bad tag (" + tag + ") at index (" + i + ") position (" + p + ")");
106             }
107         }
108         this.metadataStart = p;
109         this.cpOffset = cpOffset;
110         this.constantPoolCount = constantPoolCount;
111         this.cp = new PoolEntry[constantPoolCount];
112 
113         this.flags = readU2(p);
114         this.thisClassPos = p + 2;
115         p += 6;
116         this.interfacesPos = p;
117     }
118 
119     public ClassFileImpl context() {
120         return context;
121     }
122 
123     @Override
124     public Function<Utf8Entry, AttributeMapper<?>> customAttributes() {
125         return attributeMapper;
126     }
127 
128     @Override
129     public int size() {
130         return constantPoolCount;
131     }
132 
133     @Override
134     public int flags() {
135         return flags;
136     }
137 
138     @Override
139     public ClassEntry thisClassEntry() {
140         if (thisClass == null) {
141             thisClass = readEntry(thisClassPos, ClassEntry.class);
142         }
143         return thisClass;
144     }
145 
146     @Override
147     public Optional<ClassEntry> superclassEntry() {
148         if (superclass == null) {
149             superclass = Optional.ofNullable(readEntryOrNull(thisClassPos + 2, ClassEntry.class));
150         }
151         return superclass;
152     }
153 
154     public int thisClassPos() {
155         return thisClassPos;
156     }
157 
158     @Override
159     public int classfileLength() {
160         return classfileLength;
161     }
162 
163     //------ Bootstrap Method Table handling
164 
165     @Override
166     public int bootstrapMethodCount() {
167         return bootstrapMethodsAttribute().bootstrapMethodsSize();
168     }
169 
170     @Override
171     public BootstrapMethodEntryImpl bootstrapMethodEntry(int index) {
172         if (index < 0 || index >= bootstrapMethodCount()) {
173             throw new ConstantPoolException("Bad BSM index: " + index);
174         }
175         return bsmEntries().get(index);
176     }
177 
178     private static IllegalArgumentException outOfBoundsError(IndexOutOfBoundsException cause) {
179         return new IllegalArgumentException("Reading beyond classfile bounds", cause);
180     }
181 
182     @Override
183     public int readU1(int p) {
184         try {
185             return buffer[p] & 0xFF;
186         } catch (IndexOutOfBoundsException e) {
187             throw outOfBoundsError(e);
188         }
189     }
190 
191     @Override
192     public int readU2(int p) {
193         try {
194             int b1 = buffer[p] & 0xFF;
195             int b2 = buffer[p + 1] & 0xFF;
196             return (b1 << 8) + b2;
197         } catch (IndexOutOfBoundsException e) {
198             throw outOfBoundsError(e);
199         }
200     }
201 
202     @Override
203     public int readS1(int p) {
204         try {
205             return buffer[p];
206         } catch (IndexOutOfBoundsException e) {
207             throw outOfBoundsError(e);
208         }
209     }
210 
211     @Override
212     public int readS2(int p) {
213         try {
214             int b1 = buffer[p];
215             int b2 = buffer[p + 1] & 0xFF;
216             return (b1 << 8) + b2;
217         } catch (IndexOutOfBoundsException e) {
218             throw outOfBoundsError(e);
219         }
220     }
221 
222     @Override
223     public int readInt(int p) {
224         try {
225             int ch1 = buffer[p] & 0xFF;
226             int ch2 = buffer[p + 1] & 0xFF;
227             int ch3 = buffer[p + 2] & 0xFF;
228             int ch4 = buffer[p + 3] & 0xFF;
229             return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4;
230         } catch (IndexOutOfBoundsException e) {
231             throw outOfBoundsError(e);
232         }
233     }
234 
235     @Override
236     public long readLong(int p) {
237         try {
238             return ((long) buffer[p + 0] << 56) + ((long) (buffer[p + 1] & 255) << 48) +
239                    ((long) (buffer[p + 2] & 255) << 40) + ((long) (buffer[p + 3] & 255) << 32) +
240                    ((long) (buffer[p + 4] & 255) << 24) + ((buffer[p + 5] & 255) << 16) + ((buffer[p + 6] & 255) << 8) +
241                    (buffer[p + 7] & 255);
242         } catch (IndexOutOfBoundsException e) {
243             throw outOfBoundsError(e);
244         }
245     }
246 
247     @Override
248     public float readFloat(int p) {
249         return Float.intBitsToFloat(readInt(p));
250     }
251 
252     @Override
253     public double readDouble(int p) {
254         return Double.longBitsToDouble(readLong(p));
255     }
256 
257     @Override
258     public byte[] readBytes(int p, int len) {
259         try {
260             return Arrays.copyOfRange(buffer, p, p + len);
261         } catch (IndexOutOfBoundsException e) {
262             throw outOfBoundsError(e);
263         }
264     }
265 
266     @Override
267     public void copyBytesTo(BufWriter buf, int p, int len) {
268         try {
269             buf.writeBytes(buffer, p, len);
270         } catch (IndexOutOfBoundsException e) {
271             throw outOfBoundsError(e);
272         }
273     }
274 
275     BootstrapMethodsAttribute bootstrapMethodsAttribute() {
276 
277         if (bootstrapMethodsAttribute == null) {
278             bootstrapMethodsAttribute
279                     = containedClass.findAttribute(Attributes.bootstrapMethods())
280                                     .orElse(new UnboundAttribute.EmptyBootstrapAttribute());
281         }
282 
283         return bootstrapMethodsAttribute;
284     }
285 
286     List<BootstrapMethodEntryImpl> bsmEntries() {
287         if (bsmEntries == null) {
288             bsmEntries = new ArrayList<>();
289             BootstrapMethodsAttribute attr = bootstrapMethodsAttribute();
290             List<BootstrapMethodEntry> list = attr.bootstrapMethods();
291             if (!list.isEmpty()) {
292                 for (BootstrapMethodEntry bm : list) {
293                     AbstractPoolEntry.MethodHandleEntryImpl handle = (AbstractPoolEntry.MethodHandleEntryImpl) bm.bootstrapMethod();
294                     List<LoadableConstantEntry> args = bm.arguments();
295                     int hash = BootstrapMethodEntryImpl.computeHashCode(handle, args);
296                     bsmEntries.add(new BootstrapMethodEntryImpl(this, bsmEntries.size(), hash, handle, args));
297                 }
298             }
299         }
300         return bsmEntries;
301     }
302 
303     void setContainedClass(ClassModel containedClass) {
304         this.containedClass = containedClass;
305     }
306 
307     ClassModel getContainedClass() {
308         return containedClass;
309     }
310 
311     boolean writeBootstrapMethods(BufWriterImpl buf) {
312         Optional<BootstrapMethodsAttribute> a
313                 = containedClass.findAttribute(Attributes.bootstrapMethods());
314         if (a.isEmpty())
315             return false;
316         // BootstrapMethodAttribute implementations are all internal writable
317         ((Util.Writable) a.get()).writeTo(buf);
318         return true;
319     }
320 
321     void writeConstantPoolEntries(BufWriter buf) {
322         copyBytesTo(buf, ClassReaderImpl.CP_ITEM_START,
323                     metadataStart - ClassReaderImpl.CP_ITEM_START);
324     }
325 
326     // Constantpool
327     @Override
328     public PoolEntry entryByIndex(int index) {
329         return entryByIndex(index, PoolEntry.class);
330     }
331 
332     private static boolean checkTag(int tag, Class<?> cls) {
333         var type = switch (tag) {
334             // JVMS Table 4.4-B. Constant pool tags
335             case TAG_UTF8 -> AbstractPoolEntry.Utf8EntryImpl.class;
336             case TAG_INTEGER -> AbstractPoolEntry.IntegerEntryImpl.class;
337             case TAG_FLOAT -> AbstractPoolEntry.FloatEntryImpl.class;
338             case TAG_LONG -> AbstractPoolEntry.LongEntryImpl.class;
339             case TAG_DOUBLE -> AbstractPoolEntry.DoubleEntryImpl.class;
340             case TAG_CLASS -> AbstractPoolEntry.ClassEntryImpl.class;
341             case TAG_STRING -> AbstractPoolEntry.StringEntryImpl.class;
342             case TAG_FIELDREF -> AbstractPoolEntry.FieldRefEntryImpl.class;
343             case TAG_METHODREF -> AbstractPoolEntry.MethodRefEntryImpl.class;
344             case TAG_INTERFACE_METHODREF -> AbstractPoolEntry.InterfaceMethodRefEntryImpl.class;
345             case TAG_NAME_AND_TYPE -> AbstractPoolEntry.NameAndTypeEntryImpl.class;
346             case TAG_METHOD_HANDLE -> AbstractPoolEntry.MethodHandleEntryImpl.class;
347             case TAG_METHOD_TYPE -> AbstractPoolEntry.MethodTypeEntryImpl.class;
348             case TAG_DYNAMIC -> AbstractPoolEntry.ConstantDynamicEntryImpl.class;
349             case TAG_INVOKE_DYNAMIC -> AbstractPoolEntry.InvokeDynamicEntryImpl.class;
350             case TAG_MODULE -> AbstractPoolEntry.ModuleEntryImpl.class;
351             case TAG_PACKAGE -> AbstractPoolEntry.PackageEntryImpl.class;
352             default -> null;
353         };
354         return type != null && cls.isAssignableFrom(type);
355     }
356 
357     static <T extends PoolEntry> T checkType(PoolEntry e, int index, Class<T> cls) {
358         if (cls.isInstance(e)) return cls.cast(e);
359         throw checkTypeError(index, cls);
360     }
361 
362     private static ConstantPoolException checkTypeError(int index, Class<?> cls) {
363         return new ConstantPoolException("Not a " + cls.getSimpleName() + " at index: " + index);
364     }
365 
366     @Override
367     public <T extends PoolEntry> T entryByIndex(int index, Class<T> cls) {
368         Objects.requireNonNull(cls);
369         if (index <= 0 || index >= constantPoolCount) {
370             throw new ConstantPoolException("Bad CP index: " + index);
371         }
372         PoolEntry info = cp[index];
373         if (info == null) {
374             int offset = cpOffset[index];
375             if (offset == 0) {
376                 throw new ConstantPoolException("Unusable CP index: " + index);
377             }
378             int tag = readU1(offset);
379             if (!checkTag(tag, cls)) {
380                 throw new ConstantPoolException(
381                         "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + "), expected " + cls.getSimpleName());
382             }
383             final int q = offset + 1;
384             info = switch (tag) {
385                 case TAG_UTF8 -> new AbstractPoolEntry.Utf8EntryImpl(this, index, buffer, q + 2, readU2(q));
386                 case TAG_INTEGER -> new AbstractPoolEntry.IntegerEntryImpl(this, index, readInt(q));
387                 case TAG_FLOAT -> new AbstractPoolEntry.FloatEntryImpl(this, index, readFloat(q));
388                 case TAG_LONG -> new AbstractPoolEntry.LongEntryImpl(this, index, readLong(q));
389                 case TAG_DOUBLE -> new AbstractPoolEntry.DoubleEntryImpl(this, index, readDouble(q));
390                 case TAG_CLASS -> new AbstractPoolEntry.ClassEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class));
391                 case TAG_STRING -> new AbstractPoolEntry.StringEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class));
392                 case TAG_FIELDREF -> new AbstractPoolEntry.FieldRefEntryImpl(this, index, readEntry(q, AbstractPoolEntry.ClassEntryImpl.class),
393                         readEntry(q + 2, AbstractPoolEntry.NameAndTypeEntryImpl.class));
394                 case TAG_METHODREF -> new AbstractPoolEntry.MethodRefEntryImpl(this, index, readEntry(q, AbstractPoolEntry.ClassEntryImpl.class),
395                         readEntry(q + 2, AbstractPoolEntry.NameAndTypeEntryImpl.class));
396                 case TAG_INTERFACE_METHODREF -> new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, index, readEntry(q, AbstractPoolEntry.ClassEntryImpl.class),
397                         readEntry(q + 2, AbstractPoolEntry.NameAndTypeEntryImpl.class));
398                 case TAG_NAME_AND_TYPE -> new AbstractPoolEntry.NameAndTypeEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class),
399                         readEntry(q + 2, AbstractPoolEntry.Utf8EntryImpl.class));
400                 case TAG_METHOD_HANDLE -> new AbstractPoolEntry.MethodHandleEntryImpl(this, index, readU1(q),
401                                                                                      readEntry(q + 1, AbstractPoolEntry.AbstractMemberRefEntry.class));
402                 case TAG_METHOD_TYPE -> new AbstractPoolEntry.MethodTypeEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class));
403                 case TAG_DYNAMIC -> new AbstractPoolEntry.ConstantDynamicEntryImpl(this, index, readU2(q), readEntry(q + 2, AbstractPoolEntry.NameAndTypeEntryImpl.class));
404                 case TAG_INVOKE_DYNAMIC -> new AbstractPoolEntry.InvokeDynamicEntryImpl(this, index, readU2(q), readEntry(q + 2, AbstractPoolEntry.NameAndTypeEntryImpl.class));
405                 case TAG_MODULE -> new AbstractPoolEntry.ModuleEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class));
406                 case TAG_PACKAGE -> new AbstractPoolEntry.PackageEntryImpl(this, index, readEntry(q, AbstractPoolEntry.Utf8EntryImpl.class));
407                 default -> throw new ConstantPoolException(
408                         "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + ")");
409             };
410             cp[index] = info;
411         }
412         return checkType(info, index, cls);
413     }
414 
415     public int skipAttributeHolder(int offset) {
416         int p = offset;
417         int cnt = readU2(p);
418         p += 2;
419         for (int i = 0; i < cnt; ++i) {
420             int len = readInt(p + 2);
421             p += 6;
422             if (len < 0 || len > classfileLength - p) {
423                 throw new IllegalArgumentException("attribute " + readEntry(p - 6, Utf8Entry.class).stringValue() + " too big to handle");
424             }
425             p += len;
426         }
427         return p;
428     }
429 
430     @Override
431     public PoolEntry readEntry(int pos) {
432         return entryByIndex(readU2(pos));
433     }
434 
435     @Override
436     public <T extends PoolEntry> T readEntry(int pos, Class<T> cls) {
437         Objects.requireNonNull(cls);
438         return entryByIndex(readU2(pos), cls);
439     }
440 
441     @Override
442     public PoolEntry readEntryOrNull(int pos) {
443         int index = readU2(pos);
444         if (index == 0) {
445             return null;
446         }
447         return entryByIndex(index);
448     }
449 
450     @Override
451     public <T extends PoolEntry> T readEntryOrNull(int offset, Class<T> cls) {
452         Objects.requireNonNull(cls);
453         int index = readU2(offset);
454         if (index == 0) {
455             return null;
456         }
457         return entryByIndex(index, cls);
458     }
459 
460     public boolean compare(BufWriterImpl bufWriter,
461                            int bufWriterOffset,
462                            int classReaderOffset,
463                            int length) {
464         try {
465             return Arrays.equals(bufWriter.elems,
466                                  bufWriterOffset, bufWriterOffset + length,
467                                  buffer, classReaderOffset, classReaderOffset + length);
468         } catch (IndexOutOfBoundsException e) {
469             throw outOfBoundsError(e);
470         }
471     }
472 }