summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2017-12-06 15:47:08 -0800
committerColin Cross <ccross@android.com>2017-12-06 15:47:08 -0800
commit00ed747d46416ee1f4dcda253722d57ef48acea7 (patch)
tree357d9e70cc17ca529f75ca1f92a1f70f71884583
parent96f19e7df85b15fbfc0d6705fa969f216b7346a6 (diff)
parent399ca0fdaa1cbd7120b07df54b207aad2d6fe77c (diff)
downloaddesugar-00ed747d46416ee1f4dcda253722d57ef48acea7.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' into desugar
* aosp/upstream-master: Improve exception message to output more information. RELNOTES:None. Remove static import of inner class InferredType's fields Automatic code cleanup. Let to push lambda arguments not only with *LOAD instructions but with SIPUSH and *CONST_*. Fix canonical option list for options that implicitly require options with allowMultiple=true Change config expansion application order, gated by startup flag --expand_configs_in_place. Specialize $closeResource(Throwable, AutoCloseable) so that desugared code does not depend on AutoCloseable, as it is not available before API 19. Fix EnclosingMethod attribute when moving interface methods to companion class RELNOTES: None. Cleanup stream use in canonical list of options. Remove unused LegacyParamsFilePreProcessor. Removed test cases from OptionsParserTest are implemented in ParamsFilePreProcessorTest, ShellQuotedParamsFilePreProcessorTest and UnquotedParamsFilePreProcessorTest. Switch on tracking of specific option priorities. Compute canonical list of options using OptionValueDescription's tracking of instances. Test: m checkbuild Change-Id: Idb762b0dabc9660d9116d1b976fb2f74aa7f5e09
-rw-r--r--java/com/google/devtools/build/android/desugar/BitFlags.java14
-rw-r--r--java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java1008
-rw-r--r--java/com/google/devtools/build/android/desugar/ClassVsInterface.java63
-rw-r--r--java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java116
-rw-r--r--java/com/google/devtools/build/android/desugar/Desugar.java54
-rw-r--r--java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java44
-rw-r--r--java/com/google/devtools/build/android/desugar/Java7Compatibility.java2
-rw-r--r--java/com/google/devtools/build/android/desugar/LambdaDesugaring.java66
-rw-r--r--java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java243
-rw-r--r--java/com/google/devtools/common/options/LegacyParamsFilePreProcessor.java151
-rw-r--r--java/com/google/devtools/common/options/OptionDefinition.java7
-rw-r--r--java/com/google/devtools/common/options/OptionPriority.java39
-rw-r--r--java/com/google/devtools/common/options/OptionValueDescription.java83
-rw-r--r--java/com/google/devtools/common/options/OptionsParser.java29
-rw-r--r--java/com/google/devtools/common/options/OptionsParserImpl.java121
-rw-r--r--java/com/google/devtools/common/options/OptionsProvider.java16
-rw-r--r--test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java318
-rw-r--r--test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java240
-rw-r--r--test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt1170
-rw-r--r--test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java46
-rw-r--r--test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java38
-rw-r--r--test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java51
-rw-r--r--test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java28
-rwxr-xr-xtest/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh25
-rw-r--r--test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jarbin0 -> 3361 bytes
-rw-r--r--test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java196
26 files changed, 3858 insertions, 310 deletions
diff --git a/java/com/google/devtools/build/android/desugar/BitFlags.java b/java/com/google/devtools/build/android/desugar/BitFlags.java
index 8542719..bb32c45 100644
--- a/java/com/google/devtools/build/android/desugar/BitFlags.java
+++ b/java/com/google/devtools/build/android/desugar/BitFlags.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.android.desugar;
+import org.objectweb.asm.Opcodes;
+
/**
* Convenience method for working with {@code int} bitwise flags.
*/
@@ -34,6 +36,18 @@ class BitFlags {
return (flags & bitmask) == 0;
}
+ public static boolean isInterface(int access) {
+ return isSet(access, Opcodes.ACC_INTERFACE);
+ }
+
+ public static boolean isStatic(int access) {
+ return isSet(access, Opcodes.ACC_STATIC);
+ }
+
+ public static boolean isSynthetic(int access) {
+ return isSet(access, Opcodes.ACC_SYNTHETIC);
+ }
+
// Static methods only
private BitFlags() {}
}
diff --git a/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java b/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java
new file mode 100644
index 0000000..777a4ab
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java
@@ -0,0 +1,1008 @@
+// 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 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.ASM6);
+ 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(InferredType.INT);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ push(InferredType.FLOAT);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ pop(2);
+ push(InferredType.INT);
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.D2L:
+ pop(2);
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.DALOAD:
+ case Opcodes.L2D:
+ pop(2);
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.AALOAD:
+ InferredType arrayType = pop(2);
+ InferredType elementType = arrayType.getElementTypeIfArrayOrThrow();
+ push(elementType);
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.AASTORE:
+ pop(3);
+ break;
+ case Opcodes.LASTORE:
+ case Opcodes.DASTORE:
+ pop(4);
+ break;
+ case Opcodes.POP:
+ case Opcodes.IRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ pop();
+ break;
+ case Opcodes.POP2:
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ pop(2);
+ break;
+ case Opcodes.DUP:
+ push(top());
+ break;
+ case Opcodes.DUP_X1:
+ {
+ InferredType top = pop();
+ InferredType next = pop();
+ push(top);
+ push(next);
+ push(top);
+ break;
+ }
+ case Opcodes.DUP_X2:
+ {
+ InferredType top = pop();
+ InferredType next = pop();
+ InferredType bottom = pop();
+ push(top);
+ push(bottom);
+ push(next);
+ push(top);
+ break;
+ }
+ case Opcodes.DUP2:
+ {
+ InferredType top = pop();
+ InferredType next = pop();
+ push(next);
+ push(top);
+ push(next);
+ push(top);
+ break;
+ }
+ case Opcodes.DUP2_X1:
+ {
+ InferredType top = pop();
+ InferredType next = pop();
+ InferredType bottom = pop();
+ push(next);
+ push(top);
+ push(bottom);
+ push(next);
+ push(top);
+ break;
+ }
+ case Opcodes.DUP2_X2:
+ {
+ InferredType t1 = pop();
+ InferredType t2 = pop();
+ InferredType t3 = pop();
+ InferredType t4 = pop();
+ push(t2);
+ push(t1);
+ push(t4);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ }
+ case Opcodes.SWAP:
+ {
+ InferredType top = pop();
+ InferredType next = pop();
+ push(top);
+ push(next);
+ break;
+ }
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ case Opcodes.IAND:
+ case Opcodes.IOR:
+ case Opcodes.IXOR:
+ case Opcodes.L2I:
+ case Opcodes.D2I:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ pop(2);
+ push(InferredType.INT);
+ break;
+
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LAND:
+ case Opcodes.LOR:
+ case Opcodes.LXOR:
+ pop(4);
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ pop(3);
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.I2L:
+ case Opcodes.F2L:
+ pop();
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.I2F:
+ pop();
+ push(InferredType.FLOAT);
+ break;
+
+ case Opcodes.LCMP:
+ case Opcodes.DCMPG:
+ case Opcodes.DCMPL:
+ pop(4);
+ push(InferredType.INT);
+ break;
+
+ case Opcodes.I2D:
+ case Opcodes.F2D:
+ pop();
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.F2I:
+ case Opcodes.ARRAYLENGTH:
+ pop();
+ push(InferredType.INT);
+ break;
+ case Opcodes.FALOAD:
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.L2F:
+ case Opcodes.D2F:
+ pop(2);
+ push(InferredType.FLOAT);
+ break;
+
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ pop(4);
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ default:
+ throw new RuntimeException("Unhandled opcode " + opcode);
+ }
+ super.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ switch (opcode) {
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH:
+ push(InferredType.INT);
+ break;
+ case Opcodes.NEWARRAY:
+ pop();
+ switch (operand) {
+ case Opcodes.T_BOOLEAN:
+ pushDescriptor("[Z");
+ break;
+ case Opcodes.T_CHAR:
+ pushDescriptor("[C");
+ break;
+ case Opcodes.T_FLOAT:
+ pushDescriptor("[F");
+ break;
+ case Opcodes.T_DOUBLE:
+ pushDescriptor("[D");
+ break;
+ case Opcodes.T_BYTE:
+ pushDescriptor("[B");
+ break;
+ case Opcodes.T_SHORT:
+ pushDescriptor("[S");
+ break;
+ case Opcodes.T_INT:
+ pushDescriptor("[I");
+ break;
+ case Opcodes.T_LONG:
+ pushDescriptor("[J");
+ break;
+ default:
+ throw new RuntimeException("Unhandled operand value: " + operand);
+ }
+ break;
+ default:
+ throw new RuntimeException("Unhandled opcode " + opcode);
+ }
+ super.visitIntInsn(opcode, operand);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ switch (opcode) {
+ case Opcodes.ILOAD:
+ push(InferredType.INT);
+ break;
+ case Opcodes.LLOAD:
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.FLOAD:
+ push(InferredType.FLOAT);
+ break;
+ case Opcodes.DLOAD:
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ case Opcodes.ALOAD:
+ push(getLocalVariableType(var));
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ {
+ InferredType type = pop();
+ setLocalVariableTypes(var, type);
+ break;
+ }
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ {
+ InferredType type = pop(2);
+ setLocalVariableTypes(var, type);
+ setLocalVariableTypes(var + 1, InferredType.TOP);
+ break;
+ }
+ case Opcodes.RET:
+ throw new RuntimeException("The instruction RET is not supported");
+ default:
+ throw new RuntimeException("Unhandled opcode " + opcode);
+ }
+ super.visitVarInsn(opcode, var);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ String descriptor = convertToDescriptor(type);
+ switch (opcode) {
+ case Opcodes.NEW:
+ 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(InferredType.INT);
+ break;
+ default:
+ throw new RuntimeException("Unhandled opcode " + opcode);
+ }
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ pushDescriptor(desc);
+ break;
+ case Opcodes.PUTSTATIC:
+ popDescriptor(desc);
+ break;
+ case Opcodes.GETFIELD:
+ pop();
+ pushDescriptor(desc);
+ break;
+ case Opcodes.PUTFIELD:
+ popDescriptor(desc);
+ pop();
+ break;
+ default:
+ throw new RuntimeException(
+ "Unhandled opcode " + opcode + ", owner=" + owner + ", name=" + name + ", desc" + desc);
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKESPECIAL && "<init>".equals(name)) {
+ int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2);
+ InferredType receiverType = getTypeOfOperandFromTop(argumentSize - 1);
+ if (receiverType.isUninitialized()) {
+ InferredType realType = InferredType.create('L' + owner + ';');
+ 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(InferredType.INT);
+ } else if (cst instanceof Float) {
+ push(InferredType.FLOAT);
+ } else if (cst instanceof Long) {
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ } else if (cst instanceof Double) {
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ } else if (cst instanceof String) {
+ pushDescriptor("Ljava/lang/String;");
+ } else if (cst instanceof Type) {
+ pushDescriptor(((Type) cst).getDescriptor());
+ } else if (cst instanceof Handle) {
+ pushDescriptor("Ljava/lang/invoke/MethodHandle;");
+ } else {
+ throw new RuntimeException("Cannot handle constant " + cst + " for LDC instruction");
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ setLocalVariableTypes(var, InferredType.INT);
+ super.visitIincInsn(var, increment);
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ pop();
+ super.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ pop();
+ super.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ pop(dims);
+ pushDescriptor(desc);
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ switch (type) {
+ case Opcodes.F_NEW:
+ // Expanded form.
+ previousFrame =
+ FrameInfo.create(
+ convertTypesInStackMapFrame(nLocal, local),
+ convertTypesInStackMapFrame(nStack, stack));
+ break;
+ case Opcodes.F_SAME:
+ // This frame type indicates that the frame has exactly the same local variables as the
+ // previous frame and that the operand stack is empty.
+ previousFrame = FrameInfo.create(previousFrame.locals(), ImmutableList.of());
+ break;
+ case Opcodes.F_SAME1:
+ // This frame type indicates that the frame has exactly the same local variables as the
+ // previous frame and that the operand stack has one entry.
+ previousFrame =
+ FrameInfo.create(previousFrame.locals(), convertTypesInStackMapFrame(nStack, stack));
+ break;
+ case Opcodes.F_APPEND:
+ // This frame type indicates that the frame has the same locals as the previous frame except
+ // that k additional locals are defined, and that the operand stack is empty.
+ previousFrame =
+ FrameInfo.create(
+ appendArrayToList(previousFrame.locals(), nLocal, local), ImmutableList.of());
+ break;
+ case Opcodes.F_CHOP:
+ // This frame type indicates that the frame has the same local variables as the previous
+ // frame except that the last k local variables are absent, and that the operand stack is
+ // empty.
+ previousFrame =
+ FrameInfo.create(
+ removeBackFromList(previousFrame.locals(), nLocal), ImmutableList.of());
+ break;
+ case Opcodes.F_FULL:
+ previousFrame =
+ FrameInfo.create(
+ convertTypesInStackMapFrame(nLocal, local),
+ convertTypesInStackMapFrame(nStack, stack));
+ break;
+ default:
+ // continue below
+ }
+ // Update types for operand stack and local variables.
+ operandStack.clear();
+ operandStack.addAll(previousFrame.stack());
+ localVariableSlots.clear();
+ localVariableSlots.addAll(previousFrame.locals());
+
+ super.visitFrame(type, nLocal, local, nStack, stack);
+ }
+
+ private static String convertToDescriptor(String type) {
+ 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(InferredType.INT);
+ break;
+ case 'F':
+ push(InferredType.FLOAT);
+ break;
+ case 'D':
+ push(InferredType.DOUBLE);
+ push(InferredType.TOP);
+ break;
+ case 'J':
+ push(InferredType.LONG);
+ push(InferredType.TOP);
+ break;
+ case 'L':
+ case '[':
+ push(InferredType.create(desc.substring(index)));
+ break;
+ default:
+ throw new RuntimeException("Unhandled type: " + desc);
+ }
+ }
+
+ 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(InferredType.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(InferredType.INT);
+ break;
+ case Type.FLOAT:
+ types.add(InferredType.FLOAT);
+ break;
+ case Type.LONG:
+ types.add(InferredType.LONG);
+ types.add(InferredType.TOP);
+ break;
+ case Type.DOUBLE:
+ types.add(InferredType.DOUBLE);
+ types.add(InferredType.TOP);
+ break;
+ case Type.ARRAY:
+ case Type.OBJECT:
+ types.add(InferredType.create(argumentType.getDescriptor()));
+ break;
+ default:
+ throw new RuntimeException(
+ "Unhandled argument type: "
+ + argumentType
+ + " in "
+ + ownerClass
+ + "."
+ + methodName
+ + methodDescriptor);
+ }
+ }
+ return types;
+ }
+
+ private ImmutableList<InferredType> appendArrayToList(
+ ImmutableList<InferredType> list, int size, Object[] array) {
+ ImmutableList.Builder<InferredType> builder = ImmutableList.builder();
+ builder.addAll(list);
+ for (int i = 0; i < size; ++i) {
+ InferredType type = convertTypeInStackMapFrame(array[i]);
+ builder.add(type);
+ if (type.isCategory2()) {
+ builder.add(InferredType.TOP);
+ }
+ }
+ return builder.build();
+ }
+
+ /** Convert the type in stack map frame to inference type. */
+ private InferredType convertTypeInStackMapFrame(Object typeInStackMapFrame) {
+ if (typeInStackMapFrame == Opcodes.TOP) {
+ return InferredType.TOP;
+ } else if (typeInStackMapFrame == Opcodes.INTEGER) {
+ return InferredType.INT;
+ } else if (typeInStackMapFrame == Opcodes.FLOAT) {
+ return InferredType.FLOAT;
+ } else if (typeInStackMapFrame == Opcodes.DOUBLE) {
+ return InferredType.DOUBLE;
+ } else if (typeInStackMapFrame == Opcodes.LONG) {
+ return InferredType.LONG;
+ } else if (typeInStackMapFrame == Opcodes.NULL) {
+ return InferredType.NULL;
+ } else if (typeInStackMapFrame == Opcodes.UNINITIALIZED_THIS) {
+ return InferredType.UNINITIALIZED_THIS;
+ } else if (typeInStackMapFrame instanceof String) {
+ String referenceTypeName = (String) typeInStackMapFrame;
+ if (referenceTypeName.charAt(0) == '[') {
+ return InferredType.create(referenceTypeName);
+ } else {
+ return InferredType.create('L' + referenceTypeName + ';');
+ }
+ } else if (typeInStackMapFrame instanceof Label) {
+ 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/java/com/google/devtools/build/android/desugar/ClassVsInterface.java b/java/com/google/devtools/build/android/desugar/ClassVsInterface.java
new file mode 100644
index 0000000..cb62deb
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/ClassVsInterface.java
@@ -0,0 +1,63 @@
+// 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.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.HashMap;
+import javax.annotation.Nullable;
+import org.objectweb.asm.ClassReader;
+
+/**
+ * Simple memoizer for whether types are classes or interfaces.
+ */
+class ClassVsInterface {
+ /** Map from internal names to whether they are an interface ({@code false} thus means class). */
+ private final HashMap<String, Boolean> known = new HashMap<>();
+ private final ClassReaderFactory classpath;
+
+ public ClassVsInterface(ClassReaderFactory classpath) {
+ this.classpath = classpath;
+ }
+
+ public ClassVsInterface addKnownClass(@Nullable String internalName) {
+ if (internalName != null) {
+ Boolean previous = known.put(internalName, false);
+ checkState(previous == null || !previous, "Already recorded as interface: %s", internalName);
+ }
+ return this;
+ }
+
+ public ClassVsInterface addKnownInterfaces(String... internalNames) {
+ for (String internalName : internalNames) {
+ Boolean previous = known.put(internalName, true);
+ checkState(previous == null || previous, "Already recorded as class: %s", internalName);
+ }
+ return this;
+ }
+
+ public boolean isOuterInterface(String outerName, String innerName) {
+ Boolean result = known.get(outerName);
+ if (result == null) {
+ // We could just load the outer class here, but this tolerates incomplete classpaths better.
+ // Note the outer class should be in the Jar we're desugaring, so it should always be there.
+ ClassReader outerClass = checkNotNull(classpath.readIfKnown(outerName),
+ "Couldn't find outer class %s of %s", outerName, innerName);
+ result = BitFlags.isInterface(outerClass.getAccess());
+ known.put(outerName, result);
+ }
+ return result;
+ }
+}
diff --git a/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java b/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java
new file mode 100644
index 0000000..7567515
--- /dev/null
+++ b/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.ASM6);
+ }
+
+ @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.ASM6);
+ 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/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java
index 87aa0d7..5d3df4a 100644
--- a/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -24,7 +24,6 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
@@ -358,7 +357,7 @@ class Desugar {
}
ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
-
+ ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader);
desugarClassesInInput(
inputFiles,
outputFileProvider,
@@ -366,6 +365,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ interfaceCache,
interfaceLambdaMethodCollector);
desugarAndWriteDumpedLambdaClassesToOutput(
@@ -374,6 +374,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ interfaceCache,
interfaceLambdaMethodCollector.build(),
bridgeMethodReader);
@@ -407,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) {
@@ -445,7 +445,8 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
- Builder<String> interfaceLambdaMethodCollector)
+ ClassVsInterface interfaceCache,
+ ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
throws IOException {
for (String filename : inputFiles) {
if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(filename)) {
@@ -465,6 +466,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ interfaceCache,
interfaceLambdaMethodCollector,
writer,
reader);
@@ -492,6 +494,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
@Nullable ClassReaderFactory bridgeMethodReader)
throws IOException {
@@ -522,10 +525,12 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ interfaceCache,
interfaceLambdaMethods,
bridgeMethodReader,
lambdaClass.getValue(),
- writer);
+ writer,
+ reader);
reader.accept(visitor, 0);
String filename =
rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
@@ -564,15 +569,23 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ ClassVsInterface interfaceCache,
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
@@ -591,7 +604,12 @@ class Desugar {
visitor, classpathReader, depsCollector, bootclasspathReader, loader);
visitor =
new InterfaceDesugaring(
- visitor, depsCollector, bootclasspathReader, store, options.legacyJacocoFix);
+ visitor,
+ interfaceCache,
+ depsCollector,
+ bootclasspathReader,
+ store,
+ options.legacyJacocoFix);
}
}
visitor =
@@ -621,14 +639,21 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
- Builder<String> interfaceLambdaMethodCollector,
+ ClassVsInterface interfaceCache,
+ ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
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) {
visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
@@ -645,7 +670,12 @@ class Desugar {
visitor, classpathReader, depsCollector, bootclasspathReader, loader);
visitor =
new InterfaceDesugaring(
- visitor, depsCollector, bootclasspathReader, store, options.legacyJacocoFix);
+ visitor,
+ interfaceCache,
+ depsCollector,
+ bootclasspathReader,
+ store,
+ options.legacyJacocoFix);
}
}
// LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally,
diff --git a/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java b/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
index b93613c..b8b3ead 100644
--- a/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
+++ b/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java
@@ -44,6 +44,7 @@ class InterfaceDesugaring extends ClassVisitor {
static final String INTERFACE_STATIC_COMPANION_METHOD_SUFFIX = "$$STATIC$$";
+ private final ClassVsInterface interfaceCache;
private final DependencyCollector depsCollector;
private final ClassReaderFactory bootclasspath;
private final GeneratedClassStore store;
@@ -58,11 +59,13 @@ class InterfaceDesugaring extends ClassVisitor {
public InterfaceDesugaring(
ClassVisitor dest,
+ ClassVsInterface interfaceCache,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspath,
GeneratedClassStore store,
boolean legacyJaCoCo) {
super(Opcodes.ASM6, dest);
+ this.interfaceCache = interfaceCache;
this.depsCollector = depsCollector;
this.bootclasspath = bootclasspath;
this.store = store;
@@ -83,10 +86,14 @@ class InterfaceDesugaring extends ClassVisitor {
bytecodeVersion = version;
accessFlags = access;
if (isInterface()) {
+ interfaceCache.addKnownInterfaces(name);
// Record interface hierarchy. This helps avoid parsing .class files when double-checking
// desugaring results later using collected dependency information.
depsCollector.recordExtendedInterfaces(name, interfaces);
+ } else {
+ interfaceCache.addKnownClass(name);
}
+ interfaceCache.addKnownClass(superName).addKnownInterfaces(interfaces);
super.visit(version, access, name, signature, superName, interfaces);
}
@@ -174,16 +181,14 @@ class InterfaceDesugaring extends ClassVisitor {
checkArgument(BitFlags.noneSet(access, Opcodes.ACC_NATIVE), "Forbidden per JLS ch 9.4");
boolean isLambdaBody =
- name.startsWith("lambda$") && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC);
+ name.startsWith("lambda$") && BitFlags.isSynthetic(access);
if (isLambdaBody) {
access &= ~Opcodes.ACC_PUBLIC; // undo visibility change from LambdaDesugaring
}
- name =
- normalizeInterfaceMethodName(
- name, isLambdaBody, BitFlags.isSet(access, Opcodes.ACC_STATIC));
+ name = normalizeInterfaceMethodName(name, isLambdaBody, BitFlags.isStatic(access));
codeOwner = getCompanionClassName(internalName);
- if (BitFlags.isSet(access, Opcodes.ACC_STATIC)) {
+ if (BitFlags.isStatic(access)) {
// Completely move static interface methods, which requires rewriting call sites
result =
companion()
@@ -233,8 +238,27 @@ class InterfaceDesugaring extends ClassVisitor {
: null;
}
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // Proguard gets grumpy if an outer method doesn't exist, which can be the result of moving
+ // interface methods to companion classes (b/68260836). In that case (for which we need to
+ // figure out if "owner" is an interface) need to adjust the outer method information.
+ if (name != null && interfaceCache.isOuterInterface(owner, internalName)) {
+ // Just drop outer method info. That's unfortunate, but the only alternative would be to
+ // change the outer method to point to the companion class, which would mean the
+ // reflection methods that use this information would return a companion ($$CC) class name
+ // as well as a possibly-modified method name and signature, so it seems better to return
+ // the correct original interface name and no method information. Doing this also saves
+ // us from doing even more work to figure out whether the method is static and a lambda
+ // method, which we'd need to known to adjust name and descriptor correctly.
+ name = null;
+ desc = null;
+ } // otherwise there's no enclosing method that could've been moved, or owner is a class
+ super.visitOuterClass(owner, name, desc);
+ }
+
private boolean isInterface() {
- return BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE);
+ return BitFlags.isInterface(accessFlags);
}
private static boolean isStaticInitializer(String methodName) {
@@ -243,18 +267,16 @@ class InterfaceDesugaring extends ClassVisitor {
private static String normalizeInterfaceMethodName(
String name, boolean isLambda, boolean isStatic) {
- String suffix;
if (isLambda) {
// Rename lambda method to reflect the new owner. Not doing so confuses LambdaDesugaring
// if it's run over this class again. LambdaDesugaring has already renamed the method from
// its original name to include the interface name at this point.
- suffix = DependencyCollector.INTERFACE_COMPANION_SUFFIX;
+ return name + DependencyCollector.INTERFACE_COMPANION_SUFFIX;
} else if (isStatic) {
- suffix = INTERFACE_STATIC_COMPANION_METHOD_SUFFIX;
+ return name + INTERFACE_STATIC_COMPANION_METHOD_SUFFIX;
} else {
return name;
}
- return name + suffix;
}
static String getCompanionClassName(String interfaceName) {
@@ -410,7 +432,7 @@ class InterfaceDesugaring extends ClassVisitor {
private static class MoveJacocoFieldAccess extends MethodVisitor {
public MoveJacocoFieldAccess(MethodVisitor mv) {
- super(Opcodes.ASM5, mv);
+ super(Opcodes.ASM6, mv);
}
@Override
diff --git a/java/com/google/devtools/build/android/desugar/Java7Compatibility.java b/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
index 30de63d..37a45dd 100644
--- a/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
+++ b/java/com/google/devtools/build/android/desugar/Java7Compatibility.java
@@ -119,7 +119,7 @@ public class Java7Compatibility extends ClassVisitor {
boolean updated = false;
public UpdateBytecodeVersionIfNecessary(MethodVisitor methodVisitor) {
- super(Opcodes.ASM5, methodVisitor);
+ super(Opcodes.ASM6, methodVisitor);
}
@Override
diff --git a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
index 652f025..5f41347 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
@@ -533,12 +533,13 @@ class LambdaDesugaring extends ClassVisitor {
i,
internalName,
insn.getOpcode());
- } else if (insn.getOpcode() != paramTypes[i].getOpcode(Opcodes.ILOAD)) {
- // Otherwise expect load of a (effectively) final local variable. Not seeing that means
- // we're dealing with a method reference on some arbitrary expression, <expression>::m.
- // In that case we give up and keep using the factory method for now, since inserting
- // the NEW/DUP so the new object ends up in the right stack slot is hard in that case.
- // Note this still covers simple cases such as this::m or x::m, where x is a local.
+ } else if (!isPushForType(insn, paramTypes[i])) {
+ // Otherwise expect load of a (effectively) final local variable or a constant. Not seeing
+ // that means we're dealing with a method reference on some arbitrary expression,
+ // <expression>::m. In that case we give up and keep using the factory method for now,
+ // since inserting the NEW/DUP so the new object ends up in the right stack slot is hard
+ // in that case. Note this still covers simple cases such as this::m or x::m, where x is a
+ // local.
checkState(
paramTypes.length == 1,
"Expected a load for %s to set up parameter %s for %s but got %s",
@@ -562,6 +563,59 @@ class LambdaDesugaring extends ClassVisitor {
return true;
}
+ /**
+ * Returns whether a given instruction can be used to push argument of {@code type} on stack.
+ */
+ private /* static */ boolean isPushForType(AbstractInsnNode insn, Type type) {
+ int opcode = insn.getOpcode();
+ if (opcode == type.getOpcode(Opcodes.ILOAD)) {
+ return true;
+ }
+ // b/62060793: AsyncAwait rewrites bytecode to convert java methods into state machine with
+ // support of lambdas. Constant zero values are pushed on stack for all yet uninitialized
+ // local variables. And SIPUSH instruction is used to advance an internal state of a state
+ // machine.
+ switch (type.getSort()) {
+ case Type.BOOLEAN:
+ return opcode == Opcodes.ICONST_0
+ || opcode == Opcodes.ICONST_1;
+
+ case Type.BYTE:
+ case Type.CHAR:
+ case Type.SHORT:
+ case Type.INT:
+ return opcode == Opcodes.SIPUSH
+ || opcode == Opcodes.ICONST_0
+ || opcode == Opcodes.ICONST_1
+ || opcode == Opcodes.ICONST_2
+ || opcode == Opcodes.ICONST_3
+ || opcode == Opcodes.ICONST_4
+ || opcode == Opcodes.ICONST_5
+ || opcode == Opcodes.ICONST_M1;
+
+ case Type.LONG:
+ return opcode == Opcodes.LCONST_0
+ || opcode == Opcodes.LCONST_1;
+
+ case Type.FLOAT:
+ return opcode == Opcodes.FCONST_0
+ || opcode == Opcodes.FCONST_1
+ || opcode == Opcodes.FCONST_2;
+
+ case Type.DOUBLE:
+ return opcode == Opcodes.DCONST_0
+ || opcode == Opcodes.DCONST_1;
+
+ case Type.OBJECT:
+ case Type.ARRAY:
+ return opcode == Opcodes.ACONST_NULL;
+
+ default:
+ // Support for BIPUSH and LDC* opcodes is not implemented as there is no known use case.
+ return false;
+ }
+ }
+
private Lookup createLookup(String lookupClass) throws ReflectiveOperationException {
Class<?> clazz = loadFromInternal(lookupClass);
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
diff --git a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
index 270d32b..e4d4da5 100644
--- a/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java
+++ b/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.ASM6;
+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(ASM6, 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(ASM6, 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(ASM6, methodVisitor);
this.classLoader = classLoader;
this.internalName = internalName;
this.methodSignature = methodSignature;
+ this.typeInference = typeInference;
}
@Override
@@ -180,13 +280,42 @@ 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 %s in %s.%s should be a subclass of java.lang.Throwable.",
+ exceptionClassInternalName,
+ internalName,
+ methodSignature);
+ }
+ }
+
+ 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 +330,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 +340,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(ASM6, 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);
+ }
+ };
+ }
+ }
}
diff --git a/java/com/google/devtools/common/options/LegacyParamsFilePreProcessor.java b/java/com/google/devtools/common/options/LegacyParamsFilePreProcessor.java
deleted file mode 100644
index 56a7d2c..0000000
--- a/java/com/google/devtools/common/options/LegacyParamsFilePreProcessor.java
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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.common.options;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-/**
- * A {@link ParamsFilePreProcessor} that processes a parameter file using a custom format. This
- * format assumes each parameter is separated by whitespace and allows arguments to use single and
- * double quotes and quote and whitespace escaping.
- *
- * <p><em>NOTE:</em> This class is deprecated; use either {@link ShellQuotedParamsFilePreProcessor}
- * or {@link UnquotedParamsFilePreProcessor} depending on the format of the provided params file.
- */
-@Deprecated
-public class LegacyParamsFilePreProcessor extends ParamsFilePreProcessor {
-
- public LegacyParamsFilePreProcessor(FileSystem fs) {
- super(fs);
- }
-
- @Override
- protected List<String> parse(Path paramsFile) throws IOException, OptionsParsingException {
- try (Reader params = Files.newBufferedReader(paramsFile, StandardCharsets.UTF_8)) {
- List<String> newArgs = new ArrayList<>();
- StringBuilder arg = new StringBuilder();
- CharIterator iterator = CharIterator.wrap(params);
- while (iterator.hasNext()) {
- char next = iterator.next();
- if (Character.isWhitespace(next) && !iterator.isInQuote() && !iterator.isEscaped()) {
- newArgs.add(unescape(arg.toString()));
- arg = new StringBuilder();
- } else {
- arg.append(next);
- }
- }
- // If there is an arg in the buffer, add it.
- if (arg.length() > 0) {
- newArgs.add(arg.toString());
- }
- // If we're still in a quote by the end of the file, throw an error.
- if (iterator.isInQuote()) {
- throw new OptionsParsingException(
- String.format(ERROR_MESSAGE_FORMAT, paramsFile, iterator.getUnmatchedQuoteMessage()));
- }
- return newArgs;
- }
- }
-
- private String unescape(String arg) {
- if (arg.startsWith("'") && arg.endsWith("'")) {
- String unescaped = arg.replace("'\\''", "'");
- return unescaped.substring(1, unescaped.length() - 1);
- }
- return arg;
- }
-
- // Doesn't implement iterator to avoid autoboxing and to throw exceptions.
- private static class CharIterator {
-
- private final Reader reader;
- private int readerPosition = 0;
- private int singleQuoteStart = -1;
- private int doubleQuoteStart = -1;
- private boolean escaped = false;
- private char lastChar = (char) -1;
-
- public static CharIterator wrap(Reader reader) {
- return new CharIterator(reader);
- }
-
- public CharIterator(Reader reader) {
- this.reader = reader;
- }
-
- public boolean hasNext() throws IOException {
- return peek() != -1;
- }
-
- private int peek() throws IOException {
- reader.mark(1);
- int next = reader.read();
- reader.reset();
- return next;
- }
-
- public boolean isInQuote() {
- return singleQuoteStart != -1 || doubleQuoteStart != -1;
- }
-
- public boolean isEscaped() {
- return escaped;
- }
-
- public String getUnmatchedQuoteMessage() {
- StringBuilder message = new StringBuilder();
- if (singleQuoteStart != -1) {
- message.append(String.format(UNFINISHED_QUOTE_MESSAGE_FORMAT, "'", singleQuoteStart));
- }
- if (doubleQuoteStart != -1) {
- message.append(String.format(UNFINISHED_QUOTE_MESSAGE_FORMAT, "\"", doubleQuoteStart));
- }
- return message.toString();
- }
-
- public char next() throws IOException {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- char current = (char) reader.read();
-
- // check for \r\n line endings. If found, drop the \r for normalized parsing.
- if (current == '\r' && peek() == '\n') {
- current = (char) reader.read();
- }
-
- // check to see if the current position is escaped
- escaped = (lastChar == '\\');
-
- if (!escaped && current == '\'') {
- singleQuoteStart = singleQuoteStart == -1 ? readerPosition : -1;
- }
- if (!escaped && current == '"') {
- doubleQuoteStart = doubleQuoteStart == -1 ? readerPosition : -1;
- }
-
- readerPosition++;
- lastChar = current;
- return current;
- }
- }
-}
diff --git a/java/com/google/devtools/common/options/OptionDefinition.java b/java/com/google/devtools/common/options/OptionDefinition.java
index 1c01932..84a9d2d 100644
--- a/java/com/google/devtools/common/options/OptionDefinition.java
+++ b/java/com/google/devtools/common/options/OptionDefinition.java
@@ -29,7 +29,7 @@ import java.util.Comparator;
* the {@link Field} that is annotated, and should contain all logic about default settings and
* behavior.
*/
-public class OptionDefinition {
+public class OptionDefinition implements Comparable<OptionDefinition> {
// TODO(b/65049598) make ConstructionException checked, which will make this checked as well.
static class NotAnOptionException extends ConstructionException {
@@ -304,6 +304,11 @@ public class OptionDefinition {
}
@Override
+ public int compareTo(OptionDefinition o) {
+ return getOptionName().compareTo(o.getOptionName());
+ }
+
+ @Override
public String toString() {
return String.format("option '--%s'", getOptionName());
}
diff --git a/java/com/google/devtools/common/options/OptionPriority.java b/java/com/google/devtools/common/options/OptionPriority.java
index 96c471e..ec5d0d8 100644
--- a/java/com/google/devtools/common/options/OptionPriority.java
+++ b/java/com/google/devtools/common/options/OptionPriority.java
@@ -26,18 +26,42 @@ import java.util.Objects;
public class OptionPriority implements Comparable<OptionPriority> {
private final PriorityCategory priorityCategory;
private final int index;
+ private final boolean locked;
- private OptionPriority(PriorityCategory priorityCategory, int index) {
+ private OptionPriority(PriorityCategory priorityCategory, int index, boolean locked) {
this.priorityCategory = priorityCategory;
this.index = index;
+ this.locked = locked;
}
- public static OptionPriority lowestOptionPriorityAtCategory(PriorityCategory category) {
- return new OptionPriority(category, 0);
+ /** Get the first OptionPriority for that category. */
+ static OptionPriority lowestOptionPriorityAtCategory(PriorityCategory category) {
+ return new OptionPriority(category, 0, false);
}
- public static OptionPriority nextOptionPriority(OptionPriority priority) {
- return new OptionPriority(priority.priorityCategory, priority.index + 1);
+ /**
+ * Get the priority for the option following this one. In normal, incremental option parsing, the
+ * returned priority would compareTo as after the current one. Does not increment locked
+ * priorities.
+ */
+ static OptionPriority nextOptionPriority(OptionPriority priority) {
+ if (priority.locked) {
+ return priority;
+ }
+ return new OptionPriority(priority.priorityCategory, priority.index + 1, false);
+ }
+
+ /**
+ * Return a priority for this option that will avoid priority increases by calls to
+ * nextOptionPriority.
+ *
+ * <p>Some options are expanded in-place, and need to be all parsed at the priority of the
+ * original option. In this case, parsing one of these after another should not cause the option
+ * to be considered as higher priority than the ones before it (this would cause overlap between
+ * the expansion of --expansion_flag and a option following it in the same list of options).
+ */
+ public static OptionPriority getLockedPriority(OptionPriority priority) {
+ return new OptionPriority(priority.priorityCategory, priority.index, true);
}
public PriorityCategory getPriorityCategory() {
@@ -66,6 +90,11 @@ public class OptionPriority implements Comparable<OptionPriority> {
return Objects.hash(priorityCategory, index);
}
+ @Override
+ public String toString() {
+ return String.format("OptionPriority(%s,%s)", priorityCategory, index);
+ }
+
/**
* The priority of option values, in order of increasing priority.
*
diff --git a/java/com/google/devtools/common/options/OptionValueDescription.java b/java/com/google/devtools/common/options/OptionValueDescription.java
index 0d31137..616b3b5 100644
--- a/java/com/google/devtools/common/options/OptionValueDescription.java
+++ b/java/com/google/devtools/common/options/OptionValueDescription.java
@@ -14,7 +14,6 @@
package com.google.devtools.common.options;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
@@ -25,6 +24,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
+import javax.annotation.Nullable;
/**
* The value of an option.
@@ -74,6 +74,16 @@ public abstract class OptionValueDescription {
}
/**
+ * Returns the canonical instances of this option - the instances that affect the current value.
+ *
+ * <p>For options that do not have values in their own right, this should be the empty list. In
+ * contrast, the DefaultOptionValue does not have a canonical form at all, since it was never set,
+ * and is null.
+ */
+ @Nullable
+ public abstract List<ParsedOptionDescription> getCanonicalInstances();
+
+ /**
* For the given option, returns the correct type of OptionValueDescription, to which unparsed
* values can be added.
*
@@ -103,7 +113,7 @@ public abstract class OptionValueDescription {
return new DefaultOptionValueDescription(option);
}
- static class DefaultOptionValueDescription extends OptionValueDescription {
+ private static class DefaultOptionValueDescription extends OptionValueDescription {
private DefaultOptionValueDescription(OptionDefinition optionDefinition) {
super(optionDefinition);
@@ -125,13 +135,18 @@ public abstract class OptionValueDescription {
"Cannot add values to the default option value. Create a modifiable "
+ "OptionValueDescription using createOptionValueDescription() instead.");
}
+
+ @Override
+ public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
+ return null;
+ }
}
/**
* The form of a value for a default type of flag, one that does not accumulate multiple values
* and has no expansion.
*/
- static class SingleOptionValueDescription extends OptionValueDescription {
+ private static class SingleOptionValueDescription extends OptionValueDescription {
private ParsedOptionDescription effectiveOptionInstance;
private Object effectiveValue;
@@ -183,6 +198,11 @@ public abstract class OptionValueDescription {
// Output warnings if there is conflicting options set different values in a way that might
// not have been obvious to the user, such as through expansions and implicit requirements.
if (!effectiveValue.equals(newValue)) {
+ boolean samePriorityCategory =
+ parsedOption
+ .getPriority()
+ .getPriorityCategory()
+ .equals(effectiveOptionInstance.getPriority().getPriorityCategory());
if ((implicitDependent != null) && (optionThatDependsOnEffectiveValue != null)) {
if (!implicitDependent.equals(optionThatDependsOnEffectiveValue)) {
warnings.add(
@@ -190,8 +210,7 @@ public abstract class OptionValueDescription {
"%s is implicitly defined by both %s and %s",
optionDefinition, optionThatDependsOnEffectiveValue, implicitDependent));
}
- } else if ((implicitDependent != null)
- && parsedOption.getPriority().equals(effectiveOptionInstance.getPriority())) {
+ } else if ((implicitDependent != null) && samePriorityCategory) {
warnings.add(
String.format(
"%s is implicitly defined by %s; the implicitly set value "
@@ -203,7 +222,7 @@ public abstract class OptionValueDescription {
"A new value for %s overrides a previous implicit setting of that "
+ "option by %s",
optionDefinition, optionThatDependsOnEffectiveValue));
- } else if ((parsedOption.getPriority().equals(effectiveOptionInstance.getPriority()))
+ } else if (samePriorityCategory
&& ((optionThatExpandedToEffectiveValue == null) && (expandedFrom != null))) {
// Create a warning if an expansion option overrides an explicit option:
warnings.add(
@@ -227,14 +246,19 @@ public abstract class OptionValueDescription {
return null;
}
- @VisibleForTesting
- ParsedOptionDescription getEffectiveOptionInstance() {
- return effectiveOptionInstance;
+ @Override
+ public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
+ // If the current option is an implicit requirement, we don't need to list this value since
+ // the parent implies it. In this case, it is sufficient to not list this value at all.
+ if (effectiveOptionInstance.getImplicitDependent() == null) {
+ return ImmutableList.of(effectiveOptionInstance);
+ }
+ return ImmutableList.of();
}
}
/** The form of a value for an option that accumulates multiple values on the command line. */
- static class RepeatableOptionValueDescription extends OptionValueDescription {
+ private static class RepeatableOptionValueDescription extends OptionValueDescription {
ListMultimap<OptionPriority, ParsedOptionDescription> parsedOptions;
ListMultimap<OptionPriority, Object> optionValues;
@@ -252,8 +276,10 @@ public abstract class OptionValueDescription {
public String getSourceString() {
return parsedOptions
.asMap()
- .values()
+ .entrySet()
.stream()
+ .sorted(Comparator.comparing(Entry::getKey))
+ .map(Entry::getValue)
.flatMap(Collection::stream)
.map(ParsedOptionDescription::getSource)
.distinct()
@@ -289,6 +315,20 @@ public abstract class OptionValueDescription {
}
return null;
}
+
+ @Override
+ public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
+ return parsedOptions
+ .asMap()
+ .entrySet()
+ .stream()
+ .sorted(Comparator.comparing(Entry::getKey))
+ .map(Entry::getValue)
+ .flatMap(Collection::stream)
+ // Only provide the options that aren't implied elsewhere.
+ .filter(optionDesc -> optionDesc.getImplicitDependent() == null)
+ .collect(ImmutableList.toImmutableList());
+ }
}
/**
@@ -296,7 +336,7 @@ public abstract class OptionValueDescription {
* in place to other options. This should be used for both flags with a static expansion defined
* in {@link Option#expansion()} and flags with an {@link Option#expansionFunction()}.
*/
- static class ExpansionOptionValueDescription extends OptionValueDescription {
+ private static class ExpansionOptionValueDescription extends OptionValueDescription {
private final List<String> expansion;
private ExpansionOptionValueDescription(
@@ -337,10 +377,18 @@ public abstract class OptionValueDescription {
: String.format(
"expanded from %s (source %s)", optionDefinition, parsedOption.getSource()));
}
+
+ @Override
+ public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
+ // The options this expands to are incorporated in their own right - this option does
+ // not have a canonical form.
+ return ImmutableList.of();
+ }
}
/** The form of a value for a flag with implicit requirements. */
- static class OptionWithImplicitRequirementsValueDescription extends SingleOptionValueDescription {
+ private static class OptionWithImplicitRequirementsValueDescription
+ extends SingleOptionValueDescription {
private OptionWithImplicitRequirementsValueDescription(OptionDefinition optionDefinition) {
super(optionDefinition);
@@ -384,7 +432,7 @@ public abstract class OptionValueDescription {
}
/** Form for options that contain other options in the value text to which they expand. */
- static final class WrapperOptionValueDescription extends OptionValueDescription {
+ private static final class WrapperOptionValueDescription extends OptionValueDescription {
WrapperOptionValueDescription(OptionDefinition optionDefinition) {
super(optionDefinition);
@@ -418,6 +466,13 @@ public abstract class OptionValueDescription {
: String.format(
"unwrapped from %s (source %s)", optionDefinition, parsedOption.getSource()));
}
+
+ @Override
+ public ImmutableList<ParsedOptionDescription> getCanonicalInstances() {
+ // No wrapper options get listed in the canonical form - the options they are wrapping will
+ // be in the right place.
+ return ImmutableList.of();
+ }
}
}
diff --git a/java/com/google/devtools/common/options/OptionsParser.java b/java/com/google/devtools/common/options/OptionsParser.java
index f84ee47..fb7161c 100644
--- a/java/com/google/devtools/common/options/OptionsParser.java
+++ b/java/com/google/devtools/common/options/OptionsParser.java
@@ -549,7 +549,7 @@ public class OptionsParser implements OptionsProvider {
* or null if the value has not been set.
* @throws IllegalArgumentException if there is no option by the given name.
*/
- OptionValueDescription getOptionValueDescription(String name) {
+ public OptionValueDescription getOptionValueDescription(String name) {
return impl.getOptionValueDescription(name);
}
@@ -619,6 +619,19 @@ public class OptionsParser implements OptionsProvider {
}
}
+ public void parseOptionsFixedAtSpecificPriority(
+ OptionPriority priority, String source, List<String> args) throws OptionsParsingException {
+ Preconditions.checkNotNull(priority, "Priority not specified for arglist " + args);
+ Preconditions.checkArgument(
+ priority.getPriorityCategory() != OptionPriority.PriorityCategory.DEFAULT,
+ "Priority cannot be default, which was specified for arglist " + args);
+ residue.addAll(impl.parseOptionsFixedAtSpecificPriority(priority, o -> source, args));
+ if (!allowResidue && !residue.isEmpty()) {
+ String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
+ throw new OptionsParsingException(errorMsg);
+ }
+ }
+
/**
* @param origin the origin of this option instance, it includes the priority of the value. If
* other values have already been or will be parsed at a higher priority, they might override
@@ -652,9 +665,7 @@ public class OptionsParser implements OptionsProvider {
return ImmutableList.copyOf(residue);
}
- /**
- * Returns a list of warnings about problems encountered by previous parse calls.
- */
+ /** Returns a list of warnings about problems encountered by previous parse calls. */
public List<String> getWarnings() {
return impl.getWarnings();
}
@@ -680,7 +691,12 @@ public class OptionsParser implements OptionsProvider {
}
@Override
- public List<OptionValueDescription> asListOfEffectiveOptions() {
+ public List<ParsedOptionDescription> asListOfCanonicalOptions() {
+ return impl.asCanonicalizedListOfParsedOptions();
+ }
+
+ @Override
+ public List<OptionValueDescription> asListOfOptionValues() {
return impl.asListOfEffectiveOptions();
}
@@ -787,8 +803,7 @@ public class OptionsParser implements OptionsProvider {
* Option} annotation.
*/
private static void validateFieldsSets(
- Class<? extends OptionsBase> optionsClass,
- LinkedHashSet<Field> fieldsFromMap) {
+ Class<? extends OptionsBase> optionsClass, LinkedHashSet<Field> fieldsFromMap) {
ImmutableList<OptionDefinition> optionDefsFromClasses =
OptionsData.getAllOptionDefinitionsForClass(optionsClass);
Set<Field> fieldsFromClass =
diff --git a/java/com/google/devtools/common/options/OptionsParserImpl.java b/java/com/google/devtools/common/options/OptionsParserImpl.java
index b543328..496927b 100644
--- a/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -21,18 +21,19 @@ import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
import com.google.devtools.common.options.OptionPriority.PriorityCategory;
import com.google.devtools.common.options.OptionValueDescription.ExpansionBundle;
import com.google.devtools.common.options.OptionsParser.OptionDescription;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
@@ -68,28 +69,21 @@ class OptionsParserImpl {
*/
private final List<ParsedOptionDescription> parsedOptions = new ArrayList<>();
+ private final List<String> warnings = new ArrayList<>();
+
/**
- * The options for use with the canonicalize command are stored separately from parsedOptions so
- * that invocation policy can modify the values for canonicalization (e.g. override user-specified
- * values with default values) without corrupting the data used to represent the user's original
- * invocation for {@link #asListOfExplicitOptions()} and {@link #asCompleteListOfParsedOptions()}.
- * A LinkedHashMultimap is used so that canonicalization happens in the correct order and multiple
- * values can be stored for flags that allow multiple values.
+ * Since parse() expects multiple calls to it with the same {@link PriorityCategory} to be treated
+ * as though the args in the later call have higher priority over the earlier calls, we need to
+ * track the high water mark of option priority at each category. Each call to parse will start at
+ * this level.
*/
- private final Multimap<OptionDefinition, ParsedOptionDescription> canonicalizeValues =
- LinkedHashMultimap.create();
-
- private final List<String> warnings = new ArrayList<>();
+ private final Map<PriorityCategory, OptionPriority> nextPriorityPerPriorityCategory =
+ Stream.of(PriorityCategory.values())
+ .collect(Collectors.toMap(p -> p, OptionPriority::lowestOptionPriorityAtCategory));
private boolean allowSingleDashLongOptions = false;
- private ArgsPreProcessor argsPreProcessor =
- new ArgsPreProcessor() {
- @Override
- public List<String> preProcess(List<String> args) throws OptionsParsingException {
- return args;
- }
- };
+ private ArgsPreProcessor argsPreProcessor = args -> args;
/** Create a new parser object. Do not accept a null OptionsData object. */
OptionsParserImpl(OptionsData optionsData) {
@@ -135,36 +129,26 @@ class OptionsParserImpl {
.collect(toCollection(ArrayList::new));
}
- /**
- * Implements {@link OptionsParser#canonicalize}.
- */
+ /** Implements {@link OptionsParser#canonicalize}. */
List<String> asCanonicalizedList() {
- return canonicalizeValues
- .values()
+ return asCanonicalizedListOfParsedOptions()
.stream()
- // Sort implicit requirement options to the end, keeping their existing order, and sort
- // the other options alphabetically.
- .sorted(
- (v1, v2) -> {
- if (v1.getOptionDefinition().hasImplicitRequirements()) {
- return v2.getOptionDefinition().hasImplicitRequirements() ? 0 : 1;
- }
- if (v2.getOptionDefinition().hasImplicitRequirements()) {
- return -1;
- }
- return v1.getOptionDefinition()
- .getOptionName()
- .compareTo(v2.getOptionDefinition().getOptionName());
- })
- // Ignore expansion options.
- .filter(value -> !value.getOptionDefinition().isExpansionOption())
.map(ParsedOptionDescription::getDeprecatedCanonicalForm)
- .collect(toCollection(ArrayList::new));
+ .collect(ImmutableList.toImmutableList());
}
- /**
- * Implements {@link OptionsParser#asListOfEffectiveOptions()}.
- */
+ /** Implements {@link OptionsParser#canonicalize}. */
+ List<ParsedOptionDescription> asCanonicalizedListOfParsedOptions() {
+ return optionValues
+ .keySet()
+ .stream()
+ .sorted()
+ .map(optionDefinition -> optionValues.get(optionDefinition).getCanonicalInstances())
+ .flatMap(Collection::stream)
+ .collect(ImmutableList.toImmutableList());
+ }
+
+ /** Implements {@link OptionsParser#asListOfOptionValues()}. */
List<OptionValueDescription> asListOfEffectiveOptions() {
List<OptionValueDescription> result = new ArrayList<>();
for (Map.Entry<String, OptionDefinition> mapEntry : optionsData.getAllOptionDefinitions()) {
@@ -196,8 +180,6 @@ class OptionsParserImpl {
OptionValueDescription clearValue(OptionDefinition optionDefinition)
throws OptionsParsingException {
- // Actually remove the value from various lists tracking effective options.
- canonicalizeValues.removeAll(optionDefinition);
return optionValues.remove(optionDefinition);
}
@@ -282,12 +264,33 @@ class OptionsParserImpl {
* order.
*/
List<String> parse(
- OptionPriority.PriorityCategory priority,
+ PriorityCategory priorityCat,
Function<OptionDefinition, String> sourceFunction,
List<String> args)
throws OptionsParsingException {
- return parse(
- OptionPriority.lowestOptionPriorityAtCategory(priority), sourceFunction, null, null, args);
+ ResidueAndPriority residueAndPriority =
+ parse(nextPriorityPerPriorityCategory.get(priorityCat), sourceFunction, null, null, args);
+ nextPriorityPerPriorityCategory.put(priorityCat, residueAndPriority.nextPriority);
+ return residueAndPriority.residue;
+ }
+
+ private static final class ResidueAndPriority {
+ List<String> residue;
+ OptionPriority nextPriority;
+
+ public ResidueAndPriority(List<String> residue, OptionPriority nextPriority) {
+ this.residue = residue;
+ this.nextPriority = nextPriority;
+ }
+ }
+
+ /** Parses the args at the fixed priority. */
+ List<String> parseOptionsFixedAtSpecificPriority(
+ OptionPriority priority, Function<OptionDefinition, String> sourceFunction, List<String> args)
+ throws OptionsParsingException {
+ ResidueAndPriority residueAndPriority =
+ parse(OptionPriority.getLockedPriority(priority), sourceFunction, null, null, args);
+ return residueAndPriority.residue;
}
/**
@@ -298,7 +301,7 @@ class OptionsParserImpl {
* <p>The method treats options that have neither an implicitDependent nor an expandedFrom value
* as explicitly set.
*/
- private List<String> parse(
+ private ResidueAndPriority parse(
OptionPriority priority,
Function<OptionDefinition, String> sourceFunction,
OptionDefinition implicitDependent,
@@ -325,6 +328,7 @@ class OptionsParserImpl {
identifyOptionAndPossibleArgument(
arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom);
handleNewParsedOption(parsedOption);
+ priority = OptionPriority.nextOptionPriority(priority);
}
// Go through the final values and make sure they are valid values for their option. Unlike any
@@ -334,7 +338,7 @@ class OptionsParserImpl {
valueDescription.getValue();
}
- return unparsedArgs;
+ return new ResidueAndPriority(unparsedArgs, priority);
}
/**
@@ -401,28 +405,23 @@ class OptionsParserImpl {
// Log explicit options and expanded options in the order they are parsed (can be sorted
// later). This information is needed to correctly canonicalize flags.
parsedOptions.add(parsedOption);
- if (optionDefinition.allowsMultiple()) {
- canonicalizeValues.put(optionDefinition, parsedOption);
- } else {
- canonicalizeValues.replaceValues(optionDefinition, ImmutableList.of(parsedOption));
- }
}
if (expansionBundle != null) {
- List<String> unparsed =
+ ResidueAndPriority residueAndPriority =
parse(
- parsedOption.getPriority(),
+ OptionPriority.getLockedPriority(parsedOption.getPriority()),
o -> expansionBundle.sourceOfExpansionArgs,
optionDefinition.hasImplicitRequirements() ? optionDefinition : null,
optionDefinition.isExpansionOption() ? optionDefinition : null,
expansionBundle.expansionArgs);
- if (!unparsed.isEmpty()) {
+ if (!residueAndPriority.residue.isEmpty()) {
if (optionDefinition.isWrapperOption()) {
throw new OptionsParsingException(
"Unparsed options remain after unwrapping "
+ unconvertedValue
+ ": "
- + Joiner.on(' ').join(unparsed));
+ + Joiner.on(' ').join(residueAndPriority.residue));
} else {
// Throw an assertion here, because this indicates an error in the definition of this
// option's expansion or requirements, not with the input as provided by the user.
@@ -430,7 +429,7 @@ class OptionsParserImpl {
"Unparsed options remain after processing "
+ unconvertedValue
+ ": "
- + Joiner.on(' ').join(unparsed));
+ + Joiner.on(' ').join(residueAndPriority.residue));
}
}
}
diff --git a/java/com/google/devtools/common/options/OptionsProvider.java b/java/com/google/devtools/common/options/OptionsProvider.java
index 5fd8ac0..d467fe5 100644
--- a/java/com/google/devtools/common/options/OptionsProvider.java
+++ b/java/com/google/devtools/common/options/OptionsProvider.java
@@ -57,10 +57,20 @@ public interface OptionsProvider extends OptionsClassProvider {
List<ParsedOptionDescription> asListOfExplicitOptions();
/**
- * Returns a list of all options, including undocumented ones, and their
- * effective values. There is no guaranteed ordering for the result.
+ * Returns a list of the parsed options whose values are in the final value of the option, i.e.
+ * the options that were added explicitly, expanded if necessary to the valued options they
+ * affect. This will not include values that were set and then overridden by a later value of the
+ * same option.
+ *
+ * <p>The list includes undocumented options.
+ */
+ List<ParsedOptionDescription> asListOfCanonicalOptions();
+
+ /**
+ * Returns a list of all options, including undocumented ones, and their effective values. There
+ * is no guaranteed ordering for the result.
*/
- List<OptionValueDescription> asListOfEffectiveOptions();
+ List<OptionValueDescription> asListOfOptionValues();
/**
* Canonicalizes the list of options that this OptionsParser has parsed. The
diff --git a/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java b/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java
new file mode 100644
index 0000000..f6c3c99
--- /dev/null
+++ b/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java
@@ -0,0 +1,318 @@
+// 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 org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.ASTORE;
+import static org.objectweb.asm.Opcodes.BIPUSH;
+import static org.objectweb.asm.Opcodes.DCONST_0;
+import static org.objectweb.asm.Opcodes.DLOAD;
+import static org.objectweb.asm.Opcodes.DUP_X1;
+import static org.objectweb.asm.Opcodes.FCONST_0;
+import static org.objectweb.asm.Opcodes.FLOAD;
+import static org.objectweb.asm.Opcodes.GOTO;
+import static org.objectweb.asm.Opcodes.IADD;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
+import static org.objectweb.asm.Opcodes.ICONST_2;
+import static org.objectweb.asm.Opcodes.ICONST_4;
+import static org.objectweb.asm.Opcodes.IFNONNULL;
+import static org.objectweb.asm.Opcodes.ILOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.ISTORE;
+import static org.objectweb.asm.Opcodes.LCONST_0;
+import static org.objectweb.asm.Opcodes.LLOAD;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.SIPUSH;
+import static org.objectweb.asm.Opcodes.SWAP;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassWriter;
+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;
+
+/**
+ * Test data generator for b/62060793. This class creates a special labmda invocation that
+ * contains *CONST_0 values on stack, which are passed as lambda arguments.
+ */
+public class Bug62060793TestDataGenerator {
+
+ private static final String CLASS_NAME =
+ "com/google/devtools/build/android/desugar/testdata/ConstantArgumentsInLambda";
+
+ private static final String INTERFACE_TYPE_NAME = CLASS_NAME + "$Interface";
+
+ public static void main(String[] args) throws IOException {
+ checkArgument(
+ args.length == 1,
+ "Usage: %s <output-jar>",
+ Bug62060793TestDataGenerator.class.getName());
+ Path outputJar = Paths.get(args[0]);
+
+ try (ZipOutputStream outZip =
+ new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(outputJar)))) {
+ String className = CLASS_NAME + ".class";
+ writeToZipFile(outZip, className, createClass());
+ String interfaceName = INTERFACE_TYPE_NAME + ".class";
+ writeToZipFile(outZip, interfaceName, createInterface());
+ }
+ }
+
+ private static void writeToZipFile(ZipOutputStream outZip, String entryName, byte[] content)
+ throws IOException {
+ ZipEntry result = new ZipEntry(entryName);
+ result.setTime(0L);
+ outZip.putNextEntry(result);
+ outZip.write(content);
+ outZip.closeEntry();
+ }
+
+ private static byte[] createClass() {
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ MethodVisitor mv;
+ cw.visit(
+ V1_8, ACC_PUBLIC | ACC_SUPER,
+ CLASS_NAME,
+ null, "java/lang/Object", null);
+
+ cw.visitInnerClass(
+ INTERFACE_TYPE_NAME,
+ CLASS_NAME,
+ "Interface",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ cw.visitInnerClass(
+ "java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles",
+ "Lookup",
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(
+ ACC_PRIVATE | ACC_STATIC,
+ "method",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ null,
+ null);
+ mv.visitParameter("str", 0);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARETURN);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(
+ ACC_PRIVATE | ACC_STATIC,
+ "method",
+ "(ZCBFDJISLjava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;",
+ null,
+ null);
+ mv.visitParameter("bool", 0);
+ mv.visitParameter("c", 0);
+ mv.visitParameter("b", 0);
+ mv.visitParameter("f", 0);
+ mv.visitParameter("d", 0);
+ mv.visitParameter("l", 0);
+ mv.visitParameter("i", 0);
+ mv.visitParameter("s", 0);
+ mv.visitParameter("o", 0);
+ mv.visitParameter("array", 0);
+ mv.visitParameter("str", 0);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 10);
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "java/lang/String",
+ "valueOf",
+ "(Ljava/lang/Object;)Ljava/lang/String;", false);
+ mv.visitVarInsn(ASTORE, 13);
+ mv.visitVarInsn(ALOAD, 11);
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFNONNULL, l0);
+ mv.visitInsn(ICONST_1);
+ Label l1 = new Label();
+ mv.visitJumpInsn(GOTO, l1);
+ mv.visitLabel(l0);
+ mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"java/lang/String"}, 0, null);
+ mv.visitInsn(ICONST_0);
+ mv.visitLabel(l1);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+ mv.visitVarInsn(ISTORE, 14);
+ mv.visitIntInsn(BIPUSH, 91);
+ mv.visitVarInsn(ALOAD, 12);
+ mv.visitMethodInsn(INVOKESTATIC, "java/lang/String",
+ "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String",
+ "length", "()I", false);
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ALOAD, 13);
+ mv.visitMethodInsn(INVOKESTATIC, "java/lang/String",
+ "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String",
+ "length", "()I", false);
+ mv.visitInsn(IADD);
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP_X1);
+ mv.visitInsn(SWAP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder",
+ "<init>", "(I)V", false);
+ mv.visitVarInsn(ALOAD, 12);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(Z)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(C)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(I)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(FLOAD, 3);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(F)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(DLOAD, 4);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(D)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(LLOAD, 6);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(J)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 8);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(I)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 9);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(I)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ALOAD, 13);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
+ mv.visitVarInsn(ILOAD, 14);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "append", "(Z)Ljava/lang/StringBuilder;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
+ "toString", "()Ljava/lang/String;", false);
+ mv.visitInsn(ARETURN);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "lambdaWithConstantArguments",
+ "()L" + INTERFACE_TYPE_NAME + ";",
+ null, null);
+ mv.visitCode();
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(ICONST_2);
+ mv.visitInsn(FCONST_0);
+ mv.visitInsn(DCONST_0);
+ mv.visitInsn(LCONST_0);
+ mv.visitInsn(ICONST_4);
+ mv.visitIntInsn(SIPUSH, 9);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInvokeDynamicInsn(
+ "call",
+ "(ZCBFDJISLjava/lang/Object;[Ljava/lang/Object;)L" + INTERFACE_TYPE_NAME + ";",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;"
+ + "Ljava/lang/String;Ljava/lang/invoke/MethodType;"
+ + "Ljava/lang/invoke/MethodType;"
+ + "Ljava/lang/invoke/MethodHandle;"
+ + "Ljava/lang/invoke/MethodType;"
+ + ")Ljava/lang/invoke/CallSite;",
+ false),
+ new Object[] {
+ Type.getType("(Ljava/lang/String;)Ljava/lang/String;"),
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ CLASS_NAME,
+ "method",
+ "(ZCBFDJISLjava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;"
+ + ")Ljava/lang/String;",
+ false),
+ Type.getType("(Ljava/lang/String;)Ljava/lang/String;")});
+ mv.visitInsn(ARETURN);
+ mv.visitEnd();
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private static byte[] createInterface() {
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
+ INTERFACE_TYPE_NAME,
+ null, "java/lang/Object", null);
+
+ cw.visitInnerClass(
+ INTERFACE_TYPE_NAME,
+ CLASS_NAME,
+ "Interface",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ {
+ mv = cw.visitMethod(
+ ACC_PUBLIC | ACC_ABSTRACT,
+ "call",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ null,
+ null);
+ mv.visitParameter("input", 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+
+ }
+}
diff --git a/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java b/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java
new file mode 100644
index 0000000..fefc599
--- /dev/null
+++ b/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/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt b/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt
new file mode 100644
index 0000000..722c2ad
--- /dev/null
+++ b/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/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java b/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java
new file mode 100644
index 0000000..2573648
--- /dev/null
+++ b/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/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java b/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java
new file mode 100644
index 0000000..7c3d7ee
--- /dev/null
+++ b/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java
@@ -0,0 +1,38 @@
+// 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.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.android.desugar.testdata.ConstantArgumentsInLambda;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests uncommon lambda scenarious.
+ */
+@RunWith(JUnit4.class)
+public class DesugarLambdaTest {
+
+ /**
+ * Test for b/62060793. Verifies constant lambda arguments that were pushed using *CONST_0
+ * instructions.
+ */
+ @Test
+ public void testCallLambdaWithConstants() throws Exception {
+ assertThat(ConstantArgumentsInLambda.lambdaWithConstantArguments().call("test"))
+ .isEqualTo("testfalse\00120.00.0049nulltrue");
+ }
+}
diff --git a/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java b/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java
new file mode 100644
index 0000000..528c78e
--- /dev/null
+++ b/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/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java b/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
index 587d4f7..37afae7 100644
--- a/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
+++ b/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/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh b/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh
new file mode 100755
index 0000000..1166620
--- /dev/null
+++ b/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/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar b/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar
new file mode 100644
index 0000000..efa491b
--- /dev/null
+++ b/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar
Binary files differ
diff --git a/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java b/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java
new file mode 100644
index 0000000..b5463a7
--- /dev/null
+++ b/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();
+ }
+ }
+}