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