summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2017-12-11 12:17:15 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2017-12-11 12:17:15 +0000
commit76c6d76d69e305135f94a507eb9ef3a3b1127cb9 (patch)
tree6ae7509db463ca9f5f55dd5463fabdedb7fd4dea
parent59042c11f1da22b1df5bbcfbe57a7726db180e61 (diff)
parent046121d63e0802abe591d348bfccea8782487b83 (diff)
downloaddesugar-76c6d76d69e305135f94a507eb9ef3a3b1127cb9.tar.gz
Snap for 4496165 from 046121d63e0802abe591d348bfccea8782487b83 to pi-release
Change-Id: Iae7aba491232c2765c5a451fccf62bcabd63c409
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE202
-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
28 files changed, 4060 insertions, 310 deletions
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
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();
+ }
+ }
+}