| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.android.desugar; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.devtools.build.android.desugar.io.BitFlags.isStatic; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableList; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import com.google.errorprone.annotations.CheckReturnValue; |
| import java.util.ArrayList; |
| import java.util.Optional; |
| import org.objectweb.asm.Handle; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| |
| /** |
| * Perform type inference for byte code (local variables and operand stack) with the help of stack |
| * map frames. |
| * |
| * <p>Note: This class only guarantees the correctness of reference types, but not the primitive |
| * types, though they might be correct too. |
| */ |
| @CheckReturnValue // a good practice in general, and preparation for copying this into Truth |
| final class BytecodeTypeInference extends MethodVisitor { |
| |
| private boolean used = false; |
| private final ArrayList<InferredType> localVariableSlots; |
| private final ArrayList<InferredType> operandStack = new ArrayList<>(); |
| private FrameInfo previousFrame; |
| /** For debugging purpose. */ |
| private final String methodSignature; |
| |
| BytecodeTypeInference(int access, String owner, String name, String methodDescriptor) { |
| super(Opcodes.ASM8); |
| localVariableSlots = createInitialLocalVariableTypes(access, owner, name, methodDescriptor); |
| previousFrame = FrameInfo.create(ImmutableList.copyOf(localVariableSlots), ImmutableList.of()); |
| this.methodSignature = owner + "." + name + methodDescriptor; |
| } |
| |
| void setDelegateMethodVisitor(MethodVisitor visitor) { |
| mv = visitor; |
| } |
| |
| @Override |
| public void visitCode() { |
| checkState(!used, "Cannot reuse this method visitor."); |
| used = true; |
| super.visitCode(); |
| } |
| |
| /** Returns the type of a value in the operand. 0 means the top of the stack. */ |
| InferredType getTypeOfOperandFromTop(int offsetFromTop) { |
| int index = operandStack.size() - 1 - offsetFromTop; |
| checkState( |
| index >= 0, |
| "Invalid offset %s in the list of size %s. The current method is %s", |
| offsetFromTop, |
| operandStack.size(), |
| methodSignature); |
| return operandStack.get(index); |
| } |
| |
| String getOperandStackAsString() { |
| return operandStack.toString(); |
| } |
| |
| String getLocalsAsString() { |
| return localVariableSlots.toString(); |
| } |
| |
| @Override |
| public void visitInsn(int opcode) { |
| switch (opcode) { |
| case Opcodes.NOP: |
| case Opcodes.INEG: |
| case Opcodes.LNEG: |
| case Opcodes.FNEG: |
| case Opcodes.DNEG: |
| case Opcodes.I2B: |
| case Opcodes.I2C: |
| case Opcodes.I2S: |
| case Opcodes.RETURN: |
| break; |
| case Opcodes.ACONST_NULL: |
| push(InferredType.NULL); |
| break; |
| case Opcodes.ICONST_M1: |
| case Opcodes.ICONST_0: |
| case Opcodes.ICONST_1: |
| case Opcodes.ICONST_2: |
| case Opcodes.ICONST_3: |
| case Opcodes.ICONST_4: |
| case Opcodes.ICONST_5: |
| push(InferredType.INT); |
| break; |
| case Opcodes.LCONST_0: |
| case Opcodes.LCONST_1: |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.FCONST_0: |
| case Opcodes.FCONST_1: |
| case Opcodes.FCONST_2: |
| push(InferredType.FLOAT); |
| break; |
| case Opcodes.DCONST_0: |
| case Opcodes.DCONST_1: |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.IALOAD: |
| case Opcodes.BALOAD: |
| case Opcodes.CALOAD: |
| case Opcodes.SALOAD: |
| pop(2); |
| push(InferredType.INT); |
| break; |
| case Opcodes.LALOAD: |
| case Opcodes.D2L: |
| pop(2); |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.DALOAD: |
| case Opcodes.L2D: |
| pop(2); |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.AALOAD: |
| InferredType arrayType = pop(2); |
| InferredType elementType = arrayType.getElementTypeIfArrayOrThrow(); |
| push(elementType); |
| break; |
| case Opcodes.IASTORE: |
| case Opcodes.BASTORE: |
| case Opcodes.CASTORE: |
| case Opcodes.SASTORE: |
| case Opcodes.FASTORE: |
| case Opcodes.AASTORE: |
| pop(3); |
| break; |
| case Opcodes.LASTORE: |
| case Opcodes.DASTORE: |
| pop(4); |
| break; |
| case Opcodes.POP: |
| case Opcodes.IRETURN: |
| case Opcodes.FRETURN: |
| case Opcodes.ARETURN: |
| case Opcodes.ATHROW: |
| case Opcodes.MONITORENTER: |
| case Opcodes.MONITOREXIT: |
| pop(); |
| break; |
| case Opcodes.POP2: |
| case Opcodes.LRETURN: |
| case Opcodes.DRETURN: |
| pop(2); |
| break; |
| case Opcodes.DUP: |
| push(top()); |
| break; |
| case Opcodes.DUP_X1: |
| { |
| InferredType top = pop(); |
| InferredType next = pop(); |
| push(top); |
| push(next); |
| push(top); |
| break; |
| } |
| case Opcodes.DUP_X2: |
| { |
| InferredType top = pop(); |
| InferredType next = pop(); |
| InferredType bottom = pop(); |
| push(top); |
| push(bottom); |
| push(next); |
| push(top); |
| break; |
| } |
| case Opcodes.DUP2: |
| { |
| InferredType top = pop(); |
| InferredType next = pop(); |
| push(next); |
| push(top); |
| push(next); |
| push(top); |
| break; |
| } |
| case Opcodes.DUP2_X1: |
| { |
| InferredType top = pop(); |
| InferredType next = pop(); |
| InferredType bottom = pop(); |
| push(next); |
| push(top); |
| push(bottom); |
| push(next); |
| push(top); |
| break; |
| } |
| case Opcodes.DUP2_X2: |
| { |
| InferredType t1 = pop(); |
| InferredType t2 = pop(); |
| InferredType t3 = pop(); |
| InferredType t4 = pop(); |
| push(t2); |
| push(t1); |
| push(t4); |
| push(t3); |
| push(t2); |
| push(t1); |
| break; |
| } |
| case Opcodes.SWAP: |
| { |
| InferredType top = pop(); |
| InferredType next = pop(); |
| push(top); |
| push(next); |
| break; |
| } |
| case Opcodes.IADD: |
| case Opcodes.ISUB: |
| case Opcodes.IMUL: |
| case Opcodes.IDIV: |
| case Opcodes.IREM: |
| case Opcodes.ISHL: |
| case Opcodes.ISHR: |
| case Opcodes.IUSHR: |
| case Opcodes.IAND: |
| case Opcodes.IOR: |
| case Opcodes.IXOR: |
| case Opcodes.L2I: |
| case Opcodes.D2I: |
| case Opcodes.FCMPL: |
| case Opcodes.FCMPG: |
| pop(2); |
| push(InferredType.INT); |
| break; |
| |
| case Opcodes.LADD: |
| case Opcodes.LSUB: |
| case Opcodes.LMUL: |
| case Opcodes.LDIV: |
| case Opcodes.LREM: |
| case Opcodes.LAND: |
| case Opcodes.LOR: |
| case Opcodes.LXOR: |
| pop(4); |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| |
| case Opcodes.LSHL: |
| case Opcodes.LSHR: |
| case Opcodes.LUSHR: |
| pop(3); |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.I2L: |
| case Opcodes.F2L: |
| pop(); |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.I2F: |
| pop(); |
| push(InferredType.FLOAT); |
| break; |
| |
| case Opcodes.LCMP: |
| case Opcodes.DCMPG: |
| case Opcodes.DCMPL: |
| pop(4); |
| push(InferredType.INT); |
| break; |
| |
| case Opcodes.I2D: |
| case Opcodes.F2D: |
| pop(); |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.F2I: |
| case Opcodes.ARRAYLENGTH: |
| pop(); |
| push(InferredType.INT); |
| break; |
| case Opcodes.FALOAD: |
| case Opcodes.FADD: |
| case Opcodes.FSUB: |
| case Opcodes.FMUL: |
| case Opcodes.FDIV: |
| case Opcodes.FREM: |
| case Opcodes.L2F: |
| case Opcodes.D2F: |
| pop(2); |
| push(InferredType.FLOAT); |
| break; |
| |
| case Opcodes.DADD: |
| case Opcodes.DSUB: |
| case Opcodes.DMUL: |
| case Opcodes.DDIV: |
| case Opcodes.DREM: |
| pop(4); |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| default: |
| throw new RuntimeException("Unhandled opcode " + opcode); |
| } |
| super.visitInsn(opcode); |
| } |
| |
| @Override |
| public void visitIntInsn(int opcode, int operand) { |
| switch (opcode) { |
| case Opcodes.BIPUSH: |
| case Opcodes.SIPUSH: |
| push(InferredType.INT); |
| break; |
| case Opcodes.NEWARRAY: |
| pop(); |
| switch (operand) { |
| case Opcodes.T_BOOLEAN: |
| pushDescriptor("[Z"); |
| break; |
| case Opcodes.T_CHAR: |
| pushDescriptor("[C"); |
| break; |
| case Opcodes.T_FLOAT: |
| pushDescriptor("[F"); |
| break; |
| case Opcodes.T_DOUBLE: |
| pushDescriptor("[D"); |
| break; |
| case Opcodes.T_BYTE: |
| pushDescriptor("[B"); |
| break; |
| case Opcodes.T_SHORT: |
| pushDescriptor("[S"); |
| break; |
| case Opcodes.T_INT: |
| pushDescriptor("[I"); |
| break; |
| case Opcodes.T_LONG: |
| pushDescriptor("[J"); |
| break; |
| default: |
| throw new RuntimeException("Unhandled operand value: " + operand); |
| } |
| break; |
| default: |
| throw new RuntimeException("Unhandled opcode " + opcode); |
| } |
| super.visitIntInsn(opcode, operand); |
| } |
| |
| @Override |
| public void visitVarInsn(int opcode, int var) { |
| switch (opcode) { |
| case Opcodes.ILOAD: |
| push(InferredType.INT); |
| break; |
| case Opcodes.LLOAD: |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.FLOAD: |
| push(InferredType.FLOAT); |
| break; |
| case Opcodes.DLOAD: |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| case Opcodes.ALOAD: |
| push(getLocalVariableType(var)); |
| break; |
| case Opcodes.ISTORE: |
| case Opcodes.FSTORE: |
| case Opcodes.ASTORE: |
| { |
| InferredType type = pop(); |
| setLocalVariableTypes(var, type); |
| break; |
| } |
| case Opcodes.LSTORE: |
| case Opcodes.DSTORE: |
| { |
| InferredType type = pop(2); |
| setLocalVariableTypes(var, type); |
| setLocalVariableTypes(var + 1, InferredType.TOP); |
| break; |
| } |
| case Opcodes.RET: |
| throw new RuntimeException("The instruction RET is not supported"); |
| default: |
| throw new RuntimeException("Unhandled opcode " + opcode); |
| } |
| super.visitVarInsn(opcode, var); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| String descriptor = convertToDescriptor(type); |
| switch (opcode) { |
| case Opcodes.NEW: |
| // This should be UNINITIALIZED(label). Okay for type inference. |
| pushDescriptor(descriptor); |
| break; |
| case Opcodes.ANEWARRAY: |
| pop(); |
| pushDescriptor('[' + descriptor); |
| break; |
| case Opcodes.CHECKCAST: |
| pop(); |
| pushDescriptor(descriptor); |
| break; |
| case Opcodes.INSTANCEOF: |
| pop(); |
| push(InferredType.INT); |
| break; |
| default: |
| throw new RuntimeException("Unhandled opcode " + opcode); |
| } |
| super.visitTypeInsn(opcode, type); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| switch (opcode) { |
| case Opcodes.GETSTATIC: |
| pushDescriptor(desc); |
| break; |
| case Opcodes.PUTSTATIC: |
| popDescriptor(desc); |
| break; |
| case Opcodes.GETFIELD: |
| pop(); |
| pushDescriptor(desc); |
| break; |
| case Opcodes.PUTFIELD: |
| popDescriptor(desc); |
| pop(); |
| break; |
| default: |
| throw new RuntimeException( |
| "Unhandled opcode " + opcode + ", owner=" + owner + ", name=" + name + ", desc" + desc); |
| } |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { |
| if (opcode == Opcodes.INVOKESPECIAL && "<init>".equals(name)) { |
| int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2); |
| InferredType receiverType = getTypeOfOperandFromTop(argumentSize - 1); |
| if (receiverType.isUninitialized()) { |
| InferredType realType = InferredType.create('L' + owner + ';'); |
| replaceUninitializedTypeInStack(receiverType, realType); |
| } |
| } |
| switch (opcode) { |
| case Opcodes.INVOKESPECIAL: |
| case Opcodes.INVOKEVIRTUAL: |
| case Opcodes.INVOKESTATIC: |
| case Opcodes.INVOKEINTERFACE: |
| popDescriptor(desc); |
| if (opcode != Opcodes.INVOKESTATIC) { |
| pop(); // Pop receiver. |
| } |
| pushDescriptor(desc); |
| break; |
| default: |
| throw new RuntimeException( |
| String.format( |
| "Unhandled opcode %s, owner=%s, name=%s, desc=%s, itf=%s", |
| opcode, owner, name, desc, itf)); |
| } |
| super.visitMethodInsn(opcode, owner, name, desc, itf); |
| } |
| |
| @Override |
| public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { |
| popDescriptor(desc); |
| pushDescriptor(desc); |
| super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); |
| } |
| |
| @Override |
| public void visitJumpInsn(int opcode, Label label) { |
| switch (opcode) { |
| case Opcodes.IFEQ: |
| case Opcodes.IFNE: |
| case Opcodes.IFLT: |
| case Opcodes.IFGE: |
| case Opcodes.IFGT: |
| case Opcodes.IFLE: |
| pop(); |
| break; |
| case Opcodes.IF_ICMPEQ: |
| case Opcodes.IF_ICMPNE: |
| case Opcodes.IF_ICMPLT: |
| case Opcodes.IF_ICMPGE: |
| case Opcodes.IF_ICMPGT: |
| case Opcodes.IF_ICMPLE: |
| case Opcodes.IF_ACMPEQ: |
| case Opcodes.IF_ACMPNE: |
| pop(2); |
| break; |
| case Opcodes.GOTO: |
| break; |
| case Opcodes.JSR: |
| throw new RuntimeException("The JSR instruction is not supported."); |
| case Opcodes.IFNULL: |
| case Opcodes.IFNONNULL: |
| pop(1); |
| break; |
| default: |
| throw new RuntimeException("Unhandled opcode " + opcode); |
| } |
| super.visitJumpInsn(opcode, label); |
| } |
| |
| @Override |
| public void visitLdcInsn(Object cst) { |
| if (cst instanceof Integer) { |
| push(InferredType.INT); |
| } else if (cst instanceof Float) { |
| push(InferredType.FLOAT); |
| } else if (cst instanceof Long) { |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| } else if (cst instanceof Double) { |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| } else if (cst instanceof String) { |
| pushDescriptor("Ljava/lang/String;"); |
| } else if (cst instanceof Type) { |
| pushDescriptor(((Type) cst).getDescriptor()); |
| } else if (cst instanceof Handle) { |
| pushDescriptor("Ljava/lang/invoke/MethodHandle;"); |
| } else { |
| throw new RuntimeException("Cannot handle constant " + cst + " for LDC instruction"); |
| } |
| super.visitLdcInsn(cst); |
| } |
| |
| @Override |
| public void visitIincInsn(int var, int increment) { |
| setLocalVariableTypes(var, InferredType.INT); |
| super.visitIincInsn(var, increment); |
| } |
| |
| @Override |
| public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { |
| pop(); |
| super.visitTableSwitchInsn(min, max, dflt, labels); |
| } |
| |
| @Override |
| public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { |
| pop(); |
| super.visitLookupSwitchInsn(dflt, keys, labels); |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| pop(dims); |
| pushDescriptor(desc); |
| super.visitMultiANewArrayInsn(desc, dims); |
| } |
| |
| @Override |
| public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { |
| switch (type) { |
| case Opcodes.F_NEW: |
| // Expanded form. |
| previousFrame = |
| FrameInfo.create( |
| convertTypesInStackMapFrame(nLocal, local), |
| convertTypesInStackMapFrame(nStack, stack)); |
| break; |
| case Opcodes.F_SAME: |
| // This frame type indicates that the frame has exactly the same local variables as the |
| // previous frame and that the operand stack is empty. |
| previousFrame = FrameInfo.create(previousFrame.locals(), ImmutableList.of()); |
| break; |
| case Opcodes.F_SAME1: |
| // This frame type indicates that the frame has exactly the same local variables as the |
| // previous frame and that the operand stack has one entry. |
| previousFrame = |
| FrameInfo.create(previousFrame.locals(), convertTypesInStackMapFrame(nStack, stack)); |
| break; |
| case Opcodes.F_APPEND: |
| // This frame type indicates that the frame has the same locals as the previous frame except |
| // that k additional locals are defined, and that the operand stack is empty. |
| previousFrame = |
| FrameInfo.create( |
| appendArrayToList(previousFrame.locals(), nLocal, local), ImmutableList.of()); |
| break; |
| case Opcodes.F_CHOP: |
| // This frame type indicates that the frame has the same local variables as the previous |
| // frame except that the last k local variables are absent, and that the operand stack is |
| // empty. |
| previousFrame = |
| FrameInfo.create( |
| removeBackFromList(previousFrame.locals(), nLocal), ImmutableList.of()); |
| break; |
| case Opcodes.F_FULL: |
| previousFrame = |
| FrameInfo.create( |
| convertTypesInStackMapFrame(nLocal, local), |
| convertTypesInStackMapFrame(nStack, stack)); |
| break; |
| default: |
| // continue below |
| } |
| // Update types for operand stack and local variables. |
| operandStack.clear(); |
| operandStack.addAll(previousFrame.stack()); |
| localVariableSlots.clear(); |
| localVariableSlots.addAll(previousFrame.locals()); |
| super.visitFrame(type, nLocal, local, nStack, stack); |
| } |
| |
| private static String convertToDescriptor(String type) { |
| return (type.length() > 1 && type.charAt(0) != '[') ? 'L' + type + ';' : type; |
| } |
| |
| private void push(InferredType type) { |
| operandStack.add(type); |
| } |
| |
| private void replaceUninitializedTypeInStack(InferredType oldType, InferredType newType) { |
| checkArgument(oldType.isUninitialized(), "The old type is NOT uninitialized. %s", oldType); |
| for (int i = 0, size = operandStack.size(); i < size; ++i) { |
| InferredType type = operandStack.get(i); |
| if (type.equals(oldType)) { |
| operandStack.set(i, newType); |
| } |
| } |
| } |
| |
| private void pushDescriptor(String desc) { |
| int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; |
| switch (desc.charAt(index)) { |
| case 'V': |
| return; |
| case 'Z': |
| case 'C': |
| case 'B': |
| case 'S': |
| case 'I': |
| push(InferredType.INT); |
| break; |
| case 'F': |
| push(InferredType.FLOAT); |
| break; |
| case 'D': |
| push(InferredType.DOUBLE); |
| push(InferredType.TOP); |
| break; |
| case 'J': |
| push(InferredType.LONG); |
| push(InferredType.TOP); |
| break; |
| case 'L': |
| case '[': |
| push(InferredType.create(desc.substring(index))); |
| break; |
| default: |
| throw new RuntimeException("Unhandled type: " + desc); |
| } |
| } |
| |
| @CanIgnoreReturnValue |
| private InferredType pop() { |
| return pop(1); |
| } |
| |
| /** Pop elements from the end of the operand stack, and return the last popped element. */ |
| @CanIgnoreReturnValue |
| private InferredType pop(int count) { |
| checkArgument( |
| count >= 1, "The count should be at least one: %s (In %s)", count, methodSignature); |
| checkState( |
| operandStack.size() >= count, |
| "There are no enough elements in the stack. count=%s, stack=%s (In %s)", |
| count, |
| operandStack, |
| methodSignature); |
| int expectedLastIndex = operandStack.size() - count - 1; |
| InferredType lastPopped = null; |
| for (int i = operandStack.size() - 1; i > expectedLastIndex; --i) { |
| lastPopped = operandStack.remove(i); |
| } |
| return lastPopped; |
| } |
| |
| private void popDescriptor(String desc) { |
| char c = desc.charAt(0); |
| switch (c) { |
| case '(': |
| int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2) - 1; |
| if (argumentSize > 0) { |
| pop(argumentSize); |
| } |
| break; |
| case 'J': |
| case 'D': |
| pop(2); |
| break; |
| default: |
| pop(1); |
| break; |
| } |
| } |
| |
| private InferredType getLocalVariableType(int index) { |
| checkState( |
| index < localVariableSlots.size(), |
| "Cannot find type for var %s in method %s", |
| index, |
| methodSignature); |
| return localVariableSlots.get(index); |
| } |
| |
| private void setLocalVariableTypes(int index, InferredType type) { |
| while (localVariableSlots.size() <= index) { |
| localVariableSlots.add(InferredType.TOP); |
| } |
| localVariableSlots.set(index, type); |
| } |
| |
| private InferredType top() { |
| return operandStack.get(operandStack.size() - 1); |
| } |
| |
| /** |
| * Create the types of local variables at the very beginning of the method with the information of |
| * the declaring class and the method descriptor. |
| */ |
| private static ArrayList<InferredType> createInitialLocalVariableTypes( |
| int access, String ownerClass, String methodName, String methodDescriptor) { |
| ArrayList<InferredType> types = new ArrayList<>(); |
| |
| if (!isStatic(access)) { |
| // Instance method, and this is the receiver |
| types.add(InferredType.create(convertToDescriptor(ownerClass))); |
| } |
| Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor); |
| for (Type argumentType : argumentTypes) { |
| switch (argumentType.getSort()) { |
| case Type.BOOLEAN: |
| case Type.BYTE: |
| case Type.CHAR: |
| case Type.SHORT: |
| case Type.INT: |
| types.add(InferredType.INT); |
| break; |
| case Type.FLOAT: |
| types.add(InferredType.FLOAT); |
| break; |
| case Type.LONG: |
| types.add(InferredType.LONG); |
| types.add(InferredType.TOP); |
| break; |
| case Type.DOUBLE: |
| types.add(InferredType.DOUBLE); |
| types.add(InferredType.TOP); |
| break; |
| case Type.ARRAY: |
| case Type.OBJECT: |
| types.add(InferredType.create(argumentType.getDescriptor())); |
| break; |
| default: |
| throw new RuntimeException( |
| "Unhandled argument type: " |
| + argumentType |
| + " in " |
| + ownerClass |
| + "." |
| + methodName |
| + methodDescriptor); |
| } |
| } |
| return types; |
| } |
| |
| private static ImmutableList<InferredType> removeBackFromList( |
| ImmutableList<InferredType> list, int countToRemove) { |
| int origSize = list.size(); |
| int index = origSize - 1; |
| |
| while (index >= 0 && countToRemove > 0) { |
| InferredType type = list.get(index); |
| if (type.equals(InferredType.TOP) && index > 0 && list.get(index - 1).isCategory2()) { |
| --index; // A category 2 takes two slots. |
| } |
| --index; // Eat this local variable. |
| --countToRemove; |
| } |
| checkState( |
| countToRemove == 0, |
| "countToRemove is %s but not 0. index=%s, list=%s", |
| countToRemove, |
| index, |
| list); |
| return list.subList(0, index + 1); |
| } |
| |
| private ImmutableList<InferredType> appendArrayToList( |
| ImmutableList<InferredType> list, int size, Object[] array) { |
| ImmutableList.Builder<InferredType> builder = ImmutableList.builder(); |
| builder.addAll(list); |
| for (int i = 0; i < size; ++i) { |
| InferredType type = convertTypeInStackMapFrame(array[i]); |
| builder.add(type); |
| if (type.isCategory2()) { |
| builder.add(InferredType.TOP); |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** Convert the type in stack map frame to inference type. */ |
| private InferredType convertTypeInStackMapFrame(Object typeInStackMapFrame) { |
| if (typeInStackMapFrame == Opcodes.TOP) { |
| return InferredType.TOP; |
| } else if (typeInStackMapFrame == Opcodes.INTEGER) { |
| return InferredType.INT; |
| } else if (typeInStackMapFrame == Opcodes.FLOAT) { |
| return InferredType.FLOAT; |
| } else if (typeInStackMapFrame == Opcodes.DOUBLE) { |
| return InferredType.DOUBLE; |
| } else if (typeInStackMapFrame == Opcodes.LONG) { |
| return InferredType.LONG; |
| } else if (typeInStackMapFrame == Opcodes.NULL) { |
| return InferredType.NULL; |
| } else if (typeInStackMapFrame == Opcodes.UNINITIALIZED_THIS) { |
| return InferredType.UNINITIALIZED_THIS; |
| } else if (typeInStackMapFrame instanceof String) { |
| String referenceTypeName = (String) typeInStackMapFrame; |
| if (referenceTypeName.charAt(0) == '[') { |
| return InferredType.create(referenceTypeName); |
| } else { |
| return InferredType.create('L' + referenceTypeName + ';'); |
| } |
| } else if (typeInStackMapFrame instanceof Label) { |
| return InferredType.UNINITIALIZED; |
| } else { |
| throw new RuntimeException( |
| "Cannot reach here. Unhandled element: value=" |
| + typeInStackMapFrame |
| + ", class=" |
| + typeInStackMapFrame.getClass() |
| + ". The current method being desugared is " |
| + methodSignature); |
| } |
| } |
| |
| private ImmutableList<InferredType> convertTypesInStackMapFrame(int size, Object[] array) { |
| ImmutableList.Builder<InferredType> builder = ImmutableList.builder(); |
| for (int i = 0; i < size; ++i) { |
| InferredType type = convertTypeInStackMapFrame(array[i]); |
| builder.add(type); |
| if (type.isCategory2()) { |
| builder.add(InferredType.TOP); |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** A value class to represent a frame. */ |
| @AutoValue |
| abstract static class FrameInfo { |
| |
| static FrameInfo create(ImmutableList<InferredType> locals, ImmutableList<InferredType> stack) { |
| return new AutoValue_BytecodeTypeInference_FrameInfo(locals, stack); |
| } |
| |
| abstract ImmutableList<InferredType> locals(); |
| |
| abstract ImmutableList<InferredType> stack(); |
| } |
| |
| /** This is the type used for type inference. */ |
| @AutoValue |
| abstract static class InferredType { |
| |
| static final String UNINITIALIZED_PREFIX = "UNINIT@"; |
| |
| static final InferredType BOOLEAN = new AutoValue_BytecodeTypeInference_InferredType("Z"); |
| static final InferredType BYTE = new AutoValue_BytecodeTypeInference_InferredType("B"); |
| static final InferredType INT = new AutoValue_BytecodeTypeInference_InferredType("I"); |
| static final InferredType FLOAT = new AutoValue_BytecodeTypeInference_InferredType("F"); |
| static final InferredType LONG = new AutoValue_BytecodeTypeInference_InferredType("J"); |
| static final InferredType DOUBLE = new AutoValue_BytecodeTypeInference_InferredType("D"); |
| /** Not a real value. */ |
| static final InferredType TOP = new AutoValue_BytecodeTypeInference_InferredType("TOP"); |
| /** The value NULL */ |
| static final InferredType NULL = new AutoValue_BytecodeTypeInference_InferredType("NULL"); |
| |
| static final InferredType UNINITIALIZED_THIS = |
| new AutoValue_BytecodeTypeInference_InferredType("UNINITIALIZED_THIS"); |
| |
| static final InferredType UNINITIALIZED = |
| new AutoValue_BytecodeTypeInference_InferredType(UNINITIALIZED_PREFIX); |
| |
| /** Create a type for a value. */ |
| static InferredType create(String descriptor) { |
| if (UNINITIALIZED_PREFIX.equals(descriptor)) { |
| return UNINITIALIZED; |
| } |
| char firstChar = descriptor.charAt(0); |
| if (firstChar == 'L' || firstChar == '[') { |
| // Reference, array. |
| return new AutoValue_BytecodeTypeInference_InferredType(descriptor); |
| } |
| switch (descriptor) { |
| case "Z": |
| return BOOLEAN; |
| case "B": |
| return BYTE; |
| case "I": |
| return INT; |
| case "F": |
| return FLOAT; |
| case "J": |
| return LONG; |
| case "D": |
| return DOUBLE; |
| case "TOP": |
| return TOP; |
| case "NULL": |
| return NULL; |
| case "UNINITIALIZED_THIS": |
| return UNINITIALIZED_THIS; |
| default: |
| throw new RuntimeException("Invalid descriptor: " + descriptor); |
| } |
| } |
| |
| abstract String descriptor(); |
| |
| @Override |
| public String toString() { |
| return descriptor(); |
| } |
| |
| /** Is a category 2 value? */ |
| boolean isCategory2() { |
| String descriptor = descriptor(); |
| return descriptor.equals("J") || descriptor.equals("D"); |
| } |
| |
| /** If the type is an array, return the element type. Otherwise, throw an exception. */ |
| InferredType getElementTypeIfArrayOrThrow() { |
| String descriptor = descriptor(); |
| checkState(descriptor.charAt(0) == '[', "This type %s is not an array.", this); |
| return create(descriptor.substring(1)); |
| } |
| |
| /** Is an uninitialized value? */ |
| boolean isUninitialized() { |
| return descriptor().startsWith(UNINITIALIZED_PREFIX); |
| } |
| |
| /** Is a null value? */ |
| boolean isNull() { |
| return NULL.equals(this); |
| } |
| |
| /** |
| * If this type is a reference type, then return the internal name. Otherwise, returns empty. |
| */ |
| Optional<String> getInternalName() { |
| String descriptor = descriptor(); |
| int length = descriptor.length(); |
| if (length > 0 && descriptor.charAt(0) == 'L' && descriptor.charAt(length - 1) == ';') { |
| return Optional.of(descriptor.substring(1, length - 1)); |
| } else { |
| return Optional.empty(); |
| } |
| } |
| } |
| } |