View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.asm.methods;
7   
8   import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.FIELDORMETH;
9   import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.IINC_INSN;
10  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.IMPLVAR;
11  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.INDYMETH;
12  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.ITFMETH;
13  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LABEL;
14  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LABELW;
15  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LDCW_INSN;
16  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LDC_INSN;
17  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LOOK_INSN;
18  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.MANA_INSN;
19  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.NOARG;
20  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.SBYTE;
21  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.SHORT;
22  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.TABL_INSN;
23  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.TYPE_INSN;
24  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.VAR;
25  import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.WIDE_INSN;
26  import static mockit.asm.jvmConstants.Opcodes.IINC;
27  import static mockit.asm.jvmConstants.Opcodes.ILOAD;
28  import static mockit.asm.jvmConstants.Opcodes.ILOAD_0;
29  import static mockit.asm.jvmConstants.Opcodes.INVOKEINTERFACE;
30  import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
31  import static mockit.asm.jvmConstants.Opcodes.ISTORE;
32  import static mockit.asm.jvmConstants.Opcodes.ISTORE_0;
33  
34  import edu.umd.cs.findbugs.annotations.NonNull;
35  import edu.umd.cs.findbugs.annotations.Nullable;
36  
37  import mockit.asm.AnnotatedReader;
38  import mockit.asm.annotations.AnnotationVisitor;
39  import mockit.asm.classes.ClassReader;
40  import mockit.asm.classes.ClassVisitor;
41  import mockit.asm.controlFlow.Label;
42  import mockit.asm.jvmConstants.ConstantPoolTypes;
43  import mockit.asm.jvmConstants.JVMInstruction;
44  import mockit.asm.util.MethodHandle;
45  
46  import org.checkerframework.checker.index.qual.NonNegative;
47  
48  @SuppressWarnings("OverlyComplexClass")
49  public final class MethodReader extends AnnotatedReader {
50      @NonNull
51      private final ClassReader cr;
52      @NonNull
53      private final ClassVisitor cv;
54  
55      @Nullable
56      private String[] throwsClauseTypes;
57  
58      /**
59       * The name of the method currently being parsed.
60       */
61      private String name;
62  
63      /**
64       * The descriptor of the method currently being parsed.
65       */
66      private String desc;
67  
68      @NonNegative
69      private int methodStartCodeIndex;
70      @NonNegative
71      private int bodyStartCodeIndex;
72      @NonNegative
73      private int parameterAnnotationsCodeIndex;
74  
75      /**
76       * The label objects, indexed by bytecode offset, of the method currently being parsed (only bytecode offsets for
77       * which a label is needed have a non null associated <code>Label</code> object).
78       */
79      private Label[] labels;
80  
81      /**
82       * The visitor to visit the method being read.
83       */
84      private MethodVisitor mv;
85  
86      public MethodReader(@NonNull ClassReader cr, @NonNull ClassVisitor cv) {
87          super(cr);
88          this.cr = cr;
89          this.cv = cv;
90      }
91  
92      /**
93       * Reads each method and constructor in the class, making the {@linkplain #cr class reader}'s
94       * {@linkplain ClassReader#cv visitor} visit it.
95       *
96       * @return the offset of the first byte following the last method in the class
97       */
98      @NonNegative
99      public int readMethods() {
100         for (int methodCount = readUnsignedShort(); methodCount > 0; methodCount--) {
101             readMethod();
102         }
103 
104         return codeIndex;
105     }
106 
107     private void readMethod() {
108         readMethodDeclaration();
109         parameterAnnotationsCodeIndex = 0;
110 
111         readAttributes();
112 
113         int currentCodeIndex = codeIndex;
114         readMethodBody();
115         codeIndex = currentCodeIndex;
116     }
117 
118     private void readMethodDeclaration() {
119         access = readUnsignedShort();
120         name = readNonnullUTF8();
121         desc = readNonnullUTF8();
122 
123         methodStartCodeIndex = codeIndex;
124         bodyStartCodeIndex = 0;
125         throwsClauseTypes = null;
126     }
127 
128     @Nullable
129     @Override
130     protected Boolean readAttribute(@NonNull String attributeName) {
131         switch (attributeName) {
132             case "Code":
133                 bodyStartCodeIndex = codeIndex;
134                 return false;
135             case "Exceptions":
136                 readExceptionsInThrowsClause();
137                 return true;
138             case "RuntimeVisibleParameterAnnotations":
139                 parameterAnnotationsCodeIndex = codeIndex;
140                 return false;
141             default:
142                 return null;
143         }
144     }
145 
146     private void readExceptionsInThrowsClause() {
147         int n = readUnsignedShort();
148         String[] typeDescs = new String[n];
149 
150         for (int i = 0; i < n; i++) {
151             typeDescs[i] = readNonnullClass();
152         }
153 
154         throwsClauseTypes = typeDescs;
155     }
156 
157     private void readMethodBody() {
158         mv = cv.visitMethod(access, name, desc, signature, throwsClauseTypes);
159 
160         if (mv == null) {
161             return;
162         }
163 
164         if (mv instanceof MethodWriter) {
165             copyMethodBody();
166             return;
167         }
168 
169         readAnnotations(mv);
170         readAnnotationsOnAllParameters();
171 
172         if (bodyStartCodeIndex > 0) {
173             codeIndex = bodyStartCodeIndex;
174             readCode();
175         }
176 
177         mv.visitEnd();
178     }
179 
180     /**
181      * If the returned <code>MethodVisitor</code> is in fact a <code>MethodWriter</code>, it means there is no method
182      * adapter between the reader and the writer. In addition, it's assumed that the writer's constant pool was copied
183      * from this reader (mw.cw.cr == this.cr), and the signature of the method has not been changed; then, we skip all
184      * visit events and just copy the original code of the method to the writer.
185      */
186     private void copyMethodBody() {
187         // We do not copy directly the code into MethodWriter to save a byte array copy operation.
188         // The real copy will be done in ClassWriter.toByteArray().
189         MethodWriter mw = (MethodWriter) mv;
190         mw.classReaderOffset = methodStartCodeIndex;
191         mw.classReaderLength = codeIndex - methodStartCodeIndex;
192     }
193 
194     private void readAnnotationsOnAllParameters() {
195         if (parameterAnnotationsCodeIndex > 0) {
196             codeIndex = parameterAnnotationsCodeIndex;
197             int parameters = readUnsignedByte();
198 
199             for (int i = 0; i < parameters; i++) {
200                 readParameterAnnotations(i);
201             }
202         }
203     }
204 
205     private void readParameterAnnotations(@NonNegative int parameterIndex) {
206         for (int annotationCount = readUnsignedShort(); annotationCount > 0; annotationCount--) {
207             String annotationTypeDesc = readNonnullUTF8();
208             AnnotationVisitor av = mv.visitParameterAnnotation(parameterIndex, annotationTypeDesc);
209             readAnnotationValues(av);
210         }
211     }
212 
213     private void readCode() {
214         int maxStack = readUnsignedShort();
215         codeIndex += 2; // skip maxLocals
216 
217         int codeLength = readInt();
218         labels = new Label[codeLength + 2];
219 
220         // Reads the bytecode to find the labels.
221         int codeStartIndex = codeIndex;
222         int codeEndIndex = codeStartIndex + codeLength;
223 
224         readAllLabelsInCodeBlock(codeStartIndex, codeEndIndex);
225         readTryCatchBlocks();
226 
227         // Reads the code attributes.
228         int varTableCodeIndex = 0;
229         int[] typeTable = null;
230 
231         for (int attributeCount = readUnsignedShort(); attributeCount > 0; attributeCount--) {
232             String attrName = readNonnullUTF8();
233             int codeOffset = readInt();
234 
235             switch (attrName) {
236                 case "LocalVariableTable":
237                     varTableCodeIndex = codeIndex;
238                     readLocalVariableTable();
239                     break;
240                 case "LocalVariableTypeTable":
241                     typeTable = readLocalVariableTypeTable();
242                     break;
243                 case "LineNumberTable":
244                     readLineNumberTable();
245                     break;
246                 default:
247                     codeIndex += codeOffset;
248             }
249         }
250 
251         readBytecodeInstructionsInCodeBlock(codeStartIndex, codeEndIndex);
252         visitEndLabel(codeLength);
253         readLocalVariableTables(varTableCodeIndex, typeTable);
254         mv.visitMaxStack(maxStack);
255     }
256 
257     private void readAllLabelsInCodeBlock(@NonNegative int codeStart, @NonNegative int codeEnd) {
258         getOrCreateLabel(codeEnd - codeStart + 1);
259 
260         while (codeIndex < codeEnd) {
261             int offset = codeIndex - codeStart;
262             readLabelForInstructionIfAny(offset);
263         }
264     }
265 
266     @NonNull
267     private Label getOrCreateLabel(@NonNegative int offset) {
268         Label label = labels[offset];
269 
270         if (label == null) {
271             label = new Label();
272             labels[offset] = label;
273         }
274 
275         return label;
276     }
277 
278     private void readLabelForInstructionIfAny(@NonNegative int offset) {
279         int opcode = readUnsignedByte();
280         byte instructionType = JVMInstruction.TYPE[opcode];
281         boolean tablInsn = instructionType == TABL_INSN;
282 
283         if (tablInsn || instructionType == LOOK_INSN) {
284             readLabelsForSwitchInstruction(offset, tablInsn);
285         } else {
286             readLabelsForNonSwitchInstruction(offset, instructionType);
287         }
288     }
289 
290     private void readLabelsForSwitchInstruction(@NonNegative int offset, boolean tableNotLookup) {
291         readSwitchDefaultLabel(offset);
292 
293         int caseCount;
294 
295         if (tableNotLookup) {
296             int min = readInt();
297             int max = readInt();
298             caseCount = max - min + 1;
299         } else {
300             caseCount = readInt();
301         }
302 
303         while (caseCount > 0) {
304             if (!tableNotLookup) {
305                 codeIndex += 4;
306             }
307 
308             int caseOffset = offset + readInt();
309             getOrCreateLabel(caseOffset);
310             caseCount--;
311         }
312     }
313 
314     @NonNull
315     private Label readSwitchDefaultLabel(@NonNegative int offset) {
316         codeIndex += 3 - (offset & 3); // skips 0 to 3 padding bytes
317 
318         int defaultLabelOffset = readInt();
319         return getOrCreateLabel(offset + defaultLabelOffset);
320     }
321 
322     @SuppressWarnings("OverlyLongMethod")
323     private void readLabelsForNonSwitchInstruction(@NonNegative int offset, byte instructionType) {
324         int codeIndexSize = 0;
325 
326         // noinspection SwitchStatementWithoutDefaultBranch
327         switch (instructionType) {
328             case NOARG:
329             case IMPLVAR:
330                 return;
331             case LABEL:
332                 int labelOffset = offset + readShort();
333                 getOrCreateLabel(labelOffset);
334                 return;
335             case LABELW:
336                 int labelOffsetW = offset + readInt();
337                 getOrCreateLabel(labelOffsetW);
338                 return;
339             case WIDE_INSN:
340                 int opcode = readUnsignedByte();
341                 codeIndexSize = opcode == IINC ? 4 : 2;
342                 break;
343             case VAR:
344             case SBYTE:
345             case LDC_INSN:
346                 codeIndexSize = 1;
347                 break;
348             case SHORT:
349             case LDCW_INSN:
350             case TYPE_INSN:
351             case FIELDORMETH:
352             case IINC_INSN:
353                 codeIndexSize = 2;
354                 break;
355             case ITFMETH:
356             case INDYMETH:
357                 codeIndexSize = 4;
358                 break;
359             case MANA_INSN:
360                 codeIndexSize = 3;
361         }
362 
363         codeIndex += codeIndexSize;
364     }
365 
366     /**
367      * Reads the try catch entries to find the labels, and also visits them.
368      */
369     private void readTryCatchBlocks() {
370         for (int blockCount = readUnsignedShort(); blockCount > 0; blockCount--) {
371             Label start = getOrCreateLabel(readUnsignedShort());
372             Label end = getOrCreateLabel(readUnsignedShort());
373             Label handler = getOrCreateLabel(readUnsignedShort());
374             String type = readUTF8(readItem());
375 
376             mv.visitTryCatchBlock(start, end, handler, type);
377         }
378     }
379 
380     private void readLocalVariableTable() {
381         for (int localVarCount = readUnsignedShort(); localVarCount > 0; localVarCount--) {
382             int labelOffset = readUnsignedShort();
383             getOrCreateDebugLabel(labelOffset);
384 
385             labelOffset += readUnsignedShort();
386             getOrCreateDebugLabel(labelOffset);
387 
388             codeIndex += 6;
389         }
390     }
391 
392     @NonNull
393     private Label getOrCreateDebugLabel(@NonNegative int offset) {
394         Label label = labels[offset];
395 
396         if (label == null) {
397             label = new Label();
398             label.markAsDebug();
399             labels[offset] = label;
400         }
401 
402         return label;
403     }
404 
405     @NonNull
406     private int[] readLocalVariableTypeTable() {
407         int typeTableSize = 3 * readUnsignedShort();
408         int[] typeTable = new int[typeTableSize];
409 
410         while (typeTableSize > 0) {
411             int startIndex = readUnsignedShort();
412             int signatureCodeIndex = codeIndex + 4;
413             codeIndex += 6;
414             int varIndex = readUnsignedShort();
415 
416             typeTableSize--;
417             typeTable[typeTableSize] = signatureCodeIndex;
418             typeTableSize--;
419             typeTable[typeTableSize] = varIndex;
420             typeTableSize--;
421             typeTable[typeTableSize] = startIndex;
422         }
423 
424         return typeTable;
425     }
426 
427     private void readLineNumberTable() {
428         for (int lineCount = readUnsignedShort(); lineCount > 0; lineCount--) {
429             int labelOffset = readUnsignedShort();
430             Label debugLabel = getOrCreateDebugLabel(labelOffset);
431             debugLabel.line = readUnsignedShort();
432         }
433     }
434 
435     @SuppressWarnings({ "OverlyComplexMethod", "OverlyLongMethod" })
436     private void readBytecodeInstructionsInCodeBlock(@NonNegative int codeStartIndex, @NonNegative int codeEndIndex) {
437         codeIndex = codeStartIndex;
438 
439         while (codeIndex < codeEndIndex) {
440             int offset = codeIndex - codeStartIndex;
441             visitLabelAndLineNumber(offset);
442 
443             int opcode = readUnsignedByte();
444 
445             // noinspection SwitchStatementWithoutDefaultBranch
446             switch (JVMInstruction.TYPE[opcode]) {
447                 case NOARG:
448                     mv.visitInsn(opcode);
449                     break;
450                 case VAR:
451                     readVariableAccessInstruction(opcode);
452                     break;
453                 case IMPLVAR:
454                     readInstructionWithImplicitVariable(opcode);
455                     break;
456                 case TYPE_INSN:
457                     readTypeInsn(opcode);
458                     break;
459                 case LABEL:
460                     readJump(opcode, offset);
461                     break;
462                 case LABELW:
463                     readWideJump(opcode, offset);
464                     break;
465                 case LDC_INSN:
466                     readLDC();
467                     break;
468                 case LDCW_INSN:
469                     readLDCW();
470                     break;
471                 case IINC_INSN:
472                     readIInc();
473                     break;
474                 case SBYTE:
475                     readInstructionTakingASignedByte(opcode);
476                     break;
477                 case SHORT:
478                     readInstructionTakingASignedShort(opcode);
479                     break;
480                 case TABL_INSN:
481                     readSwitchInstruction(offset, true);
482                     break;
483                 case LOOK_INSN:
484                     readSwitchInstruction(offset, false);
485                     break;
486                 case MANA_INSN:
487                     readMultiANewArray();
488                     break;
489                 case WIDE_INSN:
490                     readWideInstruction();
491                     break;
492                 case FIELDORMETH:
493                 case ITFMETH:
494                     readFieldOrInvokeInstruction(opcode);
495                     break;
496                 case INDYMETH:
497                     readInvokeDynamicInstruction();
498                     break;
499             }
500         }
501     }
502 
503     private void visitLabelAndLineNumber(@NonNegative int offset) {
504         Label label = labels[offset];
505 
506         if (label != null) {
507             mv.visitLabel(label);
508 
509             int lineNumber = label.line;
510 
511             if (lineNumber > 0) {
512                 mv.visitLineNumber(lineNumber, label);
513             }
514         }
515     }
516 
517     private void readVariableAccessInstruction(int opcode) {
518         int varIndex = readUnsignedByte();
519         mv.visitVarInsn(opcode, varIndex);
520     }
521 
522     private void readInstructionWithImplicitVariable(int opcode) {
523         int opcodeBase;
524 
525         if (opcode > ISTORE) {
526             opcode -= ISTORE_0;
527             opcodeBase = ISTORE;
528         } else {
529             opcode -= ILOAD_0;
530             opcodeBase = ILOAD;
531         }
532 
533         int localVarOpcode = opcodeBase + (opcode >> 2);
534         int varIndex = opcode & 3;
535 
536         mv.visitVarInsn(localVarOpcode, varIndex);
537     }
538 
539     private void readTypeInsn(int opcode) {
540         String typeDesc = readNonnullClass();
541         mv.visitTypeInsn(opcode, typeDesc);
542     }
543 
544     private void readJump(int opcode, @NonNegative int offset) {
545         short targetIndex = readShort();
546         Label targetLabel = labels[offset + targetIndex];
547         mv.visitJumpInsn(opcode, targetLabel);
548     }
549 
550     private void readWideJump(int opcode, @NonNegative int offset) {
551         int targetIndex = readInt();
552         Label targetLabel = labels[offset + targetIndex];
553         mv.visitJumpInsn(opcode - 33, targetLabel);
554     }
555 
556     private void readLDC() {
557         int constIndex = readUnsignedByte();
558         Object cst = readConst(constIndex);
559         mv.visitLdcInsn(cst);
560     }
561 
562     private void readLDCW() {
563         Object cst = readConstItem();
564         mv.visitLdcInsn(cst);
565     }
566 
567     private void readIInc() {
568         int varIndex = readUnsignedByte();
569         int increment = readSignedByte();
570         mv.visitIincInsn(varIndex, increment);
571     }
572 
573     private void readInstructionTakingASignedByte(int opcode) {
574         int operand = readSignedByte();
575         mv.visitIntInsn(opcode, operand);
576     }
577 
578     private void readInstructionTakingASignedShort(int opcode) {
579         int operand = readShort();
580         mv.visitIntInsn(opcode, operand);
581     }
582 
583     private void readSwitchInstruction(@NonNegative int offset, boolean tableNotLookup) {
584         Label dfltLabel = readSwitchDefaultLabel(offset);
585         int min;
586         int max;
587         int caseCount;
588         int[] keys;
589 
590         if (tableNotLookup) {
591             min = readInt();
592             max = readInt();
593             caseCount = max - min + 1;
594             keys = null;
595         } else {
596             min = max = 0;
597             caseCount = readInt();
598             keys = new int[caseCount];
599         }
600 
601         Label[] handlerLabels = readSwitchCaseLabels(offset, caseCount, keys);
602 
603         if (tableNotLookup) {
604             mv.visitTableSwitchInsn(min, max, dfltLabel, handlerLabels);
605         } else {
606             mv.visitLookupSwitchInsn(dfltLabel, keys, handlerLabels);
607         }
608     }
609 
610     @NonNull
611     private Label[] readSwitchCaseLabels(@NonNegative int offset, @NonNegative int caseCount, @Nullable int[] keys) {
612         Label[] caseLabels = new Label[caseCount];
613 
614         for (int i = 0; i < caseCount; i++) {
615             if (keys != null) {
616                 keys[i] = readInt();
617             }
618 
619             int labelOffset = offset + readInt();
620             caseLabels[i] = labels[labelOffset];
621         }
622 
623         return caseLabels;
624     }
625 
626     private void readMultiANewArray() {
627         String arrayTypeDesc = readNonnullClass();
628         int dims = readUnsignedByte();
629         mv.visitMultiANewArrayInsn(arrayTypeDesc, dims);
630     }
631 
632     private void readWideInstruction() {
633         int opcode = readUnsignedByte();
634         int varIndex = readUnsignedShort();
635 
636         if (opcode == IINC) {
637             int increment = readShort();
638             mv.visitIincInsn(varIndex, increment);
639         } else {
640             mv.visitVarInsn(opcode, varIndex);
641             codeIndex += 2;
642         }
643     }
644 
645     private void readFieldOrInvokeInstruction(int opcode) {
646         int ownerCodeIndex = readItem();
647         String owner = readNonnullClass(ownerCodeIndex);
648         int nameCodeIndex = readItem(ownerCodeIndex + 2);
649         String memberName = readNonnullUTF8(nameCodeIndex);
650         String memberDesc = readNonnullUTF8(nameCodeIndex + 2);
651 
652         if (opcode < INVOKEVIRTUAL) {
653             mv.visitFieldInsn(opcode, owner, memberName, memberDesc);
654         } else {
655             boolean itf = code[ownerCodeIndex - 1] == ConstantPoolTypes.IMETHOD_REF;
656             mv.visitMethodInsn(opcode, owner, memberName, memberDesc, itf);
657 
658             if (opcode == INVOKEINTERFACE) {
659                 codeIndex += 2;
660             }
661         }
662     }
663 
664     private void readInvokeDynamicInstruction() {
665         int cpIndex = readItem();
666         int bsmStartIndex = readUnsignedShort(cpIndex);
667         int nameCodeIndex = readItem(cpIndex + 2);
668 
669         String bsmName = readNonnullUTF8(nameCodeIndex);
670         String bsmDesc = readNonnullUTF8(nameCodeIndex + 2);
671 
672         int bsmCodeIndex = cr.getBSMCodeIndex(bsmStartIndex);
673         MethodHandle bsmHandle = readMethodHandleItem(bsmCodeIndex);
674         int bsmArgCount = readUnsignedShort(bsmCodeIndex + 2);
675         bsmCodeIndex += 4;
676         Object[] bsmArgs = new Object[bsmArgCount];
677 
678         for (int i = 0; i < bsmArgCount; i++) {
679             bsmArgs[i] = readConstItem(bsmCodeIndex);
680             bsmCodeIndex += 2;
681         }
682 
683         mv.visitInvokeDynamicInsn(bsmName, bsmDesc, bsmHandle, bsmArgs);
684         codeIndex += 2;
685     }
686 
687     private void visitEndLabel(@NonNegative int codeLength) {
688         Label label = labels[codeLength];
689 
690         if (label != null) {
691             mv.visitLabel(label);
692         }
693     }
694 
695     private void readLocalVariableTables(@NonNegative int varTableCodeIndex, @Nullable int[] typeTable) {
696         if (varTableCodeIndex > 0) {
697             codeIndex = varTableCodeIndex;
698 
699             for (int localVarCount = readUnsignedShort(); localVarCount > 0; localVarCount--) {
700                 int start = readUnsignedShort();
701                 int length = readUnsignedShort();
702                 String varName = readNonnullUTF8();
703                 String varDesc = readNonnullUTF8();
704                 int index = readUnsignedShort();
705                 String varSignature = typeTable == null ? null : getLocalVariableSignature(typeTable, start, index);
706 
707                 mv.visitLocalVariable(varName, varDesc, varSignature, labels[start], labels[start + length], index);
708             }
709         }
710     }
711 
712     @Nullable
713     private String getLocalVariableSignature(@NonNull int[] typeTable, @NonNegative int start, @NonNegative int index) {
714         for (int i = 0, n = typeTable.length; i < n; i += 3) {
715             if (typeTable[i] == start && typeTable[i + 1] == index) {
716                 return readNonnullUTF8(typeTable[i + 2]);
717             }
718         }
719 
720         return null;
721     }
722 }