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 }