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