diff options
12 files changed, 3106 insertions, 44 deletions
diff --git a/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java b/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java new file mode 100644 index 0000000000..fefc59918d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java @@ -0,0 +1,240 @@ +// 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 com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.util.Textifier; + +/** Print the types of the operand stack for each method. */ +public class ByteCodeTypePrinter { + + public static void printClassesWithTypes(Path inputJarFile, PrintWriter printWriter) + throws IOException { + Preconditions.checkState( + Files.exists(inputJarFile), "The input jar file %s does not exist.", inputJarFile); + try (ZipFile jarFile = new ZipFile(inputJarFile.toFile())) { + for (ZipEntry entry : getSortedClassEntriess(jarFile)) { + try (InputStream classStream = jarFile.getInputStream(entry)) { + printWriter.println("\nClass: " + entry.getName()); + ClassReader classReader = new ClassReader(classStream); + ClassVisitor visitor = new ClassWithTypeDumper(printWriter); + classReader.accept(visitor, 0); + } + printWriter.println("\n"); + } + } + } + + private static ImmutableList<ZipEntry> getSortedClassEntriess(ZipFile jar) { + return jar.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .sorted() + .collect(ImmutableList.toImmutableList()); + } + + private static class ClassWithTypeDumper extends ClassVisitor { + + private String internalName; + private final PrintWriter printWriter; + + public ClassWithTypeDumper(PrintWriter printWriter) { + super(Opcodes.ASM5); + this.printWriter = printWriter; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + internalName = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + printWriter.println("Method " + name); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + BytecodeTypeInference inference = new BytecodeTypeInference(access, internalName, name, desc); + mv = new MethodIrTypeDumper(mv, inference, printWriter); + inference.setDelegateMethodVisitor(mv); + // Let the type inference runs first. + return inference; + } + } + + private static final class TextifierExt extends Textifier { + + public TextifierExt() { + super(Opcodes.ASM5); + } + + public void print(String string) { + text.add(tab2 + string); + } + } + + private static class MethodIrTypeDumper extends MethodVisitor { + + private final BytecodeTypeInference inference; + private final TextifierExt printer = new TextifierExt(); + private final PrintWriter printWriter; + + public MethodIrTypeDumper( + MethodVisitor visitor, BytecodeTypeInference inference, PrintWriter printWriter) { + super(Opcodes.ASM5, visitor); + this.inference = inference; + this.printWriter = printWriter; + } + + private void printTypeOfOperandStackTop() { + printer.print(" |__STACK: " + inference.getOperandStackAsString() + "\n"); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + printer.visitIntInsn(opcode, operand); + printTypeOfOperandStackTop(); + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitInsn(int opcode) { + printer.visitInsn(opcode); + printTypeOfOperandStackTop(); + super.visitInsn(opcode); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + printer.visitMultiANewArrayInsn(desc, dims); + printTypeOfOperandStackTop(); + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + printer.visitLookupSwitchInsn(dflt, keys, labels); + printTypeOfOperandStackTop(); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + printer.visitTableSwitchInsn(min, max, dflt, labels); + printTypeOfOperandStackTop(); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitIincInsn(int var, int increment) { + printer.visitIincInsn(var, increment); + printTypeOfOperandStackTop(); + super.visitIincInsn(var, increment); + } + + @Override + public void visitLdcInsn(Object cst) { + printer.visitLdcInsn(cst); + printTypeOfOperandStackTop(); + super.visitLdcInsn(cst); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + printer.visitJumpInsn(opcode, label); + printTypeOfOperandStackTop(); + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + printer.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + printTypeOfOperandStackTop(); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + printer.visitMethodInsn(opcode, owner, name, desc, itf); + printTypeOfOperandStackTop(); + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + printer.visitMethodInsn(opcode, owner, name, desc); + printTypeOfOperandStackTop(); + super.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + printer.visitFieldInsn(opcode, owner, name, desc); + printTypeOfOperandStackTop(); + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + printer.visitTypeInsn(opcode, type); + printTypeOfOperandStackTop(); + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitVarInsn(int opcode, int var) { + printer.visitVarInsn(opcode, var); + printTypeOfOperandStackTop(); + super.visitVarInsn(opcode, var); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + printer.visitFrame(type, nLocal, local, nStack, stack); + super.visitFrame(type, nLocal, local, nStack, stack); + } + + @Override + public void visitLabel(Label label) { + printer.visitLabel(label); + super.visitLabel(label); + } + + @Override + public void visitEnd() { + printer.print(printWriter); + printWriter.flush(); + super.visitEnd(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt new file mode 100644 index 0000000000..722c2adb1f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt @@ -0,0 +1,1170 @@ +Class: testsubjects/TestSubject.class +Method <init> + L0 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + INVOKESPECIAL java/lang/Object.<init> ()V + |__STACK: [] + RETURN + |__STACK: [] +Method catchTest + L0 + ALOAD 0 + |__STACK: [Ljava/lang/Object;] + INSTANCEOF java/lang/String + |__STACK: [I] + IFNE L1 + |__STACK: [] + L2 + GETSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [I] + IRETURN + |__STACK: [] + L1 + FRAME SAME + ALOAD 0 + |__STACK: [Ljava/lang/Object;] + CHECKCAST java/lang/String + |__STACK: [Ljava/lang/String;] + INVOKESTATIC java/util/regex/Pattern.compile (Ljava/lang/String;)Ljava/util/regex/Pattern; + |__STACK: [Ljava/util/regex/Pattern;] + POP + |__STACK: [] + L3 + GOTO L4 + |__STACK: [] + L5 + FRAME SAME1 java/util/regex/PatternSyntaxException + ASTORE 2 + |__STACK: [] + L6 + GETSTATIC testsubjects/TestSubject.VALUE_TWO : I + |__STACK: [I] + IRETURN + |__STACK: [] + L4 + FRAME SAME + GETSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [I] + IRETURN + |__STACK: [] +Method assertEquals + L0 + DLOAD 1 + |__STACK: [D, TOP] + DLOAD 3 + |__STACK: [D, TOP, D, TOP] + INVOKESTATIC java/lang/Double.compare (DD)I + |__STACK: [I] + IFNE L1 + |__STACK: [] + L2 + RETURN + |__STACK: [] + L1 + FRAME SAME + DLOAD 1 + |__STACK: [D, TOP] + DLOAD 3 + |__STACK: [D, TOP, D, TOP] + DSUB + |__STACK: [D, TOP] + INVOKESTATIC java/lang/Math.abs (D)D + |__STACK: [D, TOP] + DLOAD 5 + |__STACK: [D, TOP, D, TOP] + DCMPG + |__STACK: [I] + IFLE L3 + |__STACK: [] + L4 + NEW java/lang/RuntimeException + |__STACK: [Ljava/lang/RuntimeException;] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;] + NEW java/lang/StringBuilder + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/StringBuilder;] + INVOKESPECIAL java/lang/StringBuilder.<init> ()V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + ALOAD 0 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/String;] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + NEW java/lang/Double + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;] + DLOAD 1 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + INVOKESPECIAL java/lang/Double.<init> (D)V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + NEW java/lang/Double + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;] + DLOAD 3 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + INVOKESPECIAL java/lang/Double.<init> (D)V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/String;] + INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V + |__STACK: [Ljava/lang/RuntimeException;] + ATHROW + |__STACK: [] + L3 + FRAME SAME + RETURN + |__STACK: [] +Method simpleTryWithResources + L0 + NEW testsubjects/TestSubject$SimpleResource + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + DUP + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;, Ltestsubjects/TestSubject$SimpleResource;] + INVOKESPECIAL testsubjects/TestSubject$SimpleResource.<init> ()V + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + ASTORE 0 + |__STACK: [] + ACONST_NULL + |__STACK: [NULL] + ASTORE 1 + |__STACK: [] + L1 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + ICONST_1 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;, I] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.call (Z)V + |__STACK: [] + L2 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + IFNULL L3 + |__STACK: [] + ALOAD 1 + |__STACK: [NULL] + IFNULL L4 + |__STACK: [] + L5 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + L6 + GOTO L3 + |__STACK: [] + L7 + FRAME FULL [testsubjects/TestSubject$SimpleResource java/lang/Throwable] [java/lang/Throwable] + ASTORE 2 + |__STACK: [] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L3 + |__STACK: [] + L4 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + GOTO L3 + |__STACK: [] + L8 + FRAME SAME1 java/lang/Throwable + ASTORE 2 + |__STACK: [] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + ASTORE 1 + |__STACK: [] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L9 + FRAME SAME1 java/lang/Throwable + ASTORE 3 + |__STACK: [] + L10 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + IFNULL L11 + |__STACK: [] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L12 + |__STACK: [] + L13 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + L14 + GOTO L11 + |__STACK: [] + L15 + FRAME FULL [testsubjects/TestSubject$SimpleResource java/lang/Throwable T java/lang/Throwable] [java/lang/Throwable] + ASTORE 4 + |__STACK: [] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L11 + |__STACK: [] + L12 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + L11 + FRAME SAME + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L3 + FRAME FULL [] [] + RETURN + |__STACK: [] +Method internalCompare + L0 + ALOAD 4 + |__STACK: [Ljava/util/function/BinaryOperator;] + LLOAD 0 + |__STACK: [Ljava/util/function/BinaryOperator;, J, TOP] + INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;] + LLOAD 2 + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;, J, TOP] + INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;, Ljava/lang/Long;] + INVOKEINTERFACE java/util/function/BinaryOperator.apply (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + |__STACK: [Ljava/lang/Object;] + CHECKCAST java/lang/Long + |__STACK: [Ljava/lang/Long;] + INVOKEVIRTUAL java/lang/Long.longValue ()J + |__STACK: [J, TOP] + LRETURN + |__STACK: [] +Method closeResourceArray + L0 + ALOAD 1 + |__STACK: [[Ljava/sql/Statement;] + ASTORE 2 + |__STACK: [] + ALOAD 2 + |__STACK: [[Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + ISTORE 3 + |__STACK: [] + ICONST_0 + |__STACK: [I] + ISTORE 4 + |__STACK: [] + L1 + FRAME APPEND [[Ljava/sql/Statement; I I] + ILOAD 4 + |__STACK: [I] + ILOAD 3 + |__STACK: [I, I] + IF_ICMPGE L2 + |__STACK: [] + ALOAD 2 + |__STACK: [[Ljava/sql/Statement;] + ILOAD 4 + |__STACK: [[Ljava/sql/Statement;, I] + AALOAD + |__STACK: [Ljava/sql/Statement;] + ASTORE 5 + |__STACK: [] + L3 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + ALOAD 5 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + L4 + IINC 4 1 + |__STACK: [] + GOTO L1 + |__STACK: [] + L2 + FRAME CHOP 3 + RETURN + |__STACK: [] +Method closeResourceMultiArray + L0 + ALOAD 1 + |__STACK: [[[Ljava/sql/Statement;] + ASTORE 2 + |__STACK: [] + ALOAD 2 + |__STACK: [[[Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + ISTORE 3 + |__STACK: [] + ICONST_0 + |__STACK: [I] + ISTORE 4 + |__STACK: [] + L1 + FRAME APPEND [[[Ljava/sql/Statement; I I] + ILOAD 4 + |__STACK: [I] + ILOAD 3 + |__STACK: [I, I] + IF_ICMPGE L2 + |__STACK: [] + ALOAD 2 + |__STACK: [[[Ljava/sql/Statement;] + ILOAD 4 + |__STACK: [[[Ljava/sql/Statement;, I] + AALOAD + |__STACK: [[Ljava/sql/Statement;] + ASTORE 5 + |__STACK: [] + L3 + ALOAD 5 + |__STACK: [[Ljava/sql/Statement;] + ASTORE 6 + |__STACK: [] + ALOAD 6 + |__STACK: [[Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + ISTORE 7 + |__STACK: [] + ICONST_0 + |__STACK: [I] + ISTORE 8 + |__STACK: [] + L4 + FRAME FULL [testsubjects/TestSubject [[Ljava/sql/Statement; [[Ljava/sql/Statement; I I [Ljava/sql/Statement; [Ljava/sql/Statement; I I] [] + ILOAD 8 + |__STACK: [I] + ILOAD 7 + |__STACK: [I, I] + IF_ICMPGE L5 + |__STACK: [] + ALOAD 6 + |__STACK: [[Ljava/sql/Statement;] + ILOAD 8 + |__STACK: [[Ljava/sql/Statement;, I] + AALOAD + |__STACK: [Ljava/sql/Statement;] + ASTORE 9 + |__STACK: [] + L6 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + ALOAD 9 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + L7 + IINC 8 1 + |__STACK: [] + GOTO L4 + |__STACK: [] + L5 + FRAME FULL [testsubjects/TestSubject [[Ljava/sql/Statement; [[Ljava/sql/Statement; I I] [] + IINC 4 1 + |__STACK: [] + GOTO L1 + |__STACK: [] + L2 + FRAME CHOP 3 + RETURN + |__STACK: [] +Method closeResourceArrayList + L0 + ALOAD 1 + |__STACK: [Ljava/util/List;] + INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator; + |__STACK: [Ljava/util/Iterator;] + ASTORE 2 + |__STACK: [] + L1 + FRAME APPEND [java/util/Iterator] + ALOAD 2 + |__STACK: [Ljava/util/Iterator;] + INVOKEINTERFACE java/util/Iterator.hasNext ()Z + |__STACK: [I] + IFEQ L2 + |__STACK: [] + ALOAD 2 + |__STACK: [Ljava/util/Iterator;] + INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; + |__STACK: [Ljava/lang/Object;] + CHECKCAST java/sql/Statement + |__STACK: [Ljava/sql/Statement;] + ASTORE 3 + |__STACK: [] + L3 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + ALOAD 3 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + L4 + GOTO L1 + |__STACK: [] + L2 + FRAME CHOP 1 + RETURN + |__STACK: [] +Method closeSqlStmt + L0 + ACONST_NULL + |__STACK: [NULL] + ASTORE 2 + |__STACK: [] + L1 + ALOAD 1 + |__STACK: [Ljava/sql/Connection;] + INVOKEINTERFACE java/sql/Connection.createStatement ()Ljava/sql/Statement; + |__STACK: [Ljava/sql/Statement;] + ASTORE 2 + |__STACK: [] + L2 + GOTO L3 + |__STACK: [] + L4 + FRAME FULL [testsubjects/TestSubject java/sql/Connection java/sql/Statement] [java/sql/SQLException] + ASTORE 3 + |__STACK: [] + L5 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + ALOAD 2 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + ALOAD 3 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + L3 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + ALOAD 2 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + L6 + RETURN + |__STACK: [] +Method closeResource + L0 + ALOAD 1 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNONNULL L1 + |__STACK: [] + L2 + RETURN + |__STACK: [] + L1 + FRAME SAME + ALOAD 1 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L3 + GOTO L4 + |__STACK: [] + L5 + FRAME SAME1 java/lang/Exception + ASTORE 3 + |__STACK: [] + L6 + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L7 + |__STACK: [] + L8 + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Exception;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + L7 + FRAME APPEND [java/lang/Exception] + ALOAD 3 + |__STACK: [Ljava/lang/Exception;] + ATHROW + |__STACK: [] + L4 + FRAME CHOP 1 + RETURN + |__STACK: [] +Method intAdd + L0 + ILOAD 0 + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L1 + IINC 2 1 + |__STACK: [] + L2 + IINC 2 1 + |__STACK: [] + L3 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + IADD + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L4 + IINC 2 -1 + |__STACK: [] + L5 + IINC 2 -1 + |__STACK: [] + L6 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + ISUB + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L7 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + IMUL + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L8 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + IDIV + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L9 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + IREM + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L10 + ILOAD 2 + |__STACK: [I] + ICONST_2 + |__STACK: [I, I] + ISHL + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L11 + ILOAD 2 + |__STACK: [I] + ILOAD 1 + |__STACK: [I, I] + ISHR + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L12 + ILOAD 2 + |__STACK: [I] + ICONST_3 + |__STACK: [I, I] + IUSHR + |__STACK: [I] + ISTORE 2 + |__STACK: [] + L13 + ILOAD 2 + |__STACK: [I] + I2L + |__STACK: [J, TOP] + LSTORE 3 + |__STACK: [] + L14 + LLOAD 3 + |__STACK: [J, TOP] + ILOAD 1 + |__STACK: [J, TOP, I] + LSHL + |__STACK: [J, TOP] + LSTORE 3 + |__STACK: [] + L15 + LLOAD 3 + |__STACK: [J, TOP] + L2I + |__STACK: [I] + IRETURN + |__STACK: [] +Method createNumberWithDiamond + L0 + ACONST_NULL + |__STACK: [NULL] + ASTORE 1 + |__STACK: [] + L1 + ILOAD 0 + |__STACK: [I] + IFEQ L2 + |__STACK: [] + L3 + NEW java/lang/Integer + |__STACK: [Ljava/lang/Integer;] + DUP + |__STACK: [Ljava/lang/Integer;, Ljava/lang/Integer;] + ICONST_1 + |__STACK: [Ljava/lang/Integer;, Ljava/lang/Integer;, I] + INVOKESPECIAL java/lang/Integer.<init> (I)V + |__STACK: [Ljava/lang/Integer;] + ASTORE 1 + |__STACK: [] + GOTO L4 + |__STACK: [] + L2 + FRAME APPEND [java/lang/Number] + NEW java/lang/Double + |__STACK: [Ljava/lang/Double;] + DUP + |__STACK: [Ljava/lang/Double;, Ljava/lang/Double;] + DCONST_1 + |__STACK: [Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + INVOKESPECIAL java/lang/Double.<init> (D)V + |__STACK: [Ljava/lang/Double;] + ASTORE 1 + |__STACK: [] + L4 + FRAME SAME + ALOAD 1 + |__STACK: [Ljava/lang/Number;] + ARETURN + |__STACK: [] +Method createMultiObjectArray + L0 + ICONST_0 + |__STACK: [I] + ICONST_0 + |__STACK: [I, I] + MULTIANEWARRAY [[Ljava/lang/Object; 2 + |__STACK: [[[Ljava/lang/Object;] + ARETURN + |__STACK: [] +Method createObjectArray + L0 + ICONST_0 + |__STACK: [I] + ANEWARRAY java/lang/Object + |__STACK: [[Ljava/lang/Object;] + ARETURN + |__STACK: [] +Method createIntArray + L0 + ICONST_0 + |__STACK: [I] + NEWARRAY T_INT + |__STACK: [[I] + ARETURN + |__STACK: [] +Method staticEmpty1 + L0 + RETURN + |__STACK: [] +Method instanceEmpty1 + L0 + RETURN + |__STACK: [] +Method identity + L0 + ILOAD 0 + |__STACK: [I] + IRETURN + |__STACK: [] +Method identity2 + L0 + ILOAD 0 + |__STACK: [I] + ISTORE 1 + |__STACK: [] + L1 + ILOAD 1 + |__STACK: [I] + IRETURN + |__STACK: [] +Method readFile + L0 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + INVOKESPECIAL java/io/FileReader.<init> (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + INVOKESPECIAL java/io/BufferedReader.<init> (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + ASTORE 2 + |__STACK: [] + ACONST_NULL + |__STACK: [NULL] + ASTORE 3 + |__STACK: [] + L1 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + INVOKESPECIAL java/io/FileReader.<init> (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + INVOKESPECIAL java/io/BufferedReader.<init> (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + ASTORE 4 + |__STACK: [] + L2 + ACONST_NULL + |__STACK: [NULL] + ASTORE 5 + |__STACK: [] + L3 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + INVOKESPECIAL java/io/FileReader.<init> (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + INVOKESPECIAL java/io/BufferedReader.<init> (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + ASTORE 6 + |__STACK: [] + L4 + ACONST_NULL + |__STACK: [NULL] + ASTORE 7 + |__STACK: [] + L5 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + INVOKESPECIAL java/io/FileReader.<init> (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + INVOKESPECIAL java/io/BufferedReader.<init> (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + ASTORE 8 + |__STACK: [] + L6 + ACONST_NULL + |__STACK: [NULL] + ASTORE 9 + |__STACK: [] + L7 + ALOAD 8 + |__STACK: [Ljava/io/BufferedReader;] + IFNULL L8 + |__STACK: [] + ALOAD 9 + |__STACK: [NULL] + IFNULL L9 + |__STACK: [] + L10 + ALOAD 8 + |__STACK: [Ljava/io/BufferedReader;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L11 + GOTO L8 + |__STACK: [] + L12 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable] [java/lang/Throwable] + ASTORE 10 + |__STACK: [] + ALOAD 9 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 10 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L8 + |__STACK: [] + L9 + FRAME SAME + ALOAD 8 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L8 + FRAME CHOP 2 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L13 + |__STACK: [] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L14 + |__STACK: [] + L15 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L16 + GOTO L13 + |__STACK: [] + L17 + FRAME SAME1 java/lang/Throwable + ASTORE 8 + |__STACK: [] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L13 + |__STACK: [] + L14 + FRAME SAME + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + GOTO L13 + |__STACK: [] + L18 + FRAME SAME1 java/lang/Throwable + ASTORE 8 + |__STACK: [] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;] + ASTORE 7 + |__STACK: [] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L19 + FRAME SAME1 java/lang/Throwable + ASTORE 11 + |__STACK: [] + L20 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L21 + |__STACK: [] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L22 + |__STACK: [] + L23 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L24 + GOTO L21 + |__STACK: [] + L25 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable T T T java/lang/Throwable] [java/lang/Throwable] + ASTORE 12 + |__STACK: [] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 12 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L21 + |__STACK: [] + L22 + FRAME SAME + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L21 + FRAME SAME + ALOAD 11 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L13 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable] [] + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L26 + |__STACK: [] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L27 + |__STACK: [] + L28 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L29 + GOTO L26 + |__STACK: [] + L30 + FRAME SAME1 java/lang/Throwable + ASTORE 6 + |__STACK: [] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L26 + |__STACK: [] + L27 + FRAME SAME + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + GOTO L26 + |__STACK: [] + L31 + FRAME SAME1 java/lang/Throwable + ASTORE 6 + |__STACK: [] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;] + ASTORE 5 + |__STACK: [] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L32 + FRAME SAME1 java/lang/Throwable + ASTORE 13 + |__STACK: [] + L33 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L34 + |__STACK: [] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L35 + |__STACK: [] + L36 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L37 + GOTO L34 + |__STACK: [] + L38 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable java/lang/AutoCloseable java/lang/Throwable T T T T T T T java/lang/Throwable] [java/lang/Throwable] + ASTORE 14 + |__STACK: [] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 14 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L34 + |__STACK: [] + L35 + FRAME SAME + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L34 + FRAME SAME + ALOAD 13 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L26 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable] [] + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L39 + |__STACK: [] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L40 + |__STACK: [] + L41 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L42 + GOTO L39 + |__STACK: [] + L43 + FRAME SAME1 java/lang/Throwable + ASTORE 4 + |__STACK: [] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L39 + |__STACK: [] + L40 + FRAME SAME + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + GOTO L39 + |__STACK: [] + L44 + FRAME SAME1 java/lang/Throwable + ASTORE 4 + |__STACK: [] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;] + ASTORE 3 + |__STACK: [] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L45 + FRAME SAME1 java/lang/Throwable + ASTORE 15 + |__STACK: [] + L46 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + IFNULL L47 + |__STACK: [] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + IFNULL L48 + |__STACK: [] + L49 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L50 + GOTO L47 + |__STACK: [] + L51 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable T T T T T T T T T T T java/lang/Throwable] [java/lang/Throwable] + ASTORE 16 + |__STACK: [] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + ALOAD 16 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + GOTO L47 + |__STACK: [] + L48 + FRAME SAME + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V + |__STACK: [] + L47 + FRAME SAME + ALOAD 15 + |__STACK: [Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + L39 + FRAME FULL [testsubjects/TestSubject java/io/File] [] + GOTO L52 + |__STACK: [] + L53 + FRAME SAME1 java/io/IOException + ASTORE 2 + |__STACK: [] + L54 + ALOAD 2 + |__STACK: [Ljava/io/IOException;] + INVOKEVIRTUAL java/io/IOException.printStackTrace ()V + |__STACK: [] + L52 + FRAME SAME + RETURN + |__STACK: [] +Method <clinit> + L0 + ICONST_1 + |__STACK: [I] + PUTSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [] + L1 + ICONST_2 + |__STACK: [I] + PUTSTATIC testsubjects/TestSubject.VALUE_TWO : I + |__STACK: [] + RETURN + |__STACK: []
\ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java new file mode 100644 index 0000000000..2573648dab --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java @@ -0,0 +1,46 @@ +// 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 com.google.common.io.Files; +import com.google.common.truth.Truth; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link BytecodeTypeInference} */ +@RunWith(JUnit4.class) +public class BytecodeTypeInferenceTest { + + private static final Path JAR_PATH = Paths.get(System.getProperty("jar_path")); + private static final Path GOLDEN_PATH = Paths.get(System.getProperty("golden_file")); + + @Test + public void test() throws IOException { + StringWriter stringWriter = new StringWriter(); + try (PrintWriter printWriter = new PrintWriter(stringWriter)) { + ByteCodeTypePrinter.printClassesWithTypes(JAR_PATH, printWriter); + printWriter.close(); + } + String inferenceResult = stringWriter.toString().trim(); + String golden = Files.asCharSource(GOLDEN_PATH.toFile(), StandardCharsets.UTF_8).read().trim(); + Truth.assertThat(inferenceResult).isEqualTo(golden); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java b/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java new file mode 100644 index 0000000000..528c78e624 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java @@ -0,0 +1,51 @@ +// 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.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.FrameInfo; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link BytecodeTypeInference.FrameInfo} */ +@RunWith(JUnit4.class) +public class FrameInfoTest { + + @Test + public void testFieldsAreSetCorrectly() { + { + FrameInfo info = FrameInfo.create(ImmutableList.of(), ImmutableList.of()); + assertThat(info.locals()).isEmpty(); + assertThat(info.stack()).isEmpty(); + } + { + FrameInfo info = + FrameInfo.create(ImmutableList.of(InferredType.INT), ImmutableList.of(InferredType.BYTE)); + assertThat(info.locals()).containsExactly(InferredType.INT).inOrder(); + assertThat(info.stack()).containsExactly(InferredType.BYTE).inOrder(); + } + { + FrameInfo info = + FrameInfo.create( + ImmutableList.of(InferredType.INT, InferredType.BYTE), + ImmutableList.of(InferredType.BOOLEAN, InferredType.LONG)); + assertThat(info.locals()).containsExactly(InferredType.INT, InferredType.BYTE).inOrder(); + assertThat(info.stack()).containsExactly(InferredType.BOOLEAN, InferredType.LONG).inOrder(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java b/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java index 587d4f7b8b..37afae7236 100644 --- a/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java +++ b/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java @@ -206,16 +206,20 @@ public class TryWithResourcesRewriterTest { .isEqualTo(orig.countExtPrintStackTracePrintWriter()); assertThat(orig.countThrowableGetSuppressed()).isEqualTo(desugared.countExtGetSuppressed()); - // $closeResource is rewritten to ThrowableExtension.closeResource, so addSuppressed() is called - // in the runtime library now. - assertThat(orig.countThrowableAddSuppressed()) - .isAtLeast(desugared.countThrowableAddSuppressed()); + // $closeResource may be specialized into multiple versions. + assertThat(orig.countThrowableAddSuppressed()).isAtMost(desugared.countExtAddSuppressed()); assertThat(orig.countThrowablePrintStackTrace()).isEqualTo(desugared.countExtPrintStackTrace()); assertThat(orig.countThrowablePrintStackTracePrintStream()) .isEqualTo(desugared.countExtPrintStackTracePrintStream()); assertThat(orig.countThrowablePrintStackTracePrintWriter()) .isEqualTo(desugared.countExtPrintStackTracePrintWriter()); + if (orig.getSyntheticCloseResourceCount() > 0) { + // Depending on the specific javac version, $closeResource(Throwable, AutoCloseable) may not + // be there. + assertThat(orig.getSyntheticCloseResourceCount()).isEqualTo(1); + assertThat(desugared.getSyntheticCloseResourceCount()).isAtLeast(1); + } assertThat(desugared.countThrowablePrintStackTracePrintStream()).isEqualTo(0); assertThat(desugared.countThrowablePrintStackTracePrintStream()).isEqualTo(0); assertThat(desugared.countThrowablePrintStackTracePrintWriter()).isEqualTo(0); @@ -270,6 +274,7 @@ public class TryWithResourcesRewriterTest { private static class DesugaredThrowableMethodCallCounter extends ClassVisitor { private final ClassLoader classLoader; private final Map<String, AtomicInteger> counterMap; + private int syntheticCloseResourceCount; public DesugaredThrowableMethodCallCounter(ClassLoader loader) { super(ASM5); @@ -291,6 +296,12 @@ public class TryWithResourcesRewriterTest { @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { + if (BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC) + && name.equals("$closeResource") + && Type.getArgumentTypes(desc).length == 2 + && Type.getArgumentTypes(desc)[0].getDescriptor().equals("Ljava/lang/Throwable;")) { + ++syntheticCloseResourceCount; + } return new InvokeCounter(); } @@ -324,6 +335,10 @@ public class TryWithResourcesRewriterTest { } } + public int getSyntheticCloseResourceCount() { + return syntheticCloseResourceCount; + } + public int countThrowableAddSuppressed() { return counterMap.get("addSuppressed(Ljava/lang/Throwable;)V").get(); } @@ -396,13 +411,16 @@ public class TryWithResourcesRewriterTest { private byte[] desugarTryWithResources(String className) { try { ClassReader reader = new ClassReader(className); + CloseResourceMethodScanner scanner = new CloseResourceMethodScanner(); + reader.accept(scanner, ClassReader.SKIP_DEBUG); ClassWriter writer = new ClassWriter(reader, COMPUTE_MAXS); TryWithResourcesRewriter rewriter = new TryWithResourcesRewriter( writer, TryWithResourcesRewriterTest.class.getClassLoader(), visitedExceptionTypes, - numOfTryWithResourcesInvoked); + numOfTryWithResourcesInvoked, + scanner.hasCloseResourceMethod()); reader.accept(rewriter, 0); return writer.toByteArray(); } catch (IOException e) { diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh new file mode 100755 index 0000000000..1166620862 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Copyright 2016 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. + +# +# I intentionally create this script to create a checked-in jar, because the test cases for +# byte code type inference uses golden files, which consequently relies on the version of javac +# compilers. So instead of creating jar files at build time, we check in a jar file. +# + +javac testsubjects/TestSubject.java + +jar cf test_subjects.jar testsubjects/TestSubject.class
\ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar Binary files differnew file mode 100644 index 0000000000..efa491bb12 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java new file mode 100644 index 0000000000..b5463a7954 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java @@ -0,0 +1,196 @@ +// 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 testsubjects; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.function.BinaryOperator; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Test subject for testing bytecode type inference {@link + * com.google.devtools.build.android.desugar.BytecodeTypeInference} + */ +public class TestSubject { + + private static int VALUE_ONE = 1; + private static int VALUE_TWO = 2; + + static int catchTest(Object key, Object value) { + if (!(key instanceof String)) { + return VALUE_ONE; + } + try { + Pattern.compile((String) key); + } catch (PatternSyntaxException e) { + return VALUE_TWO; + } + return VALUE_ONE; + } + + public static void assertEquals(String message, double expected, double actual, double delta) { + if (Double.compare(expected, actual) == 0) { + return; + } + if (!(Math.abs(expected - actual) <= delta)) { + throw new RuntimeException(message + new Double(expected) + new Double(actual)); + } + } + + /** + * A simple resource implementation which implements Closeable. + */ + public static class SimpleResource implements Closeable { + + public void call(boolean throwException) { + if (throwException) { + throw new RuntimeException("exception in call()"); + } + } + + @Override + public void close() throws IOException { + throw new IOException("exception in close()."); + } + } + + public static void simpleTryWithResources() throws Exception { + // Throwable.addSuppressed(Throwable) should be called in the following block. + try (SimpleResource resource = new SimpleResource()) { + resource.call(true); + } + } + + private static long internalCompare(long a, long b, BinaryOperator<Long> func) { + return func.apply(a, b); + } + + public void closeResourceArray(Statement[] resources) throws Exception { + for (Statement stmt : resources) { + closeResource(stmt, null); + } + } + + public void closeResourceMultiArray(Statement[][] resources) throws Exception { + for (Statement[] stmts : resources) { + for (Statement stmt : stmts) { + closeResource(stmt, null); + } + } + } + + public void closeResourceArrayList(List<Statement> resources) throws Exception { + for (Statement stmt : resources) { + closeResource(stmt, null); + } + } + + public void closeSqlStmt(Connection connection) throws Exception { + Statement stmt = null; + + try { + stmt = connection.createStatement(); + } catch (SQLException e) { + closeResource(stmt, e); + } + closeResource(stmt, null); + } + + public void closeResource(AutoCloseable resource, Throwable suppressor) throws Exception { + if (resource == null) { + return; + } + try { + resource.close(); + } catch (Exception e) { + if (suppressor != null) { + suppressor.addSuppressed(e); + } + throw e; + } + } + + public static int intAdd(int i, int j) { + int tmp = i; + tmp++; + ++tmp; + tmp += j; + tmp--; + --tmp; + tmp -= j; + tmp *= j; + tmp /= j; + tmp = tmp % j; + tmp = tmp << 2; + tmp = tmp >> j; + tmp = tmp >>> 3; + long longTemp = tmp; + longTemp = longTemp << j; + return (int) longTemp; + } + + public static Number createNumberWithDiamond(boolean flag) { + Number n = null; + if (flag) { + n = new Integer(1); + } else { + n = new Double(1); + } + return n; + } + + public static Object[][] createMultiObjectArray() { + return new Object[0][0]; + } + + public static Object[] createObjectArray() { + return new Object[0]; + } + + public static int[] createIntArray() { + return new int[0]; + } + + public static void staticEmpty1() {} + + public void instanceEmpty1() {} + + public static boolean identity(boolean result) { + return result; + } + + public static boolean identity2(boolean result) { + boolean temp = result; + return temp; + } + + public void readFile(File file) throws Exception { + try (AutoCloseable reader = new BufferedReader(new FileReader(file)); + AutoCloseable reader2 = new BufferedReader(new FileReader(file)); + AutoCloseable reader3 = new BufferedReader(new FileReader(file)); + AutoCloseable reader4 = new BufferedReader(new FileReader(file))) { + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java b/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java new file mode 100644 index 0000000000..1c7d0286aa --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java @@ -0,0 +1,1013 @@ +// 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.BytecodeTypeInference.InferredType.DOUBLE; +import static com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType.FLOAT; +import static com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType.INT; +import static com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType.LONG; +import static com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType.TOP; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.HashMap; +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. + */ +public 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; + /** + * Stores mapping from "uninitialized" value to concrete value. This is for the "new" instruction. + */ + private final HashMap<InferredType, InferredType> uninitializedToConcreteTypeMap = + new HashMap<>(); + + public BytecodeTypeInference(int access, String owner, String name, String methodDescriptor) { + super(Opcodes.ASM5); + localVariableSlots = createInitialLocalVariableTypes(access, owner, name, methodDescriptor); + previousFrame = FrameInfo.create(ImmutableList.copyOf(localVariableSlots), ImmutableList.of()); + this.methodSignature = owner + "." + name + methodDescriptor; + } + + public 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. */ + public 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); + } + + public String getOperandStackAsString() { + return operandStack.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(INT); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + push(DOUBLE); + push(TOP); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INT); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(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(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(LONG); + push(TOP); + break; + + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(); + push(FLOAT); + break; + + case Opcodes.LCMP: + case Opcodes.DCMPG: + case Opcodes.DCMPL: + pop(4); + push(INT); + break; + + case Opcodes.I2D: + case Opcodes.F2D: + pop(); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + pop(); + push(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(FLOAT); + break; + + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(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(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(INT); + break; + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DLOAD: + push(DOUBLE); + push(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, 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: + pushDescriptor(descriptor); // This should be UNINITIALIZED(label). Okay for type inference. + break; + case Opcodes.ANEWARRAY: + pop(); + pushDescriptor('[' + descriptor); + break; + case Opcodes.CHECKCAST: + pop(); + pushDescriptor(descriptor); + break; + case Opcodes.INSTANCEOF: + pop(); + push(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 + ';'); + uninitializedToConcreteTypeMap.put(receiverType, realType); + 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(INT); + } else if (cst instanceof Float) { + push(FLOAT); + } else if (cst instanceof Long) { + push(LONG); + push(TOP); + } else if (cst instanceof Double) { + push(DOUBLE); + push(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, 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) { + char firstChar = type.charAt(0); + switch (firstChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'D': + case 'F': + case '[': + return type; + default: + return 'L' + 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 == oldType) { + operandStack.set(i, newType); + } + } + } + + private final 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(INT); + break; + case 'F': + push(FLOAT); + break; + case 'D': + push(InferredType.DOUBLE); + push(TOP); + break; + case 'J': + push(InferredType.LONG); + push(TOP); + break; + case 'L': + case '[': + push(InferredType.create(desc.substring(index))); + break; + default: + throw new RuntimeException("Unhandled type: " + desc); + } + } + + private final InferredType pop() { + return pop(1); + } + + private final 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 final InferredType getLocalVariableType(int index) { + checkState( + index < localVariableSlots.size(), + "Cannot find type for var %s in method %s", + index, + methodSignature); + return localVariableSlots.get(index); + } + + private final void setLocalVariableTypes(int index, InferredType type) { + while (localVariableSlots.size() <= index) { + localVariableSlots.add(TOP); + } + localVariableSlots.set(index, type); + } + + private final InferredType top() { + return operandStack.get(operandStack.size() - 1); + } + + /** Pop elements from the end of the operand stack, and return the last popped element. */ + private final 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 static ImmutableList<InferredType> removeBackFromList( + ImmutableList<InferredType> list, int countToRemove) { + int newSize = list.size() - countToRemove; + return list.subList(0, newSize); + } + + /** + * 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 (!BitFlags.isSet(access, Opcodes.ACC_STATIC)) { + // 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(INT); + break; + case Type.FLOAT: + types.add(FLOAT); + break; + case Type.LONG: + types.add(LONG); + types.add(TOP); + break; + case Type.DOUBLE: + types.add(DOUBLE); + types.add(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 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 TOP; + } else if (typeInStackMapFrame == Opcodes.INTEGER) { + return INT; + } else if (typeInStackMapFrame == Opcodes.FLOAT) { + return 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) { + Label label = (Label) typeInStackMapFrame; + return InferredType.createUninitialized(label.getOffset()); + } 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 + public abstract static class InferredType { + + public static final String UNINITIALIZED_PREFIX = "UNINIT@"; + + public static final InferredType BOOLEAN = + new AutoValue_BytecodeTypeInference_InferredType("Z"); + public static final InferredType BYTE = new AutoValue_BytecodeTypeInference_InferredType("B"); + public static final InferredType INT = new AutoValue_BytecodeTypeInference_InferredType("I"); + public static final InferredType FLOAT = new AutoValue_BytecodeTypeInference_InferredType("F"); + public static final InferredType LONG = new AutoValue_BytecodeTypeInference_InferredType("J"); + public static final InferredType DOUBLE = new AutoValue_BytecodeTypeInference_InferredType("D"); + /** Not a real value. */ + public static final InferredType TOP = new AutoValue_BytecodeTypeInference_InferredType("TOP"); + /** The value NULL */ + public static final InferredType NULL = + new AutoValue_BytecodeTypeInference_InferredType("NULL"); + + public static final InferredType UNINITIALIZED_THIS = + new AutoValue_BytecodeTypeInference_InferredType("UNINITIALIZED_THIS"); + + static InferredType create(String descriptor) { + char firstChar = descriptor.charAt(0); + if (firstChar == 'L' || firstChar == '[' || descriptor.startsWith(UNINITIALIZED_PREFIX)) { + // Reference, array, or uninitialized values. + 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); + } + } + + /** Create a type for uninitialized value. The label is generated by ASM. */ + static InferredType createUninitialized(int label) { + return create(UNINITIALIZED_PREFIX + label); + } + + abstract String descriptor(); + + @Override + public String toString() { + return descriptor(); + } + + /** Is a category 2 value? */ + public 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. */ + public 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? */ + public boolean isUninitialized() { + return descriptor().startsWith(UNINITIALIZED_PREFIX); + } + + /** Is a null value? */ + public boolean isNull() { + return NULL.equals(this); + } + + /** + * If this type is a reference type, then return the internal name. Otherwise, throw an + * exception. + */ + public String getInternalNameOrThrow() { + String descriptor = descriptor(); + int length = descriptor.length(); + checkState( + descriptor.charAt(0) == 'L' && descriptor.charAt(length - 1) == ';', + "The type is expected to be either a class or an interface: %s", + descriptor); + return descriptor.substring(1, length - 1); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java new file mode 100644 index 0000000000..a390d72705 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java @@ -0,0 +1,116 @@ +// 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 com.google.common.base.Preconditions; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * A class scanner to check whether the class has the synthetic method $closeResource(Throwable, + * AutoCloseable). + */ +public class CloseResourceMethodScanner extends ClassVisitor { + + private boolean hasCloseResourceMethod; + private String internalName; + private int classFileVersion; + + public CloseResourceMethodScanner() { + super(Opcodes.ASM5); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + Preconditions.checkState(internalName == null, "This scanner has been used."); + this.internalName = name; + this.classFileVersion = version; + super.visit(version, access, name, signature, superName, interfaces); + } + + public boolean hasCloseResourceMethod() { + return hasCloseResourceMethod; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (classFileVersion <= 50) { + // A Java 6 or below class file should not have $closeResource method. + return null; + } + if (!hasCloseResourceMethod) { + hasCloseResourceMethod = + TryWithResourcesRewriter.isSyntheticCloseResourceMethod(access, name, desc); + } + return new StackMapFrameCollector(name, desc); + } + + private class StackMapFrameCollector extends MethodVisitor { + + private final String methodSignature; + private boolean hasCallToCloseResourceMethod; + private boolean hasJumpInstructions; + private boolean hasStackMapFrame; + + public StackMapFrameCollector(String name, String desc) { + super(Opcodes.ASM5); + methodSignature = internalName + '.' + name + desc; + } + + @Override + public void visitEnd() { + if (!hasCallToCloseResourceMethod) { + return; + } + if (hasJumpInstructions && !hasStackMapFrame) { + throw new UnsupportedOperationException( + "The method " + + methodSignature + + " calls $closeResource(Throwable, AutoCloseable), " + + "and Desugar thus needs to perform type inference for it " + + "to rewrite $closeResourceMethod. " + + "However, this method has jump instructions, but does not have stack map frames. " + + "Please recompile this class with stack map frames."); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (!hasCallToCloseResourceMethod + && TryWithResourcesRewriter.isCallToSyntheticCloseResource( + internalName, opcode, owner, name, desc)) { + hasCallToCloseResourceMethod = true; + } + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + hasStackMapFrame = true; + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + hasJumpInstructions = true; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java index 2b02386856..5d3df4a406 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java @@ -408,8 +408,7 @@ class Desugar { "com.google.devtools.build.android.desugar.dependencies.MetadataCollector") .getConstructor(Boolean.TYPE) .newInstance(options.tolerateMissingDependencies); - } catch (ReflectiveOperationException - | SecurityException e) { + } catch (ReflectiveOperationException | SecurityException e) { throw new IllegalStateException("Can't emit desugaring metadata as requested"); } } else if (options.tolerateMissingDependencies) { @@ -530,7 +529,8 @@ class Desugar { interfaceLambdaMethods, bridgeMethodReader, lambdaClass.getValue(), - writer); + writer, + reader); reader.accept(visitor, 0); String filename = rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class"; @@ -573,12 +573,19 @@ class Desugar { ImmutableSet<String> interfaceLambdaMethods, @Nullable ClassReaderFactory bridgeMethodReader, LambdaInfo lambdaClass, - UnprefixingClassWriter writer) { + UnprefixingClassWriter writer, + ClassReader input) { ClassVisitor visitor = checkNotNull(writer); if (!allowTryWithResources) { + CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); + input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG); visitor = new TryWithResourcesRewriter( - visitor, loader, visitedExceptionTypes, numOfTryWithResourcesInvoked); + visitor, + loader, + visitedExceptionTypes, + numOfTryWithResourcesInvoked, + closeResourceMethodScanner.hasCloseResourceMethod()); } if (!allowCallsToObjectsNonNull) { // Not sure whether there will be implicit null check emitted by javac, so we rerun @@ -638,9 +645,15 @@ class Desugar { ClassReader input) { ClassVisitor visitor = checkNotNull(writer); if (!allowTryWithResources) { + CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); + input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG); visitor = new TryWithResourcesRewriter( - visitor, loader, visitedExceptionTypes, numOfTryWithResourcesInvoked); + visitor, + loader, + visitedExceptionTypes, + numOfTryWithResourcesInvoked, + closeResourceMethodScanner.hasCloseResourceMethod()); } if (!allowCallsToObjectsNonNull) { visitor = new ObjectsRequireNonNullMethodRewriter(visitor); diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java index f7a3a7d156..17580c4130 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java @@ -14,23 +14,32 @@ package com.google.devtools.build.android.desugar; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; import static org.objectweb.asm.Opcodes.ASM5; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import com.google.common.base.Function; +import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.MethodNode; /** * Desugar try-with-resources. This class visitor intercepts calls to the following methods, and @@ -97,22 +106,34 @@ public class TryWithResourcesRewriter extends ClassVisitor { private final ClassLoader classLoader; private final Set<String> visitedExceptionTypes; private final AtomicInteger numOfTryWithResourcesInvoked; + /** Stores the internal class names of resources that need to be closed. */ + private final LinkedHashSet<String> resourceTypeInternalNames = new LinkedHashSet<>(); + + private final boolean hasCloseResourceMethod; + private String internalName; /** * Indicate whether the current class being desugared should be ignored. If the current class is * one of the runtime extension classes, then it should be ignored. */ private boolean shouldCurrentClassBeIgnored; + /** + * A method node for $closeResource(Throwable, AutoCloseable). At then end, we specialize this + * method node. + */ + @Nullable private MethodNode closeResourceMethod; public TryWithResourcesRewriter( ClassVisitor classVisitor, ClassLoader classLoader, Set<String> visitedExceptionTypes, - AtomicInteger numOfTryWithResourcesInvoked) { + AtomicInteger numOfTryWithResourcesInvoked, + boolean hasCloseResourceMethod) { super(ASM5, classVisitor); this.classLoader = classLoader; this.visitedExceptionTypes = visitedExceptionTypes; this.numOfTryWithResourcesInvoked = numOfTryWithResourcesInvoked; + this.hasCloseResourceMethod = hasCloseResourceMethod; } @Override @@ -126,6 +147,33 @@ public class TryWithResourcesRewriter extends ClassVisitor { super.visit(version, access, name, signature, superName, interfaces); internalName = name; shouldCurrentClassBeIgnored = THROWABLE_EXT_CLASS_INTERNAL_NAMES.contains(name); + Preconditions.checkState( + !shouldCurrentClassBeIgnored || !hasCloseResourceMethod, + "The current class which will be ignored " + + "contains $closeResource(Throwable, AutoCloseable)."); + } + + @Override + public void visitEnd() { + if (!resourceTypeInternalNames.isEmpty()) { + checkNotNull(closeResourceMethod); + for (String resourceInternalName : resourceTypeInternalNames) { + boolean isInterface = isInterface(resourceInternalName.replace('/', '.')); + // We use "this" to desugar the body of the close resource method. + closeResourceMethod.accept( + new CloseResourceMethodSpecializer(cv, resourceInternalName, isInterface)); + } + } else { + checkState( + closeResourceMethod == null, + "The field resourceTypeInternalNames is empty. " + + "But the class has the $closeResource method."); + checkState( + !hasCloseResourceMethod, + "The class %s has close resource method, but resourceTypeInternalNames is empty.", + internalName); + } + super.visitEnd(); } @Override @@ -136,21 +184,70 @@ public class TryWithResourcesRewriter extends ClassVisitor { Collections.addAll(visitedExceptionTypes, exceptions); } if (isSyntheticCloseResourceMethod(access, name, desc)) { - return null; // Discard this method. + checkState(closeResourceMethod == null, "The TWR rewriter has been used."); + closeResourceMethod = new MethodNode(ASM5, access, name, desc, signature, exceptions); + // Run the TWR desugar pass over the $closeResource(Throwable, AutoCloseable) first, for + // example, to rewrite calls to AutoCloseable.close().. + TryWithResourceVisitor twrVisitor = + new TryWithResourceVisitor( + internalName, name + desc, closeResourceMethod, classLoader, null); + return twrVisitor; } MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions); - return visitor == null || shouldCurrentClassBeIgnored - ? visitor - : new TryWithResourceVisitor(internalName, name + desc, visitor, classLoader); + if (visitor == null || shouldCurrentClassBeIgnored) { + return visitor; + } + + BytecodeTypeInference inference = null; + if (hasCloseResourceMethod) { + /* + * BytecodeTypeInference will run after the TryWithResourceVisitor, because when we are + * processing a bytecode instruction, we need to know the types in the operand stack, which + * are inferred after the previous instruction. + */ + inference = new BytecodeTypeInference(access, internalName, name, desc); + inference.setDelegateMethodVisitor(visitor); + visitor = inference; + } + + TryWithResourceVisitor twrVisitor = + new TryWithResourceVisitor(internalName, name + desc, visitor, classLoader, inference); + return twrVisitor; } - private boolean isSyntheticCloseResourceMethod(int access, String name, String desc) { + public static boolean isSyntheticCloseResourceMethod(int access, String name, String desc) { return BitFlags.isSet(access, ACC_SYNTHETIC | ACC_STATIC) && CLOSE_RESOURCE_METHOD_NAME.equals(name) && CLOSE_RESOURCE_METHOD_DESC.equals(desc); } + private boolean isInterface(String className) { + try { + Class<?> klass = classLoader.loadClass(className); + return klass.isInterface(); + } catch (ClassNotFoundException e) { + throw new AssertionError("Failed to load class when desugaring class " + internalName); + } + } + + public static boolean isCallToSyntheticCloseResource( + String currentClassInternalName, int opcode, String owner, String name, String desc) { + if (opcode != INVOKESTATIC) { + return false; + } + if (!currentClassInternalName.equals(owner)) { + return false; + } + if (!CLOSE_RESOURCE_METHOD_NAME.equals(name)) { + return false; + } + if (!CLOSE_RESOURCE_METHOD_DESC.equals(desc)) { + return false; + } + return true; + } + private class TryWithResourceVisitor extends MethodVisitor { private final ClassLoader classLoader; @@ -158,16 +255,19 @@ public class TryWithResourcesRewriter extends ClassVisitor { private final String internalName; private final String methodSignature; + @Nullable private final BytecodeTypeInference typeInference; public TryWithResourceVisitor( String internalName, String methodSignature, MethodVisitor methodVisitor, - ClassLoader classLoader) { + ClassLoader classLoader, + @Nullable BytecodeTypeInference typeInference) { super(ASM5, methodVisitor); this.classLoader = classLoader; this.internalName = internalName; this.methodSignature = methodSignature; + this.typeInference = typeInference; } @Override @@ -180,13 +280,39 @@ public class TryWithResourcesRewriter extends ClassVisitor { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - if (isCallToSyntheticCloseResource(opcode, owner, name, desc)) { - // Rewrite the call to the runtime library. + if (isCallToSyntheticCloseResource(internalName, opcode, owner, name, desc)) { + checkNotNull( + typeInference, + "This method %s.%s has a call to $closeResource(Throwable, AutoCloseable) method, " + + "but the type inference is null.", + internalName, + methodSignature); + { + // Check the exception type. + InferredType exceptionClass = typeInference.getTypeOfOperandFromTop(1); + if (!exceptionClass.isNull()) { + String exceptionClassInternalName = exceptionClass.getInternalNameOrThrow(); + checkState( + isAssignableFrom( + "java.lang.Throwable", exceptionClassInternalName.replace('/', '.')), + "The exception type should be a subclass of java.lang.Throwable."); + } + } + + String resourceClassInternalName = + typeInference.getTypeOfOperandFromTop(0).getInternalNameOrThrow(); + checkState( + isAssignableFrom( + "java.lang.AutoCloseable", resourceClassInternalName.replace('/', '.')), + "The resource type should be a subclass of java.lang.AutoCloseable: %s", + resourceClassInternalName); + + resourceTypeInternalNames.add(resourceClassInternalName); super.visitMethodInsn( opcode, - THROWABLE_EXTENSION_INTERNAL_NAME, - "closeResource", - "(Ljava/lang/Throwable;Ljava/lang/Object;)V", + owner, + "$closeResource", + "(Ljava/lang/Throwable;L" + resourceClassInternalName + ";)V", itf); return; } @@ -201,23 +327,6 @@ public class TryWithResourcesRewriter extends ClassVisitor { INVOKESTATIC, THROWABLE_EXTENSION_INTERNAL_NAME, name, METHOD_DESC_MAP.get(desc), false); } - private boolean isCallToSyntheticCloseResource( - int opcode, String owner, String name, String desc) { - if (opcode != INVOKESTATIC) { - return false; - } - if (!internalName.equals(owner)) { - return false; - } - if (!CLOSE_RESOURCE_METHOD_NAME.equals(name)) { - return false; - } - if (!CLOSE_RESOURCE_METHOD_DESC.equals(desc)) { - return false; - } - return true; - } - private boolean isMethodCallTargeted(int opcode, String owner, String name, String desc) { if (opcode != INVOKEVIRTUAL) { return false; @@ -228,15 +337,80 @@ public class TryWithResourcesRewriter extends ClassVisitor { if (visitedExceptionTypes.contains(owner)) { return true; // The owner is an exception that has been visited before. } + return isAssignableFrom("java.lang.Throwable", owner.replace('/', '.')); + } + + private boolean isAssignableFrom(String baseClassName, String subClassName) { try { - Class<?> throwableClass = classLoader.loadClass("java.lang.Throwable"); - Class<?> klass = classLoader.loadClass(owner.replace('/', '.')); - return throwableClass.isAssignableFrom(klass); + Class<?> baseClass = classLoader.loadClass(baseClassName); + Class<?> subClass = classLoader.loadClass(subClassName); + return baseClass.isAssignableFrom(subClass); } catch (ClassNotFoundException e) { throw new AssertionError( - "Failed to load class when desugaring method " + internalName + "." + methodSignature, + "Failed to load class when desugaring method " + + internalName + + "." + + methodSignature + + " when checking the assignable relation for class " + + baseClassName + + " and " + + subClassName, e); } } } + + /** + * A class to specialize the method $closeResource(Throwable, AutoCloseable), which does + * + * <ul> + * <li>Rename AutoCloseable to the given concrete resource type. + * <li>Adjust the invoke instruction that calls AutoCloseable.close() + * </ul> + */ + private static class CloseResourceMethodSpecializer extends ClassRemapper { + + private final boolean isResourceAnInterface; + private final String targetResourceInternalName; + + public CloseResourceMethodSpecializer( + ClassVisitor cv, String targetResourceInternalName, boolean isResourceAnInterface) { + super( + cv, + new Remapper() { + @Override + public String map(String typeName) { + if (typeName.equals("java/lang/AutoCloseable")) { + return targetResourceInternalName; + } else { + return typeName; + } + } + }); + this.targetResourceInternalName = targetResourceInternalName; + this.isResourceAnInterface = isResourceAnInterface; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new MethodVisitor(ASM5, mv) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKEINTERFACE + && owner.endsWith("java/lang/AutoCloseable") + && name.equals("close") + && desc.equals("()V") + && itf) { + opcode = isResourceAnInterface ? INVOKEINTERFACE : INVOKEVIRTUAL; + owner = targetResourceInternalName; + itf = isResourceAnInterface; + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + }; + } + } } |